Codifier
Codifier

Reputation: 354

Detect origin tab, if any, that opened new tab/window

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

Answers (2)

Codifier
Codifier

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

the8472
the8472

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

Related Questions