[Pkg-mozext-commits] [requestpolicy] 200/280: guarantee successful Framescript -> Overlay comm.

David Prévot taffit at moszumanska.debian.org
Sat May 2 20:30:26 UTC 2015


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

taffit pushed a commit to branch master
in repository requestpolicy.

commit 7486a573d7a6fca1c41a2a37d60d1d06872d48e6
Author: Martin Kimmerle <dev at 256k.de>
Date:   Mon Feb 2 05:34:25 2015 +0100

    guarantee successful Framescript -> Overlay comm.
    
    This commit ensures that Framescript to Overlay communication
    is always successful, even if the Framescript starts up faster
    than the Overlay. Fixes #594.
---
 src/content/lib/environment.jsm                    |   6 +-
 .../lib/framescript-to-overlay-communication.jsm   | 165 +++++++++++++++++++++
 src/content/lib/manager-for-message-listeners.jsm  |  15 +-
 src/content/ui/frame.dom-content-loaded.js         |  85 ++++++-----
 src/content/ui/frame.js                            |  26 ++--
 src/content/ui/overlay.js                          |   8 +
 6 files changed, 255 insertions(+), 50 deletions(-)

diff --git a/src/content/lib/environment.jsm b/src/content/lib/environment.jsm
index 200e3b7..cbe6396 100644
--- a/src/content/lib/environment.jsm
+++ b/src/content/lib/environment.jsm
@@ -513,16 +513,18 @@ function FrameScriptEnvironment(aMM, aName="frame script environment") {
 
   Environment.call(self, _outerEnv, aName);
 
+  self.mm = aMM;
+
   self.addStartupFunction(LEVELS.INTERFACE, function() {
     // shut down the framescript on the message manager's
     // `unload`. That event will occur when the browsing context
     // (e.g. the tab) has been closed.
-    self.shutdownOnUnload(aMM);
+    self.shutdownOnUnload(self.mm);
   });
 
   // a "MessageListener"-Manager for this environment
   XPCOMUtils.defineLazyGetter(self, "mlManager", function() {
-    return new ManagerForMessageListeners(self, aMM);
+    return new ManagerForMessageListeners(self, self.mm);
   });
 }
 FrameScriptEnvironment.prototype = Object.create(Environment.prototype);
