Search code examples
javascriptmouseeventsimulationfirefox-addon-sdkkeyboard-events

How to successfully emit KeyboardEvent "Ctrl+T" in AddonSDK


I'm needing some help on a small task on Mozilla AddonSDK.

I am attempting to open the element hyperlink in a new tab while retaining browser session state and any JavaScript events.

This would mean a simple, grabbing of the element's href and tab.open(href) would be unlikely to provide ideal support.

It would be ideal to simulate the Ctrl+Left Mouse Click on a element to open the link in a new window.

Current attempt:

var tabs = require("sdk/tabs");

exports.testOpenTabCommand = function(assert, done) {
  var html = 'data:text/html,<html><title></title><body><a href="http://example.com">Click</a></body></html>';
  var script = [
    'self.port.on("openNewTab", function(selector){',
    '  var elm = document.querySelector(selector);',
    '  var ev = new KeyboardEvent(\'keydown\', {',
    '    ctrlKey: true,',
    '    key: \'t\'',
    '  });',
    '  elm.dispatchEvent(ev);',
    '  self.port.emit("tabOpened", true);',
    '})'
  ].join('');

  tabs.open({
    url: html,
    onLoad: function(tab) {
      var worker = tab.attach({
        contentScript: script,
        contentScriptWhen: 'ready'
      });
      worker.port.on('tabOpened', function (){
        // first tab is `about:blank`
        // second tab is the html above
        // third tab should be the clicked link
        assert.equal(tabs.length, 3);
        tab.close(function(){
          done();
        });
      });
      worker.port.emit('openNewTab', 'a');
    }
  });
};

require('sdk/test').run(exports);

I appreciate your answers and comments.

Thank you.


Solution

  • If I understood properly, it seems to me that you just need to add inBackground property when you open the link using tabs.open:

    tabs.open({
      url: href,
      inBackground: true
    });
    

    If that is not the case, let me know and we'll figure out alternatives.

    Updated (see the comments):

    In order to properly synthetize keystrokes and mouse events, especially in the new version of Firefox with e10s, we need to do something more complex, like:

    const tabs = require("sdk/tabs");
    const { OS } = require("sdk/system/runtime");
    const { getTabBrowserForTab } = require("sdk/tabs/utils");
    const { viewFor } = require("sdk/view/core");
    
    const { Cc, Ci } = require("chrome");
    
    const remote = (f) => "data:application/javascript," + encodeURIComponent(`(${f}())`);
    
    const globalMessageManager = Cc["@mozilla.org/globalmessagemanager;1"]
                                  .getService(Ci.nsIMessageListenerManager);
    
    // it would be better put the code of the content function in an
    // external module, and then use `framescript/manager` to load it
    globalMessageManager.loadFrameScript(remote(function() {
      let domWindowUtils = this.content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                           .getInterface(Components.interfaces.nsIDOMWindowUtils);
    
      this.addMessageListener("myaddon:click", ({target, json}) => {
        let { accelKey, selector } = json;
        let node = this.content.document.querySelector(selector);
        let { top, left } = node.getBoxQuads()[0].bounds;
    
        // Simulate the click with the proper accel key
        let accel = domWindowUtils["MODIFIER_" + accelKey];
        domWindowUtils.sendMouseEvent("mousedown", left + 1, top + 1, 0, 1, 0, accel);
        domWindowUtils.sendMouseEvent("mouseup", left + 1, top + 1, 0, 1, accel);
    
        target.sendAsyncMessage("myaddon:clicked");
      });
    }), true);
    
    const synthesizeClickFor = (tab, selector) => new Promise((resolve) => {
      let { selectedBrowser } = getTabBrowserForTab(viewFor(tab));
    
      globalMessageManager.addMessageListener("myaddon:clicked", function listener({json}) {
        this.removeMessageListener("myaddon:clicked", listener);
    
        resolve();
      });
    
      let accelKey = OS === "Darwin" ? "META" : "CONTROL";
      selectedBrowser.messageManager.sendAsyncMessage("myaddon:click", { selector, accelKey });
    });
    
    exports.testOpenTabCommand = function(assert, done) {
      var html = "data:text/html,<html><title></title><body><a href='http://mozilla.org'>Click</a></body></html>";
    
      tabs.open({
        url: html,
        onLoad: function(tab) {
          synthesizeClickFor(tab, "a").then(() => {
            assert.ok(tabs.length, 3, "expected 3 tabs")
    
            // you need to close also the 3rd tab
            tabs[2].close(() => tab.close(done));
          });
        }
      });
    };
    
    require('sdk/test').run(exports);
    

    This simulate the left click plus the accel key (ctrl (MODIFIER_CONTROL) on Win / Linux, cmd ⌘ (MODIFIER_META) on OS X). You can also synthetize keys in a similar way, see: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIDOMWindowUtils#sendKeyEvent%28%29