[Pkg-mozext-commits] [greasemonkey] 06/21: Fully asynchronous menu command implementation.

David Prévot taffit at moszumanska.debian.org
Sun Sep 13 21:27:15 UTC 2015


This is an automated email from the git hooks/post-receive script.

taffit pushed a commit to branch master
in repository greasemonkey.

commit baa276244730bbb506feb68a8223c57e32d41099
Author: Anthony Lieuallen <arantius at gmail.com>
Date:   Mon Jul 13 12:45:56 2015 -0400

    Fully asynchronous menu command implementation.
    
    1) Store menu commmands' data in a private closure, *in the sandbox*.
    2) To list registered commands:
      a) Parent/chrome passes a message to child/frame.
      b) Frame passes an event (visible to content) into the sandbox.
      c) Sandbox passes private-closure-scoped commands' data to a frame-scoped callback.
      d) Frame passes data up to parent as a message.
      e) Chrome uses this data to populate the menu, at popupshowing time.
    3) To run a command:
      a) User clicks on the menu item.
      b) Chrome sends a message to the frame.
      c) Frame sends an event to the sandbox.
      d) Sandbox finds the related registered command, calls its callback.
    
    Phew!  But no references to documents/windows/browsers are ever stored anywhere, so they can't possibly leak anymore.
    
    Along the way, simplify frame script by moving object methods to standalone functions; less state, less binding to fix "this" references.  The ContentObserver object is now really just there for `.observe()`.
    
    TODO: Restore "delayed execution" feature, the only other usage of the (removed) ScriptRunner structure.
    
    Refs: #2200
    Refs: #2067
---
 content/browser.js       |   9 +-
 content/framescript.js   | 310 ++++++++++++++++-------------------------------
 content/menucommander.js | 108 ++++++-----------
 modules/GM_openInTab.js  |  22 ++++
 modules/menucommand.js   | 129 +++++++++++++-------
 modules/sandbox.js       |  42 +++----
 modules/script.js        |   2 +-
 7 files changed, 272 insertions(+), 350 deletions(-)

diff --git a/content/browser.js b/content/browser.js
index add0da1..1b71668 100644
--- a/content/browser.js
+++ b/content/browser.js
@@ -357,22 +357,17 @@ function GM_showPopup(aEvent) {
       function(script) { point = appendScriptAfter(script, point); });
 
   // Propagate to commands sub-menu.
-  var commandsPopup = popup.querySelector(
-      'menupopup.greasemonkey-user-script-commands-popup');
-  GM_MenuCommander.onPopupShowing(commandsPopup);
+  GM_MenuCommander.onPopupShowing(aEvent);
 }
 
 /**
  * Clean up the menu after it hides to prevent memory leaks
  */
 function GM_hidePopup(aEvent) {
-  var popup = aEvent.target;
   // Only handle the actual monkey menu event.
   if (aEvent.currentTarget != aEvent.target) return;
   // Propagate to commands sub-menu.
-  var commandsPopup = popup.querySelector(
-      'menupopup.greasemonkey-user-script-commands-popup');
-  GM_MenuCommander.onPopupHiding(commandsPopup);
+  GM_MenuCommander.onPopupHiding();
 }
 
 // Short-term workaround for #1406: Tab Mix Plus breaks opening links in
diff --git a/content/framescript.js b/content/framescript.js
index bfaeac1..136a170 100644
--- a/content/framescript.js
+++ b/content/framescript.js
@@ -11,6 +11,7 @@ Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.import('chrome://greasemonkey-modules/content/GM_setClipboard.js');
 Cu.import('chrome://greasemonkey-modules/content/installPolicy.js');
 Cu.import('chrome://greasemonkey-modules/content/ipcscript.js');
+Cu.import('chrome://greasemonkey-modules/content/menucommand.js');
 Cu.import('chrome://greasemonkey-modules/content/miscapis.js');
 Cu.import('chrome://greasemonkey-modules/content/sandbox.js');
 Cu.import('chrome://greasemonkey-modules/content/scriptProtocol.js');
@@ -19,91 +20,10 @@ Cu.import('chrome://greasemonkey-modules/content/util.js');
 // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ //
 
 var gScope = this;
-var gScriptRunners = {};
 var gStripUserPassRegexp = new RegExp('(://)([^:/]+)(:[^@/]+)?@');
 
 // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ //
 
