2

I have an external application and I want it to display some information on top of the browser window. My bootstrapped extension needs to pass the browser window handle (native HWND) to my application, along with some other useful information about the window. I'm able to do the communication between them, the only thing that is missing is a way to get the native HWND of the Firefox window.

I read a lot about it and although I belive it's possible, I couldn't find a working solution. Here's what I've tried so far:

This one should give me nsIBaseWindow, so I could get nsIBaseWindow.nativeHandle or nsIBaseWindow.ParentNativeWindow, but no success:

var window = SomeDOMWindow; // Informative
var baseWindow = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                        .getInterface(Components.interfaces.nsIWebNavigation)
                        .QueryInterface(Components.interfaces.nsIDocShellTreeItem)
                        .treeOwner
                        .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                        .getInterface(Components.interfaces.nsIXULWindow)
                        .docShell
                        .QueryInterface(Components.interfaces.nsIBaseWindow);

The above code is widely spread on forums, but I couldn't get it to work for me.

The other one does not seem to be much accurate since it gets the HWND based on the window's class and title, which can lead to wrong results:

Components.utils.import("resource://gre/modules/ctypes.jsm");
var lib = ctypes.open("user32.dll");
var fww = lib.declare("FindWindowW", ctypes.winapi_abi,
  ctypes.voidptr_t, ctypes.jschar.ptr, ctypes.jschar.ptr);
var sfw = lib.declare("SetForegroundWindow", ctypes.winapi_abi,
  ctypes.int32_t, ctypes.voidptr_t);
var hwnd = fww("MozillaWindowClass", document.title);
setTimeout(function() {
  sfw(hwnd);
  lib.close();
}, 3000);

Any help would be appreciated.

Noitidart
  • 35,443
  • 37
  • 154
  • 323
karliwson
  • 3,365
  • 1
  • 24
  • 46

4 Answers4

4

window must be a root one (i.e. an instance of ChromeWindow)

The following code should work

var win = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator).getMostRecentWindow("navigator:browser");
var basewindow = win.QueryInterface(Ci.nsIInterfaceRequestor)
                 .getInterface(Ci.nsIWebNavigation)
                 .QueryInterface(Ci.nsIDocShellTreeItem)
                 .treeOwner
                 .QueryInterface(Ci.nsIInterfaceRequestor)
                 .nsIBaseWindow;
var nativehandle = basewindow.nativeHandle;
paa
  • 5,048
  • 1
  • 18
  • 22
  • I need to get the created `ChromeWindow` from inside the `xul-window-registered` event observer, so I can test your solution. I have the event handler: `observe: function(subject, topic, data) {...}` and that's the place where I need to get the handle, but I only know how to get the `nsIDOMWindow` from there, not the created `ChromeWindow`. – karliwson Feb 24 '14 at 21:41
  • Well, the subject of the `xul-window-registered` notification is the window you 're looking for. – paa Feb 24 '14 at 22:32
  • I tried to use it but it's an `nsISupports` interface. When I try to obtain the `basewindow` from it I get an error: `[nsIInterfaceRequestor.getInterface]" nsresult: "0x80004002 (NS_NOINTERFACE)"` – karliwson Feb 24 '14 at 22:44
  • 1
    to test you can copy paste his code into scratchpad with environment set to browser. works great. – Noitidart Feb 25 '14 at 07:08
  • @Kekas I guess when the observer is notified with the `xul-window-registered` topic, the initialization of the window object is not complete. Perhaps you should wait until the `load` event is fired. – paa Feb 25 '14 at 13:13
  • @Noit it really works great in scratchpad. I'm trying to make it work with my extension and will update when I get something. – karliwson Feb 25 '14 at 15:57
  • This code does not work anymore on Firefox 70. See my answer. – Elmue Aug 01 '20 at 03:59
3

The problem was that I was querying the wrong interface from the subject param in the xul-window-registered observer. I need to get an nsIDOMWindow instead of an nsIXULWindow so the first code mentioned in my question works. So now I'm doing the following, with some piece of code @Noit suggested:

