[Pkg-mozext-commits] [greasemonkey] 01/43: [WIP] Move script injection into frame script for e10s.

David Prévot taffit at moszumanska.debian.org
Sun Feb 22 21:56:08 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 1ccf7e45aa71217f2827e55511dcff737d7be4a3
Author: Ventero <ventero at ventero.de>
Date:   Tue Sep 23 18:54:39 2014 +0200

    [WIP] Move script injection into frame script for e10s.
---
 components/greasemonkey.js |  82 ++++++++--------------------
 content/browser.js         |   7 ---
 content/config.js          |  12 +++--
 content/framescript.js     | 130 +++++++++++++++++++++++++++++++++++++++++++++
 modules/ipcscript.js       |  79 +++++++++++++++++++++++++++
 modules/miscapis.js        |   2 +
 modules/sandbox.js         |   2 +
 modules/script.js          |  25 ++++++---
 8 files changed, 263 insertions(+), 76 deletions(-)

diff --git a/components/greasemonkey.js b/components/greasemonkey.js
index cb0625a..c08f23b 100644
--- a/components/greasemonkey.js
+++ b/components/greasemonkey.js
@@ -8,10 +8,9 @@ var Cc = Components.classes;
 var Ci = Components.interfaces;
 var Cu = Components.utils;
 
-Cu.import("resource://greasemonkey/third-party/getChromeWinForContentWin.js");
+Cu.import("resource://greasemonkey/ipcscript.js");
 Cu.import("resource://greasemonkey/menucommand.js");
 Cu.import("resource://greasemonkey/prefmanager.js");
-Cu.import("resource://greasemonkey/sandbox.js");
 Cu.import("resource://greasemonkey/sync.js");
 Cu.import("resource://greasemonkey/util.js");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@@ -27,17 +26,8 @@ var gTmpDir = Components.classes["@mozilla.org/file/directory_service;1"]
     .getService(Components.interfaces.nsIProperties)
     .get("TmpD", Components.interfaces.nsIFile);
 
-var gStripUserPassRegexp = new RegExp('(://)([^:/]+)(:[^@/]+)?@');
-
 /////////////////////// Component-global Helper Functions //////////////////////
 