-function isTempScript(uri) {
-  // RAGE FACE can't do files in frames!!!
-//  if (uri.scheme != "file") return false;
-//  var file = gFileProtocolHandler.getFileFromURLSpec(uri.spec);
-//  return gTmpDir.contains(file, true);
-  return false;
-}
-
-// \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ //
-
-function ScriptRunner(aWindow, aUrl) {
-  this.menuCommands = [];
-  this.window = aWindow;
-  this.windowId = GM_util.windowId(this.window);
-  this.url = aUrl;
-}
-
-ScriptRunner.prototype.injectScripts = function(aScripts) {
-  try {
-    this.window.QueryInterface(Ci.nsIDOMChromeWindow);
-    // Never ever inject scripts into a chrome context window.
-    return;
-  } catch(e) {
-    // Ignore, it's good if we can't QI to a chrome window.
-  }
-
-  var winIsTop = this.windowIsTop(this.window);
-
-  for (var i = 0, script = null; script = aScripts[i]; i++) {
-    if (script.noframes && !winIsTop) continue;
-    var sandbox = createSandbox(script, this, gScope);
-    runScriptInSandbox(script, sandbox);
-  }
-};
-
-ScriptRunner.prototype.openInTab = function(aUrl, aInBackground) {
-  var loadInBackground = ('undefined' == typeof aInBackground)
-      ? null : !!aInBackground;
-
-  // Resolve URL relative to the location of the content window.
-  var baseUri = Services.io.newURI(this.window.location.href, null, null);
-  var uri = Services.io.newURI(aUrl, null, baseUri);
-
-  sendAsyncMessage('greasemonkey:open-in-tab', {
-    inBackground: loadInBackground,
-    url: uri.spec,
-  });
-};
-
-
-ScriptRunner.prototype.registeredMenuCommand = function(aCommand) {
-  var length = this.menuCommands.push(aCommand);
-
-  sendAsyncMessage('greasemonkey:menu-command-registered', {
-    accessKey: aCommand.accessKey,
-    frozen: aCommand.frozen,
-    index: length - 1,
-    name: aCommand.name,
-    scriptName: aCommand.scriptName,
-    windowId: aCommand.contentWindowId
-  });
-};
-
-ScriptRunner.prototype.windowIsTop = function(aContentWin) {
-  try {
-    aContentWin.QueryInterface(Ci.nsIDOMWindow);
-    if (aContentWin.frameElement) return false;
-  } catch (e) {
-    var url = 'unknown';
-    try {
-      url = aContentWin.location.href;
-    } catch (e) { }
-    // Ignore non-DOM-windows.
-    dump('Could not QI this.window to nsIDOMWindow at\n' + url + ' ?!\n');
-  }
-  return true;
-};
-
-// \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ //
-
 function ContentObserver() {
 }
 
@@ -112,50 +32,6 @@ ContentObserver.prototype.QueryInterface = XPCOMUtils.generateQI([
     Ci.nsIObserver]);
 
 
-ContentObserver.prototype.blankLoad = function(aEvent) {
-  var contentWin = aEvent.target.defaultView;
-  if (contentWin.location.href.match(/^about:blank/)) {
-    // #1696: document-element-inserted doesn't see about:blank
-    this.runScripts('document-end', contentWin);
-  }
-};
-
-ContentObserver.prototype.contentLoad = function(aEvent) {
-  var contentWin = aEvent.target.defaultView;
-
-  // Now that we've seen any first load event, stop listening for any more.
-  contentWin.removeEventListener('DOMContentLoaded', gContentLoad, true);
-  contentWin.removeEventListener('load', gContentLoad, true);
-
-  this.runScripts('document-end', contentWin);
-};
-
-
-ContentObserver.prototype.createScriptFromObject = function(aObject) {
-  var script = Object.create(IPCScript.prototype);
-  // TODO: better way for this? Object.create needs property descriptors.
-  for (var key in aObject) {
-    script[key] = aObject[key];
-  }
-  return script;
-};
-
-
-ContentObserver.prototype.loadFailedScript = function(aMessage) {
-  var url = aMessage.data.url;
-  var loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
-  var referer = aMessage.data.referer
-      && GM_util.uriFromUrl(aMessage.data.referer);
-  var postData = null;
-  var headers = null;
-
-  var webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
-
-  ignoreNextScript();
-  webNav.loadURI(url, loadFlags, referer, postData, headers);
-};
-
-
 ContentObserver.prototype.observe = function(aSubject, aTopic, aData) {
   if (!GM_util.getEnabled()) return;
 
@@ -172,102 +48,101 @@ ContentObserver.prototype.observe = function(aSubject, aTopic, aData) {
       if (!GM_util.isGreasemonkeyable(url)) return;
 
       // Listen for whichever kind of load event arrives first.
-      win.addEventListener('DOMContentLoaded', gContentLoad, true);
-      win.addEventListener('load', gContentLoad, true);
+      win.addEventListener('DOMContentLoaded', contentLoad, true);
+      win.addEventListener('load', contentLoad, true);
 
-      this.runScripts('document-start', win);
+      runScripts('document-start', win);
       break;
     default:
       dump('Content frame observed unknown topic: ' + aTopic + '\n');
   }
 };
 
