Bug 1102410 - Regression test to ensure that multiple shimmed nsIAboutModule's can be accessed from the content process. feedback=ally, r=mrbkap,billm.
authorMike Conley <mconley@mozilla.com>
Wed, 26 Nov 2014 10:47:22 -0500
changeset 219106 a1bfd08797630df51d1d274d3f4d39a52cc66a52
parent 219105 46bf7d2c4ca536fab25691eac5e82d1040bf81dd
child 219107 e3cc96211c469da34f214e96f2b31c429cce7795
push id27956
push userkwierso@gmail.com
push dateFri, 12 Dec 2014 00:47:19 +0000
treeherdermozilla-central@32a2c5bd2f68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmrbkap, billm
bugs1102410
milestone37.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 1102410 - Regression test to ensure that multiple shimmed nsIAboutModule's can be accessed from the content process. feedback=ally, r=mrbkap,billm.
toolkit/components/addoncompat/RemoteAddonsChild.jsm
toolkit/components/addoncompat/RemoteAddonsParent.jsm
toolkit/components/addoncompat/tests/addon/bootstrap.js
--- a/toolkit/components/addoncompat/RemoteAddonsChild.jsm
+++ b/toolkit/components/addoncompat/RemoteAddonsChild.jsm
@@ -199,17 +199,17 @@ AboutProtocolChannel.prototype = {
     // Ask the parent to synchronously read all the data from the channel.
     let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
                .getService(Ci.nsISyncMessageSender);
     let rval = cpmm.sendRpcMessage("Addons:AboutProtocol:OpenChannel", {
       uri: this.URI.spec,
       contractID: this._contractID
     }, {
       notificationCallbacks: this.notificationCallbacks,
-      loadGroupNotificationCallbacks: this.loadGroup.notificationCallbacks
+      loadGroupNotificationCallbacks: this.loadGroup ? this.loadGroup.notificationCallbacks : null,
     });
 
     if (rval.length != 1) {
       throw Cr.NS_ERROR_FAILURE;
     }
 
     let {data, contentType} = rval[0];
     this.contentType = contentType;
--- a/toolkit/components/addoncompat/RemoteAddonsParent.jsm
+++ b/toolkit/components/addoncompat/RemoteAddonsParent.jsm
@@ -236,17 +236,21 @@ let AboutProtocolParent = {
   // return it to the child.
   openChannel: function(msg) {
     let uri = BrowserUtils.makeURI(msg.data.uri);
     let contractID = msg.data.contractID;
     let module = Cc[contractID].getService(Ci.nsIAboutModule);
     try {
       let channel = module.newChannel(uri, null);
       channel.notificationCallbacks = msg.objects.notificationCallbacks;
-      channel.loadGroup = {notificationCallbacks: msg.objects.loadGroupNotificationCallbacks};
+      if (msg.objects.loadGroupNotificationCallbacks) {
+        channel.loadGroup = {notificationCallbacks: msg.objects.loadGroupNotificationCallbacks};
+      } else {
+        channel.loadGroup = null;
+      }
       let stream = channel.open();
       let data = NetUtil.readInputStreamToString(stream, stream.available(), {});
       return {
         data: data,
         contentType: channel.contentType
       };
     } catch (e) {
       Cu.reportError(e);
--- a/toolkit/components/addoncompat/tests/addon/bootstrap.js
+++ b/toolkit/components/addoncompat/tests/addon/bootstrap.js
@@ -1,14 +1,15 @@
 var Cc = Components.classes;
 var Ci = Components.interfaces;
 var Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/BrowserUtils.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const baseURL = "http://mochi.test:8888/browser/" +
   "toolkit/components/addoncompat/tests/browser/";
 
 function forEachWindow(f)
 {
   let wins = Services.ww.getWindowEnumerator("navigator:browser");
   while (wins.hasMoreElements()) {
@@ -253,31 +254,191 @@ function testAddonContent()
       gBrowser.removeTab(tab);
       res.setSubstitution("addonshim1", null);
 
       resolve();
     });
   });
 }
 
+
+// Test for bug 1102410. We check that multiple nsIAboutModule's can be
+// registered in the parent, and that the child can browse to each of
+// the registered about: pages.
+function testAboutModuleRegistration()
+{
+  let Registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+
+  let modulesToUnregister = new Map();
+
+  /**
+   * This function creates a new nsIAboutModule and registers it. Callers
+   * should also call unregisterModules after using this function to clean
+   * up the nsIAboutModules at the end of this test.
+   *
+   * @param aboutName
+   *        This will be the string after about: used to refer to this module.
+   *        For example, if aboutName is foo, you can refer to this module by
+   *        browsing to about:foo.
+   *
+   * @param uuid
+   *        A unique identifer string for this module. For example,
+   *        "5f3a921b-250f-4ac5-a61c-8f79372e6063"
+   */
+  let createAndRegisterAboutModule = function(aboutName, uuid) {
+
+    let AboutModule = function() {};
+
+    AboutModule.prototype = {
+      classID: Components.ID(uuid),
+      classDescription: `Testing About Module for about:${aboutName}`,
+      contractID: `@mozilla.org/network/protocol/about;1?what=${aboutName}`,
+      QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]),
+
+      newChannel: (aURI) => {
+        let uri = Services.io.newURI(`data:,<html><h1>${aboutName}</h1></html>`, null, null);
+        let chan = Services.io.newChannelFromURI(uri);
+        chan.originalURI = aURI;
+        return chan;
+      },
+
+      getURIFlags: (aURI) => {
+        return Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT |
+               Ci.nsIAboutModule.ALLOW_SCRIPT;
+      },
+    };
+
+    let factory = {
+      createInstance: function(outer, iid) {
+        if (outer) {
+          throw Cr.NS_ERROR_NO_AGGREGATION;
+        }
+        return new AboutModule();
+      },
+    };
+
+    Registrar.registerFactory(AboutModule.prototype.classID,
+                              AboutModule.prototype.classDescription,
+                              AboutModule.prototype.contractID,
+                              factory);
+
+    modulesToUnregister.set(AboutModule.prototype.classID,
+                            factory);
+  };
+
+  /**
+   * Unregisters any nsIAboutModules registered with
+   * createAndRegisterAboutModule.
+   */
+  let unregisterModules = () => {
+    for (let [classID, factory] of modulesToUnregister) {
+      Registrar.unregisterFactory(classID, factory);
+    }
+  };
+
+  /**
+   * Takes a browser, and sends it a framescript to attempt to
+   * load some about: pages. The frame script will send a test:result
+   * message on completion, passing back a data object with:
+   *
+   * {
+   *   pass: true
+   * }
+   *
+   * on success, and:
+   *
+   * {
+   *   pass: false,
+   *   errorMsg: message,
+   * }
+   *
+   * on failure.
+   *
+   * @param browser
+   *        The browser to send the framescript to.
+   */
+  let testAboutModulesWork = (browser) => {
+    let testConnection = () => {
+      const XMLHttpRequest = Components.Constructor("@mozilla.org/xmlextras/xmlhttprequest;1",
+                                                    "nsIXMLHttpRequest");
+      let request = new XMLHttpRequest();
+      try {
+        request.open("GET", "about:test1", false);
+        request.send(null);
+        if (request.status != 200) {
+          throw(`about:test1 response had status ${request.status} - expected 200`);
+        }
+
+        request = new XMLHttpRequest();
+        request.open("GET", "about:test2", false);
+        request.send(null);
+
+        if (request.status != 200) {
+          throw(`about:test2 response had status ${request.status} - expected 200`);
+        }
+
+        sendAsyncMessage("test:result", {
+          pass: true,
+        });
+      } catch(e) {
+        sendAsyncMessage("test:result", {
+          pass: false,
+          errorMsg: e.toString(),
+        });
+      }
+    };
+
+    return new Promise((resolve, reject) => {
+      let mm = browser.messageManager;
+      mm.addMessageListener("test:result", function onTestResult(message) {
+        mm.removeMessageListener("test:result", onTestResult);
+        if (message.data.pass) {
+          ok(true, "Connections to about: pages were successful");
+        } else {
+          ok(false, message.data.errorMsg);
+        }
+        resolve();
+      });
+      mm.loadFrameScript("data:,(" + testConnection.toString() + ")();", false);
+    });
+  }
+
+  // Here's where the actual test is performed.
+  return new Promise((resolve, reject) => {
+    createAndRegisterAboutModule("test1", "5f3a921b-250f-4ac5-a61c-8f79372e6063");
+    createAndRegisterAboutModule("test2", "d7ec0389-1d49-40fa-b55c-a1fc3a6dbf6f");
+
+    let newTab = gBrowser.addTab();
+    gBrowser.selectedTab = newTab;
+    let browser = newTab.linkedBrowser;
+
+    testAboutModulesWork(browser).then(() => {
+      gBrowser.removeTab(newTab);
+      unregisterModules();
+      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(testAddonContent).
+    then(testAboutModuleRegistration);
 }
 
 /*
  bootstrap.js API
 */
 
 function startup(aData, aReason)
 {