Bug 1081879 - [e10s] WebProgress shims (r=mconley)
authorBill McCloskey <billm@mozilla.com>
Fri, 03 Apr 2015 07:46:53 -0700
changeset 273089 e158ed184326ceac6e2f862b17c6887c23d81e20
parent 273088 d90e308bfb98fe5c7561d05ed89cdaaff1261417
child 273090 d84644c09199d83fa4965b9bf8a315432d2ceebe
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley
bugs1081879
milestone40.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1081879 - [e10s] WebProgress shims (r=mconley)
browser/base/content/tabbrowser.xml
browser/base/content/test/general/browser_bug623155.js
toolkit/components/addoncompat/RemoteAddonsChild.jsm
toolkit/components/addoncompat/RemoteAddonsParent.jsm
toolkit/components/addoncompat/tests/addon/bootstrap.js
toolkit/content/browser-child.js
toolkit/modules/RemoteWebProgress.jsm
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -526,48 +526,42 @@
         <parameter name="aBrowser"/>
         <parameter name="aMethod"/>
         <parameter name="aArguments"/>
         <parameter name="aCallGlobalListeners"/>
         <parameter name="aCallTabsListeners"/>
         <body><![CDATA[
           var rv = true;
 
-          if (!aBrowser)
-            aBrowser = this.mCurrentBrowser;
-
-          if (aCallGlobalListeners != false &&
-              aBrowser == this.mCurrentBrowser) {
-            for (let p of this.mProgressListeners) {
+          function callListeners(listeners, args) {
+            for (let p of listeners) {
               if (aMethod in p) {
                 try {
-                  if (!p[aMethod].apply(p, aArguments))
+                  if (!p[aMethod].apply(p, args))
                     rv = false;
                 } catch (e) {
                   // don't inhibit other listeners
                   Components.utils.reportError(e);
                 }
               }
             }
           }
 
+          if (!aBrowser)
+            aBrowser = this.mCurrentBrowser;
+
+          if (aCallGlobalListeners != false &&
+              aBrowser == this.mCurrentBrowser) {
+            callListeners(this.mProgressListeners, aArguments);
+          }
+
           if (aCallTabsListeners != false) {
             aArguments.unshift(aBrowser);
 
-            for (let p of this.mTabsProgressListeners) {
-              if (aMethod in p) {
-                try {
-                  if (!p[aMethod].apply(p, aArguments))
-                    rv = false;
-                } catch (e) {
-                  // don't inhibit other listeners
-                  Components.utils.reportError(e);
-                }
-              }
-            }
+            callListeners(this.mTabsProgressListeners, aArguments);
           }
 
           return rv;
         ]]></body>
       </method>
 
       <!-- A web progress listener object definition for a given tab. -->
       <method name="mTabProgressListener">
--- a/browser/base/content/test/general/browser_bug623155.js
+++ b/browser/base/content/test/general/browser_bug623155.js
@@ -110,17 +110,17 @@ function delayed(aIsSelectedTab) {
 
   ok(isRedirectedURISpec(content.location.href),
      "The content area is redirected. aIsSelectedTab:" + aIsSelectedTab);
   is(gURLBar.value, content.location.href,
      "The URL bar shows the content URI. aIsSelectedTab:" + aIsSelectedTab);
 
   if (!aIsSelectedTab) {
     // If this was a background request, go on a foreground request.
-    content.location = REDIRECT_FROM + "#FG";
+    gBrowser.selectedBrowser.loadURI(REDIRECT_FROM + "#FG");
   }
   else {
     // Othrewise, nothing to do remains.
     finish();
   }
 }
 
 /* Cleanup */
--- a/toolkit/components/addoncompat/RemoteAddonsChild.jsm
+++ b/toolkit/components/addoncompat/RemoteAddonsChild.jsm
@@ -129,16 +129,20 @@ let NotificationTracker = {
     let watchers = this._watchers[component1];
     let index = watchers.lastIndexOf(watcher);
     if (index > -1) {
       watchers.splice(index, 1);
     }
 
     this._registered.delete(watcher);
   },
+
+  getCount(component1) {
+    return this.findPaths([component1]).length;
+  },
 };
 
 // This code registers an nsIContentPolicy in the child process. When
 // it runs, it notifies the parent that it needs to run its own
 // nsIContentPolicy list. If any policy in the parent rejects a
 // resource load, that answer is returned to the child.
 let ContentPolicyChild = {
   _classDescription: "Addon shim content policy",
@@ -538,9 +542,13 @@ let RemoteAddonsChild = {
     for (let shim of perTabShims) {
       try {
         shim.uninit();
       } catch(e) {
         Cu.reportError(e);
       }
     }
   },
+
+  get useSyncWebProgress() {
+    return NotificationTracker.getCount("web-progress") > 0;
+  },
 };
--- a/toolkit/components/addoncompat/RemoteAddonsParent.jsm
+++ b/toolkit/components/addoncompat/RemoteAddonsParent.jsm
@@ -5,16 +5,17 @@
 this.EXPORTED_SYMBOLS = ["RemoteAddonsParent"];
 
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/RemoteWebProgress.jsm");
 Cu.import('resource://gre/modules/Services.jsm');
 
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
                                   "resource://gre/modules/BrowserUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Prefetcher",
                                   "resource://gre/modules/Prefetcher.jsm");