+// \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ //
 
-ContentObserver.prototype.pagehide = function(aEvent) {
+function blankLoad(aEvent) {
   var contentWin = aEvent.target.defaultView;
-  var windowId = GM_util.windowId(contentWin);
-
-  if (!windowId || !gScriptRunners[windowId]) return;
-
-  // Small optimization: only send a notification if there's a menu command
-  // for this window.
-  if (!gScriptRunners[windowId].menuCommands.length) return;
-
-  if (aEvent.persisted) {
-    sendAsyncMessage('greasemonkey:toggle-menu-commands', {
-      frozen: true,
-      windowId: windowId
-    });
-  } else {
-    sendAsyncMessage('greasemonkey:clear-menu-commands', {
-      windowId: windowId
-    });
+  if (contentWin.location.href.match(/^about:blank/)) {
+    // #1696: document-element-inserted doesn't see about:blank
+    runScripts('document-end', contentWin);
   }
-};
+}
 
 
-ContentObserver.prototype.pageshow = function(aEvent) {
+function contentLoad(aEvent) {
   var contentWin = aEvent.target.defaultView;
-  var windowId = GM_util.windowId(contentWin);
 
-  if (!windowId || !gScriptRunners[windowId]) return;
+  // Now that we've seen any first load event, stop listening for any more.
+  contentWin.removeEventListener('DOMContentLoaded', contentLoad, true);
+  contentWin.removeEventListener('load', contentLoad, true);
+
+  runScripts('document-end', contentWin);
+}
 
-  if (!gScriptRunners[windowId].menuCommands.length) return;
 
-  sendAsyncMessage('greasemonkey:toggle-menu-commands', {
-    frozen: false,
-    windowId: windowId
-  });
+function createScriptFromObject(aObject) {
+  var script = Object.create(IPCScript.prototype);
+  // TODO: better way for this? Object.create needs property descriptors.
+  for (var key in aObject) {
+    script[key] = aObject[key];
+  }
+  return script;
 };
 
 
-ContentObserver.prototype.runDelayedScript = function(aMessage) {
+function injectDelayedScript(aMessage) {
+  // TODO: Make this work again.
   var windowId = aMessage.data.windowId;
-  var scriptRunner = gScriptRunners[windowId];
-  if (!scriptRunner) return;
+  var windowMediator = Components
+      .classes['@mozilla.org/appshell/window-mediator;1']
+      .getService(Components.interfaces.nsIWindowMediator);
+  var win = windowMediator.getOuterWindowWithId(windowId);
 
-  var script = this.createScriptFromObject(aMessage.data.script);
-  scriptRunner.injectScripts([script]);
+  console.log('injectDelayedScript; windowId='+windowId+'; win='+win+'\n');
+//  injectScripts([script], win);
 };
 
 
-ContentObserver.prototype.runMenuCommand = function(aMessage) {
-  var windowId = aMessage.data.windowId;
-  if (!gScriptRunners[windowId]) return;
+function injectScripts(aScripts, aContentWin) {
+  try {
+    aContentWin.QueryInterface(Ci.nsIDOMChromeWindow);
+    // Never ever inject scripts into a chrome context window.
+    return;
+  } catch(e) {
+    // Ignore, it's good if we can't QI to a chrome window.
+  }
 
-  var index = aMessage.data.index;
-  var command = gScriptRunners[windowId].menuCommands[index];
-  if (!command || !command.commandFunc) return;
+  var url = urlForWin(aContentWin);
+  var winIsTop = windowIsTop(aContentWin);
 
-  // Ensure |this| is set to the sandbox object inside the command function.
-  command.commandFunc.call(null);
-};
+  for (var i = 0, script = null; script = aScripts[i]; i++) {
+    if (script.noframes && !winIsTop) continue;
+    var sandbox = createSandbox(script, aContentWin, url, gScope);
+    runScriptInSandbox(script, sandbox);
+  }
+}
 
 
-ContentObserver.prototype.runScripts = function(aRunWhen, aContentWin) {
-  // See #1970
-  // When content does (e.g.) history.replacestate() in an inline script,
-  // the location.href changes between document-start and document-end time.
-  // But the content can call replacestate() much later, too.  The only way to
-  // be consistent is to ignore it.  Luckily, the  document.documentURI does
-  // _not_ change, so always use it when deciding whether to run scripts.
-  var url = aContentWin.document.documentURI;
-  // But ( #1631 ) ignore user/pass in the URL.
-  url = url.replace(gStripUserPassRegexp, '$1');
+function loadFailedScript(aMessage) {
+  var url = aMessage.data.url;
+  var loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
+  var referer = aMessage.data.referer
+      && GM_util.uriFromUrl(aMessage.data.referer);
+  var postData = null;
+  var headers = null;
+
+  var webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
+
+  ignoreNextScript();
+  webNav.loadURI(url, loadFlags, referer, postData, headers);
+}
+
 
+function runScripts(aRunWhen, aContentWin) {
+  var url = urlForWin(aContentWin);
   if (!GM_util.isGreasemonkeyable(url)) return;
 
   var windowId = GM_util.windowId(aContentWin);
-  var scriptRunner = gScriptRunners[windowId];
-  if (!scriptRunner) {
-    scriptRunner = new ScriptRunner(aContentWin, url);
-    gScriptRunners[windowId] = scriptRunner;
-  } else if (scriptRunner.window !== aContentWin) {
-    // Sanity check, shouldn't be necessary.
-    // TODO: remove
-    dump('Script runner window changed for ' + url + ' at ' + aRunWhen + '\n');
-    scriptRunner.window = aContentWin;
-  }
 
   var response = sendSyncMessage(
     'greasemonkey:scripts-for-url', {
@@ -277,28 +152,53 @@ ContentObserver.prototype.runScripts = function(aRunWhen, aContentWin) {
     });
   if (!response || !response[0]) return;
 
-  var scripts = response[0].map(this.createScriptFromObject);
-  scriptRunner.injectScripts(scripts);
-};
+  var scripts = response[0].map(createScriptFromObject);
+  injectScripts(scripts, aContentWin);
+}
 
-// \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ //
 
-var contentObserver = new ContentObserver();
-var gContentLoad = contentObserver.contentLoad.bind(contentObserver);
+function urlForWin(aContentWin) {
+  // See #1970
+  // When content does (e.g.) history.replacestate() in an inline script,
+  // the location.href changes between document-start and document-end time.
+  // But the content can call replacestate() much later, too.  The only way to
+  // be consistent is to ignore it.  Luckily, the  document.documentURI does
+  // _not_ change, so always use it when deciding whether to run scripts.
+  var url = aContentWin.document.documentURI;
+  // But ( #1631 ) ignore user/pass in the URL.
+  return url.replace(gStripUserPassRegexp, '$1');
+}
+
+
+function windowIsTop(aContentWin) {
+  try {
+    aContentWin.QueryInterface(Ci.nsIDOMWindow);
+    if (aContentWin.frameElement) return false;
+  } catch (e) {
+    var url = 'unknown';
+    try {
+      url = aContentWin.location.href;
+    } catch (e) { }
+    // Ignore non-DOM-windows.
+    dump('Could not QI window to nsIDOMWindow at\n' + url + ' ?!\n');
+  }
+  return true;
+};
 
-addEventListener(
-    'DOMContentLoaded', contentObserver.blankLoad.bind(contentObserver));
+// \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ // \\ //
 
-addEventListener('pagehide', contentObserver.pagehide.bind(contentObserver));
-addEventListener('pageshow', contentObserver.pageshow.bind(contentObserver));
+addEventListener('DOMContentLoaded', blankLoad);
 
-addMessageListener('greasemonkey:inject-script',
-    contentObserver.runDelayedScript.bind(contentObserver));
-addMessageListener('greasemonkey:load-failed-script',
-  contentObserver.loadFailedScript.bind(contentObserver));
-addMessageListener('greasemonkey:menu-command-clicked',
-    contentObserver.runMenuCommand.bind(contentObserver));
+addMessageListener('greasemonkey:inject-delayed-script', injectDelayedScript);
+addMessageListener('greasemonkey:load-failed-script', loadFailedScript);
+addMessageListener('greasemonkey:menu-command-list', function(aMessage) {
+  MenuCommandListRequest(content, aMessage);
+});
+addMessageListener('greasemonkey:menu-command-run', function(aMessage) {
+  MenuCommandRun(content, aMessage);
+});
 
+var contentObserver = new ContentObserver();
 Services.obs.addObserver(contentObserver, 'document-element-inserted', false);
 addEventListener('unload', function() {
   Services.obs.removeObserver(contentObserver, 'document-element-inserted');
diff --git a/content/menucommander.js b/content/menucommander.js
index f01f7d6..20d8c4b 100644
--- a/content/menucommander.js
+++ b/content/menucommander.js
@@ -1,56 +1,29 @@
 Components.utils.import('chrome://greasemonkey-modules/content/util.js');
 
 var GM_MenuCommander = {
-  menuCommands: {}
+  popup: null,
+  cookieShowing: null,
+  menuCommands: {},
+  messageCookie: 1,
 };
 
+
 GM_MenuCommander.initialize = function() {
-  var messageManager = Components.classes["@mozilla.org/globalmessagemanager;1"]
+  var ppmm = Components
+      .classes["@mozilla.org/parentprocessmessagemanager;1"]
       .getService(Components.interfaces.nsIMessageListenerManager);
-
-  messageManager.addMessageListener('greasemonkey:menu-command-registered',
-      GM_MenuCommander.menuCommandRegistered);
-  messageManager.addMessageListener('greasemonkey:clear-menu-commands',
-      GM_MenuCommander.clearMenuCommands);
-  messageManager.addMessageListener('greasemonkey:toggle-menu-commands',
-      GM_MenuCommander.toggleMenuCommands);
+  ppmm.addMessageListener('greasemonkey:menu-command-response',
+      GM_MenuCommander.messageMenuCommandResponse);
 };
 
-GM_MenuCommander.menuCommandRegistered = function(aMessage) {
-  var windowId = aMessage.data.windowId;
-
-  if (!GM_MenuCommander.menuCommands[windowId]) {
-    GM_MenuCommander.menuCommands[windowId] = [];
-  }
-
-  var command = aMessage.data;
-  command.browser = aMessage.target;
-  GM_MenuCommander.menuCommands[windowId].push(command);
-};
-
-GM_MenuCommander.clearMenuCommands = function(aMessage) {
-  var windowId = aMessage.data.windowId;
-  if (!windowId) return;
-
-  delete GM_MenuCommander.menuCommands[windowId];
-};
-
-GM_MenuCommander.toggleMenuCommands = function(aMessage) {
-  var frozen = aMessage.data.frozen;
-  var windowId = aMessage.data.windowId;
-
-  GM_MenuCommander.withAllMenuCommandsForWindowId(windowId, function(index, command) {
-    command.frozen = frozen;
-  });
-};
 
 GM_MenuCommander.commandClicked = function(aCommand) {
-  aCommand.browser.messageManager.sendAsyncMessage("greasemonkey:menu-command-clicked", {
-    index: aCommand.index,
-    windowId: aCommand.windowId
-  });
+  gBrowser.selectedBrowser.messageManager.sendAsyncMessage(
+      'greasemonkey:menu-command-run',
+      {'cookie': aCommand.cookie});
 };
 
+
 GM_MenuCommander.createMenuItem = function(command) {
   var menuItem = document.createElement("menuitem");
   menuItem.setAttribute("label", command.name);
@@ -66,40 +39,37 @@ GM_MenuCommander.createMenuItem = function(command) {
   return menuItem;
 };
 
-GM_MenuCommander.onPopupHiding = function(aMenuPopup) {
-  // Asynchronously.  See #1632.
-  GM_util.timeout(function() { GM_util.emptyEl(aMenuPopup); }, 0);
-};
 
-GM_MenuCommander.onPopupShowing = function(aMenuPopup) {
-  // Add menu items for commands for the active window.
-  var haveCommands = false;
-  var windowId = GM_util.windowId(gBrowser.contentWindow);
-
-  if (windowId) {
-    GM_MenuCommander.withAllMenuCommandsForWindowId(
-        windowId,
-        function(index, command) {
-          if (command.frozen) return;
-          aMenuPopup.insertBefore(
-              GM_MenuCommander.createMenuItem(command),
-              aMenuPopup.firstChild);
-          haveCommands = true;
-        });
-  }
+GM_MenuCommander.messageMenuCommandResponse = function(aMessage) {
+  if (aMessage.data.cookie != GM_MenuCommander.cookieShowing) return;
 
-  aMenuPopup.parentNode.disabled = !haveCommands;
+  for (i in aMessage.data.commands) {
+    var command = aMessage.data.commands[i];
+    var menuItem = GM_MenuCommander.createMenuItem(command);
+    GM_MenuCommander.popup.appendChild(menuItem);
+  }
 };
 
-GM_MenuCommander.withAllMenuCommandsForWindowId = function(
-    aContentWindowId, aCallback) {
-  if (!aContentWindowId) return;
 
-  var commands = GM_MenuCommander.menuCommands[aContentWindowId];
-  if (!commands) return;
+GM_MenuCommander.onPopupHiding = function() {
+  // Asynchronously.  See #1632.
+  GM_util.timeout(function() { GM_util.emptyEl(GM_MenuCommander.popup); }, 0);
+};
+
 
-  var l = commands.length - 1;
-  for (var i = l, command = null; command = commands[i]; i--) {
-    aCallback(i, command);
+GM_MenuCommander.onPopupShowing = function(aEvent) {
+  if (!GM_MenuCommander.popup) {
+    GM_MenuCommander.popup = aEvent.target.querySelector(
+        'menupopup.greasemonkey-user-script-commands-popup');
   }
+
+  GM_MenuCommander.messageCookie++;
+  GM_MenuCommander.cookieShowing = GM_MenuCommander.messageCookie;
+
+  // Start empty ...
+  GM_util.emptyEl(GM_MenuCommander.popup);
+  // ... ask the selected browser to fill up our menu.
+  gBrowser.selectedBrowser.messageManager.sendAsyncMessage(
+      'greasemonkey:menu-command-list',
+      {'cookie': GM_MenuCommander.cookieShowing});
 };
diff --git a/modules/GM_openInTab.js b/modules/GM_openInTab.js
new file mode 100644
index 0000000..99585c7
--- /dev/null
+++ b/modules/GM_openInTab.js
@@ -0,0 +1,22 @@
+var EXPORTED_SYMBOLS = ['GM_openInTab'];
+
+var Cu = Components.utils;
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+
+Cu.import('resource://gre/modules/Services.jsm');
+
+
+function GM_openInTab(aFrame, aBaseUrl, aUrl, aInBackground) {
+  var loadInBackground = ('undefined' == typeof aInBackground)
+      ? null : !!aInBackground;
+
+  // Resolve URL relative to the location of the content window.
+  var baseUri = Services.io.newURI(aBaseUrl, null, null);
+  var uri = Services.io.newURI(aUrl, null, baseUri);
+
+  aFrame.sendAsyncMessage('greasemonkey:open-in-tab', {
+    inBackground: loadInBackground,
+    url: uri.spec,
+  });
+};
diff --git a/modules/menucommand.js b/modules/menucommand.js
index 8463575..c18d3b0 100644
--- a/modules/menucommand.js
+++ b/modules/menucommand.js
@@ -1,44 +1,87 @@
-var EXPORTED_SYMBOLS = ['registerMenuCommand'];
-
-Components.utils.import("resource://gre/modules/Services.jsm");
-Components.utils.import('chrome://greasemonkey-modules/content/util.js');
-
-var gMenuCommands = [];
-var gStringBundle = Services.strings.createBundle(
-    "chrome://greasemonkey/locale/greasemonkey.properties");
-
-function registerMenuCommand(
-    scriptRunner, script,
-    commandName, commandFunc, accessKey, unused, accessKey2
-) {
-  if (scriptRunner.window.top != scriptRunner.window) {
-    // Only register menu commands for the top level window.
-    return;
-  }
-
-  // Legacy support: if all five parameters were specified, (from when two
-  // were for accelerators) use the last one as the access key.
-  if ('undefined' != typeof accessKey2) {
-    accessKey = accessKey2;
-  }
-
-  if (accessKey
-      && (("string" != typeof accessKey) || (accessKey.length != 1))
-  ) {
-    throw new Error(
-        gStringBundle.GetStringFromName('error.menu-invalid-accesskey')
-            .replace('%1', commandName)
-        );
-  }
-
-  var command = {
-    name: commandName,
-    scriptName: script.localized.name,
-    accessKey: accessKey,
-    commandFunc: commandFunc,
-    contentWindowId: scriptRunner.windowId,
-    frozen: false
-  };
+var EXPORTED_SYMBOLS = [
+    'MenuCommandListRequest', 'MenuCommandRespond',
+    'MenuCommandRun', 'MenuCommandSandbox',
+    ];
+
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+
+// Frame scope: Pass "list menu commands" message into sandbox as event.
+function MenuCommandListRequest(aContent, aMessage) {
+  var e = new aContent.CustomEvent(
+      'greasemonkey-menu-command-list', {'detail': aMessage.data.cookie});
+  aContent.dispatchEvent(e);
+}
+
+
+// Callback from script scope, pass "list menu commands" response up to
+// parent process as a message.
+function MenuCommandRespond(aCookie, aData) {
+  var cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
+      .getService(Ci.nsIMessageSender);
+  cpmm.sendAsyncMessage(
+      'greasemonkey:menu-command-response',
+      {'commands': aData, 'cookie': aCookie});
+}
 
-  scriptRunner.registeredMenuCommand(command);
-};
+
+// Frame scope: Respond to the "run this menu command" message coming
+// from the parent, pass it into the sandbox.
+function MenuCommandRun(aContent, aMessage) {
+  var e = new aContent.CustomEvent(
+      'greasemonkey-menu-command-run', {'detail': aMessage.data.cookie});
+  aContent.dispatchEvent(e);
+}
+
+
+// This function is injected into the sandbox, in a private scope wrapper, BY
+// SOURCE.  Data and sensitive references are wrapped up inside its closure.
+function MenuCommandSandbox(
+    aScriptId, aScriptName, aCommandResponder, aFrameScope,
+    aInvalidAccesskeyErrorStr) {
+  // 1) Internally to this function's private scope, maintain a set of
+  // registered menu commands.
+  var commands = {};
+  var commandCookie = 0;
+  // 2) Respond to requests to list those registered commands.
+  addEventListener('greasemonkey-menu-command-list', function(e) {
+    aCommandResponder(e.detail, commands);
+  }, true);
+  // 3) Respond to requests to run those registered commands.
+  addEventListener('greasemonkey-menu-command-run', function(e) {
+    var command = commands[e.detail];
+    if (!command) {
+      throw new Error('Could not run requested menu command!');
+    } else {
+      command.commandFunc();
+    }
+  }, true);
+  // 4) Export the "register a command" API function to the sandbox scope.
+  this.GM_registerMenuCommand = function(
+      commandName, commandFunc, accessKey, unused, accessKey2) {
+    // Legacy support: if all five parameters were specified, (from when two
+    // were for accelerators) use the last one as the access key.
+    if ('undefined' != typeof accessKey2) {
+      accessKey = accessKey2;
+    }
+
+    if (accessKey
+        && (("string" != typeof accessKey) || (accessKey.length != 1))
+    ) {
+      throw new Error(aInvalidAccesskeyErrorStr.replace('%1', commandName));
+    }
+
+    var command = {
+      cookie: ++commandCookie,
+      name: commandName,
+      scriptId: aScriptId,
+      scriptName: aScriptName,
+      accessKey: accessKey,
+      commandFunc: commandFunc,
+    };
+    commands[command.cookie] = command;
+  };
+}
diff --git a/modules/sandbox.js b/modules/sandbox.js
index 2b0f63e..863b8d7 100644
--- a/modules/sandbox.js
+++ b/modules/sandbox.js
@@ -4,6 +4,7 @@ var Cu = Components.utils;
 var Ci = Components.interfaces;
 var Cc = Components.classes;
 
+Cu.import('chrome://greasemonkey-modules/content/GM_openInTab.js');
 Cu.import('chrome://greasemonkey-modules/content/GM_setClipboard.js');
 Cu.import("chrome://greasemonkey-modules/content/menucommand.js");
 Cu.import("chrome://greasemonkey-modules/content/miscapis.js");
@@ -14,29 +15,22 @@ Cu.import("chrome://greasemonkey-modules/content/xmlhttprequester.js");
 var gStringBundle = Cc["@mozilla.org/intl/stringbundle;1"]
     .getService(Ci.nsIStringBundleService)
     .createBundle("chrome://greasemonkey/locale/greasemonkey.properties");
+var gInvalidAccesskeyErrorStr = gStringBundle
+    .GetStringFromName('error.menu-invalid-accesskey');
 
 // Only a particular set of strings are allowed.  See: http://goo.gl/ex2LJ
 var gMaxJSVersion = "ECMAv5";
 
-// TODO: Remove this, see #1318.
-function alert(msg) {
-  Cc["@mozilla.org/embedcomp/prompt-service;1"]
-      .getService(Ci.nsIPromptService)
-      .alert(null, "Greasemonkey alert", msg);
-}
-
-function createSandbox(aScript, aScriptRunner, aMessageManager) {
-  var contentWin = aScriptRunner.window;
-  var url = aScriptRunner.url;
 
+function createSandbox(aScript, aContentWin, aUrl, aFrameScope) {
   if (GM_util.inArray(aScript.grants, 'none')) {
     // If there is an explicit none grant, use a plain unwrapped sandbox
     // with no other content.
     var contentSandbox = new Components.utils.Sandbox(
-        contentWin,
+        aContentWin,
         {
           'sandboxName': aScript.id,
-          'sandboxPrototype': contentWin,
+          'sandboxPrototype': aContentWin,
           'wantXrays': false,
         });
     // GM_info is always provided.
@@ -47,20 +41,14 @@ function createSandbox(aScript, aScriptRunner, aMessageManager) {
     Components.utils.evalInSandbox(
         'const unsafeWindow = window;', contentSandbox);
 
-    if (GM_util.compareFirefoxVersion("16.0") < 0) {
-      // See #1350.  The upstream bug was fixed in Firefox 16; apply workaround
-      // only in older versions.
-      contentSandbox.alert = alert;
-    }
-
     return contentSandbox;
   }
 
   var sandbox = new Components.utils.Sandbox(
-      [contentWin],
+      [aContentWin],
       {
         'sandboxName': aScript.id,
-        'sandboxPrototype': contentWin,
+        'sandboxPrototype': aContentWin,
         'wantXrays': true,
       });
 
@@ -76,17 +64,21 @@ function createSandbox(aScript, aScriptRunner, aMessageManager) {
   sandbox.exportFunction = Cu.exportFunction;
 
   if (GM_util.inArray(aScript.grants, 'GM_addStyle')) {
-    sandbox.GM_addStyle = GM_util.hitch(null, GM_addStyle, contentWin.document);
+    sandbox.GM_addStyle = GM_util.hitch(null, GM_addStyle, aContentWin.document);
   }
   if (GM_util.inArray(aScript.grants, 'GM_log')) {
     sandbox.GM_log = GM_util.hitch(new GM_ScriptLogger(aScript), 'log');
   }
 
   if (GM_util.inArray(aScript.grants, 'GM_registerMenuCommand')) {
-    sandbox.GM_registerMenuCommand = GM_util.hitch(null, registerMenuCommand, aScriptRunner, aScript);
+    Components.utils.evalInSandbox(MenuCommandSandbox.toSource(), sandbox);
+    sandbox.MenuCommandSandbox(
+        aScript.id, aScript.name, MenuCommandRespond, aFrameScope,
+        gInvalidAccesskeyErrorStr);
+    Components.utils.evalInSandbox('delete MenuCommandSandbox;', sandbox);
   }
 
-  var scriptStorage = new GM_ScriptStorageFront(aScript, aMessageManager, sandbox);
+  var scriptStorage = new GM_ScriptStorageFront(aScript, aFrameScope, sandbox);
   if (GM_util.inArray(aScript.grants, 'GM_deleteValue')) {
     sandbox.GM_deleteValue = GM_util.hitch(scriptStorage, 'deleteValue');
   }
@@ -119,12 +111,12 @@ function createSandbox(aScript, aScriptRunner, aMessageManager) {
   }
 
   if (GM_util.inArray(aScript.grants, 'GM_openInTab')) {
-    sandbox.GM_openInTab = GM_util.hitch(aScriptRunner, 'openInTab');
+    sandbox.GM_openInTab = GM_util.hitch(null, GM_openInTab, aFrameScope, aUrl);
   }
 
   if (GM_util.inArray(aScript.grants, 'GM_xmlhttpRequest')) {
     sandbox.GM_xmlhttpRequest = GM_util.hitch(
-        new GM_xmlhttpRequester(contentWin, url, sandbox),
+        new GM_xmlhttpRequester(aContentWin, aUrl, sandbox),
         'contentStartRequest');
   }
 
diff --git a/modules/script.js b/modules/script.js
index 4b90b32..a9abd6b 100644
--- a/modules/script.js
+++ b/modules/script.js
@@ -750,7 +750,7 @@ Script.prototype.updateFromNewScript = function(newScript, url, windowId, browse
 
         if (shouldRun) {
           pendingExec.browser.messageManager.sendAsyncMessage(
-              "greasemonkey:inject-script",
+              "greasemonkey:inject-delayed-script",
               {
                 windowId: pendingExec.windowId,
                 script: new IPCScript(this, gGreasemonkeyVersion)

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-mozext/greasemonkey.git



More information about the Pkg-mozext-commits mailing list