diff --git a/src/content/lib/framescript-to-overlay-communication.jsm b/src/content/lib/framescript-to-overlay-communication.jsm
new file mode 100644
index 0000000..f1ba449
--- /dev/null
+++ b/src/content/lib/framescript-to-overlay-communication.jsm
@@ -0,0 +1,165 @@
+/*
+ * ***** BEGIN LICENSE BLOCK *****
+ *
+ * RequestPolicy - A Firefox extension for control over cross-site requests.
+ * Copyright (c) 2008-2012 Justin Samuel
+ * Copyright (c) 2014-2015 Martin Kimmerle
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * ***** END LICENSE BLOCK *****
+ */
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cu = Components.utils;
+
+let EXPORTED_SYMBOLS = ["FramescriptToOverlayCommunication"];
+
+let globalScope = this;
+
+Cu.import("chrome://requestpolicy/content/lib/script-loader.jsm");
+ScriptLoader.importModules([
+  "lib/environment",
+  "lib/logger",
+  "lib/utils/constants"
+], globalScope);
+
+
+/**
+ * The states of the communication channel to with the overlay.
+ * @enum {number}
+ */
+let States = Object.freeze({
+  "WAITING": 0,
+  "RUNNING": 1,
+  "STOPPED": 2
+});
+
+
+/**
+ * Sometimes the framescript loads and starts up faster than the
+ * Overlay of the corresponding window. This is due to the async
+ * nature of framescripts. As a result, the overlay does not
+ * receive those early messages from the framescript.
+ *
+ * This class helps to ensure that any message to the overlay is
+ * actually being received. Instances take functions ("runnables")
+ * that will be called as soon as the overlay is ready. If the
+ * overlay is already in the "RUNNING" state, the function will be
+ * called immediately.
+ *
+ * @param {Environment} aEnv - The environment to which the
+ *     communication channel's lifetime will be bound to.
+ * @constructor
+ */
+function FramescriptToOverlayCommunication(aEnv) {
+  let self = this;
+
+  /**
+   * A queue of runnables that wait for the overlay to be ready.
+   * As it's a queue, the functions `pop` and `shift` have to be
+   * used.
+   * @type  {Array.<function>}
+   */
+  self.waitingRunnables = [];
+
+  /**
+   * The state of the communication
+   * @type {States}
+   */
+  self.state = States.WAITING;
+
+  /**
+   * @type {Environment}
+   */
+  self.env = aEnv;
+
+  self.env.addStartupFunction(Environment.LEVELS.INTERFACE,
+                              startCommNowOrLater.bind(null, self));
+  self.env.addShutdownFunction(Environment.LEVELS.INTERFACE,
+                               stopCommunication.bind(null, self));
+}
+
+function dump(self, msg) {
+  Logger.dump(self.env.uid + ": " + msg);
+}
+
+/**
+ * Check whether the Overlay is ready. If it is, start the
+ * communication. If not, wait for the overlay to be ready.
+ *
+ * @param {FramescriptToOverlayCommunication} self
+ */
+function startCommNowOrLater(self) {
+  let answers = self.env.mm.sendSyncMessage(C.MM_PREFIX + "isOverlayReady");
+  if (answers.length > 0 && answers[0] === true) {
+    startCommunication(self);
+  } else {
+    // The Overlay is not ready yet, so listen for the message.
+    // Add the listener immediately.
+    self.env.mlManager.addListener("overlayIsReady",
+                                   startCommunication.bind(null, self),
+                                   true);
+  }
+}
+
+/**
+ * The overlay is ready.
+ *
+ * @param {FramescriptToOverlayCommunication} self
+ */
+function startCommunication(self) {
+  if (self.state === States.WAITING) {
+    //dump(self, "The Overlay is ready!");
+    self.state = States.RUNNING;
+
+    while (self.waitingRunnables.length !== 0) {
+      let runnable = self.waitingRunnables.shift();
+      //dump(self, "Lazily running function.");
+      runnable.call(null);
+    }
+  }
+}
+
+/**
+ * @param {FramescriptToOverlayCommunication} self
+ */
+function stopCommunication(self) {
+  self.state = States.STOPPED;
+}
+
+/**
+ * Add a function that will be called now or later.
+ *
+ * @param {function} aRunnable
+ */
+FramescriptToOverlayCommunication.prototype.run = function(aRunnable) {
+  let self = this;
+  switch (self.state) {
+    case States.RUNNING:
+      //dump(self, "Immediately running function.");
+      aRunnable.call(null);
+      break;
+
+    case States.WAITING:
+      //dump(self, "Remembering runnable.");
+      self.waitingRunnables.push(aRunnable);
+      break;
+
+    default:
+      //dump(self, "Ignoring runnable.");
+      break;
+  }
+};
diff --git a/src/content/lib/manager-for-message-listeners.jsm b/src/content/lib/manager-for-message-listeners.jsm
index 61a16cd..35a2830 100644
--- a/src/content/lib/manager-for-message-listeners.jsm
+++ b/src/content/lib/manager-for-message-listeners.jsm
@@ -97,8 +97,19 @@ function ManagerForMessageListeners(aEnv, aMM) {
 }
 
 