@@ -874,16 +875,80 @@ TabBrowserElementInterposition.getters.c
 TabBrowserElementInterposition.getters.contentDocument = function(addon, target) {
   CompatWarning.warn("Direct access to content objects will no longer work in the chrome process.",
                      addon, CompatWarning.warnings.content);
 
   let browser = target.selectedBrowser;
   return getContentDocument(addon, browser);
 };
 
+// This function returns a wrapper around an
+// nsIWebProgressListener. When the wrapper is invoked, it calls the
+// real listener but passes CPOWs for the nsIWebProgress and
+// nsIRequest arguments.
+let progressListeners = {global: new WeakMap(), tabs: new WeakMap()};
+function wrapProgressListener(kind, listener)
+{
+  if (progressListeners[kind].has(listener)) {
+    return progressListeners[kind].get(listener);
+  }
+
+  let ListenerHandler = {
+    get: function(target, name) {
+      if (name.startsWith("on")) {
+        return function(...args) {
+          listener[name].apply(listener, RemoteWebProgressManager.argumentsForAddonListener(kind, args));
+        };
+      }
+
+      return listener[name];
+    }
+  };
+  let listenerProxy = new Proxy(listener, ListenerHandler);
+
+  progressListeners[kind].set(listener, listenerProxy);
+  return listenerProxy;
+}
+
+TabBrowserElementInterposition.methods.addProgressListener = function(addon, target, listener) {
+  if (!target.ownerDocument.defaultView.gMultiProcessBrowser) {
+    return target.addProgressListener(listener);
+  }
+
+  NotificationTracker.add(["web-progress", addon]);
+  return target.addProgressListener(wrapProgressListener("global", listener));
+};
+
+TabBrowserElementInterposition.methods.removeProgressListener = function(addon, target, listener) {
+  if (!target.ownerDocument.defaultView.gMultiProcessBrowser) {
+    return target.removeProgressListener(listener);
+  }
+
+  NotificationTracker.remove(["web-progress", addon]);
+  return target.removeProgressListener(wrapProgressListener("global", listener));
+};
+
+TabBrowserElementInterposition.methods.addTabsProgressListener = function(addon, target, listener) {
+  if (!target.ownerDocument.defaultView.gMultiProcessBrowser) {
+    return target.addTabsProgressListener(listener);
+  }
+
+  NotificationTracker.add(["web-progress", addon]);
+  return target.addTabsProgressListener(wrapProgressListener("tabs", listener));
+};
+
+TabBrowserElementInterposition.methods.removeTabsProgressListener = function(addon, target, listener) {
+  if (!target.ownerDocument.defaultView.gMultiProcessBrowser) {
+    return target.removeTabsProgressListener(listener);
+  }
+
+  NotificationTracker.remove(["web-progress", addon]);
+  return target.removeTabsProgressListener(wrapProgressListener("tabs", listener));
+};
+
 let ChromeWindowInterposition = new Interposition("ChromeWindowInterposition",
                                                   EventTargetInterposition);
 
 // _content is for older add-ons like pinboard and all-in-one gestures
 // that should be using content instead.
 ChromeWindowInterposition.getters.content =
 ChromeWindowInterposition.getters._content = function(addon, target) {
   CompatWarning.warn("Direct access to content objects will no longer work in the chrome process.",
--- a/toolkit/components/addoncompat/tests/addon/bootstrap.js
+++ b/toolkit/components/addoncompat/tests/addon/bootstrap.js
@@ -489,32 +489,78 @@ function testAboutModuleRegistration()
         gBrowser.removeTab(newTab);
         unregisterModules();
         resolve();
       });
     });
   });
 }
 
