Emulating Window.openDialog with JavaScript arguments in an XPCOM component

Opening a XUL dialog from JavaScript is usually easy, you use Window.openDialog method that is almost the same as Window.open but also accepts some parameters that the dialog will be able to access via Window.arguments collection. But what if your JavaScript code runs inside an XPCOM component doesn’t have a window? Still not very hard, you use nsIWindowWatcher.openWindow method then. The tricky part here is passing parameters to the dialog however. I had to resort to hacks here in the past, and other people seem to have issues with that as well, so I thought I would share the solution.

You have to pass the parameters as nsIArray (nsISupportsArray if you want to stay compatible with Gecko 1.8), that’s how far you get with the available documentation. However, nsIArray stores its entries as nsISupports. If you simply add the JavaScript values to the array, the dialog will get its arguments wrapped as nsISupports. Fortunately, XPConnect can automatically wrap JavaScript values into nsIVariant, and those will also be unwrapped automatically.

Problem is with telling XPConnect that you want these values wrapped as nsIVariant rather than nsISupports. And that’s what nsIWritableVariant.setFromVariant method is good for — it accepts nsIVariant as parameter so XPConnect will do the wrapping. If you take it all together you get:

function openDialog(parentWindow, url, windowName, features)
{
    var array = Components.classes["@mozilla.org/array;1"]
                          .createInstance(Components.interfaces.nsIMutableArray);
    for (var i = 4; i < arguments.length; i++)
    {
        var variant = Components.classes["@mozilla.org/variant;1"]
                                .createInstance(Components.interfaces.nsIWritableVariant);
        variant.setFromVariant(arguments[i]);
        array.appendElement(variant, false);
    }

    var watcher = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
                            .getService(Components.interfaces.nsIWindowWatcher);
    return watcher.openWindow(parentWindow, url, windowName, features, array);
}

That function works the same as Window.openDialog but accepts the parent window as first parameter (can be null).

Comments

  • Michael Kaply

    I just do this:

      var win = Components.classes['@mozilla.org/appshell/window-mediator;1']
                          .getService(Components.interfaces.nsIWindowMediator)
                          .getMostRecentWindow("navigator:browser");
    .
    .
    .
            win.openDialog('chrome://msft_activities/content/addprovider.xul','addprovider','chrome,centerscreen,modal', param1, param2, param3);
    

    and pass the parameters like normal

    Wladimir Palant

    That’s assuming there is a navigator window you can use… In the two cases where I had to solve this issue I couldn’t rely on that.

  • Bonnie Shanafelt

    what is going on with my emails from pogo it won’t download.

    Wladimir Palant

    Why are you asking me?

  • Neil

    For primitive types you can also put the value in an nsISupportsPrimitive object. (C++ has to do it this way.) If you use an nsISupportsInterfacePointer then the argument will automatically get QI’d to the dataIID, but I don’t think anyone uses that feature.

    Wladimir Palant

    Actually, Window.openDialog wraps the arguments in nsIVariant, that’s where I got it from. It looks like the Gecko 1.8 version of that code used nsISupportsPrimitive however – so maybe my code will only work with Gecko 1.9 regardless of whether you use nsIArray or nsISupportsArray.

  • Mark

    How can I read the array once the window has loaded?? I always receive “void” as value

    Wladimir Palant

    @window.arguments@ (not to be used as simply @arguments@ because inside a function this will be the function parameters object)