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 = `