summaryrefslogtreecommitdiffstats
path: root/toolkit/components/addoncompat/tests/addon/bootstrap.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/addoncompat/tests/addon/bootstrap.js')
-rw-r--r--toolkit/components/addoncompat/tests/addon/bootstrap.js653
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 000000000..5e69fee22
--- /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)
+{
+}
+