Bug 1072467 - Add tests for e10s add-on shims (r=mconley,ted)
authorBill McCloskey <wmccloskey@mozilla.com>
Tue, 07 Oct 2014 11:46:24 -0700
changeset 232445 839f8ca256d48b595691bb54ddcf6ced0a253600
parent 232444 0c77607466894b4c22465c360d853e208e979898
child 232446 e5c5d33293aa89bcae840849adba3438d57810c5
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley, ted
bugs1072467
milestone35.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 1072467 - Add tests for e10s add-on shims (r=mconley,ted)
toolkit/components/addoncompat/moz.build
toolkit/components/addoncompat/tests/Makefile.in
toolkit/components/addoncompat/tests/addon/bootstrap.js
toolkit/components/addoncompat/tests/addon/chrome.manifest
toolkit/components/addoncompat/tests/addon/install.rdf
toolkit/components/addoncompat/tests/browser/browser.ini
toolkit/components/addoncompat/tests/browser/browser_addonShims.js
toolkit/components/addoncompat/tests/browser/browser_addonShims_testpage.html
toolkit/components/addoncompat/tests/browser/browser_addonShims_testpage2.html
toolkit/components/addoncompat/tests/moz.build
--- a/toolkit/components/addoncompat/moz.build
+++ b/toolkit/components/addoncompat/moz.build
@@ -1,14 +1,16 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+TEST_DIRS += ['tests']
+
 EXTRA_COMPONENTS += [
     'addoncompat.manifest',
     'multiprocessShims.js',
 ]
 
 EXTRA_JS_MODULES += [
     'RemoteAddonsChild.jsm',
     'RemoteAddonsParent.jsm',
new file mode 100644
--- /dev/null
+++ b/toolkit/components/addoncompat/tests/Makefile.in
@@ -0,0 +1,15 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+include $(topsrcdir)/config/rules.mk
+
+# This is so hacky. Waiting on bug 988938.
+addondir = $(srcdir)/addon
+TESTROOT = $(CURDIR)/$(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
+testdir = $(TESTROOT)/browser
+
+libs::
+	$(EXIT_ON_ERROR) \
+	$(NSINSTALL) -D $(testdir); \
+	(cd $(addondir) && zip -qr $(testdir)/addon.xpi *)
new file mode 100644
--- /dev/null
+++ b/toolkit/components/addoncompat/tests/addon/bootstrap.js
@@ -0,0 +1,266 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.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()) {
+    let win = wins.getNext();
+    if (win.gBrowser) {
+      f(win);
+    }
+  }
+}
+
+function addLoadListener(target, listener)
+{
+  target.addEventListener("load", function handler(event) {
+    target.removeEventListener("load", handler, true);
+    return listener(event);
+  }, true);
+}
+
+let gWin;
+let gBrowser;
+let ok, is, info;
+
+// 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.contentDocument.getElementById("link"), "link present in document");
+
+      // FIXME: Waiting on bug 1073631.
+      //is(browser.contentWindow.wrappedJSObject.global, 3, "global available on document");
+
+      gBrowser.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() {
+        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");
+
+          gBrowser.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);
+
+      gBrowser.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");
+
+        gBrowser.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");
+
+      gBrowser.removeTab(tab);
+      resolve();
+    }, true);
+  });
+}
+
+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);
+}
+
+/*
+ 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)
+{
+}
+
new file mode 100644
--- /dev/null
+++ b/toolkit/components/addoncompat/tests/addon/chrome.manifest
@@ -0,0 +1,1 @@
+content addonshim1 content/
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/toolkit/components/addoncompat/tests/addon/install.rdf
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+  <Description about="urn:mozilla:install-manifest">
+    <em:id>test-addon-shim-1@tests.mozilla.org</em:id>
+    <em:version>1</em:version>
+    <em:type>2</em:type>
+    <em:bootstrap>true</em:bootstrap>
+
+    <!-- Front End MetaData -->
+    <em:name>Test addon shim 1</em:name>
+    <em:description>Test an add-on that needs multiprocess shims.</em:description>
+    <em:multiprocessCompatible>false</em:multiprocessCompatible>
+
+    <em:iconURL>chrome://foo/skin/icon.png</em:iconURL>
+    <em:aboutURL>chrome://foo/content/about.xul</em:aboutURL>
+    <em:optionsURL>chrome://foo/content/options.xul</em:optionsURL>
+
+    <em:targetApplication>
+      <Description>
+        <em:id>toolkit@mozilla.org</em:id>
+        <em:minVersion>10.0</em:minVersion>
+        <em:maxVersion>*</em:maxVersion>
+      </Description>
+    </em:targetApplication>
+  </Description>
+</RDF>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/addoncompat/tests/browser/browser.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+support-files =
+  addon.xpi
+  browser_addonShims_testpage.html
+  browser_addonShims_testpage2.html
+generated-files =
+  addon.xpi
+
+[browser_addonShims.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/addoncompat/tests/browser/browser_addonShims.js
@@ -0,0 +1,54 @@
+let {AddonManager} = Cu.import("resource://gre/modules/AddonManager.jsm", {});
+
+const ADDON_URL = "http://example.com/browser/toolkit/components/addoncompat/tests/browser/addon.xpi";
+
+// Install a test add-on that will exercise e10s shims.
+//   url: Location of the add-on.
+function addAddon(url)
+{
+  info("Installing add-on: " + url);
+
+  return new Promise(function(resolve, reject) {
+    AddonManager.getInstallForURL(url, installer => {
+      installer.install();
+      let listener = {
+        onInstallEnded: function(addon, addonInstall) {
+          installer.removeListener(listener);
+
+          // Wait for add-on's startup scripts to execute. See bug 997408
+          executeSoon(function() {
+            resolve(addonInstall);
+          });
+        }
+      };
+      installer.addListener(listener);
+    }, "application/x-xpinstall");
+  });
+}
+
+// Uninstall a test add-on.
+//   addon: The addon reference returned from addAddon.
+function removeAddon(addon)
+{
+  info("Removing addon.");
+
+  return new Promise(function(resolve, reject) {
+    let listener = {
+      onUninstalled: function(uninstalledAddon) {
+        if (uninstalledAddon != addon) {
+          return;
+        }
+        AddonManager.removeAddonListener(listener);
+        resolve();
+      }
+    };
+    AddonManager.addAddonListener(listener);
+    addon.uninstall();
+  });
+}
+
+add_task(function* test_addon_shims() {
+  let addon = yield addAddon(ADDON_URL);
+  yield window.runAddonShimTests({ok: ok, is: is, info: info});
+  yield removeAddon(addon);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/addoncompat/tests/browser/browser_addonShims_testpage.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>shim test</title>
+</head>
+
+<body>
+Hello!
+
+<a href="browser_addonShims_testpage2.html" id="link">Link</a>
+<div id="output"></div>
+
+<script type="text/javascript">
+var global = 3;
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/addoncompat/tests/browser/browser_addonShims_testpage2.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>shim test</title>
+</head>
+
+<body>
+Hello!
+
+<a href="browser_addonShims_testpage.html" id="link">Link</a>
+
+<script type="text/javascript">
+var global = 5;
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/addoncompat/tests/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+BROWSER_CHROME_MANIFESTS += ['browser/browser.ini']