+function testProgressListener()
+{
+  const url = baseURL + "browser_addonShims_testpage.html";
+
+  let sawGlobalLocChange = false;
+  let sawTabsLocChange = false;
+
+  let globalListener = {
+    onLocationChange: function(webProgress, request, uri) {
+      if (uri.spec == url) {
+        sawGlobalLocChange = true;
+        ok(request instanceof Ci.nsIHttpChannel, "Global listener channel is an HTTP channel");
+      }
+    },
+  };
+
+  let tabsListener = {
+    onLocationChange: function(browser, webProgress, request, uri) {
+      if (uri.spec == url) {
+        sawTabsLocChange = true;
+        ok(request instanceof Ci.nsIHttpChannel, "Tab listener channel is an HTTP channel");
+      }
+    },
+  };
+
+  gBrowser.addProgressListener(globalListener);
+  gBrowser.addTabsProgressListener(tabsListener);
+  info("Added progress listeners");
+
+  return new Promise(function(resolve, reject) {
+    let tab = gBrowser.addTab(url);
+    gBrowser.selectedTab = tab;
+    addLoadListener(tab.linkedBrowser, function handler() {
+      ok(sawGlobalLocChange, "Saw global onLocationChange");
+      ok(sawTabsLocChange, "Saw tabs onLocationChange");
+
+      gBrowser.removeTab(tab);
+      gBrowser.removeProgressListener(globalListener);
+      gBrowser.removeTabsProgressListener(tabsListener);
+      resolve();
+    });
+  });
+}
+
 function runTests(win, funcs)
 {
   ok = funcs.ok;
   is = funcs.is;
   info = funcs.info;
 
   gWin = win;
   gBrowser = win.gBrowser;
 
   return testContentWindow().
     then(testListeners).
     then(testCapturing).
     then(testObserver).
     then(testSandbox).
     then(testAddonContent).
-    then(testAboutModuleRegistration);
+    then(testAboutModuleRegistration).
+    then(testProgressListener).
+    then(Promise.resolve());
 }
 
 /*
  bootstrap.js API
 */
 
 function startup(aData, aReason)
 {
--- a/toolkit/content/browser-child.js
+++ b/toolkit/content/browser-child.js
@@ -69,63 +69,73 @@ let WebProgressListener = {
     return {
       webProgress: aWebProgress || null,
       requestURI: this._requestSpec(aRequest, "URI"),
       originalRequestURI: this._requestSpec(aRequest, "originalURI"),
       documentContentType: content.document && content.document.contentType
     };
   },
 
-  _setupObjects: function setupObjects(aWebProgress) {
+  _setupObjects: function setupObjects(aWebProgress, aRequest) {
     let domWindow;
     try {
       domWindow = aWebProgress && aWebProgress.DOMWindow;
     } catch (e) {
       // If nsDocShell::Destroy has already been called, then we'll
       // get NS_NOINTERFACE when trying to get the DOM window. Ignore
       // that here.
       domWindow = null;
     }
 
     return {
       contentWindow: content,
       // DOMWindow is not necessarily the content-window with subframes.
-      DOMWindow: domWindow
+      DOMWindow: domWindow,
+      webProgress: aWebProgress,
+      request: aRequest,
     };
   },
 
+  _send(name, data, objects) {
+    if (RemoteAddonsChild.useSyncWebProgress) {
+      sendRpcMessage(name, data, objects);
+    } else {
+      sendAsyncMessage(name, data, objects);
+    }
+  },
+
   onStateChange: function onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
     let json = this._setupJSON(aWebProgress, aRequest);
-    let objects = this._setupObjects(aWebProgress);
+    let objects = this._setupObjects(aWebProgress, aRequest);
 
     json.stateFlags = aStateFlags;
     json.status = aStatus;
 
-    sendAsyncMessage("Content:StateChange", json, objects);
+    this._send("Content:StateChange", json, objects);
   },
 
   onProgressChange: function onProgressChange(aWebProgress, aRequest, aCurSelf, aMaxSelf, aCurTotal, aMaxTotal) {
     let json = this._setupJSON(aWebProgress, aRequest);
-    let objects = this._setupObjects(aWebProgress);
+    let objects = this._setupObjects(aWebProgress, aRequest);
 
     json.curSelf = aCurSelf;
     json.maxSelf = aMaxSelf;
     json.curTotal = aCurTotal;
     json.maxTotal = aMaxTotal;
 
-    sendAsyncMessage("Content:ProgressChange", json, objects);
+    this._send("Content:ProgressChange", json, objects);
   },
 
   onProgressChange64: function onProgressChange(aWebProgress, aRequest, aCurSelf, aMaxSelf, aCurTotal, aMaxTotal) {
     this.onProgressChange(aWebProgress, aRequest, aCurSelf, aMaxSelf, aCurTotal, aMaxTotal);
   },
 
   onLocationChange: function onLocationChange(aWebProgress, aRequest, aLocationURI, aFlags) {
     let json = this._setupJSON(aWebProgress, aRequest);
-    let objects = this._setupObjects(aWebProgress);
+    let objects = this._setupObjects(aWebProgress, aRequest);
 
     json.location = aLocationURI ? aLocationURI.spec : "";
     json.flags = aFlags;
 
     // These properties can change even for a sub-frame navigation.
     let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
     json.canGoBack = webNav.canGoBack;
     json.canGoForward = webNav.canGoForward;
@@ -134,37 +144,37 @@ let WebProgressListener = {
       json.documentURI = content.document.documentURIObject.spec;
       json.title = content.document.title;
       json.charset = content.document.characterSet;
       json.mayEnableCharacterEncodingMenu = docShell.mayEnableCharacterEncodingMenu;
       json.principal = content.document.nodePrincipal;
       json.synthetic = content.document.mozSyntheticDocument;
     }
 
-    sendAsyncMessage("Content:LocationChange", json, objects);
+    this._send("Content:LocationChange", json, objects);
   },
 
   onStatusChange: function onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {
     let json = this._setupJSON(aWebProgress, aRequest);
-    let objects = this._setupObjects(aWebProgress);
+    let objects = this._setupObjects(aWebProgress, aRequest);
 
     json.status = aStatus;
     json.message = aMessage;
 
-    sendAsyncMessage("Content:StatusChange", json, objects);
+    this._send("Content:StatusChange", json, objects);
   },
 
   onSecurityChange: function onSecurityChange(aWebProgress, aRequest, aState) {
     let json = this._setupJSON(aWebProgress, aRequest);
-    let objects = this._setupObjects(aWebProgress);
+    let objects = this._setupObjects(aWebProgress, aRequest);
 
     json.state = aState;
     json.status = SecurityUI.getSSLStatusAsString();
 
-    sendAsyncMessage("Content:SecurityChange", json, objects);
+    this._send("Content:SecurityChange", json, objects);
   },
 
   onRefreshAttempted: function onRefreshAttempted(aWebProgress, aURI, aDelay, aSameURI) {
     return true;
   },
 
   QueryInterface: function QueryInterface(aIID) {
     if (aIID.equals(Ci.nsIWebProgressListener) ||
--- a/toolkit/modules/RemoteWebProgress.jsm
+++ b/toolkit/modules/RemoteWebProgress.jsm
@@ -12,30 +12,35 @@ const Cu = Components.utils;
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 function newURI(spec)
 {
     return Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService)
                                                     .newURI(spec, null, null);
 }
 
-function RemoteWebProgressRequest(spec, originalSpec)
+function RemoteWebProgressRequest(spec, originalSpec, requestCPOW)
 {
+  this.wrappedJSObject = this;
+
   this._uri = newURI(spec);
   this._originalURI = newURI(originalSpec);
+  this._requestCPOW = requestCPOW;
 }
 
 RemoteWebProgressRequest.prototype = {
   QueryInterface : XPCOMUtils.generateQI([Ci.nsIChannel]),
 
   get URI() { return this._uri.clone(); },
   get originalURI() { return this._originalURI.clone(); }
 };
 
 function RemoteWebProgress(aManager, aIsTopLevel) {
+  this.wrappedJSObject = this;
+
   this._manager = aManager;
 
   this._isLoadingDocument = false;
   this._DOMWindow = null;
   this._DOMWindowID = 0;
   this._isTopLevel = aIsTopLevel;
   this._loadType = 0;
 }
@@ -70,16 +75,45 @@ RemoteWebProgress.prototype = {
 
 function RemoteWebProgressManager (aBrowser) {
   this._topLevelWebProgress = new RemoteWebProgress(this, true);
   this._progressListeners = [];
 
   this.swapBrowser(aBrowser);
 }
 
+RemoteWebProgressManager.argumentsForAddonListener = function(kind, args) {
+  function checkType(arg, typ) {
+    if (!arg) {
+      return false;
+    }
+    return (arg instanceof typ) ||
+      (arg instanceof Ci.nsISupports && arg.wrappedJSObject instanceof typ);
+  }
+
+  // Arguments for a tabs listener are shifted over one since the
+  // <browser> element is passed as the first argument.
+  let webProgressIndex = 0;
+  let requestIndex = 1;
+  if (kind == "tabs") {
+    webProgressIndex = 1;
+    requestIndex = 2;
+  }
+
+  if (checkType(args[webProgressIndex], RemoteWebProgress)) {
+    args[webProgressIndex] = args[webProgressIndex].wrappedJSObject._webProgressCPOW;
+  }
+
+  if (checkType(args[requestIndex], RemoteWebProgressRequest)) {
+    args[requestIndex] = args[requestIndex].wrappedJSObject._requestCPOW;
+  }
+
+  return args;
+};
+
 RemoteWebProgressManager.prototype = {
   swapBrowser: function(aBrowser) {
     if (this._messageManager) {
       this._messageManager.removeMessageListener("Content:StateChange", this);
       this._messageManager.removeMessageListener("Content:LocationChange", this);
       this._messageManager.removeMessageListener("Content:SecurityChange", this);
       this._messageManager.removeMessageListener("Content:StatusChange", this);
       this._messageManager.removeMessageListener("Content:ProgressChange", this);
@@ -158,23 +192,25 @@ RemoteWebProgressManager.prototype = {
       webProgress = isTopLevel ? this._topLevelWebProgress
                                : new RemoteWebProgress(this, false);
 
       // Update the actual WebProgress fields.
       webProgress._isLoadingDocument = json.webProgress.isLoadingDocument;
       webProgress._DOMWindow = objects.DOMWindow;
       webProgress._DOMWindowID = json.webProgress.DOMWindowID;
       webProgress._loadType = json.webProgress.loadType;
+      webProgress._webProgressCPOW = objects.webProgress;
     }
 
     // The WebProgressRequest object however is always dynamic.
     let request = null;
     if (json.requestURI) {
       request = new RemoteWebProgressRequest(json.requestURI,
-                                             json.originalRequestURI);
+                                             json.originalRequestURI,
+                                             objects.request);
     }
 
     if (isTopLevel) {
       this._browser._contentWindow = objects.contentWindow;
       this._browser._documentContentType = json.documentContentType;
     }
 
     switch (aMessage.name) {
@@ -221,10 +257,10 @@ RemoteWebProgressManager.prototype = {
     case "Content:StatusChange":
       this._callProgressListeners("onStatusChange", webProgress, request, json.status, json.message);
       break;
 
     case "Content:ProgressChange":
       this._callProgressListeners("onProgressChange", webProgress, request, json.curSelf, json.maxSelf, json.curTotal, json.maxTotal);
       break;
     }
-  }
+  },
 };