diff options
Diffstat (limited to 'toolkit/components/addoncompat/tests/addon/bootstrap.js')
-rw-r--r-- | toolkit/components/addoncompat/tests/addon/bootstrap.js | 653 |
1 files changed, 653 insertions, 0 deletions
diff --git a/toolkit/components/addoncompat/tests/addon/bootstrap.js b/toolkit/components/addoncompat/tests/addon/bootstrap.js new file mode 100644 index 0000000000..5e69fee22c --- /dev/null +++ b/toolkit/components/addoncompat/tests/addon/bootstrap.js @@ -0,0 +1,653 @@ +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cu = Components.utils; +var Cr = Components.results; + +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/"; + +var contentSecManager = Cc["@mozilla.org/contentsecuritymanager;1"] + .getService(Ci.nsIContentSecurityManager); + +function forEachWindow(f) +{ + let wins = Services.wm.getEnumerator("navigator:browser"); + while (wins.hasMoreElements()) { + let win = wins.getNext(); + f(win); + } +} + +function addLoadListener(target, listener) +{ + target.addEventListener("load", function handler(event) { + target.removeEventListener("load", handler, true); + return listener(event); + }, true); +} + +var gWin; +var gBrowser; +var ok, is, info; + +function removeTab(tab, done) +{ + // Remove the tab in a different turn of the event loop. This way + // the nested event loop in removeTab doesn't conflict with the + // event listener shims. + gWin.setTimeout(() => { + gBrowser.removeTab(tab); + done(); + }, 0); +} + +// Make sure that the shims for window.content, browser.contentWindow, +// and browser.contentDocument are working. +function testContentWindow() +{ + return new Promise(function(resolve, reject) { + const url = baseURL + "browser_addonShims_testpage.html"; + let tab = gBrowser.addTab(url); + gBrowser.selectedTab = tab; + let browser = tab.linkedBrowser; + addLoadListener(browser, function handler() { + ok(gWin.content, "content is defined on chrome window"); + ok(browser.contentWindow, "contentWindow is defined"); + ok(browser.contentDocument, "contentWindow is defined"); + is(gWin.content, browser.contentWindow, "content === contentWindow"); + ok(browser.webNavigation.sessionHistory, "sessionHistory is defined"); + + ok(browser.contentDocument.getElementById("link"), "link present in document"); + + // FIXME: Waiting on bug 1073631. + // is(browser.contentWindow.wrappedJSObject.global, 3, "global available on document"); + + removeTab(tab, resolve); + }); + }); +} + +// Test for bug 1060046 and bug 1072607. We want to make sure that +// adding and removing listeners works as expected. +function testListeners() +{ + return new Promise(function(resolve, reject) { + const url1 = baseURL + "browser_addonShims_testpage.html"; + const url2 = baseURL + "browser_addonShims_testpage2.html"; + + let tab = gBrowser.addTab(url2); + let browser = tab.linkedBrowser; + addLoadListener(browser, function handler() { + function dummyHandler() {} + + // Test that a removed listener stays removed (bug + // 1072607). We're looking to make sure that adding and removing + // a listener here doesn't cause later listeners to fire more + // than once. + for (let i = 0; i < 5; i++) { + gBrowser.addEventListener("load", dummyHandler, true); + gBrowser.removeEventListener("load", dummyHandler, true); + } + + // We also want to make sure that this listener doesn't fire + // after it's removed. + let loadWithRemoveCount = 0; + addLoadListener(browser, function handler1(event) { + loadWithRemoveCount++; + is(event.target.documentURI, url1, "only fire for first url"); + }); + + // Load url1 and then url2. We want to check that: + // 1. handler1 only fires for url1. + // 2. handler2 only fires once for url1 (so the second time it + // fires should be for url2). + let loadCount = 0; + browser.addEventListener("load", function handler2(event) { + loadCount++; + if (loadCount == 1) { + is(event.target.documentURI, url1, "first load is for first page loaded"); + browser.loadURI(url2); + } else { + gBrowser.removeEventListener("load", handler2, true); + + is(event.target.documentURI, url2, "second load is for second page loaded"); + is(loadWithRemoveCount, 1, "load handler is only called once"); + + removeTab(tab, resolve); + } + }, true); + + browser.loadURI(url1); + }); + }); +} + +// Test for bug 1059207. We want to make sure that adding a capturing +// listener and a non-capturing listener to the same element works as +// expected. +function testCapturing() +{ + return new Promise(function(resolve, reject) { + let capturingCount = 0; + let nonCapturingCount = 0; + + function capturingHandler(event) { + is(capturingCount, 0, "capturing handler called once"); + is(nonCapturingCount, 0, "capturing handler called before bubbling handler"); + capturingCount++; + } + + function nonCapturingHandler(event) { + is(capturingCount, 1, "bubbling handler called after capturing handler"); + is(nonCapturingCount, 0, "bubbling handler called once"); + nonCapturingCount++; + } + + gBrowser.addEventListener("mousedown", capturingHandler, true); + gBrowser.addEventListener("mousedown", nonCapturingHandler, false); + + const url = baseURL + "browser_addonShims_testpage.html"; + let tab = gBrowser.addTab(url); + let browser = tab.linkedBrowser; + addLoadListener(browser, function handler() { + let win = browser.contentWindow; + let event = win.document.createEvent("MouseEvents"); + event.initMouseEvent("mousedown", true, false, win, 1, + 1, 0, 0, 0, // screenX, screenY, clientX, clientY + false, false, false, false, // ctrlKey, altKey, shiftKey, metaKey + 0, null); // buttonCode, relatedTarget + + let element = win.document.getElementById("output"); + element.dispatchEvent(event); + + is(capturingCount, 1, "capturing handler fired"); + is(nonCapturingCount, 1, "bubbling handler fired"); + + gBrowser.removeEventListener("mousedown", capturingHandler, true); + gBrowser.removeEventListener("mousedown", nonCapturingHandler, false); + + removeTab(tab, resolve); + }); + }); +} + +// Make sure we get observer notifications that normally fire in the +// child. +function testObserver() +{ + return new Promise(function(resolve, reject) { + let observerFired = 0; + + function observer(subject, topic, data) { + Services.obs.removeObserver(observer, "document-element-inserted"); + observerFired++; + } + Services.obs.addObserver(observer, "document-element-inserted", false); + + let count = 0; + const url = baseURL + "browser_addonShims_testpage.html"; + let tab = gBrowser.addTab(url); + let browser = tab.linkedBrowser; + browser.addEventListener("load", function handler() { + count++; + if (count == 1) { + browser.reload(); + } else { + browser.removeEventListener("load", handler); + + is(observerFired, 1, "got observer notification"); + + removeTab(tab, resolve); + } + }, true); + }); +} + +// Test for bug 1072472. Make sure that creating a sandbox to run code +// in the content window works. This is essentially a test for +// Greasemonkey. +function testSandbox() +{ + return new Promise(function(resolve, reject) { + const url = baseURL + "browser_addonShims_testpage.html"; + let tab = gBrowser.addTab(url); + let browser = tab.linkedBrowser; + browser.addEventListener("load", function handler() { + browser.removeEventListener("load", handler); + + let sandbox = Cu.Sandbox(browser.contentWindow, + {sandboxPrototype: browser.contentWindow, + wantXrays: false}); + Cu.evalInSandbox("const unsafeWindow = window;", sandbox); + Cu.evalInSandbox("document.getElementById('output').innerHTML = 'hello';", sandbox); + + is(browser.contentDocument.getElementById("output").innerHTML, "hello", + "sandbox code ran successfully"); + + // Now try a sandbox with expanded principals. + sandbox = Cu.Sandbox([browser.contentWindow], + {sandboxPrototype: browser.contentWindow, + wantXrays: false}); + Cu.evalInSandbox("const unsafeWindow = window;", sandbox); + Cu.evalInSandbox("document.getElementById('output').innerHTML = 'hello2';", sandbox); + + is(browser.contentDocument.getElementById("output").innerHTML, "hello2", + "EP sandbox code ran successfully"); + + removeTab(tab, resolve); + }, true); + }); +} + +// Test for bug 1095305. We just want to make sure that loading some +// unprivileged content from an add-on package doesn't crash. +function testAddonContent() +{ + let chromeRegistry = Components.classes["@mozilla.org/chrome/chrome-registry;1"] + .getService(Components.interfaces.nsIChromeRegistry); + let base = chromeRegistry.convertChromeURL(BrowserUtils.makeURI("chrome://addonshim1/content/")); + + let res = Services.io.getProtocolHandler("resource") + .QueryInterface(Ci.nsIResProtocolHandler); + res.setSubstitution("addonshim1", base); + + return new Promise(function(resolve, reject) { + const url = "resource://addonshim1/page.html"; + let tab = gBrowser.addTab(url); + let browser = tab.linkedBrowser; + addLoadListener(browser, function handler() { + res.setSubstitution("addonshim1", null); + removeTab(tab, 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(); + + function TestChannel(uri, aLoadInfo, aboutName) { + this.aboutName = aboutName; + this.loadInfo = aLoadInfo; + this.URI = this.originalURI = uri; + } + + TestChannel.prototype = { + asyncOpen: function(listener, context) { + let stream = this.open(); + let runnable = { + run: () => { + try { + listener.onStartRequest(this, context); + } catch (e) {} + try { + listener.onDataAvailable(this, context, stream, 0, stream.available()); + } catch (e) {} + try { + listener.onStopRequest(this, context, Cr.NS_OK); + } catch (e) {} + } + }; + Services.tm.currentThread.dispatch(runnable, Ci.nsIEventTarget.DISPATCH_NORMAL); + }, + + asyncOpen2: function(listener) { + // throws an error if security checks fail + var outListener = contentSecManager.performSecurityCheck(this, listener); + return this.asyncOpen(outListener, null); + }, + + open: function() { + function getWindow(channel) { + try + { + if (channel.notificationCallbacks) + return channel.notificationCallbacks.getInterface(Ci.nsILoadContext).associatedWindow; + } catch (e) {} + + try + { + if (channel.loadGroup && channel.loadGroup.notificationCallbacks) + return channel.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext).associatedWindow; + } catch (e) {} + + return null; + } + + let data = `<html><h1>${this.aboutName}</h1></html>`; + let wnd = getWindow(this); + if (!wnd) + throw Cr.NS_ERROR_UNEXPECTED; + + let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream); + stream.setData(data, data.length); + return stream; + }, + + open2: function() { + // throws an error if security checks fail + contentSecManager.performSecurityCheck(this, null); + return this.open(); + }, + + isPending: function() { + return false; + }, + cancel: function() { + throw Cr.NS_ERROR_NOT_IMPLEMENTED; + }, + suspend: function() { + throw Cr.NS_ERROR_NOT_IMPLEMENTED; + }, + resume: function() { + throw Cr.NS_ERROR_NOT_IMPLEMENTED; + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel, Ci.nsIRequest]) + }; + + /** + * 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, aLoadInfo) => { + return new TestChannel(aURI, aLoadInfo, aboutName); + }, + + 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 = () => { + let request = new content.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`); + } + if (request.responseText.indexOf("test1") == -1) { + throw (`about:test1 response had result ${request.responseText}`); + } + + request = new content.XMLHttpRequest(); + request.open("GET", "about:test2", false); + request.send(null); + + if (request.status != 200) { + throw (`about:test2 response had status ${request.status} - expected 200`); + } + if (request.responseText.indexOf("test2") == -1) { + throw (`about:test2 response had result ${request.responseText}`); + } + + 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"); + + // This needs to be a chrome-privileged page that loads in the + // content process. It needs chrome privs because otherwise the + // XHRs for about:test[12] will fail with a privilege error + // despite the presence of URI_SAFE_FOR_UNTRUSTED_CONTENT. + let newTab = gBrowser.addTab("chrome://addonshim1/content/page.html"); + gBrowser.selectedTab = newTab; + let browser = newTab.linkedBrowser; + + addLoadListener(browser, function() { + testAboutModulesWork(browser).then(() => { + unregisterModules(); + removeTab(newTab, 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.removeProgressListener(globalListener); + gBrowser.removeTabsProgressListener(tabsListener); + removeTab(tab, resolve); + }); + }); +} + +function testRootTreeItem() +{ + return new Promise(function(resolve, reject) { + const url = baseURL + "browser_addonShims_testpage.html"; + let tab = gBrowser.addTab(url); + gBrowser.selectedTab = tab; + let browser = tab.linkedBrowser; + addLoadListener(browser, function handler() { + let win = browser.contentWindow; + + // Add-ons love this crap. + let root = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIWebNavigation) + .QueryInterface(Components.interfaces.nsIDocShellTreeItem) + .rootTreeItem + .QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindow); + is(root, gWin, "got correct chrome window"); + + removeTab(tab, resolve); + }); + }); +} + +function testImportNode() +{ + return new Promise(function(resolve, reject) { + const url = baseURL + "browser_addonShims_testpage.html"; + let tab = gBrowser.addTab(url); + gBrowser.selectedTab = tab; + let browser = tab.linkedBrowser; + addLoadListener(browser, function handler() { + let node = gWin.document.createElement("div"); + let doc = browser.contentDocument; + let result; + try { + result = doc.importNode(node, false); + } catch (e) { + ok(false, "importing threw an exception"); + } + if (browser.isRemoteBrowser) { + is(result, node, "got expected import result"); + } + + removeTab(tab, 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(testProgressListener). + then(testRootTreeItem). + then(testImportNode). + then(Promise.resolve()); +} + +/* + bootstrap.js API +*/ + +function startup(aData, aReason) +{ + forEachWindow(win => { + win.runAddonShimTests = (funcs) => runTests(win, funcs); + }); +} + +function shutdown(aData, aReason) +{ + forEachWindow(win => { + delete win.runAddonShimTests; + }); +} + +function install(aData, aReason) +{ +} + +function uninstall(aData, aReason) +{ +} + |