# HG changeset patch # User Mike Conley # Date 1417016842 18000 # Wed Nov 26 10:47:22 2014 -0500 # Node ID 914fd9c0f580364066239508c6ff0212b4bbd313 # Parent 4750d4560a7ca06f5ba4741e2a2fd6f53b4841dd Bug 1102410 - Regression test to ensure that multiple shimmed nsIAboutModule's can be accessed from the content process. feedback=ally, r=mrbkap,billm. diff --git a/toolkit/components/addoncompat/RemoteAddonsChild.jsm b/toolkit/components/addoncompat/RemoteAddonsChild.jsm --- 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; diff --git a/toolkit/components/addoncompat/RemoteAddonsParent.jsm b/toolkit/components/addoncompat/RemoteAddonsParent.jsm --- 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); diff --git a/toolkit/components/addoncompat/tests/addon/bootstrap.js b/toolkit/components/addoncompat/tests/addon/bootstrap.js --- 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:,

${aboutName}

`, 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) {