-function contentLoad(aEvent) {
-  var safeWin = aEvent.target.defaultView;
-  safeWin.removeEventListener('DOMContentLoaded', contentLoad, true);
-  safeWin.removeEventListener('load', contentLoad, true);
-  GM_util.getService().runScripts('document-end', safeWin);
-}
-
 function isTempScript(uri) {
   if (uri.scheme != "file") return false;
   var file = gFileProtocolHandler.getFileFromURLSpec(uri.spec);
@@ -58,6 +48,13 @@ function startup(aService) {
      .getService(Components.interfaces.nsIObserverService);
   observerService.addObserver(aService, 'document-element-inserted', false);
 
+  var messageManager = Cc["@mozilla.org/globalmessagemanager;1"]
+      .getService(Ci.nsIMessageListenerManager);
+
+  messageManager.addMessageListener('greasemonkey:scripts-for-url',
+      aService.getScriptsForUrl.bind(aService));
+  messageManager.loadFrameScript("chrome://greasemonkey/content/framescript.js", true);
+
   // Import this once, early, so that enqueued deletes can happen.
   Cu.import("resource://greasemonkey/util/enqueueRemoveFile.js");
 }
@@ -65,7 +62,6 @@ function startup(aService) {
 /////////////////////////////////// Service ////////////////////////////////////
 
 function service() {
-  this.contentLoad = contentLoad;
   this.filename = Components.stack.filename;
   this.wrappedJSObject = this;
 }
@@ -142,17 +138,6 @@ service.prototype.observe = function(aSubject, aTopic, aData) {
     case 'profile-after-change':
       startup(this);
       break;
-    case 'document-element-inserted':
-      if (!GM_util.getEnabled()) break;
-      var doc = aSubject;
-      var win = doc && doc.defaultView;
-      if (!doc || !win) break;
-
-      win.addEventListener('DOMContentLoaded', contentLoad, true);
-      win.addEventListener('load', contentLoad, true);
-      this.runScripts('document-start', win);
-
-      break;
   }
 };
 
@@ -171,60 +156,39 @@ service.prototype.__defineGetter__('config', function() {
   return this._config;
 });
 
-service.prototype.runScripts = function(aRunWhen, aWrappedContentWin) {
-  // 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 = aWrappedContentWin.document.documentURI;
-  // But ( #1631 ) ignore user/pass in the URL.
-  url = url.replace(gStripUserPassRegexp, '$1');
+service.prototype.getScriptsForUrl = function(aMessage) {
+  var url = aMessage.data.url;
+  var when = aMessage.data.when;
+  var windowId = aMessage.data.windowId;
+  var browser = aMessage.target;
 
-  if (!GM_util.getEnabled() || !GM_util.isGreasemonkeyable(url)) return;
+  if (!GM_util.getEnabled() || !url) return [];
+  if (!GM_util.isGreasemonkeyable(url)) return [];
 
   if (GM_prefRoot.getValue('enableScriptRefreshing')) {
-    this._config.updateModifiedScripts(aRunWhen, aWrappedContentWin);
+    this.config.updateModifiedScripts(when, url, windowId, browser);
   }
 
   var scripts = this.config.getMatchingScripts(function(script) {
     try {
-      return GM_util.scriptMatchesUrlAndRuns(script, url, aRunWhen);
+      return GM_util.scriptMatchesUrlAndRuns(script, url, when);
     } catch (e) {
       GM_util.logError(e, false, e.fileName, e.lineNumber);
       // See #1692; Prevent failures like that from being so severe.
       return false;
     }
+  }).map(function(script) {
+    // Make the script serializable so it can be sent to the frame script.
+    return new IPCScript(script);
   });
-  if (scripts.length > 0) {
-    this.injectScripts(scripts, url, aWrappedContentWin);
-  }
-};
+
+  return scripts;
+}
 
 service.prototype.ignoreNextScript = function() {
   this._ignoreNextScript = true;
 };
 
-service.prototype.injectScripts = function(
-    scripts, url, wrappedContentWin
-) {
-  try {
-    wrappedContentWin.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 chromeWin = getChromeWinForContentWin(wrappedContentWin);
-
-  for (var i = 0, script = null; script = scripts[i]; i++) {
-    var sandbox = createSandbox(script, wrappedContentWin, url);
-    runScriptInSandbox(script, sandbox);
-  }
-};
-
 //////////////////////////// Component Registration ////////////////////////////
 
 var NSGetFactory = XPCOMUtils.generateNSGetFactory([service]);
diff --git a/content/browser.js b/content/browser.js
index aa42277..f34a564 100644
--- a/content/browser.js
+++ b/content/browser.js
@@ -45,9 +45,6 @@ GM_BrowserUI.chromeLoad = function(e) {
   gBrowser.addEventListener("pageshow", GM_BrowserUI.pageshow, true);
 
   var sidebar = document.getElementById("sidebar");
-  var svc = GM_util.getService();
-  sidebar.addEventListener(
-      "DOMContentLoaded", GM_util.hitch(svc, svc.contentLoad), true);
   sidebar.addEventListener("pagehide", GM_BrowserUI.pagehide, true);
   sidebar.addEventListener("pageshow", GM_BrowserUI.pageshow, true);
 
@@ -56,10 +53,6 @@ GM_BrowserUI.chromeLoad = function(e) {
         var safeWin = aEvent.target.defaultView;
         var href = safeWin.location.href;
         GM_BrowserUI.checkDisabledScriptNavigation(aEvent, safeWin, href);
-        if (0 == href.indexOf('about:blank')) {
-          // #1696: document-element-inserted doesn't see about:blank
-          svc.contentLoad(aEvent);
-        }
       }, true);
 
   document.getElementById("contentAreaContextMenu")
diff --git a/content/config.js b/content/config.js
index abce40c..cfdf93e 100644
--- a/content/config.js
+++ b/content/config.js
@@ -220,7 +220,9 @@ Config.prototype.getMatchingScripts = function(testFunc) {
   return this._scripts.filter(testFunc);
 };
 
-Config.prototype.updateModifiedScripts = function(aWhen, aSafeWin) {
+Config.prototype.updateModifiedScripts = function(
+    aWhen, aUrl, aWindowId, aBrowser
+) {
   // Find any updated scripts or scripts with delayed injection
   var scripts = this.getMatchingScripts(
       function (script) {
@@ -236,11 +238,15 @@ Config.prototype.updateModifiedScripts = function(aWhen, aSafeWin) {
       var parsedScript = scope.parse(
           script.textContent, GM_util.uriFromUrl(script.downloadURL));
       // TODO: Show PopupNotifications about parse error(s)?
-      script.updateFromNewScript(parsedScript, aSafeWin);
+      script.updateFromNewScript(parsedScript, aUrl, aWindowId, aBrowser);
     } else {
       // We are already downloading dependencies for this script
       // so add its window to the list
-      script.pendingExec.push({'safeWin': aSafeWin});
+      script.pendingExec.push({
+        'browser': aBrowser,
+        'url': aUrl,
+        'windowId': aWindowId
+      });
     }
   }
 
diff --git a/content/framescript.js b/content/framescript.js
new file mode 100644
index 0000000..6d7dd0a
--- /dev/null
+++ b/content/framescript.js
@@ -0,0 +1,130 @@
+let {utils: Cu, interfaces: Ci, classes: Cc} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+Cu.import('resource://greasemonkey/GM_setClipboard.js');
+Cu.import("resource://greasemonkey/ipcscript.js");
+Cu.import("resource://greasemonkey/miscapis.js");
+Cu.import("resource://greasemonkey/sandbox.js");
+Cu.import('resource://greasemonkey/util.js');
+
+
+var gStripUserPassRegexp = new RegExp('(://)([^:/]+)(:[^@/]+)?@');
+var gScriptRunners = {};
+
+function ScriptRunner(aWindow) {
+  this.window = aWindow;
+}
+
+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.
+  }
+
+  for (var i = 0, script = null; script = aScripts[i]; i++) {
+    var sandbox = createSandbox(script, this.window);
+    runScriptInSandbox(script, sandbox);
+  }
+}
+
+var observer = {
+  observe: function(aSubject, aTopic, aData) {
+    if (!GM_util.getEnabled()) return;
+
+    switch (aTopic) {
+      case 'document-element-inserted':
+        var doc = aSubject;
+        var win = doc && doc.defaultView;
+
+        if (!doc || !win) break;
+
+        // TODO:
+        // Sometimes we get this notification twice with different windows but
+        // identical documentURI/location.href. In one of those cases, the call
+        // to sendSyncMessage will throw, and I can't find a way to detect which
+        // notification is the correct one. if (win !== content) would also
+        // exclude iframes.
+        this.runScripts('document-start', win);
+        break;
+      case 'inner-window-destroyed':
+        // Make sure we don't keep any window references around
+        var windowId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
+        delete gScriptRunners[windowId];
+        break;
+      default:
+        dump("received unknown topic: " + aTopic + "\n");
+    }
+  },
+
+  contentLoad: function(aEvent) {
+    if (!GM_util.getEnabled()) return;
+
+    var contentWin = aEvent.target.defaultView;
+    this.runScripts('document-end', contentWin);
+  },
+
+  runScripts: function(aRunWhen, aWrappedContentWin) {
+    // 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 = aWrappedContentWin.document.documentURI
+    // But ( #1631 ) ignore user/pass in the URL.
+    url = url.replace(gStripUserPassRegexp, '$1');
+
+    if (!GM_util.isGreasemonkeyable(url))
+      return;
+
+    var windowId = GM_util.windowId(aWrappedContentWin);
+    if (gScriptRunners[windowId]) {
+      // Update the window in case it changed, see the comment in observe().
+      gScriptRunners[windowId].window = aWrappedContentWin;
+    } else {
+      gScriptRunners[windowId] = new ScriptRunner(aWrappedContentWin);
+    }
+
+    var response = sendSyncMessage('greasemonkey:scripts-for-url', {
+      'url': url,
+      'when': aRunWhen,
+      'windowId': windowId
+    });
+
+    if (!response || !response[0]) return;
+    var scripts = response[0].map(this.createScriptFromObject);
+    gScriptRunners[windowId].injectScripts(scripts);
+  },
+
+  runDelayedScript: function(aMessage) {
+    var windowId = aMessage.data.windowId;
+    if (!gScriptRunners[windowId]) return;
+
+    var script = this.createScriptFromObject(aMessage.data.script);
+    gScriptRunners[windowId].injectScripts([script]);
+  },
+
+  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;
+  }
+}
+
+var observerService = Cc['@mozilla.org/observer-service;1']
+    .getService(Ci.nsIObserverService);
+observerService.addObserver(observer, 'document-element-inserted', false);
+observerService.addObserver(observer, 'inner-window-destroyed', false);
+
+addEventListener("DOMContentLoaded", observer.contentLoad.bind(observer));
+addEventListener("load", observer.contentLoad.bind(observer));
+
+addMessageListener("greasemonkey:inject-script",
+    observer.runDelayedScript.bind(observer));
diff --git a/modules/ipcscript.js b/modules/ipcscript.js
new file mode 100644
index 0000000..87f6794
--- /dev/null
+++ b/modules/ipcscript.js
@@ -0,0 +1,79 @@
+var EXPORTED_SYMBOLS = ['IPCScript'];
+
+Components.utils.import("resource://greasemonkey/extractMeta.js");
+Components.utils.import("resource://greasemonkey/util.js");
+
+function IPCScript(aScript) {
+  this.description = aScript.description;
+  this.excludes = aScript.excludes;
+  this.fileURL = aScript.fileURL;
+  this.grants = aScript.grants;
+  this.id = aScript.id;
+  this.includes = aScript.includes;
+  this.localized = aScript.localized;
+  this.matches = aScript.matches.map(function(m) { return m.pattern });
+  this.name = aScript.name;
+  this.namespace = aScript.namespace;
+  this.runAt = aScript.runAt;
+  this.textContent = aScript.textContent;
+  this.uuid = aScript.uuid;
+  this.version = aScript.version;
+  this.willUpdate = aScript.isRemoteUpdateAllowed();
+
+  this.requires = aScript.requires.map(function(req) {
+    return {
+      'fileURL': req.fileURL,
+      'textContent': req.textContent
+    }
+  });
+
+  this.resources = aScript.resources.map(function(res) {
+    return {
+      'name': res.name,
+      'mimetype': res.mimetype,
+      'textContent': res.textContent
+    };
+  });
+};
+
+IPCScript.prototype.__defineGetter__('metaStr',
+function IPCScript_getMetaStr() {
+  if (!this._metaStr) {
+    this._metaStr = extractMeta(this.textContent);
+  }
+
+  return this._metaStr;
+});
+
+IPCScript.prototype.info = function() {
+  var resources = {};
+  for (var i = 0, r = null; r = this.resources[i]; i++) {
+    resources[r.name] = {
+        'name': r.name,
+        'mimetype': r.mimetype,
+        };
+  }
+
+  return {
+    'uuid': this.uuid,
+    'version': "unknown", // TODO
+    'scriptMetaStr': this.metaStr,
+    'scriptSource': this.textContent,
+    'scriptWillUpdate': this.willUpdate,
+    'script': {
+      'description': this.desciption,
+      'excludes': this.excludes,
+      // 'icon': ??? source URL?
+      'includes': this.includes,
+      'localizedDescription': this.localized.description,
+      'localizedName': this.localized.name,
+      'matches': this.matches,
+      'name': this.name,
+      'namespace': this.namespace,
+      // 'requires': ??? source URL?
+      'resources': resources,
+      'run-at': this.runAt,
+      'version': this.version
+    }
+  }
+};
diff --git a/modules/miscapis.js b/modules/miscapis.js
index c4056b2..e5b99d9 100644
--- a/modules/miscapis.js
+++ b/modules/miscapis.js
@@ -300,6 +300,8 @@ function GM_openInTab(safeContentWin, url, aLoadInBackground) {
   // Get the chrome window currently corresponding to the content window, as
   // this might have changed since the script was injected (e.g. by moving
   // the tab to a different window).
+  // TODO: sendAsyncMessage("...", {url}), look for browser containing message.target,
+  //       add tab to that browser
   var chromeWin = getChromeWinForContentWin(safeContentWin);
   var browser = chromeWin.gBrowser;
   var currentTab = browser.tabs[
diff --git a/modules/sandbox.js b/modules/sandbox.js
index 060aacc..d19dbe2 100644
--- a/modules/sandbox.js
+++ b/modules/sandbox.js
@@ -34,6 +34,7 @@ function createSandbox(aScript, aContentWin, aUrl) {
           'wantXrays': false,
         });
     // GM_info is always provided.
+    // TODO: lazy getter? XPCOMUtils.defineLazyGetter
     Components.utils.evalInSandbox(
         'const GM_info = ' + uneval(aScript.info()), contentSandbox);
     // Alias unsafeWindow for compatibility.
@@ -116,6 +117,7 @@ function createSandbox(aScript, aContentWin, aUrl) {
         'contentStartRequest');
   }
 
+  // TODO: lazy getter?
   Components.utils.evalInSandbox(
       'const GM_info = ' + uneval(aScript.info()), sandbox);
 
diff --git a/modules/script.js b/modules/script.js
index 3766cfe..29cf98b 100644
--- a/modules/script.js
+++ b/modules/script.js
@@ -4,6 +4,7 @@ Components.utils.import('resource://gre/modules/AddonManager.jsm');
 Components.utils.import('resource://greasemonkey/GM_notification.js');
 Components.utils.import('resource://greasemonkey/constants.js');
 Components.utils.import('resource://greasemonkey/extractMeta.js');
+Components.utils.import('resource://greasemonkey/ipcscript.js');
 Components.utils.import('resource://greasemonkey/miscapis.js');
 Components.utils.import("resource://greasemonkey/parseScript.js");
 Components.utils.import('resource://greasemonkey/prefmanager.js');
@@ -584,7 +585,7 @@ Script.prototype.isRemoteUpdateAllowed = function(aForced) {
   }
 };
 
-Script.prototype.updateFromNewScript = function(newScript, safeWin) {
+Script.prototype.updateFromNewScript = function(newScript, url, windowId, browser) {
   // Keep a _copy_ of the old script ID, so we can eventually pass it up
   // to the Add-ons manager UI, to update this script's old entry.
   var oldScriptId = '' + this.id;
@@ -636,8 +637,12 @@ Script.prototype.updateFromNewScript = function(newScript, safeWin) {
           this.id + "\nNot running at document-start; waiting for update ...",
           true);
       this.pendingExec.push('document-start update');
-    } else {
-      if (safeWin) this.pendingExec.push({'safeWin': safeWin});
+    } else if (windowId) {
+      this.pendingExec.push({
+        'browser': browser,
+        'url': url,
+        'windowId': windowId
+      });
     }
 
     // Re-download dependencies.
@@ -704,11 +709,17 @@ Script.prototype.updateFromNewScript = function(newScript, safeWin) {
               true);
           continue;
         }
-        if (pendingExec.safeWin.closed) continue;
-        var url = pendingExec.safeWin.location.href;
-        var shouldRun = GM_util.scriptMatchesUrlAndRuns(this, url, this.runAt);
+
+        var shouldRun = GM_util.scriptMatchesUrlAndRuns(
+            this, pendingExec.url, this.runAt);
+
         if (shouldRun) {
-          GM_util.getService().injectScripts([this], url, pendingExec.safeWin);
+          pendingExec.browser.messageManager.sendAsyncMessage(
+              "greasemonkey:inject-script",
+              {
+                windowId: pendingExec.windowId,
+                script: new IPCScript(this)
+              });
         }
       }
 

-- 
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