observe: function(subject, topic, data) {
    var newWindow  = subject.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
    var basewindow = newWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                     .getInterface(Ci.nsIWebNavigation)
                     .QueryInterface(Ci.nsIDocShellTreeItem)
                     .treeOwner
                     .QueryInterface(Ci.nsIInterfaceRequestor)
                     .nsIBaseWindow;
    var nativehandle = basewindow.nativeHandle;
}

And it works!

Thank you very much for your help.

karliwson
  • 3,365
  • 1
  • 24
  • 46
  • much thanks for sharing that snippet of code it will help people – Noitidart Feb 25 '14 at 16:52
  • wow thanks again for sharing this, i ran into a problem where i was setting title of a window then doing `FindWindow` but findwindow wasnt finding it because it was running to fast after changing the window title then i remembered this topic now no need for even `FindWindow` superb man thanks again!! this is why people should always share solutions! :) – Noitidart Jun 04 '14 at 04:49
  • This code does not work anymore on Firefox 70. See my answer. – Elmue Aug 01 '20 at 03:59
0

I also just came across this, it might be nice:

Cu.import("resource://gre/modules/ctypes.jsm");

/*start getcursorpos*/
var lib = ctypes.open("user32.dll");

/*foreground window stuff*/
var FindWindowA = lib.declare('FindWindowA', ctypes.winapi_abi, ctypes.uint32_t, ctypes.jschar.ptr, ctypes.jschar.ptr)
var GetForegroundWindow = lib.declare('GetForegroundWindow', ctypes.winapi_abi, ctypes.uint32_t)
function doFindWindow() {
    var wm = Cc['@mozilla.org/appshell/window-mediator;1'].getService(Ci.nsIWindowMediator);
    var title = wm.getMostRecentWindow('navigator:browser').gBrowser.contentDocument.title;
    Cu.reportError('title=' + title)
    var ret = FindWindowA('', title + ' - Mozilla Firefox');
    //var ret = GetForegroundWindow();

    Cu.reportError(ret);
}
/*end foreground window stuff*/
Noitidart
  • 35,443
  • 37
  • 154
  • 323
  • It's not very reliable since it uses the title of the window and some distributions have a diffent name. Not to mention the fact that the title may change during the process or maybe multiple windows have the same title and you may end up getting the wrong handle. – karliwson Feb 26 '14 at 21:28
0

The code in the answer of user 'paa' worked until Firefox version 69. If you execute it in Firefox 70 you will get an exception:

TypeError: win.QueryInterface is not a function

This is strange because the variable win has the same content in Firefox 69 and 70.

When I execute alert(win) I get: "[object ChromeWindow]" in both browsers.

And alert(win.document.title) displays correctly the title of the document in both browsers.

I downloaded the sourcecode of both Firefox versions to compare them and possibly find the cause. But the source code of Firefox is huge (2 Gigabyte) and nearly completely free of comments. I found that I'm wasting my time with that approach.

It is extremely difficult to understand sourcecode of Firefox which runs spread over multiple processes which communicate with each other. It seems that the content of the variable win corresponds to the C++ class mozIDOMWindowProxy or nsChromeOuterWindowProxy. But these seem to be only wrapper classes for other classes. Finally I gave up trying to understand Firefox sourcecode.

But playing around for some hours I finally found a solution by try and error.

It is even simpler:

var baseWindow = win.docShell
                    .treeOwner
                    .nsIBaseWindow; 

It works on Firefox 70 up to 79 (which is currently the latest version). However this new code does not run on Firefox versions <= 62. On Firefox 62 or older you get the error

TypeError: win.docShell is undefined

So the Firefoxes from 63 to 69 allow both versions of code. Maybe in version 70 the QueryInterface() has been removed because it is not needed anymore and has become legacy?

NOTE: In Firefox 68 they made another change. Now there are 2 native windows: The toplevel 'MozillaWindowClass' now has a child window 'MozillaCompositorWindowClass' which runs in another process and draws the web content.

Elmue
  • 7,602
  • 3
  • 47
  • 57