Reputation: 354
For an upcoming version of an add-on I created, I want to detect from which tab, if any, a link was opened in a new tab or window.
My add-on is a zoom add-on, and I want to be able to copy the zoom level of the origin tab if a user opens a link in a new tab or window.
I've already figured out how to detect the opener if a link is opened with window.open()
from content-JavaScript (that was fairly easy), however I have no real idea how to figure out if a new tab/window was opened from the context menu (open link in new tab/new window) or Ctrl/⌘/Shift+click.
To detect whether it was opened from content-Javascript, I use:
const Cc = Components.classes;
const Ci = Components.interfaces;
let windowTracker = {
observe: function( subject, topic, data ) {
if( 'chrome-document-global-created' == topic && subject instanceof Ci.nsIDOMWindow ) {
// here I can use subject.opener
}
}
}
let observerService = Cc[ '@mozilla.org/observer-service;1' ].getService( Ci.nsIObserverService );
observerService.addObserver( this, 'chrome-document-global-created', false );
To detect any possible non-content-JavaScript opener I've tried this in observe()
:
let windowTracker = {
observe: function( subject, topic, data ) {
if( 'chrome-document-global-created' == topic && subject instanceof Ci.nsIDOMWindow ) {
let topWindow = subject.QueryInterface( Ci.nsIInterfaceRequestor )
.getInterface( Ci.nsIWebNavigation )
.QueryInterface( Ci.nsIDocShellTreeItem )
.rootTreeItem
.QueryInterface( Ci.nsIInterfaceRequestor )
.getInterface( Ci.nsIDOMWindow );
// here I tried topWindow.opener, with mixed results
}
}
}
... but got mixed results.
When I open a link in a new tab or window with the context menu or Ctrl/⌘/Shift+click, topWindow.opener
equals null
, but if I, for instance, open the Add-ons Manager with Ctrl+Shift+A, topWindow.opener
will be an instance of ChromeWindow
.
So, it seems I'm kind of on the right track, but not quite, as I'm merely interested in user initiated opening of content links in a new tab or window.
Is what I'm after doable?
In inspecting the source code of Firefox, I've noticed that there might be a method (unless I'm misinterpreting its purpose) that would give me what I am after: getOpener()
in nsIDocShell
. However, that method is not exposed in the scripting API.
Is there anything similar that I could utilize?
Upvotes: 2
Views: 2060
Reputation: 354
I've managed to get it working by intercepting ChromeWindow.openLinkIn()
with a Proxy
object:
// window is a top level ChromeWindow
window.openLinkIn = new Proxy( window.openLinkIn, {
apply: function( target, thisArg, argumentsList ) {
let url = argumentsList[ 0 ];
let where = argumentsList[ 1 ];
let params = argumentsList.length > 2 ? argumentsList[ 2 ] : {};
// links opened through the context menu or Ctrl/⌘/Shift+click
// don't have params.fromChrome = true
if( !url || !where || params.fromChrome ) {
return target.apply( thisArg, argumentsList );
}
switch( where ) {
case 'tab':
// break intentionally omitted
case 'tabshifted':
let tabBrowser = window.gBrowser;
tabBrowser.tabContainer.addEventListener( 'TabOpen', function onTabOpen( event ) {
tabBrowser.tabContainer.removeEventListener( 'TabOpen', onTabOpen, true );
let tab = event.target;
// do something with the new tab
}, true );
break;
case 'window':
let windowTracker = {
init: function() {
observerService.addObserver( this, 'toplevel-window-ready', false );
},
destroy: function() {
observerService.removeObserver( this, 'toplevel-window-ready' );
},
observe: function( subject, topic, data ) {
let self = this;
let chromeWindow = subject;
if( 'toplevel-window-ready' == topic && chromeWindow instanceof Ci.nsIDOMWindow ) {
chromeWindow.addEventListener( 'DOMContentLoaded', function onChromeWindowDOMContentLoaded( event ) {
this.removeEventListener( 'DOMContentLoaded', onChromeWindowDOMContentLoaded, true );
if( 'navigator:browser' == this.document.documentElement.getAttribute( 'windowtype' ) ) {
// do something with the new chromeWindow
// destroy the windowTracker
self.destroy();
}
}, true );
}
}
}
windowTracker.init();
break;
}
return target.apply( thisArg, argumentsList );
}
} );
PS.: Be sure to clean up and reinstall the original function, of course, when your extension is disabled. You can do this with something like this:
let originalOpenLinkIn = window.openLinkIn;
window.openLinkIn = new Proxy( window.openLinkIn, {
apply: /* implementation from above */
} );
// then when cleaning up:
window.openLinkIn = originalOpenLinkIn;
Upvotes: 2
Reputation: 43115
however I have no real idea how to figure out if a new tab/window was opened from the context menu (open link in new tab/new window) or Ctrl/⌘/Shift+click.
In such cases, especially if they are opened in a new window, the tab doesn't really have an owner. They're considered equivalent to the user just typing the URL in the address bar.
What you can do is installing a system event listener for bubbling click
, contextmenu
and submit
events on each window
and check if the target is a form or link and then check their target url. If a newly opened tab's location matches the last clicked/submitted link then it's probably from that previous tab.
Upvotes: 1