+/**
+ * Add a listener. The class will then take care about adding
+ * and removing that message listener.
+ *
+ * @param {string} aMessageName
+ * @param {function} aCallback
+ * @param {boolean} aAddImmediately - Whether the listener should be
+ *     added immediately, i.e. without waiting for the environment
+ *     to start up.
+ */
 ManagerForMessageListeners.prototype.addListener = function(aMessageName,
-                                                            aCallback) {
+                                                            aCallback,
+                                                            aAddImmediately) {
   let self = this;
   if (typeof aCallback !== 'function') {
     Logger.warning(Logger.TYPE_ERROR, "The callback for a message listener" +
@@ -120,7 +131,7 @@ ManagerForMessageListeners.prototype.addListener = function(aMessageName,
     callback: aCallback,
     listening: false
   };
-  if (self.addNewListenersImmediately) {
+  if (aAddImmediately === true || self.addNewListenersImmediately) {
     Logger.dump('Immediately adding message listener for "' +
                 listener.messageName + '". Environment: "' +
                 self.environment.name + '"');
diff --git a/src/content/ui/frame.dom-content-loaded.js b/src/content/ui/frame.dom-content-loaded.js
index f829793..9809156 100644
--- a/src/content/ui/frame.dom-content-loaded.js
+++ b/src/content/ui/frame.dom-content-loaded.js
@@ -32,9 +32,11 @@ let ManagerForDOMContentLoaded = (function() {
     // Notify the main thread that a link has been clicked.
     // Note: The <a> element is `currentTarget`! See:
     // https://developer.mozilla.org/en-US/docs/Web/API/Event.currentTarget
-    mm.sendSyncMessage(C.MM_PREFIX + "notifyLinkClicked",
-                       {origin: event.currentTarget.ownerDocument.URL,
-                        dest: event.currentTarget.href});
+    overlayComm.run(function() {
+      mm.sendSyncMessage(C.MM_PREFIX + "notifyLinkClicked",
+                         {origin: event.currentTarget.ownerDocument.URL,
+                          dest: event.currentTarget.href});
+    });
   }
 
 
@@ -67,32 +69,33 @@ let ManagerForDOMContentLoaded = (function() {
 
     onDocumentLoaded(doc);
 
-    let answers = mm.sendSyncMessage(C.MM_PREFIX + "notifyDocumentLoaded",
-                                     {documentURI: doc.documentURI});
-    if (answers.length === 0) {
-      Logger.warning(Logger.TYPE_ERROR, 'There seems to be no message ' +
-                     'listener for "notifyDocumentLoaded".');
-    } else {
-      // take only one answer. If there are more answers, they are ignored
-      // ==> there must be only one listener for 'notifyDocumentLoaded'
-      let answer = answers[0];
-
-      var blockedURIs = answer.blockedURIs;
-      //console.debug("Received " +
-      //              Object.getOwnPropertyNames(blockedURIs).length +
-      //              " blocked URIs.");
-
-      // Indicating blocked visible objects isn't an urgent task, so this should
-      // be done async.
-      Utils.runAsync(function() {
-        ManagerForBlockedContent.indicateBlockedVisibleObjects(doc, blockedURIs);
-      });
-    }
-
+    overlayComm.run(function() {
+      let answers = mm.sendSyncMessage(C.MM_PREFIX + "notifyDocumentLoaded",
+                                       {documentURI: doc.documentURI});
+      if (answers.length === 0) {
+        Logger.warning(Logger.TYPE_ERROR, 'There seems to be no message ' +
+                       'listener for "notifyDocumentLoaded".');
+      } else {
+        // take only one answer. If there are more answers, they are ignored
+        // ==> there must be only one listener for 'notifyDocumentLoaded'
+        let answer = answers[0];
+
+        var blockedURIs = answer.blockedURIs;
+        //console.debug("Received " +
+        //              Object.getOwnPropertyNames(blockedURIs).length +
+        //              " blocked URIs.");
+
+        // Indicating blocked visible objects isn't an urgent task, so this should
+        // be done async.
+        Utils.runAsync(function() {
+          ManagerForBlockedContent.indicateBlockedVisibleObjects(doc, blockedURIs);
+        });
+      }
 
-    if (isActiveTopLevelDocument(doc)) {
-      mm.sendAsyncMessage(C.MM_PREFIX + "notifyTopLevelDocumentLoaded");
-    }
+      if (isActiveTopLevelDocument(doc)) {
+        mm.sendAsyncMessage(C.MM_PREFIX + "notifyTopLevelDocumentLoaded");
+      }
+    });
   }
 
   /**
@@ -117,7 +120,9 @@ let ManagerForDOMContentLoaded = (function() {
     // other origins every time an iframe is loaded. Maybe, then, this should
     // use a timeout like observerBlockedRequests does.
     if (isActiveTopLevelDocument(iframe.ownerDocument)) {
-      mm.sendAsyncMessage(C.MM_PREFIX + "notifyDOMFrameContentLoaded");
+      overlayComm.run(function() {
+        mm.sendAsyncMessage(C.MM_PREFIX + "notifyDOMFrameContentLoaded");
+      });
     }
   }
 
@@ -188,8 +193,10 @@ let ManagerForDOMContentLoaded = (function() {
             "Another extension disabled docShell.allowMetaRedirects.");
       }
 
-      mm.sendAsyncMessage(C.MM_PREFIX + "handleMetaRefreshes",
-          {documentURI: documentURI, metaRefreshes: metaRefreshes});
+      overlayComm.run(function() {
+        mm.sendAsyncMessage(C.MM_PREFIX + "handleMetaRefreshes",
+            {documentURI: documentURI, metaRefreshes: metaRefreshes});
+      });
     }
 
     // Find all anchor tags and add click events (which also fire when enter
@@ -272,17 +279,21 @@ let ManagerForDOMContentLoaded = (function() {
   function wrapWindowFunctions(aWindow) {
     wrapWindowFunction(aWindow, "open",
         function(url, windowName, windowFeatures) {
-          mm.sendSyncMessage(C.MM_PREFIX + "notifyLinkClicked",
-                             {origin: aWindow.document.documentURI,
-                              dest: url});
+          overlayComm.run(function() {
+            mm.sendSyncMessage(C.MM_PREFIX + "notifyLinkClicked",
+                               {origin: aWindow.document.documentURI,
+                                dest: url});
+          });
         });
 
     wrapWindowFunction(aWindow, "openDialog",
         function() {
           // openDialog(url, name, features, arg1, arg2, ...)
-          mm.sendSyncMessage(C.MM_PREFIX + "notifyLinkClicked",
-                             {origin: aWindow.document.documentURI,
-                              dest: arguments[0]});
+          overlayComm.run(function() {
+            mm.sendSyncMessage(C.MM_PREFIX + "notifyLinkClicked",
+                               {origin: aWindow.document.documentURI,
+                                dest: arguments[0]});
+          });
         });
   }
   function unwrapWindowFunctions(aWindow) {
diff --git a/src/content/ui/frame.js b/src/content/ui/frame.js
index 4ddc90e..b661ec3 100644
--- a/src/content/ui/frame.js
+++ b/src/content/ui/frame.js
@@ -45,13 +45,16 @@ Components.utils.import("resource://gre/modules/devtools/Console.jsm");
   ScriptLoader.importModules([
     "lib/utils/constants",
     "lib/logger",
-    "lib/environment"
+    "lib/environment",
+    "lib/framescript-to-overlay-communication"
   ], mod);
-  let {C, Logger, Environment, FrameScriptEnvironment} = mod;
+  let {C, Logger, Environment, FrameScriptEnvironment,
+       FramescriptToOverlayCommunication} = mod;
 
 
   let framescriptEnv = new FrameScriptEnvironment(mm);
   let mlManager = framescriptEnv.mlManager;
+  let overlayComm = new FramescriptToOverlayCommunication(framescriptEnv);
 
 
   // Create a scope for the sub-scripts, which also can
@@ -72,7 +75,8 @@ Components.utils.import("resource://gre/modules/devtools/Console.jsm");
     "Environment": Environment,
 
     "framescriptEnv": framescriptEnv,
-    "mlManager": mlManager
+    "mlManager": mlManager,
+    "overlayComm": overlayComm
   };
 
   function loadSubScripts() {
@@ -128,9 +132,11 @@ Components.utils.import("resource://gre/modules/devtools/Console.jsm");
     // I believe an empty href always gets filled in with the current URL so
     // it will never actually be empty. However, I don't know this for certain.
     if (event.target.nodeName.toLowerCase() == "a" && event.target.href) {
-      sendSyncMessage(C.MM_PREFIX + "notifyLinkClicked",
-                      {origin: event.target.ownerDocument.URL,
-                       dest: event.target.href});
+      overlayComm.run(function() {
+        sendSyncMessage(C.MM_PREFIX + "notifyLinkClicked",
+                        {origin: event.target.ownerDocument.URL,
+                         dest: event.target.href});
+      });
       return;
     }
     // Form submit button clicked. This can either be directly (e.g. mouseclick,
@@ -139,9 +145,11 @@ Components.utils.import("resource://gre/modules/devtools/Console.jsm");
     if (event.target.nodeName.toLowerCase() == "input" &&
         event.target.type.toLowerCase() == "submit" &&
         event.target.form && event.target.form.action) {
-      sendSyncMessage(C.MM_PREFIX + "registerFormSubmitted",
-                      {origin: event.target.ownerDocument.URL,
-                       dest: event.target.form.action});
+      overlayComm.run(function() {
+        sendSyncMessage(C.MM_PREFIX + "registerFormSubmitted",
+                        {origin: event.target.ownerDocument.URL,
+                         dest: event.target.form.action});
+      });
       return;
     }
   };
diff --git a/src/content/ui/overlay.js b/src/content/ui/overlay.js
index bd6cf87..b9d6643 100644
--- a/src/content/ui/overlay.js
+++ b/src/content/ui/overlay.js
@@ -143,6 +143,14 @@ requestpolicy.overlay = (function() {
 
         OverlayEnvironment.shutdownOnUnload(window);
         OverlayEnvironment.startup();
+
+        // Tell the framescripts that the overlay is ready. The
+        // listener must be added immediately.
+        mlManager.addListener("isOverlayReady", function() {
+          return true;
+        });
+        window.messageManager.broadcastAsyncMessage(C.MM_PREFIX +
+                                                    "overlayIsReady", true);
       }
     } catch (e) {
       Logger.severe(Logger.TYPE_ERROR,

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



More information about the Pkg-mozext-commits mailing list