[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