merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 29 Jul 2016 11:56:43 +0200
changeset 349315 2ea3d51ba1bb9f5c3b6921c43ea63f70b4fdf5d2
parent 349222 58feaa721b44b75919423d89b359d8bb67ea7051 (current diff)
parent 349314 0d0a2960686a335eb09de6edbe1dfe573e212701 (diff)
child 349321 0d59012b60b0a9f305e0e506ae954dbc28b55ffa
child 349372 674c51af1dcc31a139b792ebef9ebcd509f87ed3
child 349492 4907812dbb41d1a4f88a5770c4410dce5f4a37cd
push id1230
push userjlund@mozilla.com
push dateMon, 31 Oct 2016 18:13:35 +0000
treeherdermozilla-release@5e06e3766db2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone50.0a1
first release with
nightly linux32
2ea3d51ba1bb / 50.0a1 / 20160729030203 / files
nightly linux64
2ea3d51ba1bb / 50.0a1 / 20160729030203 / files
nightly mac
2ea3d51ba1bb / 50.0a1 / 20160729030203 / files
nightly win32
2ea3d51ba1bb / 50.0a1 / 20160729030203 / files
nightly win64
2ea3d51ba1bb / 50.0a1 / 20160729030203 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
browser/components/extensions/test/browser/browser_ext_history.js
modules/libpref/init/all.js
toolkit/components/extensions/test/mochitest/file_download.html
toolkit/components/extensions/test/mochitest/file_download.txt
toolkit/components/extensions/test/mochitest/interruptible.sjs
toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_download.html
toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_misc.html
toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_search.html
toolkit/components/extensions/test/mochitest/test_ext_alarms.html
toolkit/components/extensions/test/mochitest/test_ext_background_generated_load_events.html
toolkit/components/extensions/test/mochitest/test_ext_background_generated_reload.html
toolkit/components/extensions/test/mochitest/test_ext_background_runtime_connect_params.html
toolkit/components/extensions/test/mochitest/test_ext_background_sub_windows.html
toolkit/components/extensions/test/mochitest/test_ext_background_window_properties.html
toolkit/components/extensions/test/mochitest/test_ext_bookmarks.html
toolkit/components/extensions/test/mochitest/test_ext_downloads.html
toolkit/components/extensions/test/mochitest/test_ext_extension.html
toolkit/components/extensions/test/mochitest/test_ext_idle.html
toolkit/components/extensions/test/mochitest/test_ext_localStorage.html
toolkit/components/extensions/test/mochitest/test_ext_onmessage_removelistener.html
toolkit/components/extensions/test/mochitest/test_ext_runtime_getPlatformInfo.html
toolkit/components/extensions/test/mochitest/test_ext_runtime_sendMessage.html
toolkit/components/extensions/test/mochitest/test_ext_simple.html
toolkit/components/extensions/test/mochitest/test_ext_storage.html
--- a/browser/components/contextualidentity/test/browser/browser_broadcastchannel.js
+++ b/browser/components/contextualidentity/test/browser/browser_broadcastchannel.js
@@ -58,24 +58,23 @@ add_task(function* test() {
         sender.browser,
         { name: channelName, message: sender.message },
         function (opts) {
           let bc = new content.window.BroadcastChannel(opts.name);
           bc.postMessage(opts.message);
         });
   }
 
-  // make sure we have received a message
-  yield ContentTask.spawn(receiver.browser, channelName,
-    function* (name) {
-      yield content.window.testPromise.then(function() {});
+  // Since sender1 sends before sender2, if the title is exactly
+  // sender2's message, sender1's message must've been blocked
+  yield ContentTask.spawn(receiver.browser, sender2.message,
+    function* (message) {
+      yield content.window.testPromise.then(function() {
+        is(content.document.title, message,
+           "should only receive messages from the same user context");
+      });
     }
   );
 
-  // Since sender1 sends before sender2, if the title is exactly
-  // sender2's message, sender1's message must've been blocked
-  is(receiver.browser.contentDocument.title, sender2.message,
-      "should only receive messages from the same user context");
-
   gBrowser.removeTab(sender1.tab);
   gBrowser.removeTab(sender2.tab);
   gBrowser.removeTab(receiver.tab);
 });
--- a/browser/components/extensions/test/browser/browser.ini
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -30,17 +30,16 @@ support-files =
 [browser_ext_contextMenus.js]
 [browser_ext_contextMenus_checkboxes.js]
 [browser_ext_contextMenus_icons.js]
 [browser_ext_contextMenus_radioGroups.js]
 [browser_ext_contextMenus_uninstall.js]
 [browser_ext_contextMenus_urlPatterns.js]
 [browser_ext_currentWindow.js]
 [browser_ext_getViews.js]
-[browser_ext_history.js]
 [browser_ext_incognito_popup.js]
 [browser_ext_lastError.js]
 [browser_ext_optionsPage_privileges.js]
 [browser_ext_pageAction_context.js]
 [browser_ext_pageAction_popup.js]
 [browser_ext_pageAction_popup_resize.js]
 [browser_ext_pageAction_simple.js]
 [browser_ext_popup_api_injection.js]
--- a/browser/components/extensions/test/xpcshell/.eslintrc
+++ b/browser/components/extensions/test/xpcshell/.eslintrc
@@ -1,3 +1,7 @@
 {
   "extends": "../../../../../testing/xpcshell/xpcshell.eslintrc",
+
+  "globals": {
+    "browser": false,
+  },
 }
--- a/browser/components/extensions/test/xpcshell/head.js
+++ b/browser/components/extensions/test/xpcshell/head.js
@@ -1,50 +1,55 @@
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
+/* exported createHttpServer */
+
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+                                  "resource://gre/modules/AppConstants.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Extension",
                                   "resource://gre/modules/Extension.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionData",
                                   "resource://gre/modules/Extension.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
+                                  "resource://gre/modules/ExtensionManagement.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ExtensionTestUtils",
+                                  "resource://testing-common/ExtensionXPCShellUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+                                  "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "HttpServer",
+                                  "resource://testing-common/httpd.js");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
                                   "resource://gre/modules/Schemas.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 
-/* exported normalizeManifest */
-
-let BASE_MANIFEST = {
-  "applications": {"gecko": {"id": "test@web.ext"}},
-
-  "manifest_version": 2,
+ExtensionTestUtils.init(this);
 
-  "name": "name",
-  "version": "0",
-};
-
-function* normalizeManifest(manifest, baseManifest = BASE_MANIFEST) {
-  const {Management} = Cu.import("resource://gre/modules/Extension.jsm", {});
-  yield Management.lazyInit();
 
-  let errors = [];
-  let context = {
-    url: null,
-
-    logError: error => {
-      errors.push(error);
-    },
+/**
+ * Creates a new HttpServer for testing, and begins listening on the
+ * specified port. Automatically shuts down the server when the test
+ * unit ends.
+ *
+ * @param {integer} [port]
+ *        The port to listen on. If omitted, listen on a random
+ *        port. The latter is the preferred behavior.
+ *
+ * @returns {HttpServer}
+ */
+function createHttpServer(port = -1) {
+  let server = new HttpServer();
+  server.start(port);
 
-    preprocessors: {},
-  };
-
-  manifest = Object.assign({}, baseManifest, manifest);
+  do_register_cleanup(() => {
+    return new Promise(resolve => {
+      server.stop(resolve);
+    });
+  });
 
-  let normalized = Schemas.normalize(manifest, "manifest.WebExtensionManifest", context);
-  normalized.errors = errors;
-
-  return normalized;
+  return server;
 }
rename from toolkit/components/extensions/test/mochitest/test_ext_bookmarks.html
rename to browser/components/extensions/test/xpcshell/test_ext_bookmarks.js
--- a/toolkit/components/extensions/test/mochitest/test_ext_bookmarks.html
+++ b/browser/components/extensions/test/xpcshell/test_ext_bookmarks.js
@@ -1,21 +1,10 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>WebExtension test</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-
-<script type="text/javascript">
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 function backgroundScript() {
   let unsortedId, ourId;
   let initialBookmarkCount = 0;
   let createdBookmarks = new Set();
   const nonExistentId = "000000000000";
   const bookmarkGuids = {
@@ -271,23 +260,23 @@ function backgroundScript() {
   }).then(results => {
     browser.test.assertEq(1, results.length, "Expected number of results returned for title search");
     checkBookmark({title: "EFF", url: "http://eff.org/", index: 0, parentId: bookmarkGuids.unfiledGuid}, results[0]);
 
     // finds menu items
     return browser.bookmarks.search("Menu Item");
   }).then(results => {
     browser.test.assertEq(1, results.length, "Expected number of results returned for menu item search");
-    checkBookmark({title: "Menu Item", url: "http://menu.org/", index: 3, parentId: bookmarkGuids.menuGuid}, results[0]);
+    checkBookmark({title: "Menu Item", url: "http://menu.org/", index: 0, parentId: bookmarkGuids.menuGuid}, results[0]);
 
     // finds toolbar items
     return browser.bookmarks.search("Toolbar Item");
   }).then(results => {
     browser.test.assertEq(1, results.length, "Expected number of results returned for toolbar item search");
-    checkBookmark({title: "Toolbar Item", url: "http://toolbar.org/", index: 2, parentId: bookmarkGuids.toolbarGuid}, results[0]);
+    checkBookmark({title: "Toolbar Item", url: "http://toolbar.org/", index: 0, parentId: bookmarkGuids.toolbarGuid}, results[0]);
 
     // finds folders
     return browser.bookmarks.search("Mozilla Folder");
   }).then(results => {
     browser.test.assertEq(1, results.length, "Expected number of folders returned");
     browser.test.assertEq("Mozilla Folder", results[0].title, "Folder has the expected title");
 
     // is case-insensitive
@@ -384,17 +373,17 @@ function backgroundScript() {
     browser.test.assertEq(2, corporationBookmark.index, "Bookmark has the expected index");
 
     return browser.bookmarks.move(corporationBookmark.id, {index: 0}).then(result => {
       browser.test.assertEq(0, result.index, "Bookmark has the expected index");
 
       return browser.bookmarks.move(corporationBookmark.id, {parentId: bookmarkGuids.menuGuid});
     }).then(result => {
       browser.test.assertEq(bookmarkGuids.menuGuid, result.parentId, "Bookmark has the expected parent");
-      browser.test.assertEq(childCount + 1, result.index, "Bookmark has the expected index");
+      browser.test.assertEq(childCount, result.index, "Bookmark has the expected index");
 
       return browser.bookmarks.move(corporationBookmark.id, {index: 1});
     }).then(result => {
       browser.test.assertEq(bookmarkGuids.menuGuid, result.parentId, "Bookmark has the expected parent");
       browser.test.assertEq(1, result.index, "Bookmark has the expected index");
 
       return browser.bookmarks.move(corporationBookmark.id, {parentId: bookmarkGuids.toolbarGuid, index: 1});
     }).then(result => {
@@ -485,13 +474,8 @@ let extensionData = {
 };
 
 add_task(function* test_contentscript() {
   let extension = ExtensionTestUtils.loadExtension(extensionData);
   yield extension.startup();
   yield extension.awaitFinish("bookmarks");
   yield extension.unload();
 });
-
-</script>
-
-</body>
-</html>
rename from browser/components/extensions/test/browser/browser_ext_history.js
rename to browser/components/extensions/test/xpcshell/test_ext_history.js
--- a/browser/components/extensions/test/browser/browser_ext_history.js
+++ b/browser/components/extensions/test/xpcshell/test_ext_history.js
@@ -80,58 +80,58 @@ add_task(function* test_delete() {
       }
     } else {
       pushVisit(visit.visits);
     }
     visits.push(visit);
   }
 
   yield PlacesUtils.history.insertMany(visits);
-  is(yield PlacesTestUtils.visitsInDB(visits[0].url), 5, "5 visits for uri found in history database");
+  equal((yield PlacesTestUtils.visitsInDB(visits[0].url)), 5, "5 visits for uri found in history database");
 
   let testUrl = visits[2].url;
-  ok(yield PlacesTestUtils.isPageInDB(testUrl), "expected url found in history database");
+  ok((yield PlacesTestUtils.isPageInDB(testUrl)), "expected url found in history database");
 
   extension.sendMessage("delete-url", testUrl);
   yield extension.awaitMessage("url-deleted");
-  is(yield PlacesTestUtils.isPageInDB(testUrl), false, "expected url not found in history database");
+  equal((yield PlacesTestUtils.isPageInDB(testUrl)), false, "expected url not found in history database");
 
   // delete 3 of the 5 visits for url 1
   let filter = {
     startTime: visits[0].visits[0].date,
     endTime: visits[0].visits[2].date,
   };
 
   extension.sendMessage("delete-range", filter);
   let removedUrls = yield extension.awaitMessage("range-deleted");
   ok(!removedUrls.includes(visits[0].url), `${visits[0].url} not received by onVisitRemoved`);
-  ok(yield PlacesTestUtils.isPageInDB(visits[0].url), "expected uri found in history database");
-  is(yield PlacesTestUtils.visitsInDB(visits[0].url), 2, "2 visits for uri found in history database");
-  ok(yield PlacesTestUtils.isPageInDB(visits[1].url), "expected uri found in history database");
-  is(yield PlacesTestUtils.visitsInDB(visits[1].url), 1, "1 visit for uri found in history database");
+  ok((yield PlacesTestUtils.isPageInDB(visits[0].url)), "expected uri found in history database");
+  equal((yield PlacesTestUtils.visitsInDB(visits[0].url)), 2, "2 visits for uri found in history database");
+  ok((yield PlacesTestUtils.isPageInDB(visits[1].url)), "expected uri found in history database");
+  equal((yield PlacesTestUtils.visitsInDB(visits[1].url)), 1, "1 visit for uri found in history database");
 
   // delete the rest of the visits for url 1, and the visit for url 2
   filter.startTime = visits[0].visits[0].date;
   filter.endTime = visits[1].visits[0].date;
 
   extension.sendMessage("delete-range", filter);
   yield extension.awaitMessage("range-deleted");
 
-  is(yield PlacesTestUtils.isPageInDB(visits[0].url), false, "expected uri not found in history database");
-  is(yield PlacesTestUtils.visitsInDB(visits[0].url), 0, "0 visits for uri found in history database");
-  is(yield PlacesTestUtils.isPageInDB(visits[1].url), false, "expected uri not found in history database");
-  is(yield PlacesTestUtils.visitsInDB(visits[1].url), 0, "0 visits for uri found in history database");
+  equal((yield PlacesTestUtils.isPageInDB(visits[0].url)), false, "expected uri not found in history database");
+  equal((yield PlacesTestUtils.visitsInDB(visits[0].url)), 0, "0 visits for uri found in history database");
+  equal((yield PlacesTestUtils.isPageInDB(visits[1].url)), false, "expected uri not found in history database");
+  equal((yield PlacesTestUtils.visitsInDB(visits[1].url)), 0, "0 visits for uri found in history database");
 
-  ok(yield PlacesTestUtils.isPageInDB(visits[3].url), "expected uri found in history database");
+  ok((yield PlacesTestUtils.isPageInDB(visits[3].url)), "expected uri found in history database");
 
   extension.sendMessage("delete-all");
   [historyClearedCount, removedUrls] = yield extension.awaitMessage("history-cleared");
-  is(PlacesUtils.history.hasHistoryEntries, false, "history is empty");
-  is(historyClearedCount, 2, "onVisitRemoved called for each clearing of history");
-  is(removedUrls.length, 3, "onVisitRemoved called the expected number of times");
+  equal(PlacesUtils.history.hasHistoryEntries, false, "history is empty");
+  equal(historyClearedCount, 2, "onVisitRemoved called for each clearing of history");
+  equal(removedUrls.length, 3, "onVisitRemoved called the expected number of times");
   for (let i = 1; i < 3; ++i) {
     let url = visits[i].url;
     ok(removedUrls.includes(url), `${url} received by onVisitRemoved`);
   }
   yield extension.unload();
 });
 
 add_task(function* test_search() {
@@ -210,45 +210,45 @@ add_task(function* test_search() {
   });
 
   function findResult(url, results) {
     return results.find(r => r.url === url);
   }
 
   function checkResult(results, url, expectedCount) {
     let result = findResult(url, results);
-    isnot(result, null, `history.search result was found for ${url}`);
-    is(result.visitCount, expectedCount, `history.search reports ${expectedCount} visit(s)`);
-    is(result.title, `test visit for ${url}`, "title for search result is correct");
+    notEqual(result, null, `history.search result was found for ${url}`);
+    equal(result.visitCount, expectedCount, `history.search reports ${expectedCount} visit(s)`);
+    equal(result.title, `test visit for ${url}`, "title for search result is correct");
   }
 
   yield extension.startup();
   yield extension.awaitMessage("ready");
   yield PlacesTestUtils.clearHistory();
 
   yield PlacesUtils.history.insertMany(PAGE_INFOS);
 
   extension.sendMessage("check-history");
 
   let results = yield extension.awaitMessage("empty-search");
-  is(results.length, 3, "history.search with empty text returned 3 results");
+  equal(results.length, 3, "history.search with empty text returned 3 results");
   checkResult(results, SINGLE_VISIT_URL, 1);
   checkResult(results, DOUBLE_VISIT_URL, 2);
   checkResult(results, MOZILLA_VISIT_URL, 1);
 
   results = yield extension.awaitMessage("text-search");
-  is(results.length, 1, "history.search with specific text returned 1 result");
+  equal(results.length, 1, "history.search with specific text returned 1 result");
   checkResult(results, MOZILLA_VISIT_URL, 1);
 
   results = yield extension.awaitMessage("max-results-search");
-  is(results.length, 1, "history.search with maxResults returned 1 result");
+  equal(results.length, 1, "history.search with maxResults returned 1 result");
   checkResult(results, DOUBLE_VISIT_URL, 2);
 
   results = yield extension.awaitMessage("date-range-search");
-  is(results.length, 2, "history.search with a date range returned 2 result");
+  equal(results.length, 2, "history.search with a date range returned 2 result");
   checkResult(results, DOUBLE_VISIT_URL, 2);
   checkResult(results, SINGLE_VISIT_URL, 1);
 
   yield extension.awaitFinish("search");
   yield extension.unload();
   yield PlacesTestUtils.clearHistory();
 });
 
@@ -295,21 +295,21 @@ add_task(function* test_add_url() {
 
   let failTestData = [
     [{transition: "generated"}, "an invalid transition", "|generated| is not a supported transition for history"],
     [{visitTime: Date.now() + 1000000}, "a future date", "cannot be a future date"],
     [{url: "about.config"}, "an invalid url", "about.config is not a valid URL"],
   ];
 
   function* checkUrl(results) {
-    ok(yield PlacesTestUtils.isPageInDB(results.details.url), `${results.details.url} found in history database`);
+    ok((yield PlacesTestUtils.isPageInDB(results.details.url)), `${results.details.url} found in history database`);
     ok(PlacesUtils.isValidGuid(results.result.id), "URL was added with a valid id");
-    is(results.result.title, results.details.title, "URL was added with the correct title");
+    equal(results.result.title, results.details.title, "URL was added with the correct title");
     if (results.details.visitTime) {
-      is(results.result.lastVisitTime,
+      equal(results.result.lastVisitTime,
          Number(ExtensionUtils.normalizeTime(results.details.visitTime)),
          "URL was added with the correct date");
     }
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: ["history"],
@@ -453,22 +453,22 @@ add_task(function* test_on_visited() {
 
   yield PlacesUtils.history.insertMany(PAGE_INFOS);
 
   let onVisitedData = yield extension.awaitMessage("on-visited-data");
 
   function checkOnVisitedData(index, expected) {
     let onVisited = onVisitedData[index];
     ok(PlacesUtils.isValidGuid(onVisited.id), "onVisited received a valid id");
-    is(onVisited.url, expected.url, "onVisited received the expected url");
+    equal(onVisited.url, expected.url, "onVisited received the expected url");
     // Title will be blank until bug 1287928 lands
     // https://bugzilla.mozilla.org/show_bug.cgi?id=1287928
-    is(onVisited.title, "", "onVisited received a blank title");
-    is(onVisited.lastVisitTime, expected.time, "onVisited received the expected time");
-    is(onVisited.visitCount, expected.visitCount, "onVisited received the expected visitCount");
+    equal(onVisited.title, "", "onVisited received a blank title");
+    equal(onVisited.lastVisitTime, expected.time, "onVisited received the expected time");
+    equal(onVisited.visitCount, expected.visitCount, "onVisited received the expected visitCount");
   }
 
   let expected = {
     url: PAGE_INFOS[0].url,
     title: PAGE_INFOS[0].title,
     time: PAGE_INFOS[0].visits[0].date.getTime(),
     visitCount: 1,
   };
--- a/browser/components/extensions/test/xpcshell/test_ext_manifest_commands.js
+++ b/browser/components/extensions/test/xpcshell/test_ext_manifest_commands.js
@@ -1,15 +1,15 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 
 add_task(function* test_manifest_commands() {
-  let normalized = yield normalizeManifest({
+  let normalized = yield ExtensionTestUtils.normalizeManifest({
     "commands": {
       "toggle-feature": {
         "suggested_key": {"default": "Shifty+Y"},
         "description": "Send a 'toggle-feature' event to the extension",
       },
     },
   });
 
--- a/browser/components/extensions/test/xpcshell/xpcshell.ini
+++ b/browser/components/extensions/test/xpcshell/xpcshell.ini
@@ -1,6 +1,8 @@
 [DEFAULT]
 head = head.js
 tail =
 firefox-appdir = browser
 
+[test_ext_bookmarks.js]
+[test_ext_history.js]
 [test_ext_manifest_commands.js]
--- a/browser/modules/ContentLinkHandler.jsm
+++ b/browser/modules/ContentLinkHandler.jsm
@@ -103,47 +103,19 @@ this.ContentLinkHandler = {
                 }
               }
             }
           } else {
             sizesType = SIZES_TELEMETRY_ENUM.NO_SIZES;
           }
           sizeHistogramTypes.add(sizesType);
 
-	  if (uri.scheme == 'blob') {
-            // Blob URLs don't work cross process, work around this by sending as a data uri
-            let channel = NetUtil.newChannel({
-              uri: uri,
-              contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE,
-              loadUsingSystemPrincipal: true
-            });
-            let listener = {
-              encoded: "",
-              bis: null,
-              onStartRequest: function(aRequest, aContext) {
-                this.bis = Components.classes["@mozilla.org/binaryinputstream;1"]
-                    .createInstance(Components.interfaces.nsIBinaryInputStream);
-              },
-              onStopRequest: function(aRequest, aContext, aStatusCode) {
-                let spec = "data:" + channel.contentType + ";base64," + this.encoded;
-                chromeGlobal.sendAsyncMessage(
-                  "Link:SetIcon",
-                  {url: spec, loadingPrincipal: link.ownerDocument.nodePrincipal});
-              },
-              onDataAvailable: function(request, context, inputStream, offset, count) {
-                this.bis.setInputStream(inputStream);
-                this.encoded += btoa(this.bis.readBytes(this.bis.available()));
-              }
-            }
-            channel.asyncOpen2(listener);
-          } else {
-            chromeGlobal.sendAsyncMessage(
-              "Link:SetIcon",
-              {url: uri.spec, loadingPrincipal: link.ownerDocument.nodePrincipal});
-          }
+          chromeGlobal.sendAsyncMessage(
+            "Link:SetIcon",
+            {url: uri.spec, loadingPrincipal: link.ownerDocument.nodePrincipal});
           iconAdded = true;
           break;
         case "search":
           if (!searchAdded && event.type == "DOMLinkAdded") {
             var type = link.type && link.type.toLowerCase();
             type = type.replace(/^\s+|\s*(?:;.*)?$/g, "");
 
             let re = /^(?:https?|ftp):/i;
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -2420,17 +2420,18 @@ nsDocument::FillStyleSet(StyleSetHandle 
 
     // Iterate backwards to maintain order
     for (StyleSheetHandle sheet : Reversed(mOnDemandBuiltInUASheets)) {
       if (sheet->IsApplicable()) {
         aStyleSet->PrependStyleSheet(SheetType::Agent, sheet);
       }
     }
   } else {
-    NS_ERROR("stylo: nsStyleSheetService doesn't handle ServoStyleSheets yet");
+    NS_WARNING("stylo: Not yet checking nsStyleSheetService for Servo-backed "
+               "documents. See bug 1290224");
   }
 
   AppendSheetsToStyleSet(aStyleSet, mAdditionalSheets[eAgentSheet],
                          SheetType::Agent);
   AppendSheetsToStyleSet(aStyleSet, mAdditionalSheets[eUserSheet],
                          SheetType::User);
   AppendSheetsToStyleSet(aStyleSet, mAdditionalSheets[eAuthorSheet],
                          SheetType::Doc);
@@ -13345,17 +13346,18 @@ nsIDocument::FlushUserFontSet()
       nsIPresShell* shell = GetShell();
       if (shell) {
         // XXXheycam ServoStyleSets don't support exposing @font-face rules yet.
         if (shell->StyleSet()->IsGecko()) {
           if (!shell->StyleSet()->AsGecko()->AppendFontFaceRules(rules)) {
             return;
           }
         } else {
-          NS_ERROR("stylo: ServoStyleSets cannot handle @font-face rules yet");
+          NS_WARNING("stylo: ServoStyleSets cannot handle @font-face rules yet. "
+                     "See bug 1290237.");
         }
       }
 
       bool changed = false;
 
       if (!mFontFaceSet && !rules.IsEmpty()) {
         nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetScopeObject());
         mFontFaceSet = new FontFaceSet(window, this);
@@ -13434,33 +13436,33 @@ nsIDocument::ReportHasScrollLinkedEffect
                                   "ScrollLinkedEffectFound2");
 }
 
 void
 nsIDocument::UpdateStyleBackendType()
 {
   MOZ_ASSERT(mStyleBackendType == StyleBackendType(0),
              "no need to call UpdateStyleBackendType now");
+
+  // Assume Gecko by default.
+  mStyleBackendType = StyleBackendType::Gecko;
+
 #ifdef MOZ_STYLO
   // XXX For now we use a Servo-backed style set only for (X)HTML documents
   // in content docshells.  This should let us avoid implementing XUL-specific
   // CSS features.  And apart from not supporting SVG properties in Servo
   // yet, the root SVG element likes to create a style sheet for an SVG
   // document before we have a pres shell (i.e. before we make the decision
   // here about whether to use a Gecko- or Servo-backed style system), so
   // we avoid Servo-backed style sets for SVG documents.
-  NS_ASSERTION(mDocumentContainer, "stylo: calling UpdateStyleBackendType "
-                                   "before we have a docshell");
-  mStyleBackendType =
-    nsLayoutUtils::SupportsServoStyleBackend(this) &&
-    mDocumentContainer ?
-      StyleBackendType::Servo :
-      StyleBackendType::Gecko;
-#else
-  mStyleBackendType = StyleBackendType::Gecko;
+  if (!mDocumentContainer) {
+    NS_WARNING("stylo: No docshell yet, assuming Gecko style system");
+  } else if (nsLayoutUtils::SupportsServoStyleBackend(this)) {
+    mStyleBackendType = StyleBackendType::Servo;
+  }
 #endif
 }
 
 nsString*
 nsDocument::CheckCustomElementName(const ElementCreationOptions& aOptions,
                                    const nsAString& aLocalName,
                                    uint32_t aNamespaceID,
                                    ErrorResult& rv)
--- a/dom/base/nsMimeTypeArray.cpp
+++ b/dom/base/nsMimeTypeArray.cpp
@@ -25,17 +25,18 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(nsMimeTy
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsMimeTypeArray)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsMimeTypeArray)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsMimeTypeArray,
                                       mWindow,
-                                      mMimeTypes)
+                                      mMimeTypes,
+                                      mCTPMimeTypes)
 
 nsMimeTypeArray::nsMimeTypeArray(nsPIDOMWindowInner* aWindow)
   : mWindow(aWindow)
 {
 }
 
 nsMimeTypeArray::~nsMimeTypeArray()
 {
@@ -52,16 +53,17 @@ nsMimeTypeArray::WrapObject(JSContext* a
 {
   return MimeTypeArrayBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
 nsMimeTypeArray::Refresh()
 {
   mMimeTypes.Clear();
+  mCTPMimeTypes.Clear();
 }
 
 nsPIDOMWindowInner*
 nsMimeTypeArray::GetParentObject() const
 {
   MOZ_ASSERT(mWindow);
   return mWindow;
 }
@@ -128,16 +130,20 @@ nsMimeTypeArray::NamedGetter(const nsASt
   nsString lowerName(aName);
   ToLowerCase(lowerName);
 
   nsMimeType* mimeType = FindMimeType(mMimeTypes, lowerName);
   if (mimeType) {
     aFound = true;
     return mimeType;
   }
+  nsMimeType* hiddenType = FindMimeType(mCTPMimeTypes, lowerName);
+  if (hiddenType) {
+    nsPluginArray::NotifyHiddenPluginTouched(hiddenType->GetEnabledPlugin());
+  }
 
   return nullptr;
 }
 
 uint32_t
 nsMimeTypeArray::Length()
 {
   if (ResistFingerprinting()) {
@@ -175,16 +181,17 @@ nsMimeTypeArray::EnsurePluginMimeTypes()
   ErrorResult rv;
   nsPluginArray *pluginArray =
     static_cast<Navigator*>(navigator.get())->GetPlugins(rv);
   if (!pluginArray) {
     return;
   }
 
   pluginArray->GetMimeTypes(mMimeTypes);
+  pluginArray->GetCTPMimeTypes(mCTPMimeTypes);
 }
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsMimeType, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsMimeType, Release)
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsMimeType, mWindow, mPluginElement)
 
 nsMimeType::nsMimeType(nsPIDOMWindowInner* aWindow,
--- a/dom/base/nsMimeTypeArray.h
+++ b/dom/base/nsMimeTypeArray.h
@@ -43,16 +43,17 @@ protected:
   void EnsurePluginMimeTypes();
   void Clear();
 
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
 
   // mMimeTypes contains MIME types handled by plugins or by an OS
   // PreferredApplicationHandler.
   nsTArray<RefPtr<nsMimeType> > mMimeTypes;
+  nsTArray<RefPtr<nsMimeType> > mCTPMimeTypes;
 };
 
 class nsMimeType final : public nsWrapperCache
 {
 public:
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(nsMimeType)
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(nsMimeType)
 
--- a/dom/base/nsPluginArray.cpp
+++ b/dom/base/nsPluginArray.cpp
@@ -109,16 +109,34 @@ nsPluginArray::GetMimeTypes(nsTArray<Ref
 
   GetPluginMimeTypes(mPlugins, aMimeTypes);
 
   // Alphabetize the enumeration order of non-hidden MIME types to reduce
   // fingerprintable entropy based on plugins' installation file times.
   aMimeTypes.Sort();
 }
 
+void
+nsPluginArray::GetCTPMimeTypes(nsTArray<RefPtr<nsMimeType>>& aMimeTypes)
+{
+  aMimeTypes.Clear();
+
+  if (!AllowPlugins()) {
+    return;
+  }
+
+  EnsurePlugins();
+
+  GetPluginMimeTypes(mCTPPlugins, aMimeTypes);
+
+  // Alphabetize the enumeration order of non-hidden MIME types to reduce
+  // fingerprintable entropy based on plugins' installation file times.
+  aMimeTypes.Sort();
+}
+
 nsPluginElement*
 nsPluginArray::Item(uint32_t aIndex)
 {
   bool unused;
   return IndexedGetter(aIndex, unused);
 }
 
 nsPluginElement*
@@ -231,31 +249,36 @@ nsPluginArray::NamedGetter(const nsAStri
 
   EnsurePlugins();
 
   nsPluginElement* plugin = FindPlugin(mPlugins, aName);
   aFound = (plugin != nullptr);
   if (!aFound) {
     nsPluginElement* hiddenPlugin = FindPlugin(mCTPPlugins, aName);
     if (hiddenPlugin) {
-      HiddenPluginEventInit init;
-      init.mTag = hiddenPlugin->PluginTag();
-      nsCOMPtr<nsIDocument> doc = hiddenPlugin->GetParentObject()->GetDoc();
-      RefPtr<HiddenPluginEvent> event =
-        HiddenPluginEvent::Constructor(doc, NS_LITERAL_STRING("HiddenPlugin"), init);
-      event->SetTarget(doc);
-      event->SetTrusted(true);
-      event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
-      bool dummy;
-      doc->DispatchEvent(event, &dummy);
+      NotifyHiddenPluginTouched(hiddenPlugin);
     }
   }
   return plugin;
 }
 
+void nsPluginArray::NotifyHiddenPluginTouched(nsPluginElement* aHiddenElement)
+{
+  HiddenPluginEventInit init;
+  init.mTag = aHiddenElement->PluginTag();
+  nsCOMPtr<nsIDocument> doc = aHiddenElement->GetParentObject()->GetDoc();
+  RefPtr<HiddenPluginEvent> event =
+    HiddenPluginEvent::Constructor(doc, NS_LITERAL_STRING("HiddenPlugin"), init);
+  event->SetTarget(doc);
+  event->SetTrusted(true);
+  event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+  bool dummy;
+  doc->DispatchEvent(event, &dummy);
+}
+
 uint32_t
 nsPluginArray::Length()
 {
   if (!AllowPlugins() || ResistFingerprinting()) {
     return 0;
   }
 
   EnsurePlugins();
--- a/dom/base/nsPluginArray.h
+++ b/dom/base/nsPluginArray.h
@@ -36,16 +36,19 @@ public:
   // nsPluginArray registers itself as an observer with a weak reference.
   // This can't be done in the constructor, because at that point its
   // refcount is 0 (and it gets destroyed upon registration). So, Init()
   // must be called after construction.
   void Init();
   void Invalidate();
 
   void GetMimeTypes(nsTArray<RefPtr<nsMimeType>>& aMimeTypes);
+  void GetCTPMimeTypes(nsTArray<RefPtr<nsMimeType>>& aMimeTypes);
+
+  static void NotifyHiddenPluginTouched(nsPluginElement* aElement);
 
   // PluginArray WebIDL methods
 
   nsPluginElement* Item(uint32_t aIndex);
   nsPluginElement* NamedItem(const nsAString& aName);
   void Refresh(bool aReloadDocuments);
   nsPluginElement* IndexedGetter(uint32_t aIndex, bool &aFound);
   nsPluginElement* NamedGetter(const nsAString& aName, bool &aFound);
--- a/dom/canvas/WebGLContextVertices.cpp
+++ b/dom/canvas/WebGLContextVertices.cpp
@@ -341,26 +341,20 @@ WebGLContext::GetVertexAttrib(JSContext*
     switch (pname) {
     case LOCAL_GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING:
         return WebGLObjectAsJSValue(cx, mBoundVertexArray->mAttribs[index].buf.get(), rv);
 
     case LOCAL_GL_VERTEX_ATTRIB_ARRAY_STRIDE:
         return JS::Int32Value(mBoundVertexArray->mAttribs[index].stride);
 
     case LOCAL_GL_VERTEX_ATTRIB_ARRAY_SIZE:
-        if (!mBoundVertexArray->mAttribs[index].enabled)
-            return JS::Int32Value(4);
-
         return JS::Int32Value(mBoundVertexArray->mAttribs[index].size);
 
     case LOCAL_GL_VERTEX_ATTRIB_ARRAY_TYPE:
-        if (!mBoundVertexArray->mAttribs[index].enabled)
-            return JS::NumberValue(uint32_t(LOCAL_GL_FLOAT));
-
-        return JS::NumberValue(uint32_t(mBoundVertexArray->mAttribs[index].type));
+        return JS::Int32Value(mBoundVertexArray->mAttribs[index].type);
 
     case LOCAL_GL_VERTEX_ATTRIB_ARRAY_INTEGER:
         if (IsWebGL2())
             return JS::BooleanValue(mBoundVertexArray->mAttribs[index].integer);
 
         break;
 
     case LOCAL_GL_VERTEX_ATTRIB_ARRAY_DIVISOR:
--- a/dom/canvas/WebGLTextureUpload.cpp
+++ b/dom/canvas/WebGLTextureUpload.cpp
@@ -257,16 +257,19 @@ WebGLTexture::TexOrSubImage(bool isSubIm
         }
 
         if (width && height && depth) {
             view.ComputeLengthAndData();
 
             bytes = view.DataAllowShared();
             byteCount = view.LengthAllowShared();
         }
+    } else if (isSubImage) {
+        mContext->ErrorInvalidValue("%s: `pixels` must not be null.", funcName);
+        return;
     }
 
     const bool isClientData = true;
     webgl::TexUnpackBytes blob(mContext, target, width, height, depth, isClientData,
                                bytes);
 
     if (bytes &&
         !ValidateUnpackBytes(mContext, funcName, width, height, depth, pi, byteCount,
new file mode 100644
--- /dev/null
+++ b/dom/canvas/crashtests/1284356-1.html
@@ -0,0 +1,9 @@
+<canvas id='i0'></canvas>
+<script>
+var c=document.getElementById('i0').getContext('2d');
+c.lineWidth=194.622602174;
+c.miterLimit=270.273509738;
+c.transform(0,0,0,0,0,0);
+c.globalCompositeOperation='soft-light';
+c.strokeText('a',200,273,722);
+</script>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/canvas/crashtests/1284578-1.html
@@ -0,0 +1,8 @@
+<canvas id='i0'></canvas>
+<script>
+var c=document.getElementById('i0').getContext('2d');
+c.bezierCurveTo(157,351,351,44,946,701);
+c.quadraticCurveTo(260,-9007199254740991,945,145);
+c.translate(-9007199254740991,239);
+c.isPointInPath(988,439);
+</script>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/canvas/crashtests/1287652-1.html
@@ -0,0 +1,8 @@
+<canvas id='i0'></canvas>
+<script>
+var c=document.getElementById('i0').getContext('2d');
+var g=c.createLinearGradient(59,9,38.89,-75.51);
+c.fillStyle=g;
+c.globalAlpha=0.62;
+c.fillText('a',0,24,30);
+</script>
--- a/dom/canvas/crashtests/crashtests.list
+++ b/dom/canvas/crashtests/crashtests.list
@@ -24,11 +24,14 @@ load 1161277-1.html
 load 1183363.html
 load 1190705.html
 load 1223740-1.html
 load 1225381-1.html
 skip-if(azureCairo) load 1229983-1.html
 load 1229932-1.html
 load 1244850-1.html
 load 1246775-1.html
+load 1284356-1.html
+load 1284578-1.html
 skip-if(d2d) load 1287515-1.html
+load 1287652-1.html
 load 1288872-1.html
 
--- a/dom/canvas/test/webgl-conf/generated-mochitest.ini
+++ b/dom/canvas/test/webgl-conf/generated-mochitest.ini
@@ -5833,17 +5833,16 @@ skip-if = (os == 'android' || os == 'lin
 [generated/test_2_conformance__textures__misc__tex-image-webgl.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance__textures__misc__tex-image-with-format-and-type.html]
 fail-if = (os == 'mac')
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance__textures__misc__tex-image-with-invalid-data.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance__textures__misc__tex-sub-image-2d-bad-args.html]
-fail-if = (os == 'mac') || (os == 'win')
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance__textures__misc__tex-sub-image-2d.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance__textures__misc__texparameter-test.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance__textures__misc__texture-active-bind-2.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance__textures__misc__texture-active-bind.html]
@@ -6717,17 +6716,16 @@ skip-if = (os == 'win' && debug)
 [generated/test_conformance__textures__misc__tex-image-canvas-corruption.html]
 [generated/test_conformance__textures__misc__tex-image-webgl.html]
 skip-if = (os == 'android')
 [generated/test_conformance__textures__misc__tex-image-with-format-and-type.html]
 [generated/test_conformance__textures__misc__tex-image-with-invalid-data.html]
 skip-if = (os == 'android')
 [generated/test_conformance__textures__misc__tex-input-validation.html]
 [generated/test_conformance__textures__misc__tex-sub-image-2d-bad-args.html]
-fail-if = (os == 'mac') || (os == 'win') || (os == 'android') || (os == 'linux')
 [generated/test_conformance__textures__misc__tex-sub-image-2d.html]
 [generated/test_conformance__textures__misc__texparameter-test.html]
 [generated/test_conformance__textures__misc__texture-active-bind-2.html]
 [generated/test_conformance__textures__misc__texture-active-bind.html]
 [generated/test_conformance__textures__misc__texture-attachment-formats.html]
 [generated/test_conformance__textures__misc__texture-clear.html]
 [generated/test_conformance__textures__misc__texture-complete.html]
 [generated/test_conformance__textures__misc__texture-copying-feedback-loops.html]
--- a/dom/canvas/test/webgl-conf/mochitest-errata.ini
+++ b/dom/canvas/test/webgl-conf/mochitest-errata.ini
@@ -143,30 +143,26 @@ fail-if = (os == 'mac') || (os == 'win')
 [generated/test_2_conformance2__rendering__draw-buffers.html]
 fail-if = (os == 'mac') || (os == 'win')
 [generated/test_2_conformance__textures__misc__tex-image-with-format-and-type.html]
 fail-if = (os == 'mac')
 [generated/test_2_conformance__attribs__gl-vertexattribpointer.html]
 fail-if = (os == 'mac') || (os == 'win')
 [generated/test_2_conformance2__glsl3__forbidden-operators.html]
 fail-if = (os == 'mac') || (os == 'win')
-[generated/test_conformance__textures__misc__tex-sub-image-2d-bad-args.html]
-fail-if = (os == 'mac') || (os == 'win') || (os == 'android') || (os == 'linux')
 [generated/test_2_conformance2__vertex_arrays__vertex-array-object.html]
 fail-if = (os == 'mac') || (os == 'win')
 [generated/test_2_conformance__rendering__negative-one-index.html]
 fail-if = (os == 'mac') || (os == 'win')
 [generated/test_conformance__extensions__oes-texture-half-float.html]
 fail-if = (os == 'mac') || (os == 'win') || (os == 'android') || (os == 'linux')
 [generated/test_2_conformance2__reading__read-pixels-pack-parameters.html]
 fail-if = (os == 'mac') || (os == 'win')
 [generated/test_conformance__attribs__gl-vertexattribpointer.html]
 fail-if = (os == 'mac') || (os == 'win') || (os == 'android') || (os == 'linux')
-[generated/test_2_conformance__textures__misc__tex-sub-image-2d-bad-args.html]
-fail-if = (os == 'mac') || (os == 'win')
 [generated/test_conformance__ogles__GL__biuDepthRange__biuDepthRange_001_to_002.html]
 fail-if = (os == 'android') || (os == 'linux')
 [generated/test_conformance__ogles__GL__gl_FragCoord__gl_FragCoord_001_to_003.html]
 fail-if = (os == 'android') || (os == 'linux')
 
 [generated/test_conformance__textures__misc__texture-size-limit.html]
 fail-if = (os == 'linux') || (os == 'android')
 skip-if = (os == 'linux' && asan)
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -2360,32 +2360,16 @@ HTMLInputElement::GetValueIfStepped(int3
     } else if (aStep < 0) {
       value -= deltaFromStep;             // partial step
       value += step * Decimal(aStep + 1); // then remaining steps
     }
   } else {
     value += step * Decimal(aStep);
   }
 
-  // For date inputs, the value can hold a string that is not a day. We do not
-  // want to round it, as it might result in a step mismatch. Instead we want to
-  // clamp to the next valid value.
-  if (mType == NS_FORM_INPUT_DATE &&
-      NS_floorModulo(Decimal(value - GetStepBase()), GetStepScaleFactor()) != Decimal(0)) {
-    MOZ_ASSERT(GetStep() > Decimal(0));
-    Decimal validStep = EuclidLCM<Decimal>(GetStep().floor(),
-                                           GetStepScaleFactor().floor());
-    if (aStep > 0) {
-      value -= NS_floorModulo(value - GetStepBase(), validStep);
-      value += validStep;
-    } else if (aStep < 0) {
-      value -= NS_floorModulo(value - GetStepBase(), validStep);
-    }
-  }
-
   if (value < minimum) {
     value = minimum;
     deltaFromStep = NS_floorModulo(value - stepBase, step);
     if (deltaFromStep != Decimal(0)) {
       value += step - deltaFromStep;
     }
   }
   if (value > maximum) {
@@ -6898,16 +6882,21 @@ HTMLInputElement::GetStep() const
     return kStepAny;
   }
 
   Decimal step = StringToDecimal(stepStr);
   if (!step.isFinite() || step <= Decimal(0)) {
     step = GetDefaultStep();
   }
 
+  // For input type=date, we round the step value to have a rounded day.
+  if (mType == NS_FORM_INPUT_DATE) {
+    step = std::max(step.round(), Decimal(1));
+  }
+
   return step * GetStepScaleFactor();
 }
 
 // nsIConstraintValidation
 
 NS_IMETHODIMP
 HTMLInputElement::SetCustomValidity(const nsAString& aError)
 {
@@ -7501,26 +7490,16 @@ HTMLInputElement::GetValidationMessage(n
       nsXPIDLString message;
 
       Decimal value = GetValueAsDecimal();
       MOZ_ASSERT(!value.isNaN());
 
       Decimal step = GetStep();
       MOZ_ASSERT(step != kStepAny && step > Decimal(0));
 
-      // In case this is a date and the step is not an integer, we don't want to
-      // display the dates corresponding to the truncated timestamps of valueLow
-      // and valueHigh because they might suffer from a step mismatch as well.
-      // Instead we want the timestamps to correspond to a rounded day. That is,
-      // we want a multiple of the step scale factor (1 day) as well as of step.
-      if (mType == NS_FORM_INPUT_DATE) {
-        step = EuclidLCM<Decimal>(step.floor(),
-                                  GetStepScaleFactor().floor());
-      }
-
       Decimal stepBase = GetStepBase();
 
       Decimal valueLow = value - NS_floorModulo(value - stepBase, step);
       Decimal valueHigh = value + step - NS_floorModulo(value - stepBase, step);
 
       Decimal maximum = GetMaximum();
 
       if (maximum.isNaN() || valueHigh <= maximum) {
--- a/dom/html/HTMLTrackElement.cpp
+++ b/dom/html/HTMLTrackElement.cpp
@@ -71,16 +71,17 @@ static constexpr nsAttrValue::EnumTable 
 
 // Invalid values are treated as "metadata" in ParseAttribute, but if no value
 // at all is specified, it's treated as "subtitles" in GetKind
 static constexpr const nsAttrValue::EnumTable* kKindTableInvalidValueDefault = &kKindTable[4];
 
 /** HTMLTrackElement */
 HTMLTrackElement::HTMLTrackElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
   : nsGenericHTMLElement(aNodeInfo)
+  , mLoadResourceDispatched(false)
 {
 }
 
 HTMLTrackElement::~HTMLTrackElement()
 {
 }
 
 NS_IMPL_ELEMENT_CLONE(HTMLTrackElement)
@@ -178,18 +179,55 @@ HTMLTrackElement::ParseAttribute(int32_t
   // Otherwise call the generic implementation.
   return nsGenericHTMLElement::ParseAttribute(aNamespaceID,
                                               aAttribute,
                                               aValue,
                                               aResult);
 }
 
 void
+HTMLTrackElement::SetSrc(const nsAString& aSrc, ErrorResult& aError)
+{
+  SetHTMLAttr(nsGkAtoms::src, aSrc, aError);
+  uint16_t oldReadyState = ReadyState();
+  SetReadyState(TextTrackReadyState::NotLoaded);
+  if (!mMediaParent) {
+    return;
+  }
+  if (mTrack && (oldReadyState != TextTrackReadyState::NotLoaded)) {
+    // Remove all the cues in MediaElement.
+    mMediaParent->RemoveTextTrack(mTrack);
+    // Recreate mTrack.
+    CreateTextTrack();
+  }
+  // Stop WebVTTListener.
+  mListener = nullptr;
+  if (mChannel) {
+    mChannel->Cancel(NS_BINDING_ABORTED);
+    mChannel = nullptr;
+  }
+
+  DispatchLoadResource();
+}
+
+void
+HTMLTrackElement::DispatchLoadResource()
+{
+  if (!mLoadResourceDispatched) {
+    RefPtr<Runnable> r = NewRunnableMethod(this, &HTMLTrackElement::LoadResource);
+    nsContentUtils::RunInStableState(r.forget());
+    mLoadResourceDispatched = true;
+  }
+}
+
+void
 HTMLTrackElement::LoadResource()
 {
+  mLoadResourceDispatched = false;
+
   // Find our 'src' url
   nsAutoString src;
   if (!GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
     return;
   }
 
   nsCOMPtr<nsIURI> uri;
   nsresult rv = NewURIFromString(src, getter_AddRefs(uri));
@@ -253,18 +291,17 @@ HTMLTrackElement::BindToTree(nsIDocument
     LOG(LogLevel::Debug, ("Track element sent notification to parent."));
 
     // We may already have a TextTrack at this point if GetTrack() has already
     // been called. This happens, for instance, if script tries to get the
     // TextTrack before its mTrackElement has been bound to the DOM tree.
     if (!mTrack) {
       CreateTextTrack();
     }
-    RefPtr<Runnable> r = NewRunnableMethod(this, &HTMLTrackElement::LoadResource);
-    nsContentUtils::RunInStableState(r.forget());
+    DispatchLoadResource();
   }
 
   return NS_OK;
 }
 
 void
 HTMLTrackElement::UnbindFromTree(bool aDeep, bool aNullParent)
 {
--- a/dom/html/HTMLTrackElement.h
+++ b/dom/html/HTMLTrackElement.h
@@ -41,20 +41,18 @@ public:
   {
     SetHTMLAttr(nsGkAtoms::kind, aKind, aError);
   }
 
   void GetSrc(DOMString& aSrc) const
   {
     GetHTMLURIAttr(nsGkAtoms::src, aSrc);
   }
-  void SetSrc(const nsAString& aSrc, ErrorResult& aError)
-  {
-    SetHTMLAttr(nsGkAtoms::src, aSrc, aError);
-  }
+
+  void SetSrc(const nsAString& aSrc, ErrorResult& aError);
 
   void GetSrclang(DOMString& aSrclang) const
   {
     GetHTMLAttr(nsGkAtoms::srclang, aSrclang);
   }
   void GetSrclang(nsAString& aSrclang) const
   {
     GetHTMLAttr(nsGkAtoms::srclang, aSrclang);
@@ -129,14 +127,18 @@ protected:
   friend class WebVTTListener;
 
   RefPtr<TextTrack> mTrack;
   nsCOMPtr<nsIChannel> mChannel;
   RefPtr<HTMLMediaElement> mMediaParent;
   RefPtr<WebVTTListener> mListener;
 
   void CreateTextTrack();
+
+private:
+  void DispatchLoadResource();
+  bool mLoadResourceDispatched;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_HTMLTrackElement_h
--- a/dom/html/test/forms/test_step_attribute.html
+++ b/dom/html/test/forms/test_step_attribute.html
@@ -180,17 +180,17 @@ for (var test of data) {
 
       input.min = '2009-02-27';
       input.value = '2009-02-28';
       checkValidity(input, false, apply, { low: "2009-02-27", high: "2009-03-01" });
 
       input.min = '2009-02-01';
       input.step = '1.1';
       input.value = '2009-02-02';
-      checkValidity(input, false, apply, { low: "2009-02-01", high: "2009-02-12" });
+      checkValidity(input, true, apply);
 
       // Without any step attribute the date is valid
       input.removeAttribute('step');
       checkValidity(input, true, apply);
 
       input.min = '1950-01-01';
       input.step = '366';
       input.value = '1951-01-01';
@@ -198,31 +198,31 @@ for (var test of data) {
 
       input.min = '1951-01-01';
       input.step = '365';
       input.value = '1952-01-01';
       checkValidity(input, true, apply);
 
       input.step = '0.9';
       input.value = '1951-01-02';
-      checkValidity(input, false, apply, { low: "1951-01-01", high: "1951-01-10" });
-
-      input.value = '1951-01-10'
+      is(input.step, '0.9', "check that step value is unchanged");
       checkValidity(input, true, apply);
 
-      input.step = '0.5';
+      input.step = '0.4';
       input.value = '1951-01-02';
+      is(input.step, '0.4', "check that step value is unchanged");
       checkValidity(input, true, apply);
 
       input.step = '1.5';
-      input.value = '1951-01-03';
-      checkValidity(input, false, apply, { low: "1951-01-01", high: "1951-01-04" });
+      input.value = '1951-01-02';
+      is(input.step, '1.5', "check that step value is unchanged");
+      checkValidity(input, false, apply, { low: "1951-01-01", high: "1951-01-03" });
 
       input.value = '1951-01-08';
-      checkValidity(input, false, apply, { low: "1951-01-07", high: "1951-01-10" });
+      checkValidity(input, false, apply, { low: "1951-01-07", high: "1951-01-09" });
 
       input.step = '3000';
       input.min= '1968-01-01';
       input.value = '1968-05-12';
       checkValidity(input, false, apply, { low: "1968-01-01", high: "1976-03-19" });
 
       input.value = '1971-01-01';
       checkValidity(input, false, apply, { low: "1968-01-01", high: "1976-03-19" });
@@ -231,36 +231,36 @@ for (var test of data) {
       checkValidity(input, false, apply, { low: "1984-06-05", high: "1992-08-22" });
 
       input.value = '1984-06-05';
       checkValidity(input, true, apply);
 
       input.value = '1992-08-22';
       checkValidity(input, true, apply);
 
-      input.step = '1.1';
+      input.step = '2.1';
       input.min = '1991-01-01';
       input.value = '1991-01-01';
       checkValidity(input, true, apply);
 
       input.value = '1991-01-02';
-      checkValidity(input, false, apply, { low: "1991-01-01", high: "1991-01-12" });
+      checkValidity(input, false, apply, { low: "1991-01-01", high: "1991-01-03" });
 
-      input.value = '1991-01-12';
+      input.value = '1991-01-03';
       checkValidity(input, true, apply);
 
-      input.step = '1.1';
+      input.step = '2.1';
       input.min = '1969-12-20';
       input.value = '1969-12-20';
       checkValidity(input, true, apply);
 
       input.value = '1969-12-21';
-      checkValidity(input, false, apply, { low: "1969-12-20", high: "1969-12-31" });
+      checkValidity(input, false, apply, { low: "1969-12-20", high: "1969-12-22" });
 
-      input.value = '1969-12-31';
+      input.value = '1969-12-22';
       checkValidity(input, true, apply);
 
       break;
     case 'number':
       // When step=0, the allowed step is 1.
       input.step = '0';
       input.value = '1.2';
       checkValidity(input, false, apply, { low: 1, high: 2 });
--- a/dom/html/test/forms/test_stepup_stepdown.html
+++ b/dom/html/test/forms/test_stepup_stepdown.html
@@ -266,22 +266,22 @@ function checkStepDown()
     [ '2012-02-29',  null,  null,  null,  -1,   '2012-03-01',   false ],
     // Float values are rounded to integer (1.1 -> 1).
     [ '2012-01-02',  null,  null,  null,  1.1,  '2012-01-01',   false ],
     [ '2012-01-02',  null,  null,  null,  1.9,  '2012-01-01',   false ],
     // With step values.
     [ '2012-01-03',  '0.5', null,  null,  null, '2012-01-02',   false ],
     [ '2012-01-02',  '0.5', null,  null,  null, '2012-01-01',   false ],
     [ '2012-01-01',  '2',   null,  null,  null, '2011-12-30',   false ],
-    [ '2012-01-02',  '0.25',null,  null,  4,    '2012-01-01',   false ],
-    [ '2012-01-15',  '1.1',  '2012-01-01', null,  1,    '2012-01-12',   false ],
-    [ '2012-01-12',  '1.1',  '2012-01-01', null,  2,    '2012-01-01',   false ],
-    [ '2012-01-23',  '1.1',  '2012-01-01', null,  10,   '2012-01-12',   false ],
-    [ '2012-01-23',  '1.1',  '2012-01-01', null,  11,   '2012-01-01',   false ],
-    [ '1968-01-12',  '1.1',  '1968-01-01', null,  8,    '1968-01-01',   false ],
+    [ '2012-01-02',  '0.25',null,  null,  4,    '2011-12-29',   false ],
+    [ '2012-01-15',  '1.1',  '2012-01-01', null,  1,    '2012-01-14',   false ],
+    [ '2012-01-12',  '1.1',  '2012-01-01', null,  2,    '2012-01-10',   false ],
+    [ '2012-01-23',  '1.1',  '2012-01-01', null,  10,   '2012-01-13',   false ],
+    [ '2012-01-23',  '1.1',  '2012-01-01', null,  11,   '2012-01-12',   false ],
+    [ '1968-01-12',  '1.1',  '1968-01-01', null,  8,    '1968-01-04',   false ],
     // step = 0 isn't allowed (-> step = 1).
     [ '2012-01-02',  '0',   null,  null,  null, '2012-01-01',   false ],
     // step < 0 isn't allowed (-> step = 1).
     [ '2012-01-02',  '-1',  null,  null,  null, '2012-01-01',   false ],
     // step = NaN isn't allowed (-> step = 1).
     [ '2012-01-02',  'foo', null,  null,  null, '2012-01-01',   false ],
     // Min values testing.
     [ '2012-01-03',  '1',    'foo',        null,  2,     '2012-01-01',  false ],
@@ -583,24 +583,23 @@ function checkStepUp()
     [ '1968-12-29',  null,  null,  null,  4,    '1969-01-02',   false ],
     [ '1970-01-01',  null,  null,  null,  -365, '1969-01-01',   false ],
     [ '2012-03-01',  null,  null,  null,  -1,   '2012-02-29',   false ],
     // Float values are rounded to integer (1.1 -> 1).
     [ '2012-01-01',  null,  null,  null,  1.1,  '2012-01-02',   false ],
     [ '2012-01-01',  null,  null,  null,  1.9,  '2012-01-02',   false ],
     // With step values.
     [ '2012-01-01',  '0.5',  null,         null,  null, '2012-01-02',   false ],
-    [ '2012-01-01',  '0.5',  null,         null,  null, '2012-01-02',   false ],
     [ '2012-01-01',  '2',    null,         null,  null, '2012-01-03',   false ],
-    [ '2012-01-01',  '0.25', null,         null,  4,    '2012-01-02',   false ],
-    [ '2012-01-01',  '1.1',  '2012-01-01', null,  1,    '2012-01-12',   false ],
-    [ '2012-01-01',  '1.1',  '2012-01-01', null,  2,    '2012-01-12',   false ],
-    [ '2012-01-01',  '1.1',  '2012-01-01', null,  10,   '2012-01-12',   false ],
-    [ '2012-01-01',  '1.1',  '2012-01-01', null,  11,   '2012-01-23',   false ],
-    [ '1968-01-01',  '1.1',  '1968-01-01', null,  8,    '1968-01-12',   false ],
+    [ '2012-01-01',  '0.25', null,         null,  4,    '2012-01-05',   false ],
+    [ '2012-01-01',  '1.1',  '2012-01-01', null,  1,    '2012-01-02',   false ],
+    [ '2012-01-01',  '1.1',  '2012-01-01', null,  2,    '2012-01-03',   false ],
+    [ '2012-01-01',  '1.1',  '2012-01-01', null,  10,   '2012-01-11',   false ],
+    [ '2012-01-01',  '1.1',  '2012-01-01', null,  11,   '2012-01-12',   false ],
+    [ '1968-01-01',  '1.1',  '1968-01-01', null,  8,    '1968-01-09',   false ],
     // step = 0 isn't allowed (-> step = 1).
     [ '2012-01-01',  '0',   null,  null,  null, '2012-01-02',   false ],
     // step < 0 isn't allowed (-> step = 1).
     [ '2012-01-01',  '-1',  null,  null,  null, '2012-01-02',   false ],
     // step = NaN isn't allowed (-> step = 1).
     [ '2012-01-01',  'foo', null,  null,  null, '2012-01-02',   false ],
     // Min values testing.
     [ '2012-01-01',  '1',   'foo',         null,  null,  '2012-01-02',  false ],
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -851,16 +851,18 @@ tags = webvtt
 tags = webvtt
 [test_texttrackregion.html]
 tags = webvtt
 [test_texttrack_moz.html]
 tags = webvtt
 [test_timeupdate_small_files.html]
 [test_trackelementevent.html]
 tags = webvtt
+[test_trackelementsrc.html]
+tags = webvtt
 [test_trackevent.html]
 tags = webvtt
 [test_unseekable.html]
 skip-if = toolkit == 'gonk' # bug 1128845 on gonk
 [test_video_to_canvas.html]
 [test_video_in_audio_element.html]
 [test_videoDocumentTitle.html]
 [test_VideoPlaybackQuality.html]
new file mode 100644
--- /dev/null
+++ b/dom/media/test/test_trackelementsrc.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1281418 - Change the src attribue for TrackElement.</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv({"set": [["media.webvtt.enabled", true],
+  ["media.webvtt.regions.enabled", true]]}, function() {
+
+var video = document.createElement("video");
+video.src = "seek.webm";
+video.preload = "metadata";
+var trackElement = document.createElement("track");
+trackElement.src = "basic.vtt";
+trackElement.default = true;
+
+document.getElementById("content").appendChild(video);
+video.appendChild(trackElement);
+
+video.addEventListener("loadedmetadata", function metadata() {
+  if (trackElement.readyState <= 1) {
+    return setTimeout(metadata, 0);
+  }
+  is(video.textTracks.length, 1, "Length should be 1.");
+  is(video.textTracks[0].cues.length, 6, "Cue length should be 6.");
+
+  trackElement.src = "sequential.vtt";
+  trackElement.track.mode = "showing";
+  video.play();
+});
+
+video.addEventListener("ended", function end() {
+  is(trackElement.readyState, 2, "readyState should be 2.")
+  is(video.textTracks.length, 1, "Length should be 1.");
+  is(video.textTracks[0].cues.length, 3, "Cue length should be 3.");
+  SimpleTest.finish();
+});
+
+});
+</script>
+</pre>
+</body>
+</html>
+
--- a/dom/workers/test/serviceworkers/download/worker.js
+++ b/dom/workers/test/serviceworkers/download/worker.js
@@ -16,13 +16,15 @@ addEventListener('fetch', function(evt) 
   if (evt.request.url.indexOf('fake_download') === -1) {
     return;
   }
 
   // We should only get a single download fetch event. Automatically unregister.
   evt.respondWith(registration.unregister().then(function() {
     return new Response('service worker generated download', {
       headers: {
-        'Content-Disposition': 'attachment; filename="fake_download.bin"'
+        'Content-Disposition': 'attachment; filename="fake_download.bin"',
+        // fake encoding header that should have no effect
+        'Content-Encoding': 'gzip',
       }
     });
   }));
 });
--- a/image/decoders/nsPNGDecoder.cpp
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -29,19 +29,22 @@ using namespace mozilla::gfx;
 using std::min;
 
 namespace mozilla {
 namespace image {
 
 static LazyLogModule sPNGLog("PNGDecoder");
 static LazyLogModule sPNGDecoderAccountingLog("PNGDecoderAccounting");
 
-// Limit image dimensions (bug #251381, #591822, and #967656)
-#ifndef MOZ_PNG_MAX_DIMENSION
-#  define MOZ_PNG_MAX_DIMENSION 32767
+// limit image dimensions (bug #251381, #591822, #967656, and #1283961)
+#ifndef MOZ_PNG_MAX_WIDTH
+#  define MOZ_PNG_MAX_WIDTH 0x7fffffff // Unlimited
+#endif
+#ifndef MOZ_PNG_MAX_HEIGHT
+#  define MOZ_PNG_MAX_HEIGHT 0x7fffffff // Unlimited
 #endif
 
 nsPNGDecoder::AnimFrameInfo::AnimFrameInfo()
  : mDispose(DisposalMethod::KEEP)
  , mBlend(BlendMethod::OVER)
  , mTimeout(0)
 { }
 
@@ -318,16 +321,17 @@ nsPNGDecoder::InitInternal()
     png_set_keep_unknown_chunks(mPNG, 1, color_chunks, 2);
   }
 
   png_set_keep_unknown_chunks(mPNG, 1, unused_chunks,
                               (int)sizeof(unused_chunks)/5);
 #endif
 
 #ifdef PNG_SET_USER_LIMITS_SUPPORTED
+  png_set_user_limits(mPNG, MOZ_PNG_MAX_WIDTH, MOZ_PNG_MAX_HEIGHT);
   if (mCMSMode != eCMSMode_Off) {
     png_set_chunk_malloc_max(mPNG, 4000000L);
   }
 #endif
 
 #ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED
   // Disallow palette-index checking, for speed; we would ignore the warning
   // anyhow.  This feature was added at libpng version 1.5.10 and is disabled
@@ -552,21 +556,16 @@ nsPNGDecoder::info_callback(png_structp 
 
   nsPNGDecoder* decoder =
                static_cast<nsPNGDecoder*>(png_get_progressive_ptr(png_ptr));
 
   // Always decode to 24-bit RGB or 32-bit RGBA
   png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
                &interlace_type, &compression_type, &filter_type);
 
-  // Are we too big?
-  if (width > MOZ_PNG_MAX_DIMENSION || height > MOZ_PNG_MAX_DIMENSION) {
-    png_longjmp(decoder->mPNG, 1);
-  }
-
   const IntRect frameRect(0, 0, width, height);
 
   // Post our size to the superclass
   decoder->PostSize(frameRect.width, frameRect.height);
   if (decoder->HasError()) {
     // Setting the size led to an error.
     png_longjmp(decoder->mPNG, 1);
   }
--- a/js/src/builtin/Array.js
+++ b/js/src/builtin/Array.js
@@ -985,17 +985,17 @@ function ArrayConcat(arg1) {
         if (IsConcatSpreadable(E)) {
             // Step 5.c.ii.
             len = ToLength(E.length);
 
             // Step 5.c.iii.
             if (n + len > MAX_NUMERIC_INDEX)
                 ThrowTypeError(JSMSG_TOO_LONG_ARRAY);
 
-            if (IsPackedArray(E)) {
+            if (IsPackedArray(A) && IsPackedArray(E)) {
                 // Step 5.c.i, 5.c.iv, and 5.c.iv.5.
                 for (k = 0; k < len; k++) {
                     // Steps 5.c.iv.1-3.
                     // IsPackedArray(E) ensures that |k in E| is always true.
                     _DefineDataProperty(A, n, E[k]);
 
                     // Step 5.c.iv.4.
                     n++;
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -1787,18 +1787,21 @@ InitDateTimeFormatClass(JSContext* cx, H
     {
         return nullptr;
     }
 
     // If the still-experimental DateTimeFormat.prototype.formatToParts method
     // is enabled, also add it.
     if (cx->compartment()->creationOptions().experimentalDateTimeFormatFormatToPartsEnabled()) {
         RootedValue ftp(cx);
-        if (!GlobalObject::getIntrinsicValue(cx, cx->global(),
-                                             cx->names().DateTimeFormatFormatToParts, &ftp))
+        HandlePropertyName name = cx->names().formatToParts;
+        if (!GlobalObject::getSelfHostedFunction(cx, cx->global(),
+                    cx->names().DateTimeFormatFormatToParts,
+                    name,
+                    0, &ftp))
         {
             return nullptr;
         }
 
         if (!DefineProperty(cx, proto, cx->names().formatToParts, ftp, nullptr, nullptr, 0))
             return nullptr;
     }
 
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -1554,16 +1554,25 @@ js::RegExpPrototypeOptimizableRaw(JSCont
     if (!GetOwnNativeGetterPure(cx, proto, NameToId(cx->names().sticky), &stickyGetter))
         return false;
 
     if (stickyGetter != regexp_sticky) {
         *result = false;
         return true;
     }
 
+    JSNative unicodeGetter;
+    if (!GetOwnNativeGetterPure(cx, proto, NameToId(cx->names().unicode), &unicodeGetter))
+        return false;
+
+    if (unicodeGetter != regexp_unicode) {
+        *result = false;
+        return true;
+    }
+
     // Check if @@match, @@search, and exec are own data properties,
     // those values should be tested in selfhosted JS.
     bool has = false;
     if (!HasOwnDataPropertyPure(cx, proto, SYMBOL_TO_JSID(cx->wellKnownSymbols().match), &has))
         return false;
     if (!has) {
         *result = false;
         return true;
--- a/js/src/builtin/RegExp.js
+++ b/js/src/builtin/RegExp.js
@@ -574,16 +574,17 @@ function IsRegExpSplitOptimizable(rx, C)
     var RegExpCtor = GetBuiltinConstructor("RegExp");
     if (C !== RegExpCtor)
         return false;
 
     var RegExpProto = RegExpCtor.prototype;
     // If RegExpPrototypeOptimizable succeeds, `RegExpProto.exec` is guaranteed
     // to be a data property.
     return RegExpPrototypeOptimizable(RegExpProto) &&
+           RegExpInstanceOptimizable(rx, RegExpProto) &&
            RegExpProto.exec === RegExp_prototype_Exec;
 }
 
 // ES 2016 draft Mar 25, 2016 21.2.5.11.
 function RegExpSplit(string, limit) {
     // Step 1.
     var rx = this;
 
@@ -598,17 +599,18 @@ function RegExpSplit(string, limit) {
     var C = SpeciesConstructor(rx, GetBuiltinConstructor("RegExp"));
 
     // Step 5.
     var flags = ToString(rx.flags);
 
     // Steps 6-7.
     var unicodeMatching = callFunction(std_String_includes, flags, "u");
 
-    var optimizable = IsRegExpSplitOptimizable(rx, C);
+    var optimizable = IsRegExpSplitOptimizable(rx, C) &&
+                      (limit === undefined || typeof limit == "number");
     var splitter;
     if (optimizable) {
         // Steps 8-9 (skipped).
 
         // Step 10.
         // If split operation is optimizable, perform non-sticky match.
         splitter = regexp_construct_no_sticky(rx, flags);
     } else {
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -859,33 +859,17 @@ GCState(JSContext* cx, unsigned argc, Va
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (args.length() != 0) {
         RootedObject callee(cx, &args.callee());
         ReportUsageError(cx, callee, "Too many arguments");
         return false;
     }
 
-    const char* state;
-    gc::State globalState = cx->runtime()->gc.state();
-    if (globalState == gc::NO_INCREMENTAL)
-        state = "none";
-    else if (globalState == gc::MARK)
-        state = "mark";
-    else if (globalState == gc::SWEEP)
-        state = "sweep";
-    else if (globalState == gc::FINALIZE)
-        state = "finalize";
-    else if (globalState == gc::COMPACT)
-        state = "compact";
-    else if (globalState == gc::DECOMMIT)
-        state = "decommit";
-    else
-        MOZ_CRASH("Unobserveable global GC state");
-
+    const char* state = StateName(cx->runtime()->gc.state());
     JSString* str = JS_NewStringCopyZ(cx, state);
     if (!str)
         return false;
     args.rval().setString(str);
     return true;
 }
 
 static bool
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -684,18 +684,18 @@ class GCRuntime
         MOZ_ASSERT(nextCellUniqueId_ > 0);
         uint64_t uid = ++nextCellUniqueId_;
         return uid;
     }
 
   public:
     // Internal public interface
     State state() const { return incrementalState; }
-    bool isHeapCompacting() const { return state() == COMPACT; }
-    bool isForegroundSweeping() const { return state() == SWEEP; }
+    bool isHeapCompacting() const { return state() == State::Compact; }
+    bool isForegroundSweeping() const { return state() == State::Sweep; }
     bool isBackgroundSweeping() { return helperState.isBackgroundSweeping(); }
     void waitBackgroundSweepEnd() { helperState.waitBackgroundSweepEnd(); }
     void waitBackgroundSweepOrAllocEnd() {
         helperState.waitBackgroundSweepEnd();
         allocTask.cancel(GCParallelTask::CancelAndWait);
     }
 
     void requestMinorGC(JS::gcreason::Reason reason);
@@ -761,17 +761,17 @@ class GCRuntime
 #endif // DEBUG
 
     void setAlwaysPreserveCode() { alwaysPreserveCode = true; }
 
     bool isIncrementalGCAllowed() const { return incrementalAllowed; }
     void disallowIncrementalGC() { incrementalAllowed = false; }
 
     bool isIncrementalGCEnabled() const { return mode == JSGC_MODE_INCREMENTAL && incrementalAllowed; }
-    bool isIncrementalGCInProgress() const { return state() != NO_INCREMENTAL; }
+    bool isIncrementalGCInProgress() const { return state() != State::NotActive; }
 
     bool isGenerationalGCEnabled() const { return generationalDisabled == 0; }
     void disableGenerationalGC();
     void enableGenerationalGC();
 
     void disableCompactingGC();
     void enableCompactingGC();
     bool isCompactingGCEnabled() const;
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -341,18 +341,18 @@ AssertZoneIsMarking(JS::Symbol* sym)
     MOZ_ASSERT(zone->isGCMarking() || zone->isAtomsZone());
 #endif
 }
 
 static void
 AssertRootMarkingPhase(JSTracer* trc)
 {
     MOZ_ASSERT_IF(trc->isMarkingTracer(),
-                  trc->runtime()->gc.state() == NO_INCREMENTAL ||
-                  trc->runtime()->gc.state() == MARK_ROOTS);
+                  trc->runtime()->gc.state() == State::NotActive ||
+                  trc->runtime()->gc.state() == State::MarkRoots);
 }
 
 
 /*** Tracing Interface ***************************************************************************/
 
 // The second parameter to BaseGCType is derived automatically based on T. The
 // relation here is that for any T, the TraceKind will automatically,
 // statically select the correct Cell layout for marking. Below, we instantiate
@@ -1940,17 +1940,17 @@ GCMarker::markDelayedChildren(Arena* are
      * allocatedDuringIncremental flag if we continue marking.
      */
 }
 
 bool
 GCMarker::markDelayedChildren(SliceBudget& budget)
 {
     GCRuntime& gc = runtime()->gc;
-    gcstats::AutoPhase ap(gc.stats, gc.state() == MARK, gcstats::PHASE_MARK_DELAYED);
+    gcstats::AutoPhase ap(gc.stats, gc.state() == State::Mark, gcstats::PHASE_MARK_DELAYED);
 
     MOZ_ASSERT(unmarkedArenaStackTop);
     do {
         /*
          * If marking gets delayed at the same arena again, we must repeat
          * marking of its things. For that we pop arena from the stack and
          * clear its hasDelayedMarking flag before we begin the marking.
          */
@@ -2434,17 +2434,17 @@ CheckIsMarkedThing(T* thingp)
 #undef IS_SAME_TYPE_OR
 
 #ifdef DEBUG
     MOZ_ASSERT(thingp);
     MOZ_ASSERT(*thingp);
     JSRuntime* rt = (*thingp)->runtimeFromAnyThread();
     MOZ_ASSERT_IF(!ThingIsPermanentAtomOrWellKnownSymbol(*thingp),
                   CurrentThreadCanAccessRuntime(rt) ||
-                  (rt->isHeapCollecting() && rt->gc.state() == SWEEP));
+                  (rt->isHeapCollecting() && rt->gc.state() == State::Sweep));
 #endif
 }
 
 template <typename T>
 static bool
 IsMarkedInternalCommon(T* thingp)
 {
     CheckIsMarkedThing(thingp);
--- a/js/src/gc/Statistics.h
+++ b/js/src/gc/Statistics.h
@@ -248,17 +248,17 @@ struct Statistics
 
     static const size_t MAX_NESTING = 20;
 
     struct SliceData {
         SliceData(SliceBudget budget, JS::gcreason::Reason reason, int64_t start,
                   double startTimestamp, size_t startFaults, gc::State initialState)
           : budget(budget), reason(reason),
             initialState(initialState),
-            finalState(gc::NO_INCREMENTAL),
+            finalState(gc::State::NotActive),
             resetReason(nullptr),
             start(start), startTimestamp(startTimestamp),
             startFaults(startFaults)
         {
             for (auto i : mozilla::MakeRange(NumTimingArrays))
                 mozilla::PodArrayZero(phaseTimes[i]);
         }
 
--- a/js/src/gc/Verifier.cpp
+++ b/js/src/gc/Verifier.cpp
@@ -198,17 +198,17 @@ gc::GCRuntime::startVerifyPreBarriers()
     trc->term = trc->edgeptr + size;
 
     if (!trc->nodemap.init())
         goto oom;
 
     /* Create the root node. */
     trc->curnode = MakeNode(trc, nullptr, JS::TraceKind(0));
 
-    incrementalState = MARK_ROOTS;
+    incrementalState = State::MarkRoots;
 
     /* Make all the roots be edges emanating from the root node. */
     markRuntime(trc, TraceRuntime, prep.session().lock);
 
     VerifyNode* node;
     node = trc->curnode;
     if (trc->edgeptr == trc->term)
         goto oom;
@@ -225,31 +225,31 @@ gc::GCRuntime::startVerifyPreBarriers()
             if (trc->edgeptr == trc->term)
                 goto oom;
         }
 
         node = NextNode(node);
     }
 
     verifyPreData = trc;
-    incrementalState = MARK;
+    incrementalState = State::Mark;
     marker.start();
 
     for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
         PurgeJITCaches(zone);
         if (!zone->usedByExclusiveThread) {
             zone->setNeedsIncrementalBarrier(true, Zone::UpdateJit);
             zone->arenas.purge();
         }
     }
 
     return;
 
 oom:
-    incrementalState = NO_INCREMENTAL;
+    incrementalState = State::NotActive;
     js_delete(trc);
     verifyPreData = nullptr;
 }
 
 static bool
 IsMarkedOrAllocated(TenuredCell* cell)
 {
     return cell->isMarked() || cell->arena()->allocatedDuringIncremental;
@@ -337,17 +337,17 @@ gc::GCRuntime::endVerifyPreBarriers()
     /*
      * We need to bump gcNumber so that the methodjit knows that jitcode has
      * been discarded.
      */
     MOZ_ASSERT(trc->number == number);
     number++;
 
     verifyPreData = nullptr;
-    incrementalState = NO_INCREMENTAL;
+    incrementalState = State::NotActive;
 
     if (!compartmentCreated && IsIncrementalGCSafe(rt)) {
         CheckEdgeTracer cetrc(rt);
 
         /* Start after the roots. */
         VerifyNode* node = NextNode(trc->root);
         while ((char*)node < trc->edgeptr) {
             cetrc.node = node;
--- a/js/src/irregexp/RegExpEngine.cpp
+++ b/js/src/irregexp/RegExpEngine.cpp
@@ -281,23 +281,16 @@ GetCaseIndependentLetters(char16_t chara
                           const char16_t* choices,
                           size_t choices_length,
                           char16_t* letters)
 {
     size_t count = 0;
     for (size_t i = 0; i < choices_length; i++) {
         char16_t c = choices[i];
 
-        // The standard requires that non-ASCII characters cannot have ASCII
-        // character codes in their equivalence class, even though this
-        // situation occurs multiple times in the unicode tables.
-        static const unsigned kMaxAsciiCharCode = 127;
-        if (!unicode && character > kMaxAsciiCharCode && c <= kMaxAsciiCharCode)
-            continue;
-
         // Skip characters that can't appear in one byte strings.
         if (!unicode && ascii_subject && c > kMaxOneByteCharCode)
             continue;
 
         // Watch for duplicates.
         bool found = false;
         for (size_t j = 0; j < count; j++) {
             if (letters[j] == c) {
@@ -327,20 +320,50 @@ GetCaseIndependentLetters(char16_t chara
             unicode::ReverseFoldCase1(character),
             unicode::ReverseFoldCase2(character),
             unicode::ReverseFoldCase3(character),
         };
         return GetCaseIndependentLetters(character, ascii_subject, unicode,
                                          choices, ArrayLength(choices), letters);
     }
 
+    char16_t upper = unicode::ToUpperCase(character);
+    unicode::CodepointsWithSameUpperCase others(character);
+    char16_t other1 = others.other1();
+    char16_t other2 = others.other2();
+    char16_t other3 = others.other3();
+
+    // ES 2017 draft 996af87b7072b3c3dd2b1def856c66f456102215 21.2.4.2
+    // step 3.g.
+    // The standard requires that non-ASCII characters cannot have ASCII
+    // character codes in their equivalence class, even though this
+    // situation occurs multiple times in the unicode tables.
+    static const unsigned kMaxAsciiCharCode = 127;
+    if (upper <= kMaxAsciiCharCode) {
+        if (character > kMaxAsciiCharCode) {
+            // If Canonicalize(character) == character, all other characters
+            // should be ignored.
+            return GetCaseIndependentLetters(character, ascii_subject, unicode,
+                                             &character, 1, letters);
+        }
+
+        if (other1 > kMaxAsciiCharCode)
+            other1 = character;
+        if (other2 > kMaxAsciiCharCode)
+            other2 = character;
+        if (other3 > kMaxAsciiCharCode)
+            other3 = character;
+    }
+
     const char16_t choices[] = {
         character,
-        unicode::ToLowerCase(character),
-        unicode::ToUpperCase(character)
+        upper,
+        other1,
+        other2,
+        other3
     };
     return GetCaseIndependentLetters(character, ascii_subject, unicode,
                                      choices, ArrayLength(choices), letters);
 }
 
 static char16_t
 ConvertNonLatin1ToLatin1(char16_t c, bool unicode)
 {
--- a/js/src/jit-test/tests/gc/bug-1138390.js
+++ b/js/src/jit-test/tests/gc/bug-1138390.js
@@ -12,17 +12,17 @@ gczeal(0);
 gc();
 
 // Start an incremental GC that includes the atoms zone
 startgc(0);
 var g = newGlobal();
 
 // Start an off thread compilation that will not run until GC has finished
 if ("gcstate" in this)
-   assertEq(gcstate(), "mark");
+   assertEq(gcstate(), "Mark");
 g.offThreadCompileScript('23;', {});
 
 // Wait for the compilation to finish, which must finish the GC first
 assertEq(23, g.runOffThreadScript());
 if ("gcstate" in this)
-   assertEq(gcstate(), "none");
+   assertEq(gcstate(), "NotActive");
 
 print("done");
--- a/js/src/jit-test/tests/gc/incremental-abort.js
+++ b/js/src/jit-test/tests/gc/incremental-abort.js
@@ -22,31 +22,33 @@ function testAbort(zoneCount, objectCoun
                 "}",
                  { global: zone });
         zone.makeObjectGraph(objectCount);
         zones.push(zone);
     }
 
     var didAbort = false;
     startgc(sliceCount, "shrinking");
-    while (gcstate() !== "none") {
+    while (gcstate() !== "NotActive") {
         var state = gcstate();
         if (state == abortState) {
             abortgc();
             didAbort = true;
             break;
         }
 
         gcslice(sliceCount);
     }
 
-    assertEq(gcstate(), "none");
+    assertEq(gcstate(), "NotActive");
     if (abortState)
         assertEq(didAbort, true);
 
     return zones;
 }
 
 gczeal(0);
 testAbort(10, 10000, 10000);
-testAbort(10, 10000, 10000, "mark");
-testAbort(10, 10000, 10000, "sweep");
-testAbort(10, 10000, 10000, "compact");
+testAbort(10, 10000, 10000, "Mark");
+testAbort(10, 10000, 10000, "Sweep");
+testAbort(10, 10000, 10000, "Compact");
+// Note: we do not yield automatically before Finalize or Decommit, as they yield internally.
+// Thus, we may not witness an incremental state in this phase and cannot test it explicitly.
--- a/js/src/jit-test/tests/gc/incremental-compacting.js
+++ b/js/src/jit-test/tests/gc/incremental-compacting.js
@@ -22,21 +22,21 @@ function testCompacting(zoneCount, objec
                  "        objects.push({ serial: i }); " +
                 "}",
                  { global: zone });
         zone.makeObjectGraph(objectCount);
         zones.push(zone);
     }
 
     // Finish any alloc-triggered incremental GC
-    if (gcstate() !== "none")
+    if (gcstate() !== "NotActive")
         gc();
 
     startgc(sliceCount, "shrinking");
-    while (gcstate() !== "none") {
+    while (gcstate() !== "NotActive") {
         gcslice(sliceCount);
     }
 
     return zones;
 }
 
 testCompacting(1, 100000, 100000);
 testCompacting(2, 100000, 100000);
--- a/js/src/jit-test/tests/gc/incremental-state.js
+++ b/js/src/jit-test/tests/gc/incremental-state.js
@@ -1,63 +1,63 @@
 // Test expected state changes during collection.
 if (!("gcstate" in this))
     quit();
 
 gczeal(0);
 
 // Non-incremental GC.
 gc();
-assertEq(gcstate(), "none");
+assertEq(gcstate(), "NotActive");
 
 // Incremental GC in minimal slice. Note that finalization always uses zero-
 // sized slices while background finalization is on-going, so we need to loop.
 gcslice(1000000);
-while (gcstate() == "finalize") { gcslice(1); }
-while (gcstate() == "decommit") { gcslice(1); }
-assertEq(gcstate(), "none");
+while (gcstate() == "Finalize") { gcslice(1); }
+while (gcstate() == "Decommit") { gcslice(1); }
+assertEq(gcstate(), "NotActive");
 
 // Incremental GC in multiple slices: if marking takes more than one slice,
 // we yield before we start sweeping.
 gczeal(0);
 gcslice(1);
-assertEq(gcstate(), "mark");
+assertEq(gcstate(), "Mark");
 gcslice(1000000);
-assertEq(gcstate(), "mark");
+assertEq(gcstate(), "Mark");
 gcslice(1000000);
-while (gcstate() == "finalize") { gcslice(1); }
-while (gcstate() == "decommit") { gcslice(1); }
-assertEq(gcstate(), "none");
+while (gcstate() == "Finalize") { gcslice(1); }
+while (gcstate() == "Decommit") { gcslice(1); }
+assertEq(gcstate(), "NotActive");
 
 // Zeal mode 8: Incremental GC in two main slices:
 //   1) mark roots
 //   2) mark and sweep
 //   *) finalize.
 gczeal(8, 0);
 gcslice(1);
-assertEq(gcstate(), "mark");
+assertEq(gcstate(), "Mark");
 gcslice(1);
-while (gcstate() == "finalize") { gcslice(1); }
-while (gcstate() == "decommit") { gcslice(1); }
-assertEq(gcstate(), "none");
+while (gcstate() == "Finalize") { gcslice(1); }
+while (gcstate() == "Decommit") { gcslice(1); }
+assertEq(gcstate(), "NotActive");
 
 // Zeal mode 9: Incremental GC in two main slices:
 //   1) mark roots and marking
 //   2) new marking and sweeping
 //   *) finalize.
 gczeal(9, 0);
 gcslice(1);
-assertEq(gcstate(), "mark");
+assertEq(gcstate(), "Mark");
 gcslice(1);
-while (gcstate() == "finalize") { gcslice(1); }
-while (gcstate() == "decommit") { gcslice(1); }
-assertEq(gcstate(), "none");
+while (gcstate() == "Finalize") { gcslice(1); }
+while (gcstate() == "Decommit") { gcslice(1); }
+assertEq(gcstate(), "NotActive");
 
 // Zeal mode 10: Incremental GC in multiple slices (always yeilds before
 // sweeping). This test uses long slices to prove that this zeal mode yields
 // in sweeping, where normal IGC (above) does not.
 gczeal(10, 0);
 gcslice(1000000);
-assertEq(gcstate(), "sweep");
+assertEq(gcstate(), "Sweep");
 gcslice(1000000);
-while (gcstate() == "finalize") { gcslice(1); }
-while (gcstate() == "decommit") { gcslice(1); }
-assertEq(gcstate(), "none");
+while (gcstate() == "Finalize") { gcslice(1); }
+while (gcstate() == "Decommit") { gcslice(1); }
+assertEq(gcstate(), "NotActive");
--- a/js/src/jit/arm/Simulator-arm.cpp
+++ b/js/src/jit/arm/Simulator-arm.cpp
@@ -1497,21 +1497,19 @@ Simulator::exclusiveMonitorGetAndClear(b
 
 void
 Simulator::exclusiveMonitorClear()
 {
     exclusiveMonitorHeld_ = false;
 }
 
 int
-Simulator::readW(int32_t addr, SimInstruction* instr)
+Simulator::readW(int32_t addr, SimInstruction* instr, UnalignedPolicy f)
 {
-    // The regexp engine emits unaligned loads, so we don't check for them here
-    // like most of the other methods do.
-    if ((addr & 3) == 0 || !HasAlignmentFault()) {
+    if ((addr & 3) == 0 || (f == AllowUnaligned && !HasAlignmentFault())) {
         intptr_t* ptr = reinterpret_cast<intptr_t*>(addr);
         return *ptr;
     }
 
     // In WebAssembly, we want unaligned accesses to either raise a signal or
     // do the right thing. Making this simulator properly emulate the behavior
     // of raising a signal is complex, so as a special-case, when in wasm code,
     // we just do the right thing.
@@ -1522,19 +1520,19 @@ Simulator::readW(int32_t addr, SimInstru
         return value;
     }
 
     printf("Unaligned read at 0x%08x, pc=%p\n", addr, instr);
     MOZ_CRASH();
 }
 
 void
-Simulator::writeW(int32_t addr, int value, SimInstruction* instr)
+Simulator::writeW(int32_t addr, int value, SimInstruction* instr, UnalignedPolicy f)
 {
-    if ((addr & 3) == 0 || !HasAlignmentFault()) {
+    if ((addr & 3) == 0 || (f == AllowUnaligned && !HasAlignmentFault())) {
         intptr_t* ptr = reinterpret_cast<intptr_t*>(addr);
         *ptr = value;
         return;
     }
 
     // See the comments above in readW.
     if (wasm::IsPCInWasmCode(reinterpret_cast<void *>(get_pc()))) {
         char* ptr = reinterpret_cast<char*>(addr);
@@ -1587,70 +1585,104 @@ Simulator::writeExW(int32_t addr, int va
     if ((addr & 3) == 0) {
         SharedMem<int32_t*> ptr = SharedMem<int32_t*>::shared(reinterpret_cast<int32_t*>(addr));
         bool held;
         int32_t expected = int32_t(exclusiveMonitorGetAndClear(&held));
         if (!held)
             return 1;
         int32_t old = compareExchangeRelaxed(ptr, expected, int32_t(value));
         return old != expected;
-    } else {
-        printf("Unaligned write at 0x%08x, pc=%p\n", addr, instr);
-        MOZ_CRASH();
     }
+
+    printf("Unaligned write at 0x%08x, pc=%p\n", addr, instr);
+    MOZ_CRASH();
 }
 
 uint16_t
 Simulator::readHU(int32_t addr, SimInstruction* instr)
 {
     // The regexp engine emits unaligned loads, so we don't check for them here
     // like most of the other methods do.
     if ((addr & 1) == 0 || !HasAlignmentFault()) {
         uint16_t* ptr = reinterpret_cast<uint16_t*>(addr);
         return *ptr;
     }
+
+    // See comments in readW.
+    if (wasm::IsPCInWasmCode(reinterpret_cast<void *>(get_pc()))) {
+        char* ptr = reinterpret_cast<char*>(addr);
+        uint16_t value;
+        memcpy(&value, ptr, sizeof(value));
+        return value;
+    }
+
     printf("Unaligned unsigned halfword read at 0x%08x, pc=%p\n", addr, instr);
     MOZ_CRASH();
     return 0;
 }
 
 int16_t
 Simulator::readH(int32_t addr, SimInstruction* instr)
 {
     if ((addr & 1) == 0 || !HasAlignmentFault()) {
         int16_t* ptr = reinterpret_cast<int16_t*>(addr);
         return *ptr;
     }
+
+    // See comments in readW.
+    if (wasm::IsPCInWasmCode(reinterpret_cast<void *>(get_pc()))) {
+        char* ptr = reinterpret_cast<char*>(addr);
+        int16_t value;
+        memcpy(&value, ptr, sizeof(value));
+        return value;
+    }
+
     printf("Unaligned signed halfword read at 0x%08x\n", addr);
     MOZ_CRASH();
     return 0;
 }
 
 void
 Simulator::writeH(int32_t addr, uint16_t value, SimInstruction* instr)
 {
     if ((addr & 1) == 0 || !HasAlignmentFault()) {
         uint16_t* ptr = reinterpret_cast<uint16_t*>(addr);
         *ptr = value;
-    } else {
-        printf("Unaligned unsigned halfword write at 0x%08x, pc=%p\n", addr, instr);
-        MOZ_CRASH();
+        return;
     }
+
+    // See the comments above in readW.
+    if (wasm::IsPCInWasmCode(reinterpret_cast<void *>(get_pc()))) {
+        char* ptr = reinterpret_cast<char*>(addr);
+        memcpy(ptr, &value, sizeof(value));
+        return;
+    }
+
+    printf("Unaligned unsigned halfword write at 0x%08x, pc=%p\n", addr, instr);
+    MOZ_CRASH();
 }
 
 void
 Simulator::writeH(int32_t addr, int16_t value, SimInstruction* instr)
 {
     if ((addr & 1) == 0 || !HasAlignmentFault()) {
         int16_t* ptr = reinterpret_cast<int16_t*>(addr);
         *ptr = value;
-    } else {
-        printf("Unaligned halfword write at 0x%08x, pc=%p\n", addr, instr);
-        MOZ_CRASH();
+        return;
     }
+
+    // See the comments above in readW.
+    if (wasm::IsPCInWasmCode(reinterpret_cast<void *>(get_pc()))) {
+        char* ptr = reinterpret_cast<char*>(addr);
+        memcpy(ptr, &value, sizeof(value));
+        return;
+    }
+
+    printf("Unaligned halfword write at 0x%08x, pc=%p\n", addr, instr);
+    MOZ_CRASH();
 }
 
 uint16_t
 Simulator::readExHU(int32_t addr, SimInstruction* instr)
 {
     // The regexp engine emits unaligned loads, so we don't check for them here
     // like most of the other methods do.
     if ((addr & 1) == 0 || !HasAlignmentFault()) {
@@ -3189,19 +3221,19 @@ Simulator::decodeType2(SimInstruction* i
             uint8_t val = readBU(addr);
             set_register(rd, val);
         } else {
             uint8_t val = get_register(rd);
             writeB(addr, val);
         }
     } else {
         if (instr->hasL())
-            set_register(rd, readW(addr, instr));
+            set_register(rd, readW(addr, instr, AllowUnaligned));
         else
-            writeW(addr, get_register(rd), instr);
+            writeW(addr, get_register(rd), instr, AllowUnaligned);
     }
 }
 
 static uint32_t
 rotateBytes(uint32_t val, int32_t rotate)
 {
     switch (rotate) {
       default:
@@ -3456,19 +3488,19 @@ Simulator::decodeType3(SimInstruction* i
             uint8_t byte = readB(addr);
             set_register(rd, byte);
         } else {
             uint8_t byte = get_register(rd);
             writeB(addr, byte);
         }
     } else {
         if (instr->hasL())
-            set_register(rd, readW(addr, instr));
+            set_register(rd, readW(addr, instr, AllowUnaligned));
         else
-            writeW(addr, get_register(rd), instr);
+            writeW(addr, get_register(rd), instr, AllowUnaligned);
     }
 }
 
 void
 Simulator::decodeType4(SimInstruction* instr)
 {
     // Only allowed to be set in privileged mode.
     MOZ_ASSERT(instr->bit(22) == 0);
@@ -4283,18 +4315,20 @@ Simulator::decodeSpecialCondition(SimIns
               default:
                 MOZ_CRASH();
                 break;
             }
             int r = 0;
             while (r < regs) {
                 uint32_t data[2];
                 get_d_register(Vd + r, data);
-                writeW(address, data[0], instr);
-                writeW(address + 4, data[1], instr);
+                // TODO: We should AllowUnaligned here only if the alignment attribute of
+                // the instruction calls for default alignment.
+                writeW(address, data[0], instr, AllowUnaligned);
+                writeW(address + 4, data[1], instr, AllowUnaligned);
                 address += 8;
                 r++;
             }
             if (Rm != 15) {
                 if (Rm == 13)
                     set_register(Rn, address);
                 else
                     set_register(Rn, get_register(Rn) + get_register(Rm));
@@ -4322,18 +4356,20 @@ Simulator::decodeSpecialCondition(SimIns
                 break;
               default:
                 MOZ_CRASH();
                 break;
             }
             int r = 0;
             while (r < regs) {
                 uint32_t data[2];
-                data[0] = readW(address, instr);
-                data[1] = readW(address + 4, instr);
+                // TODO: We should AllowUnaligned here only if the alignment attribute of
+                // the instruction calls for default alignment.
+                data[0] = readW(address, instr, AllowUnaligned);
+                data[1] = readW(address + 4, instr, AllowUnaligned);
                 set_d_register(Vd + r, data);
                 address += 8;
                 r++;
             }
             if (Rm != 15) {
                 if (Rm == 13)
                     set_register(Rn, address);
                 else
--- a/js/src/jit/arm/Simulator-arm.h
+++ b/js/src/jit/arm/Simulator-arm.h
@@ -199,16 +199,30 @@ class Simulator
         bad_lr = -1,
         // A pc value used to signal the simulator to stop execution. Generally
         // the lr is set to this value on transition from native C code to
         // simulated execution, so that the simulator can "return" to the native
         // C code.
         end_sim_pc = -2
     };
 
+    // ForbidUnaligned means "always fault on unaligned access".
+    //
+    // AllowUnaligned means "allow the unaligned access if other conditions are
+    // met".  The "other conditions" vary with the instruction: For all
+    // instructions the base condition is !HasAlignmentFault(), ie, the chip is
+    // configured to allow unaligned accesses.  For instructions like VLD1
+    // there is an additional constraint that the alignment attribute in the
+    // instruction must be set to "default alignment".
+
+    enum UnalignedPolicy {
+        ForbidUnaligned,
+        AllowUnaligned
+    };
+
     bool init();
 
     // Checks if the current instruction should be executed based on its
     // condition bits.
     inline bool conditionallyExecute(SimInstruction* instr);
 
     // Helper functions to set the conditional flags in the architecture state.
     void setNZFlags(int32_t val);
@@ -256,18 +270,18 @@ class Simulator
     inline int16_t readH(int32_t addr, SimInstruction* instr);
     // Note: Overloaded on the sign of the value.
     inline void writeH(int32_t addr, uint16_t value, SimInstruction* instr);
     inline void writeH(int32_t addr, int16_t value, SimInstruction* instr);
 
     inline uint16_t readExHU(int32_t addr, SimInstruction* instr);
     inline int32_t writeExH(int32_t addr, uint16_t value, SimInstruction* instr);
 
-    inline int readW(int32_t addr, SimInstruction* instr);
-    inline void writeW(int32_t addr, int value, SimInstruction* instr);
+    inline int readW(int32_t addr, SimInstruction* instr, UnalignedPolicy f = ForbidUnaligned);
+    inline void writeW(int32_t addr, int value, SimInstruction* instr, UnalignedPolicy f = ForbidUnaligned);
 
     inline int readExW(int32_t addr, SimInstruction* instr);
     inline int writeExW(int32_t addr, int value, SimInstruction* instr);
 
     int32_t* readDW(int32_t addr);
     void writeDW(int32_t addr, int32_t value1, int32_t value2);
 
     int32_t readExDW(int32_t addr, int32_t* hibits);
--- a/js/src/jit/none/MacroAssembler-none.h
+++ b/js/src/jit/none/MacroAssembler-none.h
@@ -80,16 +80,17 @@ static constexpr Register64 ReturnReg64(
 
 static constexpr Register ABINonArgReg0 = { Registers::invalid_reg };
 static constexpr Register ABINonArgReg1 = { Registers::invalid_reg };
 static constexpr Register ABINonArgReturnReg0 = { Registers::invalid_reg };
 static constexpr Register ABINonArgReturnReg1 = { Registers::invalid_reg };
 
 static constexpr Register WasmTableCallPtrReg = { Registers::invalid_reg };
 static constexpr Register WasmTableCallSigReg = { Registers::invalid_reg };
+static constexpr Register WasmTlsReg = { Registers::invalid_reg };
 
 static constexpr uint32_t ABIStackAlignment = 4;
 static constexpr uint32_t CodeAlignment = 4;
 static constexpr uint32_t JitStackAlignment = 8;
 static constexpr uint32_t JitStackValueAlignment = JitStackAlignment / sizeof(Value);
 
 static const Scale ScalePointer = TimesOne;
 
--- a/js/src/jsapi-tests/moz.build
+++ b/js/src/jsapi-tests/moz.build
@@ -39,16 +39,17 @@ UNIFIED_SOURCES += [
     'testFreshGlobalEvalRedefinition.cpp',
     'testFunctionProperties.cpp',
     'testGCAllocator.cpp',
     'testGCCellPtr.cpp',
     'testGCChunkPool.cpp',
     'testGCExactRooting.cpp',
     'testGCFinalizeCallback.cpp',
     'testGCHeapPostBarriers.cpp',
+    'testGCHooks.cpp',
     'testGCMarking.cpp',
     'testGCOutOfMemory.cpp',
     'testGCStoreBufferRemoval.cpp',
     'testGCUniqueId.cpp',
     'testGCWeakCache.cpp',
     'testGCWeakRef.cpp',
     'testGetPropertyDescriptor.cpp',
     'testHashTable.cpp',
--- a/js/src/jsapi-tests/testGCFinalizeCallback.cpp
+++ b/js/src/jsapi-tests/testGCFinalizeCallback.cpp
@@ -98,17 +98,17 @@ BEGIN_TEST(testGCFinalizeCallback)
 
     /* Full GC with reset due to new compartment, becoming compartment GC. */
 
     FinalizeCalls = 0;
     JS_SetGCZeal(cx, 9, 1000000);
     JS::PrepareForFullGC(cx);
     js::SliceBudget budget(js::WorkBudget(1));
     cx->gc.startDebugGC(GC_NORMAL, budget);
-    CHECK(cx->gc.state() == js::gc::MARK);
+    CHECK(cx->gc.state() == js::gc::State::Mark);
     CHECK(cx->gc.isFullGc());
 
     JS::RootedObject global4(cx, createTestGlobal());
     budget = js::SliceBudget(js::WorkBudget(1));
     cx->gc.debugGCSlice(budget);
     while (cx->gc.isIncrementalGCInProgress())
         cx->gc.debugGCSlice(budget);
     CHECK(!cx->gc.isIncrementalGCInProgress());
new file mode 100644
--- /dev/null
+++ b/js/src/jsapi-tests/testGCHooks.cpp
@@ -0,0 +1,36 @@
+/* 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 "mozilla/UniquePtr.h"
+
+#include "js/GCAPI.h"
+
+#include "jsapi-tests/tests.h"
+
+static unsigned gSliceCallbackCount = 0;
+
+static void
+NonIncrementalGCSliceCallback(JSContext* cx, JS::GCProgress progress, const JS::GCDescription& desc)
+{
+    ++gSliceCallbackCount;
+    MOZ_RELEASE_ASSERT(progress == JS::GC_CYCLE_BEGIN || progress == JS::GC_CYCLE_END);
+    MOZ_RELEASE_ASSERT(desc.isCompartment_ == false);
+    MOZ_RELEASE_ASSERT(desc.invocationKind_ == GC_NORMAL);
+    MOZ_RELEASE_ASSERT(desc.reason_ == JS::gcreason::API);
+    if (progress == JS::GC_CYCLE_END) {
+        mozilla::UniquePtr<char16_t> summary(desc.formatSummaryMessage(cx));
+        mozilla::UniquePtr<char16_t> message(desc.formatSliceMessage(cx));
+        mozilla::UniquePtr<char16_t> json(desc.formatJSON(cx, 0));
+    }
+}
+
+BEGIN_TEST(testGCSliceCallback)
+{
+    JS::SetGCSliceCallback(cx, NonIncrementalGCSliceCallback);
+    JS_GC(cx);
+    JS::SetGCSliceCallback(cx, nullptr);
+    CHECK(gSliceCallbackCount == 2);
+    return true;
+}
+END_TEST(testGCSliceCallback)
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -48,56 +48,59 @@
  * passed.
  *
  * Collector states
  * ----------------
  *
  * The collector proceeds through the following states, the current state being
  * held in JSRuntime::gcIncrementalState:
  *
- *  - MARK_ROOTS - marks the stack and other roots
- *  - MARK       - incrementally marks reachable things
- *  - SWEEP      - sweeps zones in groups and continues marking unswept zones
+ *  - MarkRoots  - marks the stack and other roots
+ *  - Mark       - incrementally marks reachable things
+ *  - Sweep      - sweeps zones in groups and continues marking unswept zones
+ *  - Finalize   - performs background finalization, concurrent with mutator
+ *  - Compact    - incrementally compacts by zone
+ *  - Decommit   - performs background decommit and chunk removal
  *
- * The MARK_ROOTS activity always takes place in the first slice. The next two
+ * The MarkRoots activity always takes place in the first slice. The next two
  * states can take place over one or more slices.
  *
  * In other words an incremental collection proceeds like this:
  *
- * Slice 1:   MARK_ROOTS: Roots pushed onto the mark stack.
- *            MARK:       The mark stack is processed by popping an element,
+ * Slice 1:   MarkRoots:  Roots pushed onto the mark stack.
+ *            Mark:       The mark stack is processed by popping an element,
  *                        marking it, and pushing its children.
  *
  *          ... JS code runs ...
  *
- * Slice 2:   MARK:       More mark stack processing.
+ * Slice 2:   Mark:       More mark stack processing.
  *
  *          ... JS code runs ...
  *
- * Slice n-1: MARK:       More mark stack processing.
+ * Slice n-1: Mark:       More mark stack processing.
  *
  *          ... JS code runs ...
  *
- * Slice n:   MARK:       Mark stack is completely drained.
- *            SWEEP:      Select first group of zones to sweep and sweep them.
+ * Slice n:   Mark:       Mark stack is completely drained.
+ *            Sweep:      Select first group of zones to sweep and sweep them.
  *
  *          ... JS code runs ...
  *
- * Slice n+1: SWEEP:      Mark objects in unswept zones that were newly
+ * Slice n+1: Sweep:      Mark objects in unswept zones that were newly
  *                        identified as alive (see below). Then sweep more zone
  *                        groups.
  *
  *          ... JS code runs ...
  *
- * Slice n+2: SWEEP:      Mark objects in unswept zones that were newly
+ * Slice n+2: Sweep:      Mark objects in unswept zones that were newly
  *                        identified as alive. Then sweep more zone groups.
  *
  *          ... JS code runs ...
  *
- * Slice m:   SWEEP:      Sweeping is finished, and background sweeping
+ * Slice m:   Sweep:      Sweeping is finished, and background sweeping
  *                        started on the helper thread.
  *
  *          ... JS code runs, remaining sweeping done on background thread ...
  *
  * When background sweeping finishes the GC is complete.
  *
  * Incremental marking
  * -------------------
@@ -136,17 +139,17 @@
  * about to be destroyed.
  *
  * Sweeping all finalizable objects in one go would introduce long pauses, so
  * instead sweeping broken up into groups of zones. Zones which are not yet
  * being swept are still marked, so the issue above does not apply.
  *
  * The order of sweeping is restricted by cross compartment pointers - for
  * example say that object |a| from zone A points to object |b| in zone B and
- * neither object was marked when we transitioned to the SWEEP phase. Imagine we
+ * neither object was marked when we transitioned to the Sweep phase. Imagine we
  * sweep B first and then return to the mutator. It's possible that the mutator
  * could cause |a| to become alive through a read barrier (perhaps it was a
  * shape that was accessed via a shape table). Then we would need to mark |b|,
  * which |a| points to, but |b| has already been swept.
  *
  * So if there is such a pointer then marking of zone B must not finish before
  * marking of zone A.  Pointers which form a cycle between zones therefore
  * restrict those zones to being swept at the same time, and these are found
@@ -817,17 +820,17 @@ GCRuntime::GCRuntime(JSRuntime* rt) :
     majorGCNumber(0),
     jitReleaseNumber(0),
     number(0),
     startNumber(0),
     isFull(false),
 #ifdef DEBUG
     disableStrictProxyCheckingCount(0),
 #endif
-    incrementalState(gc::NO_INCREMENTAL),
+    incrementalState(gc::State::NotActive),
     lastMarkSlice(false),
     sweepOnBackgroundThread(false),
     foundBlackGrayEdges(false),
     blocksToFreeAfterSweeping(JSRuntime::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
     blocksToFreeAfterMinorGC(JSRuntime::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
     zoneGroupIndex(0),
     zoneGroups(nullptr),
     currentZoneGroup(nullptr),
@@ -4171,17 +4174,17 @@ js::gc::MarkingValidator::nonIncremental
     /*
      * After this point, the function should run to completion, so we shouldn't
      * do anything fallible.
      */
     initialized = true;
 
     /* Re-do all the marking, but non-incrementally. */
     js::gc::State state = gc->incrementalState;
-    gc->incrementalState = MARK_ROOTS;
+    gc->incrementalState = State::MarkRoots;
 
     {
         gcstats::AutoPhase ap(gc->stats, gcstats::PHASE_MARK);
 
         {
             gcstats::AutoPhase ap(gc->stats, gcstats::PHASE_UNMARK);
 
             for (GCZonesIter zone(runtime); !zone.done(); zone.next())
@@ -4191,22 +4194,22 @@ js::gc::MarkingValidator::nonIncremental
             gcmarker->reset();
 
             for (auto chunk = gc->allNonEmptyChunks(); !chunk.done(); chunk.next())
                 chunk->bitmap.clear();
         }
 
         gc->markRuntime(gcmarker, GCRuntime::MarkRuntime, lock);
 
-        gc->incrementalState = MARK;
+        gc->incrementalState = State::Mark;
         auto unlimited = SliceBudget::unlimited();
         MOZ_RELEASE_ASSERT(gc->marker.drainMarkStack(unlimited));
     }
 
-    gc->incrementalState = SWEEP;
+    gc->incrementalState = State::Sweep;
     {
         gcstats::AutoPhase ap1(gc->stats, gcstats::PHASE_SWEEP);
         gcstats::AutoPhase ap2(gc->stats, gcstats::PHASE_SWEEP_MARK);
 
         gc->markAllWeakReferences(gcstats::PHASE_SWEEP_MARK_WEAK);
 
         /* Update zone state for gray marking. */
         for (GCZonesIter zone(runtime); !zone.done(); zone.next()) {
@@ -5616,44 +5619,48 @@ AutoTraceSession::~AutoTraceSession()
         runtime->heapState_ = prevState;
     }
 }
 
 void
 GCRuntime::resetIncrementalGC(const char* reason, AutoLockForExclusiveAccess& lock)
 {
     switch (incrementalState) {
-      case NO_INCREMENTAL:
+      case State::NotActive:
         return;
 
-      case MARK: {
+      case State::MarkRoots:
+        MOZ_CRASH("resetIncrementalGC did not expect MarkRoots state");
+        break;
+
+      case State::Mark: {
         /* Cancel any ongoing marking. */
         marker.reset();
         marker.stop();
         clearBufferedGrayRoots();
 
         for (GCCompartmentsIter c(rt); !c.done(); c.next())
             ResetGrayList(c);
 
         for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
             MOZ_ASSERT(zone->isGCMarking());
             zone->setNeedsIncrementalBarrier(false, Zone::UpdateJit);
             zone->setGCState(Zone::NoGC);
         }
 
         blocksToFreeAfterSweeping.freeAll();
 
-        incrementalState = NO_INCREMENTAL;
+        incrementalState = State::NotActive;
 
         MOZ_ASSERT(!marker.shouldCheckCompartments());
 
         break;
       }
 
-      case SWEEP: {
+      case State::Sweep: {
         marker.reset();
 
         for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next())
             c->scheduledForDestruction = false;
 
         /* Finish sweeping the current zone group, then abort. */
         abortSweepAfterCurrentGroup = true;
 
@@ -5668,68 +5675,65 @@ GCRuntime::resetIncrementalGC(const char
 
         {
             gcstats::AutoPhase ap(stats, gcstats::PHASE_WAIT_BACKGROUND_THREAD);
             rt->gc.waitBackgroundSweepOrAllocEnd();
         }
         break;
       }
 
-      case FINALIZE: {
+      case State::Finalize: {
         {
             gcstats::AutoPhase ap(stats, gcstats::PHASE_WAIT_BACKGROUND_THREAD);
             rt->gc.waitBackgroundSweepOrAllocEnd();
         }
 
         bool wasCompacting = isCompacting;
         isCompacting = false;
 
         auto unlimited = SliceBudget::unlimited();
         incrementalCollectSlice(unlimited, JS::gcreason::RESET, lock);
 
         isCompacting = wasCompacting;
 
         break;
       }
 
-      case COMPACT: {
+      case State::Compact: {
         bool wasCompacting = isCompacting;
 
         isCompacting = true;
         startedCompacting = true;
         zonesToMaybeCompact.clear();
 
         auto unlimited = SliceBudget::unlimited();
         incrementalCollectSlice(unlimited, JS::gcreason::RESET, lock);
 
         isCompacting = wasCompacting;
         break;
       }
 
-      case DECOMMIT: {
+      case State::Decommit: {
         auto unlimited = SliceBudget::unlimited();
         incrementalCollectSlice(unlimited, JS::gcreason::RESET, lock);
         break;
       }
-
-      default:
-        MOZ_CRASH("Invalid incremental GC state");
     }
 
     stats.reset(reason);
 
 #ifdef DEBUG
     assertBackgroundSweepingFinished();
     for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
         MOZ_ASSERT(!zone->isCollecting());
         MOZ_ASSERT(!zone->needsIncrementalBarrier());
         MOZ_ASSERT(!zone->isOnList());
     }
     MOZ_ASSERT(zonesToMaybeCompact.isEmpty());
-    MOZ_ASSERT(incrementalState == NO_INCREMENTAL);
+    MOZ_ASSERT(incrementalState == State::NotActive);
 #endif
 }
 
 namespace {
 
 class AutoGCSlice {
   public:
     explicit AutoGCSlice(JSRuntime* rt);
@@ -5838,70 +5842,70 @@ GCRuntime::incrementalCollectSlice(Slice
         /*
          * Yields between slices occurs at predetermined points in these modes;
          * the budget is not used.
          */
         budget.makeUnlimited();
     }
 
     switch (incrementalState) {
-      case NO_INCREMENTAL:
+      case State::NotActive:
         initialReason = reason;
         cleanUpEverything = ShouldCleanUpEverything(reason, invocationKind);
         isCompacting = shouldCompact();
         lastMarkSlice = false;
 
-        incrementalState = MARK_ROOTS;
+        incrementalState = State::MarkRoots;
 
         MOZ_FALLTHROUGH;
 
-      case MARK_ROOTS:
+      case State::MarkRoots:
         if (!beginMarkPhase(reason, lock)) {
-            incrementalState = NO_INCREMENTAL;
+            incrementalState = State::NotActive;
             return;
         }
 
         if (!destroyingRuntime)
             pushZealSelectedObjects();
 
-        incrementalState = MARK;
+        incrementalState = State::Mark;
 
         if (isIncremental && useZeal && hasZealMode(ZealMode::IncrementalRootsThenFinish))
             break;
 
         MOZ_FALLTHROUGH;
 
-      case MARK:
+      case State::Mark:
         AutoGCRooter::traceAllWrappers(&marker);
 
         /* If we needed delayed marking for gray roots, then collect until done. */
         if (!hasBufferedGrayRoots()) {
             budget.makeUnlimited();
             isIncremental = false;
         }
 
         if (drainMarkStack(budget, gcstats::PHASE_MARK) == NotFinished)
             break;
 
         MOZ_ASSERT(marker.isDrained());
 
         if (!lastMarkSlice && isIncremental && useZeal &&
-            ((initialState == MARK && !hasZealMode(ZealMode::IncrementalRootsThenFinish)) ||
+            ((initialState == State::Mark && !hasZealMode(ZealMode::IncrementalRootsThenFinish)) ||
              hasZealMode(ZealMode::IncrementalMarkAllThenFinish)))
         {
             /*
              * Yield with the aim of starting the sweep in the next
              * slice.  We will need to mark anything new on the stack
-             * when we resume, so we stay in MARK state.
+             * when we resume, so we stay in Mark state.
              */
             lastMarkSlice = true;
             break;
         }
 
-        incrementalState = SWEEP;
+        incrementalState = State::Sweep;
 
         /*
          * This runs to completion, but we don't continue if the budget is
          * now exhasted.
          */
         beginSweepPhase(destroyingRuntime, lock);
         if (budget.isOverBudget())
             break;
@@ -5910,31 +5914,31 @@ GCRuntime::incrementalCollectSlice(Slice
          * Always yield here when running in incremental multi-slice zeal
          * mode, so RunDebugGC can reset the slice buget.
          */
         if (isIncremental && useZeal && hasZealMode(ZealMode::IncrementalMultipleSlices))
             break;
 
         MOZ_FALLTHROUGH;
 
-      case SWEEP:
+      case State::Sweep:
         if (sweepPhase(budget, lock) == NotFinished)
             break;
 
         endSweepPhase(destroyingRuntime, lock);
 
-        incrementalState = FINALIZE;
+        incrementalState = State::Finalize;
 
         /* Yield before compacting since it is not incremental. */
         if (isCompacting && isIncremental)
             break;
 
         MOZ_FALLTHROUGH;
 
-      case FINALIZE:
+      case State::Finalize:
         {
             gcstats::AutoPhase ap(stats, gcstats::PHASE_WAIT_BACKGROUND_THREAD);
 
             // Yield until background finalization is done.
             if (isIncremental) {
                 // Poll for end of background sweeping
                 AutoLockGC lock(rt);
                 if (isBackgroundSweeping())
@@ -5950,53 +5954,50 @@ GCRuntime::incrementalCollectSlice(Slice
             gcstats::AutoPhase ap1(stats, gcstats::PHASE_SWEEP);
             gcstats::AutoPhase ap2(stats, gcstats::PHASE_DESTROY);
             AutoSetThreadIsSweeping threadIsSweeping;
             FreeOp fop(rt);
             sweepZones(&fop, destroyingRuntime);
         }
 
         MOZ_ASSERT(!startedCompacting);
-        incrementalState = COMPACT;
+        incrementalState = State::Compact;
 
         MOZ_FALLTHROUGH;
 
-      case COMPACT:
+      case State::Compact:
         if (isCompacting) {
             if (!startedCompacting)
                 beginCompactPhase();
 
             if (compactPhase(reason, budget, lock) == NotFinished)
                 break;
 
             endCompactPhase(reason);
         }
 
         startDecommit();
-        incrementalState = DECOMMIT;
+        incrementalState = State::Decommit;
 
         MOZ_FALLTHROUGH;
 
-      case DECOMMIT:
+      case State::Decommit:
         {
             gcstats::AutoPhase ap(stats, gcstats::PHASE_WAIT_BACKGROUND_THREAD);
 
             // Yield until background decommit is done.
             if (isIncremental && decommitTask.isRunning())
                 break;
 
             decommitTask.join();
         }
 
         finishCollection(reason);
-        incrementalState = NO_INCREMENTAL;
+        incrementalState = State::NotActive;
         break;
-
-      default:
-        MOZ_CRASH("unexpected GC incrementalState");
     }
 }
 
 IncrementalSafety
 gc::IsIncrementalGCSafe(JSRuntime* rt)
 {
     MOZ_ASSERT(!rt->mainThread.suppressGC);
 
@@ -6149,17 +6150,17 @@ GCRuntime::gcCycle(bool nonincrementalBy
 
         stats.nonincremental("requested");
         budget.makeUnlimited();
     } else {
         budgetIncrementalGC(budget, session.lock);
     }
 
     /* The GC was reset, so we need a do-over. */
-    if (prevState != NO_INCREMENTAL && !isIncrementalGCInProgress())
+    if (prevState != State::NotActive && !isIncrementalGCInProgress())
         return true;
 
     TraceMajorGCStart();
 
     incrementalCollectSlice(budget, reason, session.lock);
 
 #ifndef JS_MORE_DETERMINISTIC
     nextFullGCTime = PRMJ_Now() + GC_IDLE_FULL_SPAN;
@@ -6356,17 +6357,17 @@ void
 GCRuntime::finishGC(JS::gcreason::Reason reason)
 {
     MOZ_ASSERT(isIncrementalGCInProgress());
 
     // If we're not collecting because we're out of memory then skip the
     // compacting phase if we need to finish an ongoing incremental GC
     // non-incrementally to avoid janking the browser.
     if (!IsOOMReason(initialReason)) {
-        if (incrementalState == COMPACT) {
+        if (incrementalState == State::Compact) {
             abortGC();
             return;
         }
 
         isCompacting = false;
     }
 
     collect(false, SliceBudget::unlimited(), reason);
@@ -6794,18 +6795,18 @@ GCRuntime::runDebugGC()
             invocationKind = GC_SHRINK;
         collect(false, budget, JS::gcreason::DEBUG_GC);
 
         /*
          * For multi-slice zeal, reset the slice size when we get to the sweep
          * or compact phases.
          */
         if (hasZealMode(ZealMode::IncrementalMultipleSlices)) {
-            if ((initialState == MARK && incrementalState == SWEEP) ||
-                (initialState == SWEEP && incrementalState == COMPACT))
+            if ((initialState == State::Mark && incrementalState == State::Sweep) ||
+                (initialState == State::Sweep && incrementalState == State::Compact))
             {
                 incrementalLimit = zealFrequency / 2;
             }
         }
     } else if (hasZealMode(ZealMode::Compact)) {
         gc(GC_SHRINK, JS::gcreason::DEBUG_GC);
     } else {
         gc(GC_NORMAL, JS::gcreason::DEBUG_GC);
@@ -7308,17 +7309,17 @@ JS_PUBLIC_API(bool)
 JS::IsIncrementalGCInProgress(JSContext* cx)
 {
     return cx->gc.isIncrementalGCInProgress() && !cx->gc.isVerifyPreBarriersEnabled();
 }
 
 JS_PUBLIC_API(bool)
 JS::IsIncrementalBarrierNeeded(JSContext* cx)
 {
-    return cx->gc.state() == gc::MARK && !cx->isHeapBusy();
+    return cx->gc.state() == gc::State::Mark && !cx->isHeapBusy();
 }
 
 struct IncrementalReferenceBarrierFunctor {
     template <typename T> void operator()(T* t) { T::writeBarrierPre(t); }
 };
 
 JS_PUBLIC_API(void)
 JS::IncrementalReferenceBarrier(GCCellPtr thing)
@@ -7591,28 +7592,22 @@ NewMemoryInfoObject(JSContext* cx)
     }
 
     return obj;
 }
 
 const char*
 StateName(State state)
 {
-    static const char* names[] = {
-        "None",
-        "MarkRoots",
-        "Mark",
-        "Sweep",
-        "Finalize",
-        "Compact",
-        "Decommit"
-    };
-    MOZ_ASSERT(ArrayLength(names) == NUM_STATES);
-    MOZ_ASSERT(state < NUM_STATES);
-    return names[state];
+    switch(state) {
+#define MAKE_CASE(name) case State::name: return #name;
+      GCSTATES(MAKE_CASE)
+#undef MAKE_CASE
+    }
+    MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("invalide gc::State enum value");
 }
 
 void
 AutoAssertHeapBusy::checkCondition(JSRuntime *rt)
 {
     this->rt = rt;
     MOZ_ASSERT(rt->isHeapBusy());
 }
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -38,26 +38,28 @@ struct Statistics;
 } // namespace gcstats
 
 class Nursery;
 
 namespace gc {
 
 struct FinalizePhase;
 
-enum State {
-    NO_INCREMENTAL,
-    MARK_ROOTS,
-    MARK,
-    SWEEP,
-    FINALIZE,
-    COMPACT,
-    DECOMMIT,
-
-    NUM_STATES
+#define GCSTATES(D) \
+    D(NotActive) \
+    D(MarkRoots) \
+    D(Mark) \
+    D(Sweep) \
+    D(Finalize) \
+    D(Compact) \
+    D(Decommit)
+enum class State {
+#define MAKE_STATE(name) name,
+    GCSTATES(MAKE_STATE)
+#undef MAKE_STATE
 };
 
 /*
  * Map from C++ type to alloc kind for non-object types. JSObject does not have
  * a 1:1 mapping, so must use Arena::thingSize.
  *
  * The AllocKind is available as MapTypeToFinalizeKind<SomeType>::kind.
  */
--- a/js/src/old-configure.in
+++ b/js/src/old-configure.in
@@ -523,16 +523,17 @@ if test "$ac_cv_static_assertion_macros_
 fi
 fi # COMPILE_ENVIRONMENT
 
 dnl ========================================================
 dnl Android libstdc++, placed here so it can use MOZ_ARCH
 dnl computed above.
 dnl ========================================================
 
+MOZ_ANDROID_CPU_ARCH
 MOZ_ANDROID_STLPORT
 
 dnl ========================================================
 dnl Suppress Clang Argument Warnings
 dnl ========================================================
 if test -n "${CLANG_CC}${CLANG_CL}"; then
     _WARNINGS_CFLAGS="-Qunused-arguments ${_WARNINGS_CFLAGS}"
     CPPFLAGS="-Qunused-arguments ${CPPFLAGS}"
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Array/concat-proxy.js
@@ -0,0 +1,25 @@
+var BUGNUMBER = 1287520;
+var summary = 'Array.prototype.concat should check HasProperty everytime for non-dense array';
+
+print(BUGNUMBER + ": " + summary);
+
+var a = [1, 2, 3];
+a.constructor = {
+  [Symbol.species]: function(...args) {
+    var p = new Proxy(new Array(...args), {
+      defineProperty(target, propertyKey, receiver) {
+        if (propertyKey === "0") delete a[1];
+        return Reflect.defineProperty(target, propertyKey, receiver);
+      }
+    });
+    return p;
+  }
+};
+
+var p = a.concat();
+assertEq(0 in p, true);
+assertEq(1 in p, false);
+assertEq(2 in p, true);
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/RegExp/ignoreCase-multiple.js
@@ -0,0 +1,71 @@
+var BUGNUMBER = 1280046;
+var summary = "ignoreCase match should perform Canonicalize both on input and pattern.";
+
+print(BUGNUMBER + ": " + summary);
+
+// Each element [code1, upper, code2] satisfies the following condition:
+//   ToUpperCase(code1) == upper
+//   ToUpperCase(code2) == upper
+var pairs =
+    [
+        // U+00B5: MICRO SIGN
+        // U+039C: GREEK CAPITAL LETTER MU
+        // U+03BC: GREEK SMALL LETTER MU
+        ["\u00B5", "\u039C", "\u03BC"],
+        // U+0345: COMBINING GREEK YPOGEGRAMMENI
+        // U+0399: GREEK CAPITAL LETTER IOTA
+        // U+03B9: GREEK SMALL LETTER IOTA
+        ["\u0345", "\u0399", "\u03B9"],
+        // U+03C2: GREEK SMALL LETTER FINAL SIGMA
+        // U+03A3: GREEK CAPITAL LETTER SIGMA
+        // U+03C3: GREEK SMALL LETTER SIGMA
+        ["\u03C2", "\u03A3", "\u03C3"],
+        // U+03D0: GREEK BETA SYMBOL
+        // U+0392: GREEK CAPITAL LETTER BETA
+        // U+03B2: GREEK SMALL LETTER BETA
+        ["\u03D0", "\u0392", "\u03B2"],
+        // U+03D1: GREEK THETA SYMBOL
+        // U+0398: GREEK CAPITAL LETTER THETA
+        // U+03B8: GREEK SMALL LETTER THETA
+        ["\u03D1", "\u0398", "\u03B8"],
+        // U+03D5: GREEK PHI SYMBOL
+        // U+03A6: GREEK CAPITAL LETTER PHI
+        // U+03C6: GREEK SMALL LETTER PHI
+        ["\u03D5", "\u03A6", "\u03C6"],
+        // U+03D6: GREEK PI SYMBOL
+        // U+03A0: GREEK CAPITAL LETTER PI
+        // U+03C0: GREEK SMALL LETTER PI
+        ["\u03D6", "\u03A0", "\u03C0"],
+        // U+03F0: GREEK KAPPA SYMBOL
+        // U+039A: GREEK CAPITAL LETTER KAPPA
+        // U+03BA: GREEK SMALL LETTER KAPPA
+        ["\u03F0", "\u039A", "\u03BA"],
+        // U+03F1: GREEK RHO SYMBOL
+        // U+03A1: GREEK CAPITAL LETTER RHO
+        // U+03C1: GREEK SMALL LETTER RHO
+        ["\u03F1", "\u03A1", "\u03C1"],
+        // U+03F5: GREEK LUNATE EPSILON SYMBOL
+        // U+0395: GREEK CAPITAL LETTER EPSILON
+        // U+03B5: GREEK SMALL LETTER EPSILON
+        ["\u03F5", "\u0395", "\u03B5"],
+        // U+1E9B: LATIN SMALL LETTER LONG S WITH DOT ABOVE
+        // U+1E60: LATIN CAPITAL LETTER S WITH DOT ABOVE
+        // U+1E61: LATIN SMALL LETTER S WITH DOT ABOVE
+        ["\u1E9B", "\u1E60", "\u1E61"],
+        // U+1FBE: GREEK PROSGEGRAMMENI
+        // U+0399: GREEK CAPITAL LETTER IOTA
+        // U+03B9: GREEK SMALL LETTER IOTA
+        ["\u1FBE", "\u0399", "\u03B9"],
+    ];
+
+for (var [code1, upper, code2] of pairs) {
+    assertEq(new RegExp(code1, "i").test(code2), true);
+    assertEq(new RegExp(code1, "i").test(upper), true);
+    assertEq(new RegExp(upper, "i").test(code1), true);
+    assertEq(new RegExp(upper, "i").test(code2), true);
+    assertEq(new RegExp(code2, "i").test(code1), true);
+    assertEq(new RegExp(code2, "i").test(upper), true);
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/RegExp/replace-global-unicode.js
@@ -0,0 +1,18 @@
+var BUGNUMBER = 1287524;
+var summary = 'RegExp.prototype[@@replace] should not use optimized path if RegExp.prototype.unicode is modified.';
+
+print(BUGNUMBER + ": " + summary);
+
+Object.defineProperty(RegExp.prototype, "unicode", {
+  get() {
+    RegExp.prototype.exec = () => null;
+  }
+});
+
+var rx = RegExp("a", "g");
+var s = "abba";
+var r = rx[Symbol.replace](s, "c");
+assertEq(r, "abba");
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/RegExp/split-limit.js
@@ -0,0 +1,14 @@
+var BUGNUMBER = 1287525;
+var summary = "RegExp.prototype[@@split] shouldn't use optimized path if limit is not number.";
+
+print(BUGNUMBER + ": " + summary);
+
+var rx = /a/;
+var r = rx[Symbol.split]("abba", {valueOf() {
+  RegExp.prototype.exec = () => null;
+  return 100;
+}});
+assertEq(r.length, 1);
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/RegExp/split-prop-access.js
@@ -0,0 +1,19 @@
+var BUGNUMBER = 1287525;
+var summary = 'String.prototype.split should call ToUint32(limit) before ToString(separator).';
+
+print(BUGNUMBER + ": " + summary);
+
+var accessed = false;
+
+var rx = /a/;
+Object.defineProperty(rx, Symbol.match, {
+  get() {
+    accessed = true;
+  }
+});
+rx[Symbol.split]("abba");
+
+assertEq(accessed, true);
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -2824,22 +2824,22 @@ Debugger::markCrossCompartmentEdges(JSTr
  * This method is also used during compacting GC to update cross compartment
  * pointers in zones that are not currently being compacted.
  */
 /* static */ void
 Debugger::markIncomingCrossCompartmentEdges(JSTracer* trc)
 {
     JSRuntime* rt = trc->runtime();
     gc::State state = rt->gc.state();
-    MOZ_ASSERT(state == gc::MARK_ROOTS || state == gc::COMPACT);
+    MOZ_ASSERT(state == gc::State::MarkRoots || state == gc::State::Compact);
 
     for (Debugger* dbg : rt->debuggerList) {
         Zone* zone = MaybeForwarded(dbg->object.get())->zone();
-        if ((state == gc::MARK_ROOTS && !zone->isCollecting()) ||
-            (state == gc::COMPACT && !zone->isGCCompacting()))
+        if ((state == gc::State::MarkRoots && !zone->isCollecting()) ||
+            (state == gc::State::Compact && !zone->isGCCompacting()))
         {
             dbg->markCrossCompartmentEdges(trc);
         }
     }
 }
 
 /*
  * This method has two tasks:
--- a/js/src/vm/RegExpObject.h
+++ b/js/src/vm/RegExpObject.h
@@ -321,16 +321,17 @@ class RegExpCompartment
      * the input/index properties faster.
      */
     ReadBarriered<ArrayObject*> matchResultTemplateObject_;
 
     /*
      * The shape of RegExp.prototype object that satisfies following:
      *   * RegExp.prototype.global getter is not modified
      *   * RegExp.prototype.sticky getter is not modified
+     *   * RegExp.prototype.unicode getter is not modified
      *   * RegExp.prototype.exec is an own data property
      *   * RegExp.prototype[@@match] is an own data property
      *   * RegExp.prototype[@@search] is an own data property
      */
     ReadBarriered<Shape*> optimizableRegExpPrototypeShape_;
 
     /*
      * The shape of RegExp instance that satisfies following:
--- a/js/src/vm/StructuredClone.cpp
+++ b/js/src/vm/StructuredClone.cpp
@@ -1375,18 +1375,25 @@ JSStructuredCloneWriter::transferOwnersh
         }
 
         LittleEndian::writeUint64(point++, PairToUInt64(tag, ownership));
         LittleEndian::writeUint64(point++, reinterpret_cast<uint64_t>(content));
         LittleEndian::writeUint64(point++, extraData);
     }
 
     MOZ_ASSERT(point <= out.rawBuffer() + out.count());
-    MOZ_ASSERT_IF(point < out.rawBuffer() + out.count(),
-                  uint32_t(LittleEndian::readUint64(point) >> 32) < SCTAG_TRANSFER_MAP_HEADER);
+#if DEBUG
+    // Make sure there aren't any more transfer map entries after the expected
+    // number we read out.
+    if (point < out.rawBuffer() + out.count()) {
+        uint32_t tag, data;
+        SCInput::getPair(point, &tag, &data);
+        MOZ_ASSERT(tag < SCTAG_TRANSFER_MAP_HEADER || tag >= SCTAG_TRANSFER_MAP_END_OF_BUILTIN_TYPES);
+    }
+#endif
 
     return true;
 }
 
 bool
 JSStructuredCloneWriter::write(HandleValue v)
 {
     if (!startWrite(v))
--- a/js/src/vm/Unicode.cpp
+++ b/js/src/vm/Unicode.cpp
@@ -767,16 +767,332 @@ const uint8_t unicode::index2[] = {
       0,   0,   0,   0,   0,   0,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,
       5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,
       5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,
       5,   5,   5,   5,   5,   5,   5,   5,   5,   0,   0,   0,   5,   5,   5,   5,   5,   5,
       0,   0,   5,   5,   5,   5,   5,   5,   0,   0,   5,   5,   5,   5,   5,   5,   0,   0,
       5,   5,   5,   0,   0,   0,
 };
 
+const CodepointsWithSameUpperCaseInfo unicode::js_codepoints_with_same_upper_info[] = {
+    {0, 0, 0},
+    {32, 0, 0},
+    {32, 232, 0},
+    {32, 300, 0},
+    {0, 200, 0},
+    {0, 268, 0},
+    {0, 775, 0},
+    {1, 0, 0},
+    {65336, 0, 0},
+    {65415, 0, 0},
+    {65268, 0, 0},
+    {210, 0, 0},
+    {206, 0, 0},
+    {205, 0, 0},
+    {79, 0, 0},
+    {202, 0, 0},
+    {203, 0, 0},
+    {207, 0, 0},
+    {211, 0, 0},
+    {209, 0, 0},
+    {213, 0, 0},
+    {214, 0, 0},
+    {218, 0, 0},
+    {217, 0, 0},
+    {219, 0, 0},
+    {1, 2, 0},
+    {0, 1, 0},
+    {65535, 0, 0},
+    {65439, 0, 0},
+    {65480, 0, 0},
+    {65406, 0, 0},
+    {10795, 0, 0},
+    {65373, 0, 0},
+    {10792, 0, 0},
+    {65341, 0, 0},
+    {69, 0, 0},
+    {71, 0, 0},
+    {0, 116, 7289},
+    {38, 0, 0},
+    {37, 0, 0},
+    {64, 0, 0},
+    {63, 0, 0},
+    {32, 62, 0},
+    {32, 96, 0},
+    {32, 57, 0},
+    {65452, 32, 7205},
+    {32, 86, 0},
+    {64793, 32, 0},
+    {32, 54, 0},
+    {32, 80, 0},
+    {31, 32, 0},
+    {32, 47, 0},
+    {0, 30, 0},
+    {0, 64, 0},
+    {0, 25, 0},
+    {65420, 0, 7173},
+    {0, 54, 0},
+    {64761, 0, 0},
+    {0, 22, 0},
+    {0, 48, 0},
+    {0, 15, 0},
+    {8, 0, 0},
+    {65506, 0, 0},
+    {65511, 0, 0},
+    {65521, 0, 0},
+    {65514, 0, 0},
+    {65482, 0, 0},
+    {65488, 0, 0},
+    {65472, 0, 0},
+    {65529, 0, 0},
+    {80, 0, 0},
+    {15, 0, 0},
+    {48, 0, 0},
+    {7264, 0, 0},
+    {1, 59, 0},
+    {0, 58, 0},
+    {65478, 0, 0},
+    {65528, 0, 0},
+    {65462, 0, 0},
+    {65527, 0, 0},
+    {58247, 58363, 0},
+    {65450, 0, 0},
+    {65436, 0, 0},
+    {65424, 0, 0},
+    {65408, 0, 0},
+    {65410, 0, 0},
+    {28, 0, 0},
+    {16, 0, 0},
+    {26, 0, 0},
+    {54793, 0, 0},
+    {61722, 0, 0},
+    {54809, 0, 0},
+    {54756, 0, 0},
+    {54787, 0, 0},
+    {54753, 0, 0},
+    {54754, 0, 0},
+    {54721, 0, 0},
+    {30204, 0, 0},
+    {23256, 0, 0},
+    {23228, 0, 0},
+};
+
+const uint8_t unicode::codepoints_with_same_upper_index1[] = {
+      0,   1,   2,   3,   4,   5,   6,   7,   8,   9,   0,   0,   0,  10,  11,  12,  13,  14,
+     15,  16,  17,  18,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  19,  20,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  21,  22,  23,  21,  24,  25,
+     26,  27,   0,   0,   0,   0,  28,  29,  30,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,  31,  32,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  33,  34,  21,  35,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  36,
+     37,   0,  38,  39,  40,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  41,   0,   0,   0,
+};
+
+const uint8_t unicode::codepoints_with_same_upper_index2[] = {
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   1,   1,   1,   1,   1,   1,   1,
+      1,   2,   1,   1,   1,   1,   1,   1,   1,   1,   1,   3,   1,   1,   1,   1,   1,   1,
+      1,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   4,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   5,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   6,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   1,   1,   1,   1,   1,   1,
+      1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   0,
+      1,   1,   1,   1,   1,   1,   1,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   0,   8,
+      7,   0,   7,   0,   7,   0,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,
+      0,   7,   0,   7,   0,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   9,   7,
+      0,   7,   0,   7,   0,  10,   0,  11,   7,   0,   7,   0,  12,   7,   0,  13,  13,   7,
+      0,   0,  14,  15,  16,   7,   0,  13,  17,   0,  18,  19,   7,   0,   0,   0,  18,  20,
+      0,  21,   7,   0,   7,   0,   7,   0,  22,   7,   0,  22,   0,   0,   7,   0,  22,   7,
+      0,  23,  23,   7,   0,   7,   0,  24,   7,   0,   0,   0,   7,   0,   0,   0,   0,   0,
+      0,   0,  25,  26,  27,  25,  26,  27,  25,  26,  27,   7,   0,   7,   0,   7,   0,   7,
+      0,   7,   0,   7,   0,   7,   0,   7,   0,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   0,  25,  26,  27,   7,   0,  28,  29,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,  30,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   0,   0,   0,   0,   0,   0,  31,   7,   0,  32,  33,   0,
+      0,   7,   0,  34,  35,  36,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  37,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   7,   0,   7,   0,   0,   0,   7,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,  38,   0,  39,  39,  39,   0,  40,   0,  41,  41,
+      0,   1,  42,   1,   1,  43,   1,   1,  44,  45,  46,   1,  47,   1,   1,   1,  48,  49,
+      0,  50,   1,   1,  51,   1,   1,   1,   1,   1,   0,   0,   0,   0,   0,   0,  52,   0,
+      0,  53,   0,   0,  54,  55,  56,   0,  57,   0,   0,   0,  58,  59,  26,  27,   0,   0,
+     60,   0,   0,   0,   0,   0,   0,   0,   0,  61,  62,  63,   0,   0,   0,  64,  65,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,  66,  67,   0,   0,   0,  68,   0,   7,   0,  69,   7,   0,
+      0,  30,  30,  30,  70,  70,  70,  70,  70,  70,  70,  70,  70,  70,  70,  70,  70,  70,
+     70,  70,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
+      1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   0,   0,   0,   0,   0,   0,   0,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,  71,   7,
+      0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,
+     72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,
+     72,  72,  72,  72,  72,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,  73,  73,  73,  73,  73,  73,  73,  73,  73,  73,  73,  73,
+     73,  73,  73,  73,  73,  73,  73,  73,  73,  73,  73,  73,  73,  73,  73,  73,  73,  73,
+     73,  73,  73,  73,  73,  73,  73,  73,   0,  73,   0,   0,   0,   0,   0,  73,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+     74,  75,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      0,   0,   0,   0,   0,  76,   0,   0,   0,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   0,   0,   0,   0,   0,   0,   0,   0,  77,  77,  77,  77,
+     77,  77,  77,  77,   0,   0,   0,   0,   0,   0,   0,   0,  77,  77,  77,  77,  77,  77,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  77,  77,  77,  77,  77,  77,  77,  77,
+      0,   0,   0,   0,   0,   0,   0,   0,  77,  77,  77,  77,  77,  77,  77,  77,   0,   0,
+      0,   0,   0,   0,   0,   0,  77,  77,  77,  77,  77,  77,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,  77,   0,  77,   0,  77,   0,  77,   0,   0,   0,   0,   0,   0,
+      0,   0,  77,  77,  77,  77,  77,  77,  77,  77,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  77,  77,
+     77,  77,  77,  77,  77,  77,   0,   0,   0,   0,   0,   0,   0,   0,  77,  77,  77,  77,
+     77,  77,  77,  77,   0,   0,   0,   0,   0,   0,   0,   0,  77,  77,  77,  77,  77,  77,
+     77,  77,   0,   0,   0,   0,   0,   0,   0,   0,  77,  77,  78,  78,  79,   0,  80,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,  81,  81,  81,  81,  79,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,  77,  77,  82,  82,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,  77,  77,  83,  83,  69,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,  84,  84,  85,  85,  79,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,  86,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  87,  87,
+     87,  87,  87,  87,  87,  87,  87,  87,  87,  87,  87,  87,  87,  87,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   7,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,  88,  88,  88,  88,  88,  88,  88,  88,  88,  88,  88,  88,  88,  88,
+     88,  88,  88,  88,  88,  88,  88,  88,  88,  88,  88,  88,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,
+     72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,
+     72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   7,   0,  89,  90,  91,   0,
+      0,   7,   0,   7,   0,   7,   0,  92,  93,  94,  95,   0,   7,   0,   0,   7,   0,   0,
+      0,   0,   0,   0,   0,   0,  96,  96,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   0,   0,   0,   0,   0,   0,   0,   7,   0,   7,
+      0,   0,   0,   0,   7,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   0,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   7,   0,   7,
+      0,  97,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   0,   0,   0,   7,   0,  98,
+      0,   0,   7,   0,   7,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,  99,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   1,   1,   1,   1,   1,   1,   1,
+      1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
+      1,   0,   0,   0,   0,   0,
+};
+
 const FoldingInfo unicode::js_foldinfo[] = {
     {0, 0, 0, 0},
     {32, 0, 0, 0},
     {32, 8415, 0, 0},
     {32, 300, 0, 0},
     {0, 65504, 0, 0},
     {0, 65504, 8383, 0},
     {0, 65504, 268, 0},
--- a/js/src/vm/Unicode.h
+++ b/js/src/vm/Unicode.h
@@ -274,16 +274,86 @@ ToLowerCaseNonBMPTrail(char16_t lead, ch
     if (lead == LEAD && trail >= TRAIL_FROM && trail <= TRAIL_TO) \
         return trail + DIFF;
     FOR_EACH_NON_BMP_LOWERCASE(CALC_TRAIL)
 #undef CALL_TRAIL
 
     return trail;
 }
 
+/*
+ * For a codepoint C, CodepointsWithSameUpperCaseInfo stores three offsets
+ * from C to up to three codepoints with same uppercase (no codepoint in
+ * UnicodeData.txt has more than three such codepoints).
+ *
+ * To illustrate, consider the codepoint U+0399 GREEK CAPITAL LETTER IOTA, the
+ * uppercased form of these three codepoints:
+ *
+ *   U+03B9 GREEK SMALL LETTER IOTA
+ *   U+1FBE GREEK PROSGEGRAMMENI
+ *   U+0345 COMBINING GREEK YPOGEGRAMMENI
+ *
+ * For the CodepointsWithSameUpperCaseInfo corresponding to this codepoint,
+ * delta{1,2,3} are 16-bit modular deltas from 0x0399 to each respective
+ * codepoint:
+ *   uint16_t(0x03B9 - 0x0399),
+ *   uint16_t(0x1FBE - 0x0399),
+ *   uint16_t(0x0345 - 0x0399)
+ * in an unimportant order.
+ *
+ * If there are fewer than three other codepoints, some fields are zero.
+ * Consider the codepoint U+03B9 above, the other two codepoints U+1FBE and
+ * U+0345 have same uppercase (U+0399 is not).  For the
+ * CodepointsWithSameUpperCaseInfo corresponding to this codepoint,
+ * delta{1,2,3} are:
+ *   uint16_t(0x1FBE - 0x03B9),
+ *   uint16_t(0x0345 - 0x03B9),
+ *   uint16_t(0)
+ * in an unimportant order.
+ *
+ * Because multiple codepoints map to a single CodepointsWithSameUpperCaseInfo,
+ * a CodepointsWithSameUpperCaseInfo and its delta{1,2,3} have no meaning
+ * standing alone: they have meaning only with respect to a codepoint mapping
+ * to that CodepointsWithSameUpperCaseInfo.
+ */
+class CodepointsWithSameUpperCaseInfo
+{
+  public:
+    uint16_t delta1;
+    uint16_t delta2;
+    uint16_t delta3;
+};
+
+extern const uint8_t codepoints_with_same_upper_index1[];
+extern const uint8_t codepoints_with_same_upper_index2[];
+extern const CodepointsWithSameUpperCaseInfo js_codepoints_with_same_upper_info[];
+
+class CodepointsWithSameUpperCase
+{
+    const CodepointsWithSameUpperCaseInfo& info_;
+    const char16_t code_;
+
+    static const CodepointsWithSameUpperCaseInfo& computeInfo(char16_t code) {
+        const size_t shift = 6;
+        size_t index = codepoints_with_same_upper_index1[code >> shift];
+        index = codepoints_with_same_upper_index2[(index << shift) + (code & ((1 << shift) - 1))];
+        return js_codepoints_with_same_upper_info[index];
+    }
+
+  public:
+    explicit CodepointsWithSameUpperCase(char16_t code)
+      : info_(computeInfo(code)),
+        code_(code)
+    {}
+
+    char16_t other1() const { return uint16_t(code_) + info_.delta1; }
+    char16_t other2() const { return uint16_t(code_) + info_.delta2; }
+    char16_t other3() const { return uint16_t(code_) + info_.delta3; }
+};
+
 class FoldingInfo {
   public:
     uint16_t folding;
     uint16_t reverse1;
     uint16_t reverse2;
     uint16_t reverse3;
 };
 
--- a/js/src/vm/make_unicode.py
+++ b/js/src/vm/make_unicode.py
@@ -140,16 +140,21 @@ def make_non_bmp_convert_macro(out_file,
 def generate_unicode_stuff(unicode_data, case_folding,
                            data_file, non_bmp_file,
                            test_mapping, test_non_bmp_mapping,
                            test_space, test_icase):
     dummy = (0, 0, 0)
     table = [dummy]
     cache = {dummy: 0}
     index = [0] * (MAX + 1)
+    same_upper_map = {}
+    same_upper_dummy = (0, 0, 0)
+    same_upper_table = [same_upper_dummy]
+    same_upper_cache = {same_upper_dummy: 0}
+    same_upper_index = [0] * (MAX + 1)
     folding_map = {}
     rev_folding_map = {}
     folding_dummy = (0, 0, 0, 0)
     folding_table = [folding_dummy]
     folding_cache = {folding_dummy: 0}
     folding_index = [0] * (MAX + 1)
     test_table = {}
     test_space_table = []
@@ -167,16 +172,21 @@ def generate_unicode_stuff(unicode_data,
         category = row[2]
         alias = row[-5]
         uppercase = row[-3]
         lowercase = row[-2]
         flags = 0
 
         if uppercase:
             upper = int(uppercase, 16)
+
+            if upper not in same_upper_map:
+                same_upper_map[upper] = [code]
+            else:
+                same_upper_map[upper].append(code)
         else:
             upper = code
 
         if lowercase:
             lower = int(lowercase, 16)
         else:
             lower = code
 
@@ -211,16 +221,46 @@ def generate_unicode_stuff(unicode_data,
 
         i = cache.get(item)
         if i is None:
             assert item not in table
             cache[item] = i = len(table)
             table.append(item)
         index[code] = i
 
+    for code in range(0, MAX + 1):
+        entry = test_table.get(code)
+
+        if not entry:
+            continue
+
+        (upper, lower, name, alias) = entry
+
+        if upper not in same_upper_map:
+            continue
+
+        same_upper_ds = [v - code for v in same_upper_map[upper]]
+
+        assert len(same_upper_ds) <= 3
+        assert all([v > -65535 and v < 65535 for v in same_upper_ds])
+
+        same_upper = [v & 0xffff for v in same_upper_ds]
+        same_upper_0 = same_upper[0] if len(same_upper) >= 1 else 0
+        same_upper_1 = same_upper[1] if len(same_upper) >= 2 else 0
+        same_upper_2 = same_upper[2] if len(same_upper) >= 3 else 0
+
+        item = (same_upper_0, same_upper_1, same_upper_2)
+
+        i = same_upper_cache.get(item)
+        if i is None:
+            assert item not in same_upper_table
+            same_upper_cache[item] = i = len(same_upper_table)
+            same_upper_table.append(item)
+        same_upper_index[code] = i
+
     for row in read_case_folding(case_folding):
         code = row[0]
         mapping = row[2]
         folding_map[code] = mapping
 
         if code > MAX:
             non_bmp_folding_map[code] = mapping
             non_bmp_rev_folding_map[mapping] = code
@@ -305,17 +345,17 @@ def generate_unicode_stuff(unicode_data,
 
     test_mapping.write('/* Generated by make_unicode.py DO NOT MODIFY */\n')
     test_mapping.write(public_domain)
     test_mapping.write('var mapping = [\n')
     for code in range(0, MAX + 1):
         entry = test_table.get(code)
 
         if entry:
-            upper, lower, name, alias = entry
+            (upper, lower, name, alias) = entry
             test_mapping.write('  [' + hex(upper) + ', ' + hex(lower) + '], /* ' +
                        name + (' (' + alias + ')' if alias else '') + ' */\n')
         else:
             test_mapping.write('  [' + hex(code) + ', ' + hex(code) + '],\n')
     test_mapping.write('];')
     test_mapping.write("""
 assertEq(mapping.length, 0x10000);
 for (var i = 0; i <= 0xffff; i++) {
@@ -384,31 +424,45 @@ if (typeof reportCompare === "function")
     reportCompare(true, true);
 """)
 
     index1, index2, shift = splitbins(index)
 
     # Don't forget to update CharInfo in Unicode.cpp if you need to change this
     assert shift == 5
 
+    same_upper_index1, same_upper_index2, same_upper_shift = splitbins(same_upper_index)
+
+    # Don't forget to update CharInfo in Unicode.cpp if you need to change this
+    assert same_upper_shift == 6
+
     folding_index1, folding_index2, folding_shift = splitbins(folding_index)
 
     # Don't forget to update CharInfo in Unicode.cpp if you need to change this
     assert folding_shift == 6
 
     # verify correctness
     for char in index:
         test = table[index[char]]
 
         idx = index1[char >> shift]
         idx = index2[(idx << shift) + (char & ((1 << shift) - 1))]
 
         assert test == table[idx]
 
     # verify correctness
+    for char in same_upper_index:
+        test = same_upper_table[same_upper_index[char]]
+
+        idx = same_upper_index1[char >> same_upper_shift]
+        idx = same_upper_index2[(idx << same_upper_shift) + (char & ((1 << same_upper_shift) - 1))]
+
+        assert test == same_upper_table[idx]
+
+    # verify correctness
     for char in folding_index:
         test = folding_table[folding_index[char]]
 
         idx = folding_index1[char >> folding_shift]
         idx = folding_index2[(idx << folding_shift) + (char & ((1 << folding_shift) - 1))]
 
         assert test == folding_table[idx]
 
@@ -492,16 +546,29 @@ if (typeof reportCompare === "function")
         file.write('\n'.join(lines))
         file.write('\n};\n')
 
     dump(index1, 'index1', data_file)
     data_file.write('\n')
     dump(index2, 'index2', data_file)
     data_file.write('\n')
 
+    data_file.write('const CodepointsWithSameUpperCaseInfo unicode::js_codepoints_with_same_upper_info[] = {\n')
+    for d in same_upper_table:
+        data_file.write('    {')
+        data_file.write(', '.join((str(e) for e in d)))
+        data_file.write('},\n')
+    data_file.write('};\n')
+    data_file.write('\n')
+
+    dump(same_upper_index1, 'codepoints_with_same_upper_index1', data_file)
+    data_file.write('\n')
+    dump(same_upper_index2, 'codepoints_with_same_upper_index2', data_file)
+    data_file.write('\n')
+
     data_file.write('const FoldingInfo unicode::js_foldinfo[] = {\n')
     for d in folding_table:
         data_file.write('    {')
         data_file.write(', '.join((str(e) for e in d)))
         data_file.write('},\n')
     data_file.write('};\n')
     data_file.write('\n')
 
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -45,24 +45,24 @@ ServoRestyleManager::PostRestyleEvent(El
   }
 
   presShell->GetDocument()->SetNeedStyleFlush();
 }
 
 void
 ServoRestyleManager::PostRestyleEventForLazyConstruction()
 {
-  NS_ERROR("stylo: ServoRestyleManager::PostRestyleEventForLazyConstruction not implemented");
+  NS_WARNING("stylo: ServoRestyleManager::PostRestyleEventForLazyConstruction not implemented");
 }
 
 void
 ServoRestyleManager::RebuildAllStyleData(nsChangeHint aExtraHint,
                                          nsRestyleHint aRestyleHint)
 {
-  NS_ERROR("stylo: ServoRestyleManager::RebuildAllStyleData not implemented");
+  NS_WARNING("stylo: ServoRestyleManager::RebuildAllStyleData not implemented");
 }
 
 void
 ServoRestyleManager::PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint,
                                                   nsRestyleHint aRestyleHint)
 {
   MOZ_CRASH("stylo: ServoRestyleManager::PostRebuildAllStyleDataEvent not implemented");
 }
@@ -161,17 +161,17 @@ ServoRestyleManager::NoteRestyleHint(Ele
       if (cur->IsContent()) {
         cur->SetIsDirtyForServo();
       }
     }
   }
 
   // TODO: Handle all other nsRestyleHint values.
   if (aHint & ~(eRestyle_Self | eRestyle_Subtree | eRestyle_LaterSiblings)) {
-    NS_ERROR(nsPrintfCString("stylo: Unhandled restyle hint %s",
+    NS_WARNING(nsPrintfCString("stylo: Unhandled restyle hint %s",
                              RestyleManagerBase::RestyleHintToString(aHint).get()).get());
   }
 }
 
 void
 ServoRestyleManager::ProcessPendingRestyles()
 {
   if (!HasPendingRestyles()) {
@@ -221,32 +221,32 @@ ServoRestyleManager::ProcessPendingResty
 
   IncrementRestyleGeneration();
 }
 
 void
 ServoRestyleManager::RestyleForInsertOrChange(Element* aContainer,
                                               nsIContent* aChild)
 {
-  NS_ERROR("stylo: ServoRestyleManager::RestyleForInsertOrChange not implemented");
+  NS_WARNING("stylo: ServoRestyleManager::RestyleForInsertOrChange not implemented");
 }
 
 void
 ServoRestyleManager::RestyleForAppend(Element* aContainer,
                                       nsIContent* aFirstNewContent)
 {
-  NS_ERROR("stylo: ServoRestyleManager::RestyleForAppend not implemented");
+  NS_WARNING("stylo: ServoRestyleManager::RestyleForAppend not implemented");
 }
 
 void
 ServoRestyleManager::RestyleForRemove(Element* aContainer,
                                       nsIContent* aOldChild,
                                       nsIContent* aFollowingSibling)
 {
-  NS_ERROR("stylo: ServoRestyleManager::RestyleForRemove not implemented");
+  NS_WARNING("stylo: ServoRestyleManager::RestyleForRemove not implemented");
 }
 
 nsresult
 ServoRestyleManager::ContentStateChanged(nsIContent* aContent,
                                          EventStates aChangedBits)
 {
   if (!aContent->IsElement()) {
     return NS_OK;
@@ -291,28 +291,20 @@ ServoRestyleManager::AttributeWillChange
                                          int32_t aNameSpaceID,
                                          nsIAtom* aAttribute, int32_t aModType,
                                          const nsAttrValue* aNewValue)
 {
   ServoElementSnapshot* snapshot = SnapshotForElement(aElement);
   snapshot->AddAttrs(aElement);
 }
 
-void
-ServoRestyleManager::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
-                                      nsIAtom* aAttribute, int32_t aModType,
-                                      const nsAttrValue* aOldValue)
-{
-  NS_ERROR("stylo: ServoRestyleManager::AttributeChanged not implemented");
-}
-
 nsresult
 ServoRestyleManager::ReparentStyleContext(nsIFrame* aFrame)
 {
-  NS_ERROR("stylo: ServoRestyleManager::ReparentStyleContext not implemented");
+  NS_WARNING("stylo: ServoRestyleManager::ReparentStyleContext not implemented");
   return NS_OK;
 }
 
 ServoElementSnapshot*
 ServoRestyleManager::SnapshotForElement(Element* aElement)
 {
   ServoElementSnapshot* snapshot = mModifiedElements.LookupOrAdd(aElement);
   if (!snapshot->HasAny(
--- a/layout/base/ServoRestyleManager.h
+++ b/layout/base/ServoRestyleManager.h
@@ -57,19 +57,22 @@ public:
                         nsIContent* aFollowingSibling);
   nsresult ContentStateChanged(nsIContent* aContent,
                                EventStates aStateMask);
   void AttributeWillChange(dom::Element* aElement,
                            int32_t aNameSpaceID,
                            nsIAtom* aAttribute,
                            int32_t aModType,
                            const nsAttrValue* aNewValue);
+
+  // XXXbholley: We should assert that the element is already snapshotted.
   void AttributeChanged(dom::Element* aElement, int32_t aNameSpaceID,
                         nsIAtom* aAttribute, int32_t aModType,
-                        const nsAttrValue* aOldValue);
+                        const nsAttrValue* aOldValue) {}
+
   nsresult ReparentStyleContext(nsIFrame* aFrame);
 
   bool HasPendingRestyles() { return !mModifiedElements.IsEmpty(); }
 
 protected:
   ~ServoRestyleManager() {}
 
 private:
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -2686,17 +2686,17 @@ nsCSSFrameConstructor::ConstructRootFram
 
   // Set up our style rule observer.
   // XXXbz wouldn't this make more sense as part of presshell init?
   if (styleSet->IsGecko()) {
     // XXXheycam We don't support XBL bindings providing style to
     // ServoStyleSets yet.
     styleSet->AsGecko()->SetBindingManager(mDocument->BindingManager());
   } else {
-    NS_ERROR("stylo: cannot get ServoStyleSheets from XBL bindings yet");
+    NS_WARNING("stylo: cannot get ServoStyleSheets from XBL bindings yet. See bug 1290276.");
   }
 
   // --------- BUILD VIEWPORT -----------
   RefPtr<nsStyleContext> viewportPseudoStyle =
     styleSet->ResolveAnonymousBoxStyle(nsCSSAnonBoxes::viewport, nullptr);
   ViewportFrame* viewportFrame =
     NS_NewViewportFrame(mPresShell, viewportPseudoStyle);
 
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -2342,17 +2342,18 @@ nsDocumentViewer::CreateStyleSet(nsIDocu
       for (StyleSheetHandle sheet : *sheetService->AgentStyleSheets()) {
         styleSet->AppendStyleSheet(SheetType::Agent, sheet);
       }
       for (StyleSheetHandle sheet : Reversed(*sheetService->UserStyleSheets())) {
         styleSet->PrependStyleSheet(SheetType::User, sheet);
       }
     }
   } else {
-    NS_ERROR("stylo: nsStyleSheetService doesn't handle ServoStyleSheets yet");
+    NS_WARNING("stylo: Not yet checking nsStyleSheetService for Servo-backed "
+               "documents. See bug 1290224");
   }
 
   // Caller will handle calling EndUpdate, per contract.
   return styleSet;
 }
 
 NS_IMETHODIMP
 nsDocumentViewer::ClearHistoryEntry()
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -1862,18 +1862,18 @@ nsPresContext::MediaFeatureValuesChanged
   if (mShell) {
     // XXXheycam ServoStyleSets don't support responding to medium
     // changes yet.
     if (mShell->StyleSet()->IsGecko()) {
       if (mShell->StyleSet()->AsGecko()->MediumFeaturesChanged()) {
         aRestyleHint |= eRestyle_Subtree;
       }
     } else {
-      NS_ERROR("stylo: ServoStyleSets don't support responding to medium "
-               "changes yet");
+      NS_WARNING("stylo: ServoStyleSets don't support responding to medium "
+                 "changes yet. See bug 1290228.");
     }
   }
 
   if (mUsesViewportUnits && mPendingViewportChange) {
     // Rebuild all style data without rerunning selector matching.
     aRestyleHint |= eRestyle_ForceDescendants;
   }
 
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -4227,18 +4227,18 @@ PresShell::DocumentStatesChanged(nsIDocu
   NS_PRECONDITION(!mIsDocumentGone, "Unexpected DocumentStatesChanged");
   NS_PRECONDITION(aDocument == mDocument, "Unexpected aDocument");
 
   nsStyleSet* styleSet = mStyleSet->GetAsGecko();
   if (!styleSet) {
     // XXXheycam ServoStyleSets don't support document state selectors,
     // but these are only used in chrome documents, which we are not
     // aiming to support yet.
-    NS_ERROR("stylo: ServoStyleSets cannot respond to document state "
-             "changes yet");
+    NS_WARNING("stylo: ServoStyleSets cannot respond to document state "
+               "changes yet (only matters for chrome documents). See bug 1290285.");
     return;
   }
 
   if (mDidInitialize &&
       styleSet->HasDocumentStateDependentStyle(mDocument->GetRootElement(),
                                                aStateMask)) {
     mPresContext->RestyleManager()->PostRestyleEvent(mDocument->GetRootElement(),
                                                      eRestyle_Subtree,
--- a/layout/style/Loader.cpp
+++ b/layout/style/Loader.cpp
@@ -1289,17 +1289,17 @@ Loader::PrepareSheet(StyleSheetHandle aS
                      nsMediaList* aMediaList,
                      Element* aScopeElement,
                      bool isAlternate)
 {
   NS_PRECONDITION(aSheet, "Must have a sheet!");
 
   // XXXheycam Need to set media, title, etc. on ServoStyleSheets.
   if (aSheet->IsServo()) {
-    NS_ERROR("stylo: should set metadata on ServoStyleSheets");
+    NS_WARNING("stylo: should set metadata on ServoStyleSheets. See bug 1290209.");
     return;
   }
 
   CSSStyleSheet* sheet = aSheet->AsGecko();
 
   RefPtr<nsMediaList> mediaList(aMediaList);
 
   if (!aMediaString.IsEmpty()) {
@@ -1971,17 +1971,17 @@ Loader::DoSheetComplete(SheetLoadData* a
                                            aLoadData->mSheet->GetReferrerPolicy());
         NS_ASSERTION(sheet->IsComplete(),
                      "Should only be caching complete sheets");
         mSheets->mCompleteSheets.Put(&key, sheet);
 #ifdef MOZ_XUL
       }
 #endif
     } else {
-      NS_ERROR("stylo: not caching ServoStyleSheet");
+      NS_WARNING("stylo: Stylesheet caching not yet supported - see bug 1290218.");
     }
   }
 
   NS_RELEASE(aLoadData);  // this will release parents and siblings and all that
 }
 
 nsresult
 Loader::LoadInlineStyle(nsIContent* aElement,
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -373,27 +373,27 @@ ServoStyleSet::ProbePseudoElementStyle(E
   }
   return ProbePseudoElementStyle(aParentElement, aType, aParentContext);
 }
 
 nsRestyleHint
 ServoStyleSet::HasStateDependentStyle(dom::Element* aElement,
                                       EventStates aStateMask)
 {
-  NS_ERROR("stylo: HasStateDependentStyle not implemented");
+  NS_WARNING("stylo: HasStateDependentStyle always returns zero!");
   return nsRestyleHint(0);
 }
 
 nsRestyleHint
 ServoStyleSet::HasStateDependentStyle(dom::Element* aElement,
                                       CSSPseudoElementType aPseudoType,
                                      dom::Element* aPseudoElement,
                                      EventStates aStateMask)
 {
-  NS_ERROR("stylo: HasStateDependentStyle not implemented");
+  NS_WARNING("stylo: HasStateDependentStyle always returns zero!");
   return nsRestyleHint(0);
 }
 
 nsRestyleHint
 ServoStyleSet::ComputeRestyleHint(dom::Element* aElement,
                                   ServoElementSnapshot* aSnapshot)
 {
   return Servo_ComputeRestyleHint(aElement, aSnapshot, mRawSet.get());
--- a/layout/xul/test/test_windowminmaxsize.xul
+++ b/layout/xul/test/test_windowminmaxsize.xul
@@ -194,17 +194,22 @@ function nextPopupTest(panel)
     setattr("maxwidth");
     setattr("maxheight");
 
     // Remove the flexibility as it causes the resizer to not shrink down
     // when resizing.
     if ("last" in popupTests[gTestId])
       document.getElementById("popupresizer").removeAttribute("flex");
 
-    panel.openPopup();
+    // Prevent event loop starvation as a result of popup events being
+    // synchronous. See bug 1131576.
+    SimpleTest.executeSoon(() => {
+      // Non-chrome shells require focus to open a popup.
+      SimpleTest.waitForFocus(() => { panel.openPopup() });
+    });
   }
 }
 
 function titledPanelWindowOpened(panelwindow)
 {
   var panel = panelwindow.document.documentElement.firstChild;
   panel.openPopup();
   panel.addEventListener("popupshown", () => doTitledPanelTest(panel), false);
--- a/media/libpng/pnglibconf.h
+++ b/media/libpng/pnglibconf.h
@@ -1,35 +1,38 @@
 /* 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/. */
 
 #ifndef PNGLCONF_H
 #define PNGLCONF_H
 
-/* limit image dimensions (bug #251381, #591822, and #967656) */
-#ifndef MOZ_PNG_MAX_DIMENSION
-# define MOZ_PNG_MAX_DIMENSION 32767
+/* Limit image dimensions (bug #251381, #591822, #967656, and #1283961) */
+#ifndef MOZ_PNG_MAX_WIDTH
+#  define MOZ_PNG_MAX_WIDTH 0x7fffffffL /* Unlimited */
+#endif
+#ifndef MOZ_PNG_MAX_HEIGHT
+#  define MOZ_PNG_MAX_HEIGHT 0x7fffffffL /* Unlimited */
 #endif
 
 #define PNG_API_RULE 0
 #define PNG_COST_SHIFT 3
 #define PNG_GAMMA_THRESHOLD_FIXED 5000
 #define PNG_IDAT_READ_SIZE PNG_ZBUF_SIZE
 #define PNG_INFLATE_BUF_SIZE 1024
 #define PNG_LINKAGE_API extern
 #define PNG_LINKAGE_CALLBACK extern
 #define PNG_LINKAGE_DATA extern
 #define PNG_LINKAGE_FUNCTION extern
 #define PNG_MAX_GAMMA_8 11
 #define PNG_sRGB_PROFILE_CHECKS -1
 #define PNG_USER_CHUNK_CACHE_MAX 128
 #define PNG_USER_CHUNK_MALLOC_MAX 4000000L
-#define PNG_USER_HEIGHT_MAX MOZ_PNG_MAX_DIMENSION
-#define PNG_USER_WIDTH_MAX MOZ_PNG_MAX_DIMENSION
+#define PNG_USER_HEIGHT_MAX MOZ_PNG_MAX_WIDTH
+#define PNG_USER_WIDTH_MAX MOZ_PNG_MAX_HEIGHT
 #define PNG_WEIGHT_SHIFT 8
 #define PNG_ZBUF_SIZE 8192
 #define PNG_Z_DEFAULT_COMPRESSION (-1)
 #define PNG_Z_DEFAULT_NOFILTER_STRATEGY 0
 #define PNG_Z_DEFAULT_STRATEGY 1
 
 #ifdef _MSC_VER
 /* The PNG_PEDANTIC_WARNINGS (attributes) fail to build with some MSC
--- a/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
+++ b/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
@@ -1285,16 +1285,23 @@ NS_IMETHODIMP nsHttpChannelAuthProvider:
     if (!mAuthChannel)
         return NS_OK;
 
     if (userCancel) {
         if (!mRemainingChallenges.IsEmpty()) {
             // there are still some challenges to process, do so
             nsresult rv;
 
+            // Get rid of current continuationState to avoid reusing it in
+            // next challenges since it is no longer relevant.
+            if (mProxyAuth) {
+                NS_IF_RELEASE(mProxyAuthContinuationState);
+            } else {
+                NS_IF_RELEASE(mAuthContinuationState);
+            }
             nsAutoCString creds;
             rv = GetCredentials(mRemainingChallenges.get(), mProxyAuth, creds);
             if (NS_SUCCEEDED(rv)) {
                 // GetCredentials loaded the credentials from the cache or
                 // some other way in a synchronous manner, process those
                 // credentials now
                 mRemainingChallenges.Truncate();
                 return ContinueOnAuthAvailable(creds);
--- a/python/mozbuild/mozbuild/compilation/codecomplete.py
+++ b/python/mozbuild/mozbuild/compilation/codecomplete.py
@@ -42,17 +42,22 @@ class Introspection(MachCommandBase):
             path_arg.relpath())
 
         if make_dir is None and make_target is None:
             return 1
 
         build_vars = util.get_build_vars(make_dir, self)
 
         if what.endswith('.c'):
+            cc = 'CC'
             name = 'COMPILE_CFLAGS'
         else:
+            cc = 'CXX'
             name = 'COMPILE_CXXFLAGS'
 
         if name not in build_vars:
             return
 
+        # Drop the first flag since that is the pathname of the compiler.
+        flags = (shell_split(build_vars[cc]) + shell_split(build_vars[name]))[1:]
+
         print(' '.join(shell_quote(arg)
-                       for arg in util.sanitize_cflags(shell_split(build_vars[name]))))
+                       for arg in util.sanitize_cflags(flags)))
--- a/security/sandbox/linux/SandboxFilter.cpp
+++ b/security/sandbox/linux/SandboxFilter.cpp
@@ -669,16 +669,21 @@ public:
     case __NR_clone:
       return ClonePolicy(Error(EPERM));
 
 #ifdef __NR_fadvise64
     case __NR_fadvise64:
       return Allow();
 #endif
 
+#ifdef __NR_fadvise64_64
+    case __NR_fadvise64_64:
+      return Allow();
+#endif
+
     case __NR_fallocate:
       return Allow();
 
     case __NR_get_mempolicy:
       return Allow();
 
 #endif // DESKTOP
 
--- a/security/sandbox/mac/Sandbox.mm
+++ b/security/sandbox/mac/Sandbox.mm
@@ -453,20 +453,16 @@ static const char contentSandboxRules[] 
   "      (iokit-user-client-class \"AppleGraphicsControlClient\")\n"
   "      (iokit-user-client-class \"AppleGraphicsPolicyClient\"))\n"
   "\n"
   "; bug 1153809\n"
   "  (allow iokit-open\n"
   "      (iokit-user-client-class \"NVDVDContextTesla\")\n"
   "      (iokit-user-client-class \"Gen6DVDContext\"))\n"
   "\n"
-  "; bug 1190032\n"
-  "  (allow file*\n"
-  "      (home-regex \"/Library/Caches/TemporaryItems/plugtmp.*\"))\n"
-  "\n"
   "; bug 1201935\n"
   "  (allow file-read*\n"
   "      (home-subpath \"/Library/Caches/TemporaryItems\"))\n"
   "\n"
   "; bug 1237847\n"
   "  (allow file-read*\n"
   "      (subpath appTempDir))\n"
   "  (allow file-write*\n"
--- a/taskcluster/ci/legacy/tasks/builds/sm_base.yml
+++ b/taskcluster/ci/legacy/tasks/builds/sm_base.yml
@@ -1,12 +1,12 @@
 $inherits:
   from: 'tasks/builds/firefox_docker_base.yml'
 task:
-  workerType: spidermonkey
+  workerType: dbg-linux64
 
   routes:
     - 'index.buildbot.branches.{{project}}.sm-plain'
     - 'index.buildbot.revisions.{{head_rev}}.{{project}}.sm-plain'
 
   scopes:
     - 'docker-worker:cache:tooltool-cache'
     - 'docker-worker:cache:level-{{level}}-{{project}}-build-spidermonkey-workspace'
--- a/taskcluster/ci/legacy/tasks/builds/sm_msan.yml
+++ b/taskcluster/ci/legacy/tasks/builds/sm_msan.yml
@@ -9,8 +9,9 @@ task:
       SPIDERMONKEY_VARIANT: 'msan'
       TOOLTOOL_MANIFEST: 'browser/config/tooltool-manifests/linux64/msan.manifest'
   metadata:
     name: '[TC] Spidermonkey Memory Sanitizer'
     description: 'Spidermonkey Memory Sanitizer'
   extra:
     treeherder:
       symbol: msan
+      tier: 3
--- a/taskcluster/ci/legacy/tasks/builds/sm_tsan.yml
+++ b/taskcluster/ci/legacy/tasks/builds/sm_tsan.yml
@@ -9,8 +9,9 @@ task:
       SPIDERMONKEY_VARIANT: 'tsan'
       TOOLTOOL_MANIFEST: 'browser/config/tooltool-manifests/linux64/tsan.manifest'
   metadata:
     name: '[TC] Spidermonkey Thread Sanitizer'
     description: 'Spidermonkey Thread Sanitizer'
   extra:
     treeherder:
       symbol: tsan
+      tier: 3
--- a/taskcluster/ci/legacy/tasks/tests/eslint-gecko.yml
+++ b/taskcluster/ci/legacy/tasks/tests/eslint-gecko.yml
@@ -24,16 +24,17 @@ task:
       - /home/worker/workspace/gecko
       - bash
       - -cx
       - >
           cd /home/worker/workspace/gecko/tools/lint/eslint &&
           /build/tooltool.py fetch -m manifest.tt &&
           tar xvfz eslint.tar.gz &&
           rm eslint.tar.gz &&
+          ln -s ../eslint-plugin-mozilla node_modules &&
           cd ../../.. &&
           tools/lint/eslint/node_modules/.bin/eslint --quiet --plugin html --ext [.js,.jsm,.jsx,.xml,.html] -f tools/lint/eslint-formatter .
 
   extra:
     locations:
         build: null
         tests: null
     treeherder:
--- a/testing/web-platform/meta/html/semantics/forms/constraints/form-validation-reportValidity.html.ini
+++ b/testing/web-platform/meta/html/semantics/forms/constraints/form-validation-reportValidity.html.ini
@@ -1,13 +1,10 @@
 [form-validation-reportValidity.html]
   type: testharness
-  expected: TIMEOUT
-  disabled:
-    if debug and (os == "mac"): https://bugzilla.mozilla.org/show_bug.cgi?id=1273105
   [[INPUT in TEXT status\] suffering from being too long]
     expected: FAIL
 
   [[INPUT in TEXT status\] suffering from being too long (in a form)]
     expected: FAIL
 
   [[INPUT in SEARCH status\] suffering from being too long]
     expected: FAIL
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -51,16 +51,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                   "resource://gre/modules/AppConstants.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel",
                                   "resource://gre/modules/MessageChannel.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
 
+Cu.import("resource://gre/modules/ExtensionContent.jsm");
 Cu.import("resource://gre/modules/ExtensionManagement.jsm");
 
 const BASE_SCHEMA = "chrome://extensions/content/schemas/manifest.json";
 const CATEGORY_EXTENSION_SCHEMAS = "webextension-schemas";
 const CATEGORY_EXTENSION_SCRIPTS = "webextension-scripts";
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
@@ -321,17 +322,17 @@ class ProxyContext extends ExtensionCont
 
   get externallyVisible() {
     return false;
   }
 }
 
 function findPathInObject(obj, path) {
   for (let elt of path) {
-    obj = obj[elt];
+    obj = obj[elt] || undefined;
   }
   return obj;
 }
 
 let ParentAPIManager = {
   proxyContexts: new Map(),
 
   init() {
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/ExtensionXPCShellUtils.jsm
@@ -0,0 +1,285 @@
+/* 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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["ExtensionTestUtils"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Components.utils.import("resource://gre/modules/Task.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Extension",
+                                  "resource://gre/modules/Extension.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+                                  "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
+                                  "resource://gre/modules/Schemas.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+                                  "resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "uuidGenerator",
+                                   "@mozilla.org/uuid-generator;1", "nsIUUIDGenerator");
+
+/* exported ExtensionTestUtils */
+
+let BASE_MANIFEST = Object.freeze({
+  "applications": Object.freeze({
+    "gecko": Object.freeze({
+      "id": "test@web.ext",
+    }),
+  }),
+
+  "manifest_version": 2,
+
+  "name": "name",
+  "version": "0",
+});
+
+class ExtensionWrapper {
+  constructor(extension, testScope) {
+    this.extension = extension;
+    this.testScope = testScope;
+
+    this.state = "uninitialized";
+
+    this.testResolve = null;
+    this.testDone = new Promise(resolve => { this.testResolve = resolve; });
+
+    this.messageHandler = new Map();
+    this.messageAwaiter = new Map();
+
+    this.messageQueue = new Set();
+
+    this.testScope.do_register_cleanup(() => {
+      if (this.messageQueue.size) {
+        let names = Array.from(this.messageQueue, ([msg]) => msg);
+        this.testScope.equal(JSON.stringify(names), "[]", "message queue is empty");
+      }
+      if (this.messageAwaiter.size) {
+        let names = Array.from(this.messageAwaiter.keys());
+        this.testScope.equal(JSON.stringify(names), "[]", "no tasks awaiting on messages");
+      }
+    });
+
+    /* eslint-disable mozilla/balanced-listeners */
+    extension.on("test-eq", (kind, pass, msg, expected, actual) => {
+      this.testScope.ok(pass, `${msg} - Expected: ${expected}, Actual: ${actual}`);
+    });
+    extension.on("test-log", (kind, pass, msg) => {
+      this.testScope.do_print(msg);
+    });
+    extension.on("test-result", (kind, pass, msg) => {
+      this.testScope.ok(pass, msg);
+    });
+    extension.on("test-done", (kind, pass, msg, expected, actual) => {
+      this.testScope.ok(pass, msg);
+      this.testResolve(msg);
+    });
+
+    extension.on("test-message", (kind, msg, ...args) => {
+      let handler = this.messageHandler.get(msg);
+      if (handler) {
+        handler(...args);
+      } else {
+        this.messageQueue.add([msg, ...args]);
+        this.checkMessages();
+      }
+    });
+    /* eslint-enable mozilla/balanced-listeners */
+
+    this.testScope.do_register_cleanup(() => {
+      if (this.state == "pending" || this.state == "running") {
+        this.testScope.equal(this.state, "unloaded", "Extension left running at test shutdown");
+        return this.unload();
+      } else if (extension.state == "unloading") {
+        this.testScope.equal(this.state, "unloaded", "Extension not fully unloaded at test shutdown");
+      }
+    });
+
+    this.testScope.do_print(`Extension loaded`);
+  }
+
+  startup() {
+    if (this.state != "uninitialized") {
+      throw new Error("Extension already started");
+    }
+    this.state = "pending";
+
+    return this.extension.startup().then(
+      result => {
+        this.state = "running";
+
+        return result;
+      },
+      error => {
+        this.state = "failed";
+
+        return Promise.reject(error);
+      });
+  }
+
+  unload() {
+    if (this.state != "running") {
+      throw new Error("Extension not running");
+    }
+    this.state = "unloading";
+
+    this.extension.shutdown();
+    this.state = "unloaded";
+
+    return Promise.resolve();
+  }
+
+  sendMessage(...args) {
+    this.extension.testMessage(...args);
+  }
+
+  awaitFinish(msg) {
+    return this.testDone.then(actual => {
+      if (msg) {
+        this.testScope.equal(actual, msg, "test result correct");
+      }
+      return actual;
+    });
+  }
+
+  checkMessages() {
+    for (let message of this.messageQueue) {
+      let [msg, ...args] = message;
+
+      let listener = this.messageAwaiter.get(msg);
+      if (listener) {
+        this.messageQueue.delete(message);
+        this.messageAwaiter.delete(msg);
+
+        listener.resolve(...args);
+        return;
+      }
+    }
+  }
+
+  checkDuplicateListeners(msg) {
+    if (this.messageHandler.has(msg) || this.messageAwaiter.has(msg)) {
+      throw new Error("only one message handler allowed");
+    }
+  }
+
+  awaitMessage(msg) {
+    return new Promise(resolve => {
+      this.checkDuplicateListeners(msg);
+
+      this.messageAwaiter.set(msg, {resolve});
+      this.checkMessages();
+    });
+  }
+
+  onMessage(msg, callback) {
+    this.checkDuplicateListeners(msg);
+    this.messageHandler.set(msg, callback);
+  }
+}
+
+var ExtensionTestUtils = {
+  BASE_MANIFEST,
+
+  normalizeManifest: Task.async(function* (manifest, baseManifest = BASE_MANIFEST) {
+    const {Management} = Cu.import("resource://gre/modules/Extension.jsm", {});
+
+    yield Management.lazyInit();
+
+    let errors = [];
+    let context = {
+      url: null,
+
+      logError: error => {
+        errors.push(error);
+      },
+
+      preprocessors: {},
+    };
+
+    manifest = Object.assign({}, baseManifest, manifest);
+
+    let normalized = Schemas.normalize(manifest, "manifest.WebExtensionManifest", context);
+    normalized.errors = errors;
+
+    return normalized;
+  }),
+
+  currentScope: null,
+
+  profileDir: null,
+
+  init(scope) {
+    this.currentScope = scope;
+
+    this.profileDir = scope.do_get_profile();
+
+    // We need to load at least one frame script into every message
+    // manager to ensure that the scriptable wrapper for its global gets
+    // created before we try to access it externally. If we don't, we
+    // fail sanity checks on debug builds the first time we try to
+    // create a wrapper, because we should never have a global without a
+    // cached wrapper.
+    Services.mm.loadFrameScript("data:text/javascript,//", true);
+
+
+    let tmpD = this.profileDir.clone();
+    tmpD.append("tmp");
+    tmpD.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
+
+    let dirProvider = {
+      getFile(prop, persistent) {
+        persistent.value = false;
+        if (prop == "TmpD") {
+          return tmpD.clone();
+        }
+        return null;
+      },
+
+      QueryInterface: XPCOMUtils.generateQI([Ci.nsIDirectoryServiceProvider]),
+    };
+    Services.dirsvc.registerProvider(dirProvider);
+
+
+    scope.do_register_cleanup(() => {
+      tmpD.remove(true);
+      Services.dirsvc.unregisterProvider(dirProvider);
+
+      this.currentScope = null;
+    });
+  },
+
+  addonManagerStarted: false,
+
+  startAddonManager() {
+    if (this.addonManagerStarted) {
+      return;
+    }
+    this.addonManagerStarted = true;
+
+    let appInfo = {};
+    Cu.import("resource://testing-common/AppInfo.jsm", appInfo);
+
+    appInfo.updateAppInfo({
+      ID: "xpcshell@tests.mozilla.org",
+      name: "XPCShell",
+      version: "48",
+      platformVersion: "48",
+    });
+
+
+    let manager = Cc["@mozilla.org/addons/integration;1"].getService(Ci.nsIObserver)
+                                                         .QueryInterface(Ci.nsITimerCallback);
+    manager.observe(null, "addons-startup", null);
+  },
+
+  loadExtension(data, id = uuidGenerator.generateUUID().number) {
+    let extension = Extension.generate(id, data);
+
+    return new ExtensionWrapper(extension, this.currentScope);
+  },
+};
--- a/toolkit/components/extensions/moz.build
+++ b/toolkit/components/extensions/moz.build
@@ -14,15 +14,19 @@ EXTRA_JS_MODULES += [
     'NativeMessaging.jsm',
     'Schemas.jsm',
 ]
 
 EXTRA_COMPONENTS += [
     'extensions-toolkit.manifest',
 ]
 
+TESTING_JS_MODULES += [
+    'ExtensionXPCShellUtils.jsm',
+]
+
 DIRS += ['schemas']
 
 JAR_MANIFESTS += ['jar.mn']
 
 MOCHITEST_MANIFESTS += ['test/mochitest/mochitest.ini']
 MOCHITEST_CHROME_MANIFESTS += ['test/mochitest/chrome.ini']
 XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini']
--- a/toolkit/components/extensions/test/mochitest/chrome.ini
+++ b/toolkit/components/extensions/test/mochitest/chrome.ini
@@ -1,24 +1,18 @@
 [DEFAULT]
 support-files =
   chrome_head.js
   head.js
-  file_download.html
-  file_download.txt
-  interruptible.sjs
   file_sample.html
 
 [test_chrome_ext_background_debug_global.html]
 skip-if = (os == 'android') # android doesn't have devtools
 [test_chrome_ext_background_page.html]
 skip-if = (toolkit == 'android') # android doesn't have devtools
-[test_chrome_ext_downloads_download.html]
-[test_chrome_ext_downloads_misc.html]
-[test_chrome_ext_downloads_search.html]
 [test_chrome_ext_eventpage_warning.html]
 [test_chrome_ext_native_messaging.html]
 skip-if = os == "android"  # native messaging is not supported on android
 [test_chrome_ext_contentscript_unrecognizedprop_warning.html]
 skip-if = (os == 'android') # browser.tabs is undefined. Bug 1258975 on android.
 [test_chrome_ext_trustworthy_origin.html]
 [test_chrome_ext_webnavigation_resolved_urls.html]
 skip-if = (os == 'android') # browser.tabs is undefined. Bug 1258975 on android.
deleted file mode 100644
--- a/toolkit/components/extensions/test/mochitest/interruptible.sjs
+++ /dev/null
@@ -1,38 +0,0 @@
-const TEST_DATA = "This is 31 bytes of sample data";
-const TOTAL_LEN = TEST_DATA.length;
-const PARTIAL_LEN = 15;
-
-// A handler to let us systematically test pausing/resuming/canceling
-// of downloads.  This target represents a small text file but a simple
-// GET will stall after sending part of the data, to give the test code
-// a chance to pause or do other operations on an in-progress download.
-// A resumed download (ie, a GET with a Range: header) will allow the
-// download to complete.
-function handleRequest(request, response) {
-  response.setHeader("Content-Type", "text/plain", false);
-
-  if (request.hasHeader("Range")) {
-    let start, end;
-    let matches = request.getHeader("Range")
-        .match(/^\s*bytes=(\d+)?-(\d+)?\s*$/);
-    if (matches != null) {
-      start = matches[1] ? parseInt(matches[1], 10) : 0;
-      end = matches[2] ? pareInt(matchs[2], 10) : (TOTAL_LEN - 1);
-    }
-
-    if (end == undefined || end >= TOTAL_LEN) {
-      response.setStatusLine(request.httpVersion, 416, "Requested Range Not Satisfiable");
-      response.setHeader("Content-Range", `*/${TOTAL_LEN}`, false);
-      response.finish();
-      return;
-    }
-
-    response.setStatusLine(request.httpVersion, 206, "Partial Content");
-    response.setHeader("Content-Range", `${start}-${end}/${TOTAL_LEN}`, false);
-    response.write(TEST_DATA.slice(start, end + 1));
-  } else {
-    response.processAsync();
-    response.setHeader("Content-Length", `${TOTAL_LEN}`, false);
-    response.write(TEST_DATA.slice(0, PARTIAL_LEN));
-  }
-}
--- a/toolkit/components/extensions/test/mochitest/mochitest.ini
+++ b/toolkit/components/extensions/test/mochitest/mochitest.ini
@@ -29,74 +29,56 @@ support-files =
   file_script_bad.js
   file_script_redirect.js
   file_script_xhr.js
   file_sample.html
   redirection.sjs
   file_privilege_escalation.html
   file_ext_test_api_injection.js
   file_permission_xhr.html
-  file_download.txt
 
-[test_ext_extension.html]
 [test_ext_inIncognitoContext_window.html]
 skip-if = os == 'android' # Android does not currently support windows.
-[test_ext_simple.html]
 [test_ext_geturl.html]
 [test_ext_background_canvas.html]
 [test_ext_content_security_policy.html]
 [test_ext_contentscript.html]
 skip-if = buildapp == 'b2g' # runat != document_idle is not supported.
 [test_ext_contentscript_api_injection.html]
 [test_ext_contentscript_create_iframe.html]
 [test_ext_contentscript_devtools_metadata.html]
 [test_ext_contentscript_exporthelpers.html]
 [test_ext_contentscript_css.html]
-[test_ext_downloads.html]
 [test_ext_exclude_include_globs.html]
 [test_ext_i18n_css.html]
 [test_ext_generate.html]
-[test_ext_idle.html]
-[test_ext_localStorage.html]
-[test_ext_onmessage_removelistener.html]
 [test_ext_notifications.html]
 [test_ext_permission_xhr.html]
 skip-if = buildapp == 'b2g' # JavaScript error: jar:remoteopenfile:///data/local/tmp/generated-extension.xpi!/content.js, line 46: NS_ERROR_ILLEGAL_VALUE:
 [test_ext_runtime_connect.html]
 skip-if = (os == 'android' || buildapp == 'b2g') # port.sender.tab is undefined on b2g. Bug 1258975 on android.
 [test_ext_runtime_connect_twoway.html]
 skip-if = (os == 'android' || buildapp == 'b2g') # port.sender.tab is undefined on b2g. Bug 1258975 on android.
 [test_ext_runtime_connect2.html]
 skip-if = (os == 'android' || buildapp == 'b2g') # port.sender.tab is undefined on b2g. Bug 1258975 on android.
 [test_ext_runtime_disconnect.html]
-[test_ext_runtime_getPlatformInfo.html]
 [test_ext_runtime_id.html]
-[test_ext_runtime_sendMessage.html]
 [test_ext_sandbox_var.html]
 [test_ext_sendmessage_reply.html]
 skip-if = (os == 'android' || buildapp == 'b2g') # sender.tab is undefined on b2g. Bug 1258975 on android.
 [test_ext_sendmessage_reply2.html]
 skip-if = (os == 'android' || buildapp == 'b2g') # sender.tab is undefined on b2g. Bug 1258975 on android.
 [test_ext_sendmessage_doublereply.html]
 skip-if = (os == 'android' || buildapp == 'b2g') # sender.tab is undefined on b2g. Bug 1258975 on android.
-[test_ext_storage.html]
 [test_ext_storage_content.html]
 [test_ext_storage_tab.html]
 skip-if = os == 'android' # Android does not currently support tabs.
-[test_ext_background_runtime_connect_params.html]
 [test_ext_cookies.html]
-[test_ext_bookmarks.html]
-skip-if = (os == 'android' || buildapp == 'b2g') # unimplemented api. Bug 1258975 on android.
-[test_ext_alarms.html]
-[test_ext_background_window_properties.html]
-[test_ext_background_sub_windows.html]
 [test_ext_background_api_injection.html]
 [test_ext_background_generated_url.html]
-[test_ext_background_generated_reload.html]
-[test_ext_background_generated_load_events.html]
 [test_ext_i18n.html]
 skip-if = (os == 'android') # Bug 1258975 on android.
 [test_ext_web_accessible_resources.html]
 skip-if = (os == 'android') # Bug 1258975 on android.
 [test_ext_webrequest.html]
 skip-if = (os == 'android' || buildapp == 'b2g') # webrequest api uninplemented (bug 1199504). Bug 1258975 on android.
 [test_ext_webnavigation.html]
 skip-if = (os == 'android' || buildapp == 'b2g') # needs TabManager which is not yet implemented. Bug 1258975 on android.
deleted file mode 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_background_runtime_connect_params.html
+++ /dev/null
@@ -1,81 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>WebExtension test</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-
-<script type="text/javascript">
-"use strict";
-
-function backgroundScript() {
-  let detected_invalid_connect_params = 0;
-  let received_ports_number = 0;
-
-  const invalid_connect_params = [
-    // too many params
-    ["fake-extensions-id", {name: "fake-conn-name"}, "unexpected third params"],
-    // invalid params format
-    [{}, {}],
-    ["fake-extensions-id", "invalid-connect-info-format"],
-  ];
-
-  const expected_detected_invalid_connect_params = invalid_connect_params.length;
-  const expected_received_ports_number = 1;
-
-  function assertInvalidConnectParamsException(params) {
-    try {
-      browser.runtime.connect(...params);
-    } catch (e) {
-      detected_invalid_connect_params++;
-      browser.test.assertTrue(e.toString().indexOf("Incorrect argument types for runtime.connect.") >= 0, "exception message is correct");
-    }
-  }
-
-  function countReceivedPorts(port) {
-    received_ports_number++;
-
-    if (port.name == "check-results") {
-      browser.runtime.onConnect.removeListener(countReceivedPorts);
-
-      browser.test.assertEq(expected_detected_invalid_connect_params, detected_invalid_connect_params, "all invalid runtime.connect params detected");
-      browser.test.assertEq(expected_received_ports_number, received_ports_number, "invalid connect should not create a port");
-
-      browser.test.notifyPass("runtime.connect invalid params");
-    }
-  }
-
-  browser.runtime.onConnect.addListener(countReceivedPorts);
-
-  for (let params of invalid_connect_params) {
-    assertInvalidConnectParamsException(params);
-  }
-
-  browser.runtime.connect(browser.runtime.id, {name: "check-results"});
-}
-
-let extensionData = {
-  background: "(" + backgroundScript.toString() + ")()",
-  manifest: {},
-  files: {},
-};
-
-add_task(function* test_backgroundRuntimeConnectParams() {
-  let extension = ExtensionTestUtils.loadExtension(extensionData);
-  yield extension.startup();
-  info("extension loaded");
-
-  yield extension.awaitFinish("runtime.connect invalid params");
-
-  yield extension.unload();
-  info("extension unloaded");
-});
-</script>
-
-</body>
-</html>
deleted file mode 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_extension.html
+++ /dev/null
@@ -1,73 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>WebExtension test</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-
-<script type="text/javascript">
-"use strict";
-
-add_task(function* test_is_allowed_incognito_access() {
-  function background() {
-    browser.extension.isAllowedIncognitoAccess().then(isAllowedIncognitoAccess => {
-      browser.test.assertEq(true, isAllowedIncognitoAccess, "isAllowedIncognitoAccess is true");
-      browser.test.notifyPass("isAllowedIncognitoAccess");
-    });
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${background})()`,
-    manifest: {},
-  });
-
-  yield extension.startup();
-  yield extension.awaitFinish("isAllowedIncognitoAccess");
-  yield extension.unload();
-});
-
-add_task(function* test_in_incognito_context_false() {
-  function background() {
-    browser.test.assertEq(false, browser.extension.inIncognitoContext, "inIncognitoContext returned false");
-    browser.test.notifyPass("inIncognitoContext");
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${background})()`,
-    manifest: {},
-  });
-
-  yield extension.startup();
-  yield extension.awaitFinish("inIncognitoContext");
-  yield extension.unload();
-});
-
-add_task(function* test_is_allowed_file_scheme_access() {
-  function backgroundScript() {
-    browser.extension.isAllowedFileSchemeAccess().then(isAllowedFileSchemeAccess => {
-      browser.test.assertEq(false, isAllowedFileSchemeAccess, "isAllowedFileSchemeAccess is false");
-      browser.test.notifyPass("isAllowedFileSchemeAccess");
-    });
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
-    manifest: {},
-  });
-
-  yield extension.startup();
-  info("extension loaded");
-  yield extension.awaitFinish("isAllowedFileSchemeAccess");
-  yield extension.unload();
-  info("extension unloaded");
-});
-
-</script>
-
-</body>
-</html>
deleted file mode 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_idle.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>WebExtension idle API test</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-
-<script type="text/javascript">
-"use strict";
-
-add_task(function* testIdle() {
-  function background() {
-    browser.idle.queryState(15).then(status => {
-      browser.test.assertEq("active", status, "Expected status");
-      browser.test.notifyPass("idle");
-    },
-    e => {
-      browser.test.fail(`Error: ${e} :: ${e.stack}`);
-      browser.test.notifyFail("idle");
-    });
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${background})()`,
-
-    manifest: {
-      permissions: ["idle"],
-    },
-  });
-
-  yield extension.startup();
-
-  yield extension.awaitFinish("idle");
-
-  yield extension.unload();
-});
-</script>
-</body>
deleted file mode 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_onmessage_removelistener.html
+++ /dev/null
@@ -1,47 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>WebExtension test</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-
-<script type="text/javascript">
-"use strict";
-
-function backgroundScript() {
-  function listener() {
-    browser.test.notifyFail("listener should not be invoked");
-  }
-
-  browser.runtime.onMessage.addListener(listener);
-  browser.runtime.onMessage.removeListener(listener);
-  browser.runtime.sendMessage("hello");
-
-  // Make sure that, if we somehow fail to remove the listener, then we'll run
-  // the listener before the test is marked as passing.
-  setTimeout(function() {
-    browser.test.notifyPass("onmessage_removelistener");
-  }, 0);
-}
-
-let extensionData = {
-  background: "(" + backgroundScript.toString() + ")()",
-};
-
-add_task(function* test_contentscript() {
-  let extension = ExtensionTestUtils.loadExtension(extensionData);
-  yield extension.startup();
-  info("extension loaded");
-  yield extension.awaitFinish("onmessage_removelistener");
-  yield extension.unload();
-  info("extension unloaded");
-});
-</script>
-
-</body>
-</html>
deleted file mode 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_runtime_getPlatformInfo.html
+++ /dev/null
@@ -1,40 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>WebExtension test</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-
-<script type="text/javascript">
-"use strict";
-
-function backgroundScript() {
-  browser.runtime.getPlatformInfo(info => {
-    let validOSs = ["mac", "win", "android", "cros", "linux", "openbsd", "gonk"];
-    let validArchs = ["arm", "x86-32", "x86-64"];
-
-    browser.test.assertTrue(validOSs.indexOf(info.os) != -1, "OS is valid");
-    browser.test.assertTrue(validArchs.indexOf(info.arch) != -1, "Architecture is valid");
-    browser.test.notifyPass("runtime.getPlatformInfo");
-  });
-}
-
-let extensionData = {
-  background: "(" + backgroundScript.toString() + ")()",
-};
-
-add_task(function* test_contentscript() {
-  let extension = ExtensionTestUtils.loadExtension(extensionData);
-  yield extension.startup();
-  yield extension.awaitFinish("runtime.getPlatformInfo");
-  yield extension.unload();
-});
-</script>
-
-</body>
-</html>
deleted file mode 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_runtime_sendMessage.html
+++ /dev/null
@@ -1,85 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>WebExtension test</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-
-<script type="text/javascript">
-"use strict";
-
-add_task(function* tabsSendMessageReply() {
-  function background() {
-    browser.runtime.onMessage.addListener((msg, sender, respond) => {
-      if (msg == "respond-now") {
-        respond(msg);
-      } else if (msg == "respond-soon") {
-        setTimeout(() => { respond(msg); }, 0);
-        return true;
-      } else if (msg == "respond-promise") {
-        return Promise.resolve(msg);
-      } else if (msg == "respond-never") {
-        return;
-      } else if (msg == "respond-error") {
-        return Promise.reject(new Error(msg));
-      } else if (msg == "throw-error") {
-        throw new Error(msg);
-      }
-    });
-
-    browser.runtime.onMessage.addListener((msg, sender, respond) => {
-      if (msg == "respond-now") {
-        respond("hello");
-      } else if (msg == "respond-now-2") {
-        respond(msg);
-      }
-    });
-
-    browser.runtime.sendMessage("respond-never", response => {
-      browser.test.fail(`Got unexpected response callback: ${response}`);
-      browser.test.notifyFail("sendMessage");
-    });
-
-    Promise.all([
-      browser.runtime.sendMessage("respond-now"),
-      browser.runtime.sendMessage("respond-now-2"),
-      new Promise(resolve => browser.runtime.sendMessage("respond-soon", resolve)),
-      browser.runtime.sendMessage("respond-promise"),
-      browser.runtime.sendMessage("respond-never"),
-
-      browser.runtime.sendMessage("respond-error").catch(error => Promise.resolve({error})),
-      browser.runtime.sendMessage("throw-error").catch(error => Promise.resolve({error})),
-    ]).then(([respondNow, respondNow2, respondSoon, respondPromise, respondNever, respondError, throwError]) => {
-      browser.test.assertEq("respond-now", respondNow, "Got the expected immediate response");
-      browser.test.assertEq("respond-now-2", respondNow2, "Got the expected immediate response from the second listener");
-      browser.test.assertEq("respond-soon", respondSoon, "Got the expected delayed response");
-      browser.test.assertEq("respond-promise", respondPromise, "Got the expected promise response");
-      browser.test.assertEq(undefined, respondNever, "Got the expected no-response resolution");
-
-      browser.test.assertEq("respond-error", respondError.error.message, "Got the expected error response");
-      browser.test.assertEq("throw-error", throwError.error.message, "Got the expected thrown error response");
-
-      browser.test.notifyPass("sendMessage");
-    }).catch(e => {
-      browser.test.fail(`Error: ${e} :: ${e.stack}`);
-      browser.test.notifyFail("sendMessage");
-    });
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${background})()`,
-  });
-
-  yield extension.startup();
-  yield extension.awaitFinish("sendMessage");
-  yield extension.unload();
-});
-</script>
-
-</body>
-</html>
deleted file mode 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_simple.html
+++ /dev/null
@@ -1,73 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Test for simple WebExtension</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-
-<script type="text/javascript">
-"use strict";
-
-add_task(function* test_simple() {
-  let extensionData = {
-    manifest: {
-      "name": "Simple extension test",
-      "version": "1.0",
-      "manifest_version": 2,
-      "description": "",
-    },
-  };
-
-  let extension = ExtensionTestUtils.loadExtension(extensionData);
-  info("load complete");
-  yield extension.startup();
-  info("startup complete");
-  yield extension.unload();
-  info("extension unloaded successfully");
-});
-
-add_task(function* test_background() {
-  function backgroundScript() {
-    browser.test.log("running background script");
-
-    browser.test.onMessage.addListener((x, y) => {
-      browser.test.assertEq(x, 10, "x is 10");
-      browser.test.assertEq(y, 20, "y is 20");
-
-      browser.test.notifyPass("background test passed");
-    });
-
-    browser.test.sendMessage("running", 1);
-  }
-
-  let extensionData = {
-    background: "(" + backgroundScript.toString() + ")()",
-    manifest: {
-      "name": "Simple extension test",
-      "version": "1.0",
-      "manifest_version": 2,
-      "description": "",
-    },
-  };
-
-  let extension = ExtensionTestUtils.loadExtension(extensionData);
-  info("load complete");
-  let [, x] = yield Promise.all([extension.startup(), extension.awaitMessage("running")]);
-  is(x, 1, "got correct value from extension");
-  info("startup complete");
-  extension.sendMessage(10, 20);
-  yield extension.awaitFinish();
-  info("test complete");
-  yield extension.unload();
-  info("extension unloaded successfully");
-});
-
-</script>
-
-</body>
-</html>
--- a/toolkit/components/extensions/test/xpcshell/.eslintrc
+++ b/toolkit/components/extensions/test/xpcshell/.eslintrc
@@ -1,3 +1,7 @@
 {
   "extends": "../../../../../testing/xpcshell/xpcshell.eslintrc",
+
+  "globals": {
+    "browser": false,
+  },
 }
rename from toolkit/components/extensions/test/mochitest/file_download.html
rename to toolkit/components/extensions/test/xpcshell/data/file_download.html
rename from toolkit/components/extensions/test/mochitest/file_download.txt
rename to toolkit/components/extensions/test/xpcshell/data/file_download.txt
--- a/toolkit/components/extensions/test/xpcshell/head.js
+++ b/toolkit/components/extensions/test/xpcshell/head.js
@@ -1,51 +1,54 @@
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
+/* exported createHttpServer */
+
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+                                  "resource://gre/modules/AppConstants.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Extension",
                                   "resource://gre/modules/Extension.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionData",
                                   "resource://gre/modules/Extension.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
+                                  "resource://gre/modules/ExtensionManagement.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ExtensionTestUtils",
+                                  "resource://testing-common/ExtensionXPCShellUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+                                  "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "HttpServer",
+                                  "resource://testing-common/httpd.js");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
                                   "resource://gre/modules/Schemas.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 
-/* exported normalizeManifest */
-
-let BASE_MANIFEST = {
-  "applications": {"gecko": {"id": "test@web.ext"}},
-
-  "manifest_version": 2,
-
-  "name": "name",
-  "version": "0",
-};
-
-function* normalizeManifest(manifest, baseManifest = BASE_MANIFEST) {
-  const {Management} = Cu.import("resource://gre/modules/Extension.jsm", {});
-
-  yield Management.lazyInit();
+ExtensionTestUtils.init(this);
 
-  let errors = [];
-  let context = {
-    url: null,
-
-    logError: error => {
-      errors.push(error);
-    },
+/**
+ * Creates a new HttpServer for testing, and begins listening on the
+ * specified port. Automatically shuts down the server when the test
+ * unit ends.
+ *
+ * @param {integer} [port]
+ *        The port to listen on. If omitted, listen on a random
+ *        port. The latter is the preferred behavior.
+ *
+ * @returns {HttpServer}
+ */
+function createHttpServer(port = -1) {
+  let server = new HttpServer();
+  server.start(port);
 
-    preprocessors: {},
-  };
-
-  manifest = Object.assign({}, baseManifest, manifest);
+  do_register_cleanup(() => {
+    return new Promise(resolve => {
+      server.stop(resolve);
+    });
+  });
 
-  let normalized = Schemas.normalize(manifest, "manifest.WebExtensionManifest", context);
-  normalized.errors = errors;
-
-  return normalized;
+  return server;
 }
rename from toolkit/components/extensions/test/mochitest/test_ext_alarms.html
rename to toolkit/components/extensions/test/xpcshell/test_ext_alarms.js
--- a/toolkit/components/extensions/test/mochitest/test_ext_alarms.html
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_alarms.js
@@ -1,21 +1,10 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>WebExtension test</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-
-<script type="text/javascript">
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 add_task(function* test_alarm_without_permissions() {
   function backgroundScript() {
     browser.test.assertTrue(!browser.alarms,
                             "alarm API is not available when the alarm permission is not required");
     browser.test.notifyPass("alarms_permission");
   }
@@ -63,47 +52,16 @@ add_task(function* test_alarm_fires() {
   });
 
   yield extension.startup();
   yield extension.awaitFinish("alarm-fires");
   yield extension.unload();
 });
 
 
-add_task(function* test_cleared_alarm_does_not_fire() {
-  function backgroundScript() {
-    let ALARM_NAME = "test_ext_alarms";
-
-    browser.alarms.onAlarm.addListener(alarm => {
-      browser.test.fail("cleared alarm does not fire");
-      browser.test.notifyFail("alarm-cleared");
-    });
-    browser.alarms.create(ALARM_NAME, {when: Date.now() + 1000});
-
-    browser.alarms.clear(ALARM_NAME).then(wasCleared => {
-      browser.test.assertTrue(wasCleared, "alarm was cleared");
-      setTimeout(() => {
-        browser.test.notifyPass("alarm-cleared");
-      }, 2000);
-    });
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
-    manifest: {
-      permissions: ["alarms"],
-    },
-  });
-
-  yield extension.startup();
-  yield extension.awaitFinish("alarm-cleared");
-  yield extension.unload();
-});
-
-
 add_task(function* test_alarm_fires_with_when() {
   function backgroundScript() {
     let ALARM_NAME = "test_ext_alarms";
     let timer;
 
     browser.alarms.onAlarm.addListener(alarm => {
       browser.test.assertEq(ALARM_NAME, alarm.name, "alarm has the expected name");
       clearTimeout(timer);
@@ -186,57 +144,16 @@ add_task(function* test_alarm_get_and_cl
   });
 
   yield extension.startup();
   yield extension.awaitFinish("alarm-single-arg");
   yield extension.unload();
 });
 
 
-add_task(function* test_periodic_alarm_fires() {
-  function backgroundScript() {
-    const ALARM_NAME = "test_ext_alarms";
-    let count = 0;
-    let timer;
-
-    browser.alarms.onAlarm.addListener(alarm => {
-      browser.test.assertEq(alarm.name, ALARM_NAME, "alarm has the expected name");
-      if (count++ === 3) {
-        clearTimeout(timer);
-        browser.alarms.clear(ALARM_NAME).then(wasCleared => {
-          browser.test.assertTrue(wasCleared, "alarm was cleared");
-          browser.test.notifyPass("alarm-periodic");
-        });
-      }
-    });
-
-    browser.alarms.create(ALARM_NAME, {periodInMinutes: 0.02});
-
-    timer = setTimeout(() => {
-      browser.test.fail("alarm fired expected number of times");
-      browser.alarms.clear(ALARM_NAME).then(wasCleared => {
-        browser.test.assertTrue(wasCleared, "alarm was cleared");
-      });
-      browser.test.notifyFail("alarm-periodic");
-    }, 30000);
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
-    manifest: {
-      permissions: ["alarms"],
-    },
-  });
-
-  yield extension.startup();
-  yield extension.awaitFinish("alarm-periodic");
-  yield extension.unload();
-});
-
-
 add_task(function* test_get_get_all_clear_all_alarms() {
   function backgroundScript() {
     const ALARM_NAME = "test_alarm";
 
     let suffixes = [0, 1, 2];
 
     for (let suffix of suffixes) {
       browser.alarms.create(ALARM_NAME + suffix, {when: Date.now() + (suffix + 1) * 10000});
@@ -297,54 +214,8 @@ add_task(function* test_get_get_all_clea
     extension.awaitMessage("get-1"),
     extension.awaitMessage("get-2"),
     extension.awaitMessage("clear"),
     extension.awaitMessage("get-invalid"),
     extension.awaitMessage("clearAll"),
   ]);
   yield extension.unload();
 });
-
-add_task(function* test_duplicate_alarm_name_replaces_alarm() {
-  function backgroundScript() {
-    let count = 0;
-
-    browser.alarms.onAlarm.addListener(alarm => {
-      if (alarm.name === "master alarm") {
-        browser.alarms.create("child alarm", {delayInMinutes: 0.05});
-        browser.alarms.getAll().then(results => {
-          browser.test.assertEq(2, results.length, "exactly two alarms exist");
-          browser.test.assertEq("master alarm", results[0].name, "first alarm has the expected name");
-          browser.test.assertEq("child alarm", results[1].name, "second alarm has the expected name");
-        }).then(() => {
-          if (count++ === 3) {
-            browser.alarms.clear("master alarm").then(wasCleared => {
-              return browser.alarms.clear("child alarm");
-            }).then(wasCleared => {
-              browser.test.notifyPass("alarm-duplicate");
-            });
-          }
-        });
-      } else {
-        browser.test.fail("duplicate named alarm replaced existing alarm");
-        browser.test.notifyFail("alarm-duplicate");
-      }
-    });
-
-    browser.alarms.create("master alarm", {delayInMinutes: 0.025, periodInMinutes: 0.025});
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
-    manifest: {
-      permissions: ["alarms"],
-    },
-  });
-
-  yield extension.startup();
-  yield extension.awaitFinish("alarm-duplicate");
-  yield extension.unload();
-});
-
-</script>
-
-</body>
-</html>
copy from toolkit/components/extensions/test/mochitest/test_ext_alarms.html
copy to toolkit/components/extensions/test/xpcshell/test_ext_alarms_does_not_fire.js
--- a/toolkit/components/extensions/test/mochitest/test_ext_alarms.html
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_alarms_does_not_fire.js
@@ -1,78 +1,12 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>WebExtension test</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-
-<script type="text/javascript">
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-add_task(function* test_alarm_without_permissions() {
-  function backgroundScript() {
-    browser.test.assertTrue(!browser.alarms,
-                            "alarm API is not available when the alarm permission is not required");
-    browser.test.notifyPass("alarms_permission");
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
-    manifest: {
-      permissions: [],
-    },
-  });
-
-  yield extension.startup();
-  yield extension.awaitFinish("alarms_permission");
-  yield extension.unload();
-});
-
-
-add_task(function* test_alarm_fires() {
-  function backgroundScript() {
-    let ALARM_NAME = "test_ext_alarms";
-    let timer;
-
-    browser.alarms.onAlarm.addListener(alarm => {
-      browser.test.assertEq(ALARM_NAME, alarm.name, "alarm has the correct name");
-      clearTimeout(timer);
-      browser.test.notifyPass("alarm-fires");
-    });
-
-    browser.alarms.create(ALARM_NAME, {delayInMinutes: 0.02});
-
-    timer = setTimeout(() => {
-      browser.test.fail("alarm fired within expected time");
-      browser.alarms.clear(ALARM_NAME).then(wasCleared => {
-        browser.test.assertTrue(wasCleared, "alarm was cleared");
-      });
-      browser.test.notifyFail("alarm-fires");
-    }, 10000);
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
-    manifest: {
-      permissions: ["alarms"],
-    },
-  });
-
-  yield extension.startup();
-  yield extension.awaitFinish("alarm-fires");
-  yield extension.unload();
-});
-
-
 add_task(function* test_cleared_alarm_does_not_fire() {
   function backgroundScript() {
     let ALARM_NAME = "test_ext_alarms";
 
     browser.alarms.onAlarm.addListener(alarm => {
       browser.test.fail("cleared alarm does not fire");
       browser.test.notifyFail("alarm-cleared");
     });
@@ -92,259 +26,8 @@ add_task(function* test_cleared_alarm_do
       permissions: ["alarms"],
     },
   });
 
   yield extension.startup();
   yield extension.awaitFinish("alarm-cleared");
   yield extension.unload();
 });
-
-
-add_task(function* test_alarm_fires_with_when() {
-  function backgroundScript() {
-    let ALARM_NAME = "test_ext_alarms";
-    let timer;
-
-    browser.alarms.onAlarm.addListener(alarm => {
-      browser.test.assertEq(ALARM_NAME, alarm.name, "alarm has the expected name");
-      clearTimeout(timer);
-      browser.test.notifyPass("alarm-when");
-    });
-
-    browser.alarms.create(ALARM_NAME, {when: Date.now() + 1000});
-
-    timer = setTimeout(() => {
-      browser.test.fail("alarm fired within expected time");
-      browser.alarms.clear(ALARM_NAME).then(wasCleared => {
-        browser.test.assertTrue(wasCleared, "alarm was cleared");
-      });
-      browser.test.notifyFail("alarm-when");
-    }, 10000);
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
-    manifest: {
-      permissions: ["alarms"],
-    },
-  });
-
-  yield extension.startup();
-  yield extension.awaitFinish("alarm-when");
-  yield extension.unload();
-});
-
-
-add_task(function* test_alarm_clear_non_matching_name() {
-  function backgroundScript() {
-    let ALARM_NAME = "test_ext_alarms";
-
-    browser.alarms.create(ALARM_NAME, {when: Date.now() + 2000});
-
-    browser.alarms.clear(ALARM_NAME + "1").then(wasCleared => {
-      browser.test.assertFalse(wasCleared, "alarm was not cleared");
-      return browser.alarms.getAll();
-    }).then(alarms => {
-      browser.test.assertEq(1, alarms.length, "alarm was not removed");
-      browser.test.notifyPass("alarm-clear");
-    });
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
-    manifest: {
-      permissions: ["alarms"],
-    },
-  });
-
-  yield extension.startup();
-  yield extension.awaitFinish("alarm-clear");
-  yield extension.unload();
-});
-
-
-add_task(function* test_alarm_get_and_clear_single_argument() {
-  function backgroundScript() {
-    browser.alarms.create({when: Date.now() + 2000});
-
-    browser.alarms.get().then(alarm => {
-      browser.test.assertEq("", alarm.name, "expected alarm returned");
-      return browser.alarms.clear();
-    }).then(wasCleared => {
-      browser.test.assertTrue(wasCleared, "alarm was cleared");
-      return browser.alarms.getAll();
-    }).then(alarms => {
-      browser.test.assertEq(0, alarms.length, "alarm was removed");
-      browser.test.notifyPass("alarm-single-arg");
-    });
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
-    manifest: {
-      permissions: ["alarms"],
-    },
-  });
-
-  yield extension.startup();
-  yield extension.awaitFinish("alarm-single-arg");
-  yield extension.unload();
-});
-
-
-add_task(function* test_periodic_alarm_fires() {
-  function backgroundScript() {
-    const ALARM_NAME = "test_ext_alarms";
-    let count = 0;
-    let timer;
-
-    browser.alarms.onAlarm.addListener(alarm => {
-      browser.test.assertEq(alarm.name, ALARM_NAME, "alarm has the expected name");
-      if (count++ === 3) {
-        clearTimeout(timer);
-        browser.alarms.clear(ALARM_NAME).then(wasCleared => {
-          browser.test.assertTrue(wasCleared, "alarm was cleared");
-          browser.test.notifyPass("alarm-periodic");
-        });
-      }
-    });
-
-    browser.alarms.create(ALARM_NAME, {periodInMinutes: 0.02});
-
-    timer = setTimeout(() => {
-      browser.test.fail("alarm fired expected number of times");
-      browser.alarms.clear(ALARM_NAME).then(wasCleared => {
-        browser.test.assertTrue(wasCleared, "alarm was cleared");
-      });
-      browser.test.notifyFail("alarm-periodic");
-    }, 30000);
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
-    manifest: {
-      permissions: ["alarms"],
-    },
-  });
-
-  yield extension.startup();
-  yield extension.awaitFinish("alarm-periodic");
-  yield extension.unload();
-});
-
-
-add_task(function* test_get_get_all_clear_all_alarms() {
-  function backgroundScript() {
-    const ALARM_NAME = "test_alarm";
-
-    let suffixes = [0, 1, 2];
-
-    for (let suffix of suffixes) {
-      browser.alarms.create(ALARM_NAME + suffix, {when: Date.now() + (suffix + 1) * 10000});
-    }
-
-    browser.alarms.getAll().then(alarms => {
-      browser.test.assertEq(suffixes.length, alarms.length, "expected number of alarms were found");
-      alarms.forEach((alarm, index) => {
-        browser.test.assertEq(ALARM_NAME + index, alarm.name, "alarm has the expected name");
-      });
-
-      return Promise.all(
-        suffixes.map(suffix => {
-          return browser.alarms.get(ALARM_NAME + suffix).then(alarm => {
-            browser.test.assertEq(ALARM_NAME + suffix, alarm.name, "alarm has the expected name");
-            browser.test.sendMessage(`get-${suffix}`);
-          });
-        })
-      );
-    }).then(() => {
-      return browser.alarms.clear(ALARM_NAME + suffixes[0]);
-    }).then(wasCleared => {
-      browser.test.assertTrue(wasCleared, "alarm was cleared");
-
-      return browser.alarms.getAll();
-    }).then(alarms => {
-      browser.test.assertEq(2, alarms.length, "alarm was removed");
-
-      return browser.alarms.get(ALARM_NAME + suffixes[0]);
-    }).then(alarm => {
-      browser.test.assertEq(undefined, alarm, "non-existent alarm is undefined");
-      browser.test.sendMessage(`get-invalid`);
-
-      return browser.alarms.clearAll();
-    }).then(wasCleared => {
-      browser.test.assertTrue(wasCleared, "alarms were cleared");
-
-      return browser.alarms.getAll();
-    }).then(alarms => {
-      browser.test.assertEq(0, alarms.length, "no alarms exist");
-      browser.test.sendMessage("clearAll");
-      browser.test.sendMessage("clear");
-      browser.test.sendMessage("getAll");
-    });
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
-    manifest: {
-      permissions: ["alarms"],
-    },
-  });
-
-  yield Promise.all([
-    extension.startup(),
-    extension.awaitMessage("getAll"),
-    extension.awaitMessage("get-0"),
-    extension.awaitMessage("get-1"),
-    extension.awaitMessage("get-2"),
-    extension.awaitMessage("clear"),
-    extension.awaitMessage("get-invalid"),
-    extension.awaitMessage("clearAll"),
-  ]);
-  yield extension.unload();
-});
-
-add_task(function* test_duplicate_alarm_name_replaces_alarm() {
-  function backgroundScript() {
-    let count = 0;
-
-    browser.alarms.onAlarm.addListener(alarm => {
-      if (alarm.name === "master alarm") {
-        browser.alarms.create("child alarm", {delayInMinutes: 0.05});
-        browser.alarms.getAll().then(results => {
-          browser.test.assertEq(2, results.length, "exactly two alarms exist");
-          browser.test.assertEq("master alarm", results[0].name, "first alarm has the expected name");
-          browser.test.assertEq("child alarm", results[1].name, "second alarm has the expected name");
-        }).then(() => {
-          if (count++ === 3) {
-            browser.alarms.clear("master alarm").then(wasCleared => {
-              return browser.alarms.clear("child alarm");
-            }).then(wasCleared => {
-              browser.test.notifyPass("alarm-duplicate");
-            });
-          }
-        });
-      } else {
-        browser.test.fail("duplicate named alarm replaced existing alarm");
-        browser.test.notifyFail("alarm-duplicate");
-      }
-    });
-
-    browser.alarms.create("master alarm", {delayInMinutes: 0.025, periodInMinutes: 0.025});
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
-    manifest: {
-      permissions: ["alarms"],
-    },
-  });
-
-  yield extension.startup();
-  yield extension.awaitFinish("alarm-duplicate");
-  yield extension.unload();
-});
-
-</script>
-
-</body>
-</html>
copy from toolkit/components/extensions/test/mochitest/test_ext_alarms.html
copy to toolkit/components/extensions/test/xpcshell/test_ext_alarms_periodic.js
--- a/toolkit/components/extensions/test/mochitest/test_ext_alarms.html
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_alarms_periodic.js
@@ -1,201 +1,12 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>WebExtension test</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-
-<script type="text/javascript">
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-add_task(function* test_alarm_without_permissions() {
-  function backgroundScript() {
-    browser.test.assertTrue(!browser.alarms,
-                            "alarm API is not available when the alarm permission is not required");
-    browser.test.notifyPass("alarms_permission");
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
-    manifest: {
-      permissions: [],
-    },
-  });
-
-  yield extension.startup();
-  yield extension.awaitFinish("alarms_permission");
-  yield extension.unload();
-});
-
-
-add_task(function* test_alarm_fires() {
-  function backgroundScript() {
-    let ALARM_NAME = "test_ext_alarms";
-    let timer;
-
-    browser.alarms.onAlarm.addListener(alarm => {
-      browser.test.assertEq(ALARM_NAME, alarm.name, "alarm has the correct name");
-      clearTimeout(timer);
-      browser.test.notifyPass("alarm-fires");
-    });
-
-    browser.alarms.create(ALARM_NAME, {delayInMinutes: 0.02});
-
-    timer = setTimeout(() => {
-      browser.test.fail("alarm fired within expected time");
-      browser.alarms.clear(ALARM_NAME).then(wasCleared => {
-        browser.test.assertTrue(wasCleared, "alarm was cleared");
-      });
-      browser.test.notifyFail("alarm-fires");
-    }, 10000);
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
-    manifest: {
-      permissions: ["alarms"],
-    },
-  });
-
-  yield extension.startup();
-  yield extension.awaitFinish("alarm-fires");
-  yield extension.unload();
-});
-
-
-add_task(function* test_cleared_alarm_does_not_fire() {
-  function backgroundScript() {
-    let ALARM_NAME = "test_ext_alarms";
-
-    browser.alarms.onAlarm.addListener(alarm => {
-      browser.test.fail("cleared alarm does not fire");
-      browser.test.notifyFail("alarm-cleared");
-    });
-    browser.alarms.create(ALARM_NAME, {when: Date.now() + 1000});
-
-    browser.alarms.clear(ALARM_NAME).then(wasCleared => {
-      browser.test.assertTrue(wasCleared, "alarm was cleared");
-      setTimeout(() => {
-        browser.test.notifyPass("alarm-cleared");
-      }, 2000);
-    });
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
-    manifest: {
-      permissions: ["alarms"],
-    },
-  });
-
-  yield extension.startup();
-  yield extension.awaitFinish("alarm-cleared");
-  yield extension.unload();
-});
-
-
-add_task(function* test_alarm_fires_with_when() {
-  function backgroundScript() {
-    let ALARM_NAME = "test_ext_alarms";
-    let timer;
-
-    browser.alarms.onAlarm.addListener(alarm => {
-      browser.test.assertEq(ALARM_NAME, alarm.name, "alarm has the expected name");
-      clearTimeout(timer);
-      browser.test.notifyPass("alarm-when");
-    });
-
-    browser.alarms.create(ALARM_NAME, {when: Date.now() + 1000});
-
-    timer = setTimeout(() => {
-      browser.test.fail("alarm fired within expected time");
-      browser.alarms.clear(ALARM_NAME).then(wasCleared => {
-        browser.test.assertTrue(wasCleared, "alarm was cleared");
-      });
-      browser.test.notifyFail("alarm-when");
-    }, 10000);
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
-    manifest: {
-      permissions: ["alarms"],
-    },
-  });
-
-  yield extension.startup();
-  yield extension.awaitFinish("alarm-when");
-  yield extension.unload();
-});
-
-
-add_task(function* test_alarm_clear_non_matching_name() {
-  function backgroundScript() {
-    let ALARM_NAME = "test_ext_alarms";
-
-    browser.alarms.create(ALARM_NAME, {when: Date.now() + 2000});
-
-    browser.alarms.clear(ALARM_NAME + "1").then(wasCleared => {
-      browser.test.assertFalse(wasCleared, "alarm was not cleared");
-      return browser.alarms.getAll();
-    }).then(alarms => {
-      browser.test.assertEq(1, alarms.length, "alarm was not removed");
-      browser.test.notifyPass("alarm-clear");
-    });
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
-    manifest: {
-      permissions: ["alarms"],
-    },
-  });
-
-  yield extension.startup();
-  yield extension.awaitFinish("alarm-clear");
-  yield extension.unload();
-});
-
-
-add_task(function* test_alarm_get_and_clear_single_argument() {
-  function backgroundScript() {
-    browser.alarms.create({when: Date.now() + 2000});
-
-    browser.alarms.get().then(alarm => {
-      browser.test.assertEq("", alarm.name, "expected alarm returned");
-      return browser.alarms.clear();
-    }).then(wasCleared => {
-      browser.test.assertTrue(wasCleared, "alarm was cleared");
-      return browser.alarms.getAll();
-    }).then(alarms => {
-      browser.test.assertEq(0, alarms.length, "alarm was removed");
-      browser.test.notifyPass("alarm-single-arg");
-    });
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
-    manifest: {
-      permissions: ["alarms"],
-    },
-  });
-
-  yield extension.startup();
-  yield extension.awaitFinish("alarm-single-arg");
-  yield extension.unload();
-});
-
-
 add_task(function* test_periodic_alarm_fires() {
   function backgroundScript() {
     const ALARM_NAME = "test_ext_alarms";
     let count = 0;
     let timer;
 
     browser.alarms.onAlarm.addListener(alarm => {
       browser.test.assertEq(alarm.name, ALARM_NAME, "alarm has the expected name");
@@ -225,126 +36,8 @@ add_task(function* test_periodic_alarm_f
       permissions: ["alarms"],
     },
   });
 
   yield extension.startup();
   yield extension.awaitFinish("alarm-periodic");
   yield extension.unload();
 });
-
-
-add_task(function* test_get_get_all_clear_all_alarms() {
-  function backgroundScript() {
-    const ALARM_NAME = "test_alarm";
-
-    let suffixes = [0, 1, 2];
-
-    for (let suffix of suffixes) {
-      browser.alarms.create(ALARM_NAME + suffix, {when: Date.now() + (suffix + 1) * 10000});
-    }
-
-    browser.alarms.getAll().then(alarms => {
-      browser.test.assertEq(suffixes.length, alarms.length, "expected number of alarms were found");
-      alarms.forEach((alarm, index) => {
-        browser.test.assertEq(ALARM_NAME + index, alarm.name, "alarm has the expected name");
-      });
-
-      return Promise.all(
-        suffixes.map(suffix => {
-          return browser.alarms.get(ALARM_NAME + suffix).then(alarm => {
-            browser.test.assertEq(ALARM_NAME + suffix, alarm.name, "alarm has the expected name");
-            browser.test.sendMessage(`get-${suffix}`);
-          });
-        })
-      );
-    }).then(() => {
-      return browser.alarms.clear(ALARM_NAME + suffixes[0]);
-    }).then(wasCleared => {
-      browser.test.assertTrue(wasCleared, "alarm was cleared");
-
-      return browser.alarms.getAll();
-    }).then(alarms => {
-      browser.test.assertEq(2, alarms.length, "alarm was removed");
-
-      return browser.alarms.get(ALARM_NAME + suffixes[0]);
-    }).then(alarm => {
-      browser.test.assertEq(undefined, alarm, "non-existent alarm is undefined");
-      browser.test.sendMessage(`get-invalid`);
-
-      return browser.alarms.clearAll();
-    }).then(wasCleared => {
-      browser.test.assertTrue(wasCleared, "alarms were cleared");
-
-      return browser.alarms.getAll();
-    }).then(alarms => {
-      browser.test.assertEq(0, alarms.length, "no alarms exist");
-      browser.test.sendMessage("clearAll");
-      browser.test.sendMessage("clear");
-      browser.test.sendMessage("getAll");
-    });
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
-    manifest: {
-      permissions: ["alarms"],
-    },
-  });
-
-  yield Promise.all([
-    extension.startup(),
-    extension.awaitMessage("getAll"),
-    extension.awaitMessage("get-0"),
-    extension.awaitMessage("get-1"),
-    extension.awaitMessage("get-2"),
-    extension.awaitMessage("clear"),
-    extension.awaitMessage("get-invalid"),
-    extension.awaitMessage("clearAll"),
-  ]);
-  yield extension.unload();
-});
-
-add_task(function* test_duplicate_alarm_name_replaces_alarm() {
-  function backgroundScript() {
-    let count = 0;
-
-    browser.alarms.onAlarm.addListener(alarm => {
-      if (alarm.name === "master alarm") {
-        browser.alarms.create("child alarm", {delayInMinutes: 0.05});
-        browser.alarms.getAll().then(results => {
-          browser.test.assertEq(2, results.length, "exactly two alarms exist");
-          browser.test.assertEq("master alarm", results[0].name, "first alarm has the expected name");
-          browser.test.assertEq("child alarm", results[1].name, "second alarm has the expected name");
-        }).then(() => {
-          if (count++ === 3) {
-            browser.alarms.clear("master alarm").then(wasCleared => {
-              return browser.alarms.clear("child alarm");
-            }).then(wasCleared => {
-              browser.test.notifyPass("alarm-duplicate");
-            });
-          }
-        });
-      } else {
-        browser.test.fail("duplicate named alarm replaced existing alarm");
-        browser.test.notifyFail("alarm-duplicate");
-      }
-    });
-
-    browser.alarms.create("master alarm", {delayInMinutes: 0.025, periodInMinutes: 0.025});
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
-    manifest: {
-      permissions: ["alarms"],
-    },
-  });
-
-  yield extension.startup();
-  yield extension.awaitFinish("alarm-duplicate");
-  yield extension.unload();
-});
-
-</script>
-
-</body>
-</html>
copy from toolkit/components/extensions/test/mochitest/test_ext_alarms.html
copy to toolkit/components/extensions/test/xpcshell/test_ext_alarms_replaces.js
--- a/toolkit/components/extensions/test/mochitest/test_ext_alarms.html
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_alarms_replaces.js
@@ -1,312 +1,12 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>WebExtension test</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-
-<script type="text/javascript">
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-add_task(function* test_alarm_without_permissions() {
-  function backgroundScript() {
-    browser.test.assertTrue(!browser.alarms,
-                            "alarm API is not available when the alarm permission is not required");
-    browser.test.notifyPass("alarms_permission");
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
-    manifest: {
-      permissions: [],
-    },
-  });
-
-  yield extension.startup();
-  yield extension.awaitFinish("alarms_permission");
-  yield extension.unload();
-});
-
-
-add_task(function* test_alarm_fires() {
-  function backgroundScript() {
-    let ALARM_NAME = "test_ext_alarms";
-    let timer;
-
-    browser.alarms.onAlarm.addListener(alarm => {
-      browser.test.assertEq(ALARM_NAME, alarm.name, "alarm has the correct name");
-      clearTimeout(timer);
-      browser.test.notifyPass("alarm-fires");
-    });
-
-    browser.alarms.create(ALARM_NAME, {delayInMinutes: 0.02});
-
-    timer = setTimeout(() => {
-      browser.test.fail("alarm fired within expected time");
-      browser.alarms.clear(ALARM_NAME).then(wasCleared => {
-        browser.test.assertTrue(wasCleared, "alarm was cleared");
-      });
-      browser.test.notifyFail("alarm-fires");
-    }, 10000);
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
-    manifest: {
-      permissions: ["alarms"],
-    },
-  });
-
-  yield extension.startup();
-  yield extension.awaitFinish("alarm-fires");
-  yield extension.unload();
-});
-
-
-add_task(function* test_cleared_alarm_does_not_fire() {
-  function backgroundScript() {
-    let ALARM_NAME = "test_ext_alarms";
-
-    browser.alarms.onAlarm.addListener(alarm => {
-      browser.test.fail("cleared alarm does not fire");
-      browser.test.notifyFail("alarm-cleared");
-    });
-    browser.alarms.create(ALARM_NAME, {when: Date.now() + 1000});
-
-    browser.alarms.clear(ALARM_NAME).then(wasCleared => {
-      browser.test.assertTrue(wasCleared, "alarm was cleared");
-      setTimeout(() => {
-        browser.test.notifyPass("alarm-cleared");
-      }, 2000);
-    });
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
-    manifest: {
-      permissions: ["alarms"],
-    },
-  });
-
-  yield extension.startup();
-  yield extension.awaitFinish("alarm-cleared");
-  yield extension.unload();
-});
-
-
-add_task(function* test_alarm_fires_with_when() {
-  function backgroundScript() {
-    let ALARM_NAME = "test_ext_alarms";
-    let timer;
-
-    browser.alarms.onAlarm.addListener(alarm => {
-      browser.test.assertEq(ALARM_NAME, alarm.name, "alarm has the expected name");
-      clearTimeout(timer);
-      browser.test.notifyPass("alarm-when");
-    });
-
-    browser.alarms.create(ALARM_NAME, {when: Date.now() + 1000});
-
-    timer = setTimeout(() => {
-      browser.test.fail("alarm fired within expected time");
-      browser.alarms.clear(ALARM_NAME).then(wasCleared => {
-        browser.test.assertTrue(wasCleared, "alarm was cleared");
-      });
-      browser.test.notifyFail("alarm-when");
-    }, 10000);
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
-    manifest: {
-      permissions: ["alarms"],
-    },
-  });
-
-  yield extension.startup();
-  yield extension.awaitFinish("alarm-when");
-  yield extension.unload();
-});
-
-
-add_task(function* test_alarm_clear_non_matching_name() {
-  function backgroundScript() {
-    let ALARM_NAME = "test_ext_alarms";
-
-    browser.alarms.create(ALARM_NAME, {when: Date.now() + 2000});
-
-    browser.alarms.clear(ALARM_NAME + "1").then(wasCleared => {
-      browser.test.assertFalse(wasCleared, "alarm was not cleared");
-      return browser.alarms.getAll();
-    }).then(alarms => {
-      browser.test.assertEq(1, alarms.length, "alarm was not removed");
-      browser.test.notifyPass("alarm-clear");
-    });
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
-    manifest: {
-      permissions: ["alarms"],
-    },
-  });
-
-  yield extension.startup();
-  yield extension.awaitFinish("alarm-clear");
-  yield extension.unload();
-});
-
-
-add_task(function* test_alarm_get_and_clear_single_argument() {
-  function backgroundScript() {
-    browser.alarms.create({when: Date.now() + 2000});
-
-    browser.alarms.get().then(alarm => {
-      browser.test.assertEq("", alarm.name, "expected alarm returned");
-      return browser.alarms.clear();
-    }).then(wasCleared => {
-      browser.test.assertTrue(wasCleared, "alarm was cleared");
-      return browser.alarms.getAll();
-    }).then(alarms => {
-      browser.test.assertEq(0, alarms.length, "alarm was removed");
-      browser.test.notifyPass("alarm-single-arg");
-    });
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
-    manifest: {
-      permissions: ["alarms"],
-    },
-  });
-
-  yield extension.startup();
-  yield extension.awaitFinish("alarm-single-arg");
-  yield extension.unload();
-});
-
-
-add_task(function* test_periodic_alarm_fires() {
-  function backgroundScript() {
-    const ALARM_NAME = "test_ext_alarms";
-    let count = 0;
-    let timer;
-
-    browser.alarms.onAlarm.addListener(alarm => {
-      browser.test.assertEq(alarm.name, ALARM_NAME, "alarm has the expected name");
-      if (count++ === 3) {
-        clearTimeout(timer);
-        browser.alarms.clear(ALARM_NAME).then(wasCleared => {
-          browser.test.assertTrue(wasCleared, "alarm was cleared");
-          browser.test.notifyPass("alarm-periodic");
-        });
-      }
-    });
-
-    browser.alarms.create(ALARM_NAME, {periodInMinutes: 0.02});
-
-    timer = setTimeout(() => {
-      browser.test.fail("alarm fired expected number of times");
-      browser.alarms.clear(ALARM_NAME).then(wasCleared => {
-        browser.test.assertTrue(wasCleared, "alarm was cleared");
-      });
-      browser.test.notifyFail("alarm-periodic");
-    }, 30000);
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
-    manifest: {
-      permissions: ["alarms"],
-    },
-  });
-
-  yield extension.startup();
-  yield extension.awaitFinish("alarm-periodic");
-  yield extension.unload();
-});
-
-
-add_task(function* test_get_get_all_clear_all_alarms() {
-  function backgroundScript() {
-    const ALARM_NAME = "test_alarm";
-
-    let suffixes = [0, 1, 2];
-
-    for (let suffix of suffixes) {
-      browser.alarms.create(ALARM_NAME + suffix, {when: Date.now() + (suffix + 1) * 10000});
-    }
-
-    browser.alarms.getAll().then(alarms => {
-      browser.test.assertEq(suffixes.length, alarms.length, "expected number of alarms were found");
-      alarms.forEach((alarm, index) => {
-        browser.test.assertEq(ALARM_NAME + index, alarm.name, "alarm has the expected name");
-      });
-
-      return Promise.all(
-        suffixes.map(suffix => {
-          return browser.alarms.get(ALARM_NAME + suffix).then(alarm => {
-            browser.test.assertEq(ALARM_NAME + suffix, alarm.name, "alarm has the expected name");
-            browser.test.sendMessage(`get-${suffix}`);
-          });
-        })
-      );
-    }).then(() => {
-      return browser.alarms.clear(ALARM_NAME + suffixes[0]);
-    }).then(wasCleared => {
-      browser.test.assertTrue(wasCleared, "alarm was cleared");
-
-      return browser.alarms.getAll();
-    }).then(alarms => {
-      browser.test.assertEq(2, alarms.length, "alarm was removed");
-
-      return browser.alarms.get(ALARM_NAME + suffixes[0]);
-    }).then(alarm => {
-      browser.test.assertEq(undefined, alarm, "non-existent alarm is undefined");
-      browser.test.sendMessage(`get-invalid`);
-
-      return browser.alarms.clearAll();
-    }).then(wasCleared => {
-      browser.test.assertTrue(wasCleared, "alarms were cleared");
-
-      return browser.alarms.getAll();
-    }).then(alarms => {
-      browser.test.assertEq(0, alarms.length, "no alarms exist");
-      browser.test.sendMessage("clearAll");
-      browser.test.sendMessage("clear");
-      browser.test.sendMessage("getAll");
-    });
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
-    manifest: {
-      permissions: ["alarms"],
-    },
-  });
-
-  yield Promise.all([
-    extension.startup(),
-    extension.awaitMessage("getAll"),
-    extension.awaitMessage("get-0"),
-    extension.awaitMessage("get-1"),
-    extension.awaitMessage("get-2"),
-    extension.awaitMessage("clear"),
-    extension.awaitMessage("get-invalid"),
-    extension.awaitMessage("clearAll"),
-  ]);
-  yield extension.unload();
-});
 
 add_task(function* test_duplicate_alarm_name_replaces_alarm() {
   function backgroundScript() {
     let count = 0;
 
     browser.alarms.onAlarm.addListener(alarm => {
       if (alarm.name === "master alarm") {
         browser.alarms.create("child alarm", {delayInMinutes: 0.05});
@@ -338,13 +38,8 @@ add_task(function* test_duplicate_alarm_
       permissions: ["alarms"],
     },
   });
 
   yield extension.startup();
   yield extension.awaitFinish("alarm-duplicate");
   yield extension.unload();
 });
-
-</script>
-
-</body>
-</html>
rename from toolkit/components/extensions/test/mochitest/test_ext_background_generated_load_events.html
rename to toolkit/components/extensions/test/xpcshell/test_ext_background_generated_load_events.js
--- a/toolkit/components/extensions/test/mochitest/test_ext_background_generated_load_events.html
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_background_generated_load_events.js
@@ -1,47 +1,23 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Test load events in _generated_background_page.html</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
-</head>
-<body>
-
-<script>
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 /* eslint-disable mozilla/balanced-listeners */
 
 add_task(function* test_DOMContentLoaded_in_generated_background_page() {
-  function backgroundScript() {
-    function reportListener(event) {
-      browser.test.sendMessage("eventname", event.type);
-    }
-    document.addEventListener("DOMContentLoaded", reportListener);
-    window.addEventListener("load", reportListener);
-  }
   let extension = ExtensionTestUtils.loadExtension({
-    manifest: {
-      background: {
-        scripts: ["bg.js"],
-      },
-      web_accessible_resources: ["_generated_background_page.html"],
-    },
-    files: {
-      "bg.js": `(${backgroundScript})();`,
+    background() {
+      function reportListener(event) {
+        browser.test.sendMessage("eventname", event.type);
+      }
+      document.addEventListener("DOMContentLoaded", reportListener);
+      window.addEventListener("load", reportListener);
     },
   });
 
   yield extension.startup();
-  is("DOMContentLoaded", yield extension.awaitMessage("eventname"));
-  is("load", yield extension.awaitMessage("eventname"));
+  equal("DOMContentLoaded", yield extension.awaitMessage("eventname"));
+  equal("load", yield extension.awaitMessage("eventname"));
 
   yield extension.unload();
 });
-
-</script>
-</body>
-</html>
rename from toolkit/components/extensions/test/mochitest/test_ext_background_generated_reload.html
rename to toolkit/components/extensions/test/xpcshell/test_ext_background_generated_reload.js
--- a/toolkit/components/extensions/test/mochitest/test_ext_background_generated_reload.html
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_background_generated_reload.js
@@ -1,50 +1,24 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Test reload of _generated_background_page.html</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
-</head>
-<body>
-
-<script>
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 add_task(function* test_reload_generated_background_page() {
-  function backgroundScript() {
-    if (location.hash !== "#firstrun") {
-      browser.test.sendMessage("first run");
-      location.hash = "#firstrun";
-      browser.test.assertEq("#firstrun", location.hash);
-      location.reload();
-    } else {
-      browser.test.notifyPass("second run");
-    }
-  }
   let extension = ExtensionTestUtils.loadExtension({
-    manifest: {
-      background: {
-        scripts: ["bg.js"],
-      },
-    },
-    files: {
-      "bg.js": `(${backgroundScript})();`,
+    background() {
+      if (location.hash !== "#firstrun") {
+        browser.test.sendMessage("first run");
+        location.hash = "#firstrun";
+        browser.test.assertEq("#firstrun", location.hash);
+        location.reload();
+      } else {
+        browser.test.notifyPass("second run");
+      }
     },
   });
 
   yield extension.startup();
-  info("Waiting for first message");
   yield extension.awaitMessage("first run");
-  info("Waiting for second message");
   yield extension.awaitFinish("second run");
-  info("Received both messages");
 
   yield extension.unload();
 });
-
-</script>
-</body>
-</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_background_runtime_connect_params.js
@@ -0,0 +1,64 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+function backgroundScript() {
+  let detected_invalid_connect_params = 0;
+  let received_ports_number = 0;
+
+  const invalid_connect_params = [
+    // too many params
+    ["fake-extensions-id", {name: "fake-conn-name"}, "unexpected third params"],
+    // invalid params format
+    [{}, {}],
+    ["fake-extensions-id", "invalid-connect-info-format"],
+  ];
+
+  const expected_detected_invalid_connect_params = invalid_connect_params.length;
+  const expected_received_ports_number = 1;
+
+  function assertInvalidConnectParamsException(params) {
+    try {
+      browser.runtime.connect(...params);
+    } catch (e) {
+      detected_invalid_connect_params++;
+      browser.test.assertTrue(e.toString().indexOf("Incorrect argument types for runtime.connect.") >= 0, "exception message is correct");
+    }
+  }
+
+  function countReceivedPorts(port) {
+    received_ports_number++;
+
+    if (port.name == "check-results") {
+      browser.runtime.onConnect.removeListener(countReceivedPorts);
+
+      browser.test.assertEq(expected_detected_invalid_connect_params, detected_invalid_connect_params, "all invalid runtime.connect params detected");
+      browser.test.assertEq(expected_received_ports_number, received_ports_number, "invalid connect should not create a port");
+
+      browser.test.notifyPass("runtime.connect invalid params");
+    }
+  }
+
+  browser.runtime.onConnect.addListener(countReceivedPorts);
+
+  for (let params of invalid_connect_params) {
+    assertInvalidConnectParamsException(params);
+  }
+
+  browser.runtime.connect(browser.runtime.id, {name: "check-results"});
+}
+
+let extensionData = {
+  background: backgroundScript,
+  manifest: {},
+  files: {},
+};
+
+add_task(function* test_backgroundRuntimeConnectParams() {
+  let extension = ExtensionTestUtils.loadExtension(extensionData);
+  yield extension.startup();
+
+  yield extension.awaitFinish("runtime.connect invalid params");
+
+  yield extension.unload();
+});
rename from toolkit/components/extensions/test/mochitest/test_ext_background_sub_windows.html
rename to toolkit/components/extensions/test/xpcshell/test_ext_background_sub_windows.js
--- a/toolkit/components/extensions/test/mochitest/test_ext_background_sub_windows.html
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_background_sub_windows.js
@@ -1,26 +1,15 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Test for sub-frames of WebExtension background pages</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-
-<script type="text/javascript">
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 add_task(function* testBackgroundWindow() {
   let extension = ExtensionTestUtils.loadExtension({
-    background: "new " + function() {
+    background() {
       browser.test.log("background script executed");
 
       browser.test.sendMessage("background-script-load");
 
       let img = document.createElement("img");
       img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
       document.body.appendChild(img);
 
@@ -36,30 +25,21 @@ add_task(function* testBackgroundWindow(
             browser.test.notifyPass("background sub-window test done");
           }, 0);
         };
         document.body.appendChild(iframe);
       };
     },
   });
 
-  info("extension loaded");
-
   let loadCount = 0;
   extension.onMessage("background-script-load", () => {
     loadCount++;
   });
 
   yield extension.startup();
 
-  info("startup complete loaded");
-
   yield extension.awaitFinish("background sub-window test done");
 
-  is(loadCount, 1, "background script loaded only once");
+  equal(loadCount, 1, "background script loaded only once");
 
   yield extension.unload();
 });
-
-</script>
-
-</body>
-</html>
rename from toolkit/components/extensions/test/mochitest/test_ext_background_window_properties.html
rename to toolkit/components/extensions/test/xpcshell/test_ext_background_window_properties.js
--- a/toolkit/components/extensions/test/mochitest/test_ext_background_window_properties.html
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_background_window_properties.js
@@ -1,26 +1,15 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Test for simple WebExtension</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-
-<script type="text/javascript">
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 add_task(function* testBackgroundWindowProperties() {
   let extension = ExtensionTestUtils.loadExtension({
-    background: "(" + function() {
+    background() {
       let expectedValues = {
         screenX: 0,
         screenY: 0,
         outerWidth: 0,
         outerHeight: 0,
       };
 
       for (let k in window) {
@@ -32,22 +21,14 @@ add_task(function* testBackgroundWindowP
             void window[k];
           }
         } catch (e) {
           browser.test.assertEq(null, e, `unexpected exception accessing window property: ${k}`);
         }
       }
 
       browser.test.notifyPass("background.testWindowProperties.done");
-    } + ")();",
+    },
   });
-  info("load complete");
   yield extension.startup();
-  info("startup complete");
   yield extension.awaitFinish("background.testWindowProperties.done");
   yield extension.unload();
-  info("extension unloaded successfully");
 });
-
-</script>
-
-</body>
-</html>
rename from toolkit/components/extensions/test/mochitest/test_ext_downloads.html
rename to toolkit/components/extensions/test/xpcshell/test_ext_downloads.js
--- a/toolkit/components/extensions/test/mochitest/test_ext_downloads.html
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_downloads.js
@@ -1,100 +1,79 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>WebExtension test</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-
-<script type="text/javascript">
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 add_task(function* test_downloads_api_namespace_and_permissions() {
   function backgroundScript() {
-    browser.test.assertTrue(!!chrome.downloads, "`downloads` API is present.");
-    browser.test.assertTrue(!!chrome.downloads.FilenameConflictAction,
+    browser.test.assertTrue(!!browser.downloads, "`downloads` API is present.");
+    browser.test.assertTrue(!!browser.downloads.FilenameConflictAction,
                             "`downloads.FilenameConflictAction` enum is present.");
-    browser.test.assertTrue(!!chrome.downloads.InterruptReason,
+    browser.test.assertTrue(!!browser.downloads.InterruptReason,
                             "`downloads.InterruptReason` enum is present.");
-    browser.test.assertTrue(!!chrome.downloads.DangerType,
+    browser.test.assertTrue(!!browser.downloads.DangerType,
                             "`downloads.DangerType` enum is present.");
-    browser.test.assertTrue(!!chrome.downloads.State,
+    browser.test.assertTrue(!!browser.downloads.State,
                             "`downloads.State` enum is present.");
     browser.test.notifyPass("downloads tests");
   }
 
   let extensionData = {
-    background: "(" + backgroundScript.toString() + ")()",
+    background: backgroundScript,
     manifest: {
       permissions: ["downloads", "downloads.open", "downloads.shelf"],
     },
   };
 
   let extension = ExtensionTestUtils.loadExtension(extensionData);
   yield extension.startup();
-  info("extension loaded");
   yield extension.awaitFinish("downloads tests");
   yield extension.unload();
-  info("extension unloaded");
 });
 
 add_task(function* test_downloads_open_permission() {
   function backgroundScript() {
     browser.test.assertFalse("open" in browser.downloads,
                              "`downloads.open` permission is required.");
     browser.test.notifyPass("downloads tests");
   }
 
   let extensionData = {
-    background: "(" + backgroundScript.toString() + ")()",
+    background: backgroundScript,
     manifest: {
       permissions: ["downloads"],
     },
   };
 
   let extension = ExtensionTestUtils.loadExtension(extensionData);
   yield extension.startup();
-  info("extension loaded");
   yield extension.awaitFinish("downloads tests");
   yield extension.unload();
-  info("extension unloaded");
 });
 
 add_task(function* test_downloads_open() {
   function backgroundScript() {
-    browser.downloads.open(10, () => {
-      let error = chrome.runtime.lastError;
-      browser.test.assertTrue(error, "An error exists.");
-      browser.test.assertTrue(
-        error.message === "Invalid download id 10",
-        `The error is informative. (${error.message})`);
+    browser.downloads.open(10).then(() => {
+      browser.test.fail("Expected an error");
+      browser.test.notifyFail("downloads tests");
+    }, error => {
+      browser.test.assertEq(error.message, "Invalid download id 10",
+                            "The error is informative.");
+
       browser.test.notifyPass("downloads tests");
     });
 
     // TODO: Once downloads.{pause,cancel,resume} lands (bug 1245602) test that this gives a good
     // error when called with an incompleted download.
   }
 
   let extensionData = {
-    background: "(" + backgroundScript.toString() + ")()",
+    background: backgroundScript,
     manifest: {
       permissions: ["downloads", "downloads.open"],
     },
   };
 
   let extension = ExtensionTestUtils.loadExtension(extensionData);
   yield extension.startup();
-  info("extension loaded");
   yield extension.awaitFinish("downloads tests");
   yield extension.unload();
-  info("extension unloaded");
 });
-
-</script>
-
-</body>
-</html>
rename from toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_download.html
rename to toolkit/components/extensions/test/xpcshell/test_ext_downloads_download.js
--- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_download.html
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_downloads_download.js
@@ -1,52 +1,50 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>WebExtension test</title>
-  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
-  <script src="chrome://mochikit/content/tests/SimpleTest/ExtensionTestUtils.js"></script>
-  <script type="text/javascript" src="chrome_head.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" href="chrome://mochikit/contents/tests/SimpleTest/test.css"/>
-</head>
-<body>
-
-<script type="text/javascript">
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 /* global OS */
 
 Cu.import("resource://gre/modules/osfile.jsm");
-Cu.import("resource://gre/modules/AppConstants.jsm");
-Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/Downloads.jsm");
 
-const WINDOWS = (AppConstants.platform == "win");
+const server = createHttpServer();
+server.registerDirectory("/data/", do_get_file("data"));
 
-const BASE = "http://mochi.test:8888/chrome/toolkit/components/extensions/test/mochitest";
+const WINDOWS = AppConstants.platform == "win";
+
+const BASE = `http://localhost:${server.identity.primaryPort}/data`;
 const FILE_NAME = "file_download.txt";
 const FILE_URL = BASE + "/" + FILE_NAME;
 const FILE_NAME_UNIQUE = "file_download(1).txt";
 const FILE_LEN = 46;
 
 let downloadDir;
 
 function setup() {
   downloadDir = FileUtils.getDir("TmpD", ["downloads"]);
   downloadDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
-  info(`Using download directory ${downloadDir.path}`);
+  do_print(`Using download directory ${downloadDir.path}`);
 
   Services.prefs.setIntPref("browser.download.folderList", 2);
   Services.prefs.setComplexValue("browser.download.dir", Ci.nsIFile, downloadDir);
 
-  SimpleTest.registerCleanupFunction(() => {
+  do_register_cleanup(() => {
     Services.prefs.clearUserPref("browser.download.folderList");
     Services.prefs.clearUserPref("browser.download.dir");
+
+    let entries = downloadDir.directoryEntries;
+    while (entries.hasMoreElements()) {
+      let entry = entries.getNext().QueryInterface(Ci.nsIFile);
+      ok(false, `Leftover file ${entry.path} in download directory`);
+      entry.remove(false);
+    }
+
+    downloadDir.remove(false);
   });
 }
 
 function backgroundScript() {
   let blobUrl;
   browser.test.onMessage.addListener((msg, ...args) => {
     if (msg == "download.request") {
       let options = args[0];
@@ -114,29 +112,29 @@ add_task(function* test_downloads() {
 
   function download(options) {
     extension.sendMessage("download.request", options);
     return extension.awaitMessage("download.done");
   }
 
   function testDownload(options, localFile, expectedSize, description) {
     return download(options).then(msg => {
-      is(msg.status, "success", `downloads.download() works with ${description}`);
+      equal(msg.status, "success", `downloads.download() works with ${description}`);
       return waitForDownloads();
     }).then(() => {
       let localPath = downloadDir.clone();
       localPath.append(localFile);
-      is(localPath.fileSize, expectedSize, "Downloaded file has expected size");
+      equal(localPath.fileSize, expectedSize, "Downloaded file has expected size");
       localPath.remove(false);
     });
   }
 
   yield extension.startup();
   yield extension.awaitMessage("ready");
-  info("extension started");
+  do_print("extension started");
 
   // Call download() with just the url property.
   yield testDownload({url: FILE_URL}, FILE_NAME, FILE_LEN, "just source");
 
   // Call download() with a filename property.
   yield testDownload({
     url: FILE_URL,
     filename: "newpath.txt",
@@ -155,65 +153,65 @@ add_task(function* test_downloads() {
   touch(FILE_NAME);
   yield testDownload({
     url: FILE_URL,
     conflictAction: "overwrite",
   }, FILE_NAME, FILE_LEN, "conflictAction=overwrite");
 
   // Try to download in invalid url
   yield download({url: "this is not a valid URL"}).then(msg => {
-    is(msg.status, "error", "downloads.download() fails with invalid url");
+    equal(msg.status, "error", "downloads.download() fails with invalid url");
     ok(/not a valid URL/.test(msg.errmsg), "error message for invalid url is correct");
   });
 
   // Try to download to an empty path.
   yield download({
     url: FILE_URL,
     filename: "",
   }).then(msg => {
-    is(msg.status, "error", "downloads.download() fails with empty filename");
-    is(msg.errmsg, "filename must not be empty", "error message for empty filename is correct");
+    equal(msg.status, "error", "downloads.download() fails with empty filename");
+    equal(msg.errmsg, "filename must not be empty", "error message for empty filename is correct");
   });
 
   // Try to download to an absolute path.
   const absolutePath = OS.Path.join(WINDOWS ? "\\tmp" : "/tmp", "file_download.txt");
   yield download({
     url: FILE_URL,
     filename: absolutePath,
   }).then(msg => {
-    is(msg.status, "error", "downloads.download() fails with absolute filename");
-    is(msg.errmsg, "filename must not be an absolute path", `error message for absolute path (${absolutePath}) is correct`);
+    equal(msg.status, "error", "downloads.download() fails with absolute filename");
+    equal(msg.errmsg, "filename must not be an absolute path", `error message for absolute path (${absolutePath}) is correct`);
   });
 
   if (WINDOWS) {
     yield download({
       url: FILE_URL,
       filename: "C:\\file_download.txt",
     }).then(msg => {
-      is(msg.status, "error", "downloads.download() fails with absolute filename");
-      is(msg.errmsg, "filename must not be an absolute path", "error message for absolute path with drive letter is correct");
+      equal(msg.status, "error", "downloads.download() fails with absolute filename");
+      equal(msg.errmsg, "filename must not be an absolute path", "error message for absolute path with drive letter is correct");
     });
   }
 
   // Try to download to a relative path containing ..
   yield download({
     url: FILE_URL,
     filename: OS.Path.join("..", "file_download.txt"),
   }).then(msg => {
-    is(msg.status, "error", "downloads.download() fails with back-references");
-    is(msg.errmsg, "filename must not contain back-references (..)", "error message for back-references is correct");
+    equal(msg.status, "error", "downloads.download() fails with back-references");
+    equal(msg.errmsg, "filename must not contain back-references (..)", "error message for back-references is correct");
   });
 
   // Try to download to a long relative path containing ..
   yield download({
     url: FILE_URL,
     filename: OS.Path.join("foo", "..", "..", "file_download.txt"),
   }).then(msg => {
-    is(msg.status, "error", "downloads.download() fails with back-references");
-    is(msg.errmsg, "filename must not contain back-references (..)", "error message for back-references is correct");
+    equal(msg.status, "error", "downloads.download() fails with back-references");
+    equal(msg.errmsg, "filename must not contain back-references (..)", "error message for back-references is correct");
   });
 
   // Try to download a blob url
   const BLOB_STRING = "Hello, world";
   yield testDownload({
     blobme: [BLOB_STRING],
     filename: FILE_NAME,
   }, FILE_NAME, BLOB_STRING.length, "blob url");
@@ -222,25 +220,8 @@ add_task(function* test_downloads() {
   // Try to download a blob url without a given filename
   yield testDownload({
     blobme: [BLOB_STRING],
   }, "download", BLOB_STRING.length, "blob url with no filename");
   extension.sendMessage("killTheBlob");
 
   yield extension.unload();
 });
-
-// check for leftover files in the download directory
-add_task(function* () {
-  let entries = downloadDir.directoryEntries;
-  while (entries.hasMoreElements()) {
-    let entry = entries.getNext().QueryInterface(Ci.nsIFile);
-    ok(false, `Leftover file ${entry.path} in download directory`);
-    entry.remove(false);
-  }
-
-  downloadDir.remove(false);
-});
-
-</script>
-
-</body>
-</html>
rename from toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_misc.html
rename to toolkit/components/extensions/test/xpcshell/test_ext_downloads_misc.js
--- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_misc.html
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_downloads_misc.js
@@ -1,39 +1,79 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>WebExtension test</title>
-  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
-  <script src="chrome://mochikit/content/tests/SimpleTest/ExtensionTestUtils.js"></script>
-  <script type="text/javascript" src="chrome_head.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" href="chrome://mochikit/contents/tests/SimpleTest/test.css"/>
-</head>
-<body>
-
-<script type="text/javascript">
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/Downloads.jsm");
 
-const BASE = "http://mochi.test:8888/chrome/toolkit/components/extensions/test/mochitest";
+const server = createHttpServer();
+server.registerDirectory("/data/", do_get_file("data"));
+
+const ROOT = `http://localhost:${server.identity.primaryPort}`;
+const BASE = `${ROOT}/data`;
 const TXT_FILE = "file_download.txt";
 const TXT_URL = BASE + "/" + TXT_FILE;
 
 // Keep these in sync with code in interruptible.sjs
 const INT_PARTIAL_LEN = 15;
 const INT_TOTAL_LEN = 31;
 
+const TEST_DATA = "This is 31 bytes of sample data";
+const TOTAL_LEN = TEST_DATA.length;
+const PARTIAL_LEN = 15;
+
+// A handler to let us systematically test pausing/resuming/canceling
+// of downloads.  This target represents a small text file but a simple
+// GET will stall after sending part of the data, to give the test code
+// a chance to pause or do other operations on an in-progress download.
+// A resumed download (ie, a GET with a Range: header) will allow the
+// download to complete.
+function handleRequest(request, response) {
+  response.setHeader("Content-Type", "text/plain", false);
+
+  if (request.hasHeader("Range")) {
+    let start, end;
+    let matches = request.getHeader("Range")
+        .match(/^\s*bytes=(\d+)?-(\d+)?\s*$/);
+    if (matches != null) {
+      start = matches[1] ? parseInt(matches[1], 10) : 0;
+      end = matches[2] ? parseInt(matches[2], 10) : (TOTAL_LEN - 1);
+    }
+
+    if (end == undefined || end >= TOTAL_LEN) {
+      response.setStatusLine(request.httpVersion, 416, "Requested Range Not Satisfiable");
+      response.setHeader("Content-Range", `*/${TOTAL_LEN}`, false);
+      response.finish();
+      return;
+    }
+
+    response.setStatusLine(request.httpVersion, 206, "Partial Content");
+    response.setHeader("Content-Range", `${start}-${end}/${TOTAL_LEN}`, false);
+    response.write(TEST_DATA.slice(start, end + 1));
+  } else {
+    response.processAsync();
+    response.setHeader("Content-Length", `${TOTAL_LEN}`, false);
+    response.write(TEST_DATA.slice(0, PARTIAL_LEN));
+  }
+
+  do_register_cleanup(() => {
+    try {
+      response.finish();
+    } catch (e) {
+      // This will throw, but we don't care at this point.
+    }
+  });
+}
+
+server.registerPathHandler("/interruptible.html", handleRequest);
+
 let interruptibleCount = 0;
 function getInterruptibleUrl() {
   let n = interruptibleCount++;
-  return `${BASE}/interruptible.sjs?count=${n}`;
+  return `${ROOT}/interruptible.html?count=${n}`;
 }
 
 function backgroundScript() {
   let events = new Set();
   let eventWaiter = null;
 
   browser.downloads.onCreated.addListener(data => {
     events.add({type: "onCreated", data});
@@ -192,83 +232,84 @@ function waitForProgress(url, bytes) {
                     list.addView(view);
                   }));
 }
 
 add_task(function* setup() {
   const nsIFile = Ci.nsIFile;
   downloadDir = FileUtils.getDir("TmpD", ["downloads"]);
   downloadDir.createUnique(nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
-  info(`downloadDir ${downloadDir.path}`);
+  do_print(`downloadDir ${downloadDir.path}`);
 
   Services.prefs.setIntPref("browser.download.folderList", 2);
   Services.prefs.setComplexValue("browser.download.dir", nsIFile, downloadDir);
 
-  SimpleTest.registerCleanupFunction(() => {
+  do_register_cleanup(() => {
     Services.prefs.clearUserPref("browser.download.folderList");
     Services.prefs.clearUserPref("browser.download.dir");
     downloadDir.remove(true);
+
     return clearDownloads();
   });
 
   yield clearDownloads().then(downloads => {
-    info(`removed ${downloads.length} pre-existing downloads from history`);
+    do_print(`removed ${downloads.length} pre-existing downloads from history`);
   });
 
   extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
+    background: backgroundScript,
     manifest: {
       permissions: ["downloads"],
     },
   });
 
   yield extension.startup();
   yield extension.awaitMessage("ready");
-  info("extension started");
 });
 
 add_task(function* test_events() {
   let msg = yield runInExtension("download", {url: TXT_URL});
-  is(msg.status, "success", "download() succeeded");
+  equal(msg.status, "success", "download() succeeded");
   const id = msg.result;
 
   msg = yield runInExtension("waitForEvents", [
     {type: "onCreated", data: {id, url: TXT_URL}},
     {
       type: "onChanged",
       data: {
         id,
         state: {
           previous: "in_progress",
           current: "complete",
         },
       },
     },
   ]);
-  is(msg.status, "success", "got onCreated and onChanged events");
+  equal(msg.status, "success", "got onCreated and onChanged events");
 });
 
 add_task(function* test_cancel() {
   let url = getInterruptibleUrl();
+  do_print(url);
   let msg = yield runInExtension("download", {url});
-  is(msg.status, "success", "download() succeeded");
+  equal(msg.status, "success", "download() succeeded");
   const id = msg.result;
 
   let progressPromise = waitForProgress(url, INT_PARTIAL_LEN);
 
   msg = yield runInExtension("waitForEvents", [
     {type: "onCreated", data: {id}},
   ]);
-  is(msg.status, "success", "got created and changed events");
+  equal(msg.status, "success", "got created and changed events");
 
   yield progressPromise;
-  info(`download reached ${INT_PARTIAL_LEN} bytes`);
+  do_print(`download reached ${INT_PARTIAL_LEN} bytes`);
 
   msg = yield runInExtension("cancel", id);
-  is(msg.status, "success", "cancel() succeeded");
+  equal(msg.status, "success", "cancel() succeeded");
 
   // This sequence of events is bogus (bug 1256243)
   msg = yield runInExtension("waitForEvents", [
     {
       type: "onChanged",
       data: {
         state: {
           previous: "in_progress",
@@ -294,54 +335,54 @@ add_task(function* test_cancel() {
         id,
         paused: {
           previous: true,
           current: false,
         },
       },
     },
   ]);
-  is(msg.status, "success", "got onChanged events corresponding to cancel()");
+  equal(msg.status, "success", "got onChanged events corresponding to cancel()");
 
   msg = yield runInExtension("search", {error: "USER_CANCELED"});
-  is(msg.status, "success", "search() succeeded");
-  is(msg.result.length, 1, "search() found 1 download");
-  is(msg.result[0].id, id, "download.id is correct");
-  is(msg.result[0].state, "interrupted", "download.state is correct");
-  is(msg.result[0].paused, false, "download.paused is correct");
-  is(msg.result[0].canResume, false, "download.canResume is correct");
-  is(msg.result[0].error, "USER_CANCELED", "download.error is correct");
-  is(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct");
-  is(msg.result[0].exists, false, "download.exists is correct");
+  equal(msg.status, "success", "search() succeeded");
+  equal(msg.result.length, 1, "search() found 1 download");
+  equal(msg.result[0].id, id, "download.id is correct");
+  equal(msg.result[0].state, "interrupted", "download.state is correct");
+  equal(msg.result[0].paused, false, "download.paused is correct");
+  equal(msg.result[0].canResume, false, "download.canResume is correct");
+  equal(msg.result[0].error, "USER_CANCELED", "download.error is correct");
+  equal(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct");
+  equal(msg.result[0].exists, false, "download.exists is correct");
 
   msg = yield runInExtension("pause", id);
-  is(msg.status, "error", "cannot pause a canceled download");
+  equal(msg.status, "error", "cannot pause a canceled download");
 
   msg = yield runInExtension("resume", id);
-  is(msg.status, "error", "cannot resume a canceled download");
+  equal(msg.status, "error", "cannot resume a canceled download");
 });
 
 add_task(function* test_pauseresume() {
   let url = getInterruptibleUrl();
   let msg = yield runInExtension("download", {url});
-  is(msg.status, "success", "download() succeeded");
+  equal(msg.status, "success", "download() succeeded");
   const id = msg.result;
 
   let progressPromise = waitForProgress(url, INT_PARTIAL_LEN);
 
   msg = yield runInExtension("waitForEvents", [
     {type: "onCreated", data: {id}},
   ]);
-  is(msg.status, "success", "got created and changed events");
+  equal(msg.status, "success", "got created and changed events");
 
   yield progressPromise;
-  info(`download reached ${INT_PARTIAL_LEN} bytes`);
+  do_print(`download reached ${INT_PARTIAL_LEN} bytes`);
 
   msg = yield runInExtension("pause", id);
-  is(msg.status, "success", "pause() succeeded");
+  equal(msg.status, "success", "pause() succeeded");
 
   msg = yield runInExtension("waitForEvents", [
     {
       type: "onChanged",
       data: {
         id,
         state: {
           previous: "in_progress",
@@ -361,40 +402,40 @@ add_task(function* test_pauseresume() {
       data: {
         id,
         error: {
           previous: null,
           current: "USER_CANCELED",
         },
       },
     }]);
-  is(msg.status, "success", "got onChanged event corresponding to pause");
+  equal(msg.status, "success", "got onChanged event corresponding to pause");
 
   msg = yield runInExtension("search", {paused: true});
-  is(msg.status, "success", "search() succeeded");
-  is(msg.result.length, 1, "search() found 1 download");
-  is(msg.result[0].id, id, "download.id is correct");
-  is(msg.result[0].state, "interrupted", "download.state is correct");
-  is(msg.result[0].paused, true, "download.paused is correct");
-  is(msg.result[0].canResume, true, "download.canResume is correct");
-  is(msg.result[0].error, "USER_CANCELED", "download.error is correct");
-  is(msg.result[0].bytesReceived, INT_PARTIAL_LEN, "download.bytesReceived is correct");
-  is(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct");
-  is(msg.result[0].exists, false, "download.exists is correct");
+  equal(msg.status, "success", "search() succeeded");
+  equal(msg.result.length, 1, "search() found 1 download");
+  equal(msg.result[0].id, id, "download.id is correct");
+  equal(msg.result[0].state, "interrupted", "download.state is correct");
+  equal(msg.result[0].paused, true, "download.paused is correct");
+  equal(msg.result[0].canResume, true, "download.canResume is correct");
+  equal(msg.result[0].error, "USER_CANCELED", "download.error is correct");
+  equal(msg.result[0].bytesReceived, INT_PARTIAL_LEN, "download.bytesReceived is correct");
+  equal(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct");
+  equal(msg.result[0].exists, false, "download.exists is correct");
 
   msg = yield runInExtension("search", {error: "USER_CANCELED"});
-  is(msg.status, "success", "search() succeeded");
+  equal(msg.status, "success", "search() succeeded");
   let found = msg.result.filter(item => item.id == id);
-  is(found.length, 1, "search() by error found the paused download");
+  equal(found.length, 1, "search() by error found the paused download");
 
   msg = yield runInExtension("pause", id);
-  is(msg.status, "error", "cannot pause an already paused download");
+  equal(msg.status, "error", "cannot pause an already paused download");
 
   msg = yield runInExtension("resume", id);
-  is(msg.status, "success", "resume() succeeded");
+  equal(msg.status, "success", "resume() succeeded");
 
   msg = yield runInExtension("waitForEvents", [
     {
       type: "onChanged",
       data: {
         id,
         state: {
           previous: "interrupted",
@@ -420,54 +461,54 @@ add_task(function* test_pauseresume() {
         id,
         state: {
           previous: "in_progress",
           current: "complete",
         },
       },
     },
   ]);
-  is(msg.status, "success", "got onChanged events for resume and complete");
+  equal(msg.status, "success", "got onChanged events for resume and complete");
 
   msg = yield runInExtension("search", {id});
-  is(msg.status, "success", "search() succeeded");
-  is(msg.result.length, 1, "search() found 1 download");
-  is(msg.result[0].state, "complete", "download.state is correct");
-  is(msg.result[0].paused, false, "download.paused is correct");
-  is(msg.result[0].canResume, false, "download.canResume is correct");
-  is(msg.result[0].error, null, "download.error is correct");
-  is(msg.result[0].bytesReceived, INT_TOTAL_LEN, "download.bytesReceived is correct");
-  is(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct");
-  is(msg.result[0].exists, true, "download.exists is correct");
+  equal(msg.status, "success", "search() succeeded");
+  equal(msg.result.length, 1, "search() found 1 download");
+  equal(msg.result[0].state, "complete", "download.state is correct");
+  equal(msg.result[0].paused, false, "download.paused is correct");
+  equal(msg.result[0].canResume, false, "download.canResume is correct");
+  equal(msg.result[0].error, null, "download.error is correct");
+  equal(msg.result[0].bytesReceived, INT_TOTAL_LEN, "download.bytesReceived is correct");
+  equal(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct");
+  equal(msg.result[0].exists, true, "download.exists is correct");
 
   msg = yield runInExtension("pause", id);
-  is(msg.status, "error", "cannot pause a completed download");
+  equal(msg.status, "error", "cannot pause a completed download");
 
   msg = yield runInExtension("resume", id);
-  is(msg.status, "error", "cannot resume a completed download");
+  equal(msg.status, "error", "cannot resume a completed download");
 });
 
 add_task(function* test_pausecancel() {
   let url = getInterruptibleUrl();
   let msg = yield runInExtension("download", {url});
-  is(msg.status, "success", "download() succeeded");
+  equal(msg.status, "success", "download() succeeded");
   const id = msg.result;
 
   let progressPromise = waitForProgress(url, INT_PARTIAL_LEN);
 
   msg = yield runInExtension("waitForEvents", [
     {type: "onCreated", data: {id}},
   ]);
-  is(msg.status, "success", "got created and changed events");
+  equal(msg.status, "success", "got created and changed events");
 
   yield progressPromise;
-  info(`download reached ${INT_PARTIAL_LEN} bytes`);
+  do_print(`download reached ${INT_PARTIAL_LEN} bytes`);
 
   msg = yield runInExtension("pause", id);
-  is(msg.status, "success", "pause() succeeded");
+  equal(msg.status, "success", "pause() succeeded");
 
   msg = yield runInExtension("waitForEvents", [
     {
       type: "onChanged",
       data: {
         id,
         state: {
           previous: "in_progress",
@@ -487,37 +528,37 @@ add_task(function* test_pausecancel() {
       data: {
         id,
         error: {
           previous: null,
           current: "USER_CANCELED",
         },
       },
     }]);
-  is(msg.status, "success", "got onChanged event corresponding to pause");
+  equal(msg.status, "success", "got onChanged event corresponding to pause");
 
   msg = yield runInExtension("search", {paused: true});
-  is(msg.status, "success", "search() succeeded");
-  is(msg.result.length, 1, "search() found 1 download");
-  is(msg.result[0].id, id, "download.id is correct");
-  is(msg.result[0].state, "interrupted", "download.state is correct");
-  is(msg.result[0].paused, true, "download.paused is correct");
-  is(msg.result[0].canResume, true, "download.canResume is correct");
-  is(msg.result[0].error, "USER_CANCELED", "download.error is correct");
-  is(msg.result[0].bytesReceived, INT_PARTIAL_LEN, "download.bytesReceived is correct");
-  is(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct");
-  is(msg.result[0].exists, false, "download.exists is correct");
+  equal(msg.status, "success", "search() succeeded");
+  equal(msg.result.length, 1, "search() found 1 download");
+  equal(msg.result[0].id, id, "download.id is correct");
+  equal(msg.result[0].state, "interrupted", "download.state is correct");
+  equal(msg.result[0].paused, true, "download.paused is correct");
+  equal(msg.result[0].canResume, true, "download.canResume is correct");
+  equal(msg.result[0].error, "USER_CANCELED", "download.error is correct");
+  equal(msg.result[0].bytesReceived, INT_PARTIAL_LEN, "download.bytesReceived is correct");
+  equal(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct");
+  equal(msg.result[0].exists, false, "download.exists is correct");
 
   msg = yield runInExtension("search", {error: "USER_CANCELED"});
-  is(msg.status, "success", "search() succeeded");
+  equal(msg.status, "success", "search() succeeded");
   let found = msg.result.filter(item => item.id == id);
-  is(found.length, 1, "search() by error found the paused download");
+  equal(found.length, 1, "search() by error found the paused download");
 
   msg = yield runInExtension("cancel", id);
-  is(msg.status, "success", "cancel() succeeded");
+  equal(msg.status, "success", "cancel() succeeded");
 
   msg = yield runInExtension("waitForEvents", [
     {
       type: "onChanged",
       data: {
         id,
         paused: {
           previous: true,
@@ -525,96 +566,95 @@ add_task(function* test_pausecancel() {
         },
         canResume: {
           previous: true,
           current: false,
         },
       },
     },
   ]);
-  is(msg.status, "success", "got onChanged event for cancel");
+  equal(msg.status, "success", "got onChanged event for cancel");
 
   msg = yield runInExtension("search", {id});
-  is(msg.status, "success", "search() succeeded");
-  is(msg.result.length, 1, "search() found 1 download");
-  is(msg.result[0].state, "interrupted", "download.state is correct");
-  is(msg.result[0].paused, false, "download.paused is correct");
-  is(msg.result[0].canResume, false, "download.canResume is correct");
-  is(msg.result[0].error, "USER_CANCELED", "download.error is correct");
-  is(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct");
-  is(msg.result[0].exists, false, "download.exists is correct");
+  equal(msg.status, "success", "search() succeeded");
+  equal(msg.result.length, 1, "search() found 1 download");
+  equal(msg.result[0].state, "interrupted", "download.state is correct");
+  equal(msg.result[0].paused, false, "download.paused is correct");
+  equal(msg.result[0].canResume, false, "download.canResume is correct");
+  equal(msg.result[0].error, "USER_CANCELED", "download.error is correct");
+  equal(msg.result[0].totalBytes, INT_TOTAL_LEN, "download.totalBytes is correct");
+  equal(msg.result[0].exists, false, "download.exists is correct");
 });
 
 add_task(function* test_pause_resume_cancel_badargs() {
   let BAD_ID = 1000;
 
   let msg = yield runInExtension("pause", BAD_ID);
-  info(JSON.stringify(msg));
-  is(msg.status, "error", "pause() failed with a bad download id");
+  equal(msg.status, "error", "pause() failed with a bad download id");
   ok(/Invalid download id/.test(msg.errmsg), "error message is descriptive");
 
   msg = yield runInExtension("resume", BAD_ID);
-  is(msg.status, "error", "resume() failed with a bad download id");
+  equal(msg.status, "error", "resume() failed with a bad download id");
   ok(/Invalid download id/.test(msg.errmsg), "error message is descriptive");
 
   msg = yield runInExtension("cancel", BAD_ID);
-  is(msg.status, "error", "cancel() failed with a bad download id");
+  equal(msg.status, "error", "cancel() failed with a bad download id");
   ok(/Invalid download id/.test(msg.errmsg), "error message is descriptive");
 });
 
 add_task(function* test_file_removal() {
   let msg = yield runInExtension("download", {url: TXT_URL});
-  is(msg.status, "success", "download() succeeded");
+  equal(msg.status, "success", "download() succeeded");
   const id = msg.result;
 
   msg = yield runInExtension("waitForEvents", [
     {type: "onCreated", data: {id, url: TXT_URL}},
     {
       type: "onChanged",
       data: {
         id,
         state: {
           previous: "in_progress",
           current: "complete",
         },
       },
     },
   ]);
 
-  is(msg.status, "success", "got onCreated and onChanged events");
+  equal(msg.status, "success", "got onCreated and onChanged events");
 
   msg = yield runInExtension("removeFile", id);
-  is(msg.status, "success", "removeFile() succeeded");
+  equal(msg.status, "success", "removeFile() succeeded");
 
   msg = yield runInExtension("removeFile", id);
-  is(msg.status, "error", "removeFile() fails since the file was already removed.");
+  equal(msg.status, "error", "removeFile() fails since the file was already removed.");
   ok(/file doesn't exist/.test(msg.errmsg), "removeFile() failed on removed file.");
 
   msg = yield runInExtension("removeFile", 1000);
   ok(/Invalid download id/.test(msg.errmsg), "removeFile() failed due to non-existent id");
 });
 
 add_task(function* test_removal_of_incomplete_download() {
   let url = getInterruptibleUrl();
   let msg = yield runInExtension("download", {url});
-  is(msg.status, "success", "download() succeeded");
+  equal(msg.status, "success", "download() succeeded");
   const id = msg.result;
 
   let progressPromise = waitForProgress(url, INT_PARTIAL_LEN);
 
   msg = yield runInExtension("waitForEvents", [
     {type: "onCreated", data: {id}},
   ]);
-  is(msg.status, "success", "got created and changed events");
+  equal(msg.status, "success", "got created and changed events");
 
   yield progressPromise;
-  info(`download reached ${INT_PARTIAL_LEN} bytes`);
+  do_print(`download reached ${INT_PARTIAL_LEN} bytes`);
 
   msg = yield runInExtension("pause", id);
-  is(msg.status, "success", "pause() succeeded");
+  equal(msg.status, "success", "pause() succeeded");
 
   msg = yield runInExtension("waitForEvents", [
     {
       type: "onChanged",
       data: {
         id,
         state: {
           previous: "in_progress",
@@ -634,25 +674,25 @@ add_task(function* test_removal_of_incom
       data: {
         id,
         error: {
           previous: null,
           current: "USER_CANCELED",
         },
       },
     }]);
-  is(msg.status, "success", "got onChanged event corresponding to pause");
+  equal(msg.status, "success", "got onChanged event corresponding to pause");
 
   msg = yield runInExtension("removeFile", id);
-  is(msg.status, "error", "removeFile() on paused download failed");
+  equal(msg.status, "error", "removeFile() on paused download failed");
 
   ok(/Cannot remove incomplete download/.test(msg.errmsg), "removeFile() failed due to download being incomplete");
 
   msg = yield runInExtension("resume", id);
-  is(msg.status, "success", "resume() succeeded");
+  equal(msg.status, "success", "resume() succeeded");
 
   msg = yield runInExtension("waitForEvents", [
     {
       type: "onChanged",
       data: {
         id,
         state: {
           previous: "interrupted",
@@ -678,144 +718,145 @@ add_task(function* test_removal_of_incom
         id,
         state: {
           previous: "in_progress",
           current: "complete",
         },
       },
     },
   ]);
-  is(msg.status, "success", "got onChanged events for resume and complete");
+  equal(msg.status, "success", "got onChanged events for resume and complete");
 
   msg = yield runInExtension("removeFile", id);
-  is(msg.status, "success", "removeFile() succeeded following completion of resumed download.");
+  equal(msg.status, "success", "removeFile() succeeded following completion of resumed download.");
 });
 
 // Test erase().  We don't do elaborate testing of the query handling
 // since it uses the exact same engine as search() which is tested
 // more thoroughly in test_chrome_ext_downloads_search.html
 add_task(function* test_erase() {
   yield clearDownloads();
 
   yield runInExtension("clearEvents");
 
   function* download() {
     let msg = yield runInExtension("download", {url: TXT_URL});
-    is(msg.status, "success", "download succeeded");
+    equal(msg.status, "success", "download succeeded");
     let id = msg.result;
 
     msg = yield runInExtension("waitForEvents", [{
       type: "onChanged", data: {id, state: {current: "complete"}},
     }], {exact: false});
-    is(msg.status, "success", "download finished");
+    equal(msg.status, "success", "download finished");
 
     return id;
   }
 
   let ids = {};
   ids.dl1 = yield download();
   ids.dl2 = yield download();
   ids.dl3 = yield download();
 
   let msg = yield runInExtension("search", {});
-  is(msg.status, "success", "search succeded");
-  is(msg.result.length, 3, "search found 3 downloads");
+  equal(msg.status, "success", "search succeded");
+  equal(msg.result.length, 3, "search found 3 downloads");
 
   msg = yield runInExtension("clearEvents");
 
   msg = yield runInExtension("erase", {id: ids.dl1});
-  is(msg.status, "success", "erase by id succeeded");
+  equal(msg.status, "success", "erase by id succeeded");
 
   msg = yield runInExtension("waitForEvents", [
     {type: "onErased", data: ids.dl1},
   ]);
-  is(msg.status, "success", "received onErased event");
+  equal(msg.status, "success", "received onErased event");
 
   msg = yield runInExtension("search", {});
-  is(msg.status, "success", "search succeded");
-  is(msg.result.length, 2, "search found 2 downloads");
+  equal(msg.status, "success", "search succeded");
+  equal(msg.result.length, 2, "search found 2 downloads");
 
   msg = yield runInExtension("erase", {});
-  is(msg.status, "success", "erase everything succeeded");
+  equal(msg.status, "success", "erase everything succeeded");
 
   msg = yield runInExtension("waitForEvents", [
     {type: "onErased", data: ids.dl2},
     {type: "onErased", data: ids.dl3},
   ], {inorder: false});
-  is(msg.status, "success", "received 2 onErased events");
+  equal(msg.status, "success", "received 2 onErased events");
 
   msg = yield runInExtension("search", {});
-  is(msg.status, "success", "search succeded");
-  is(msg.result.length, 0, "search found 0 downloads");
+  equal(msg.status, "success", "search succeded");
+  equal(msg.result.length, 0, "search found 0 downloads");
 });
 
 function loadImage(img, data) {
   return new Promise((resolve) => {
-    let handle = () => {
-      img.removeEventListener("load", handle);
-      resolve();
-    };
-    img.addEventListener("load", handle);
     img.src = data;
+    img.onload = resolve;
   });
 }
 
 add_task(function* test_getFileIcon() {
-  let img = document.createElement("img");
+  let webNav = Services.appShell.createWindowlessBrowser(false);
+  let docShell = webNav.QueryInterface(Ci.nsIInterfaceRequestor)
+                       .getInterface(Ci.nsIDocShell);
+
+  let system = Services.scriptSecurityManager.getSystemPrincipal();
+  docShell.createAboutBlankContentViewer(system);
+
+  let img = webNav.document.createElement("img");
+
   let msg = yield runInExtension("download", {url: TXT_URL});
-  is(msg.status, "success", "download() succeeded");
+  equal(msg.status, "success", "download() succeeded");
   const id = msg.result;
 
   msg = yield runInExtension("getFileIcon", id);
-  is(msg.status, "success", "getFileIcon() succeeded");
+  equal(msg.status, "success", "getFileIcon() succeeded");
   yield loadImage(img, msg.result);
-  is(img.height, 32, "returns an icon with the right height");
-  is(img.width, 32, "returns an icon with the right width");
+  equal(img.height, 32, "returns an icon with the right height");
+  equal(img.width, 32, "returns an icon with the right width");
 
   msg = yield runInExtension("waitForEvents", [
     {type: "onCreated", data: {id, url: TXT_URL}},
     {type: "onChanged"},
   ]);
-  is(msg.status, "success", "got events");
+  equal(msg.status, "success", "got events");
 
   msg = yield runInExtension("getFileIcon", id);
-  is(msg.status, "success", "getFileIcon() succeeded");
+  equal(msg.status, "success", "getFileIcon() succeeded");
   yield loadImage(img, msg.result);
-  is(img.height, 32, "returns an icon with the right height after download");
-  is(img.width, 32, "returns an icon with the right width after download");
+  equal(img.height, 32, "returns an icon with the right height after download");
+  equal(img.width, 32, "returns an icon with the right width after download");
 
   msg = yield runInExtension("getFileIcon", id + 100);
-  is(msg.status, "error", "getFileIcon() failed");
+  equal(msg.status, "error", "getFileIcon() failed");
   ok(msg.errmsg.includes("Invalid download id"), "download id is invalid");
 
   msg = yield runInExtension("getFileIcon", id, {size: 127});
-  is(msg.status, "success", "getFileIcon() succeeded");
+  equal(msg.status, "success", "getFileIcon() succeeded");
   yield loadImage(img, msg.result);
-  is(img.height, 127, "returns an icon with the right custom height");
-  is(img.width, 127, "returns an icon with the right custom width");
+  equal(img.height, 127, "returns an icon with the right custom height");
+  equal(img.width, 127, "returns an icon with the right custom width");
 
   msg = yield runInExtension("getFileIcon", id, {size: 1});
-  is(msg.status, "success", "getFileIcon() succeeded");
+  equal(msg.status, "success", "getFileIcon() succeeded");
   yield loadImage(img, msg.result);
-  is(img.height, 1, "returns an icon with the right custom height");
-  is(img.width, 1, "returns an icon with the right custom width");
+  equal(img.height, 1, "returns an icon with the right custom height");
+  equal(img.width, 1, "returns an icon with the right custom width");
 
   msg = yield runInExtension("getFileIcon", id, {size: "foo"});
-  is(msg.status, "error", "getFileIcon() fails");
+  equal(msg.status, "error", "getFileIcon() fails");
   ok(msg.errmsg.includes("Error processing size"), "size is not a number");
 
   msg = yield runInExtension("getFileIcon", id, {size: 0});
-  is(msg.status, "error", "getFileIcon() fails");
+  equal(msg.status, "error", "getFileIcon() fails");
   ok(msg.errmsg.includes("Error processing size"), "size is too small");
 
   msg = yield runInExtension("getFileIcon", id, {size: 128});
-  is(msg.status, "error", "getFileIcon() fails");
+  equal(msg.status, "error", "getFileIcon() fails");
   ok(msg.errmsg.includes("Error processing size"), "size is too big");
+
+  webNav.close();
 });
 
 add_task(function* cleanup() {
   yield extension.unload();
 });
-
-</script>
-
-</body>
-</html>
rename from toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_search.html
rename to toolkit/components/extensions/test/xpcshell/test_ext_downloads_search.js
--- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_downloads_search.html
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_downloads_search.js
@@ -1,28 +1,18 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>WebExtension test</title>
-  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
-  <script src="chrome://mochikit/content/tests/SimpleTest/ExtensionTestUtils.js"></script>
-  <script type="text/javascript" src="chrome_head.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" href="chrome://mochikit/contents/tests/SimpleTest/test.css"/>
-</head>
-<body>
-
-<script type="text/javascript">
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/Downloads.jsm");
 
-const BASE = "http://mochi.test:8888/chrome/toolkit/components/extensions/test/mochitest";
+const server = createHttpServer();
+server.registerDirectory("/data/", do_get_file("data"));
+
+const BASE = `http://localhost:${server.identity.primaryPort}/data`;
 const TXT_FILE = "file_download.txt";
 const TXT_URL = BASE + "/" + TXT_FILE;
 const TXT_LEN = 46;
 const HTML_FILE = "file_download.html";
 const HTML_URL = BASE + "/" + HTML_FILE;
 const HTML_LEN = 117;
 const BIG_LEN = 1000;  // something bigger both TXT_LEN and HTML_LEN
 
@@ -86,104 +76,103 @@ function clearDownloads(callback) {
     });
   });
 }
 
 add_task(function* test_search() {
   const nsIFile = Ci.nsIFile;
   let downloadDir = FileUtils.getDir("TmpD", ["downloads"]);
   downloadDir.createUnique(nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
-  info(`downloadDir ${downloadDir.path}`);
+  do_print(`downloadDir ${downloadDir.path}`);
 
   function downloadPath(filename) {
     let path = downloadDir.clone();
     path.append(filename);
     return path.path;
   }
 
   Services.prefs.setIntPref("browser.download.folderList", 2);
   Services.prefs.setComplexValue("browser.download.dir", nsIFile, downloadDir);
 
-  SimpleTest.registerCleanupFunction(() => {
+  do_register_cleanup(() => {
     Services.prefs.clearUserPref("browser.download.folderList");
     Services.prefs.clearUserPref("browser.download.dir");
     downloadDir.remove(true);
     return clearDownloads();
   });
 
   yield clearDownloads().then(downloads => {
-    info(`removed ${downloads.length} pre-existing downloads from history`);
+    do_print(`removed ${downloads.length} pre-existing downloads from history`);
   });
 
   let extension = ExtensionTestUtils.loadExtension({
-    background: `(${backgroundScript})()`,
+    background: backgroundScript,
     manifest: {
       permissions: ["downloads"],
     },
   });
 
   function download(options) {
     extension.sendMessage("download.request", options);
     return extension.awaitMessage("download.done").then(result => {
       let promise;
       if (result.status == "success") {
-        info(`wait for onChanged event to indicate ${result.id} is complete`);
+        do_print(`wait for onChanged event to indicate ${result.id} is complete`);
         extension.sendMessage("waitForComplete.request", result.id);
         promise = extension.awaitMessage("waitForComplete.done");
       } else {
         promise = Promise.resolve();
       }
       return promise.then(() => result);
     });
   }
 
   function search(query) {
     extension.sendMessage("search.request", query);
     return extension.awaitMessage("search.done");
   }
 
   yield extension.startup();
   yield extension.awaitMessage("ready");
-  info("extension started");
 
   // Do some downloads...
   const time1 = new Date();
 
   let downloadIds = {};
   let msg = yield download({url: TXT_URL});
-  is(msg.status, "success", "download() succeeded");
+  equal(msg.status, "success", "download() succeeded");
   downloadIds.txt1 = msg.id;
 
   const TXT_FILE2 = "NewFile.txt";
   msg = yield download({url: TXT_URL, filename: TXT_FILE2});
-  is(msg.status, "success", "download() succeeded");
+  equal(msg.status, "success", "download() succeeded");
   downloadIds.txt2 = msg.id;
 
   const time2 = new Date();
 
   msg = yield download({url: HTML_URL});
-  is(msg.status, "success", "download() succeeded");
+  equal(msg.status, "success", "download() succeeded");
   downloadIds.html1 = msg.id;
 
   const HTML_FILE2 = "renamed.html";
   msg = yield download({url: HTML_URL, filename: HTML_FILE2});
-  is(msg.status, "success", "download() succeeded");
+  equal(msg.status, "success", "download() succeeded");
   downloadIds.html2 = msg.id;
 
   const time3 = new Date();
 
   // Search for each individual download and check
   // the corresponding DownloadItem.
   function* checkDownloadItem(id, expect) {
     let msg = yield search({id});
-    is(msg.status, "success", "search() succeeded");
-    is(msg.downloads.length, 1, "search() found exactly 1 download");
+    equal(msg.status, "success", "search() succeeded");
+    equal(msg.downloads.length, 1, "search() found exactly 1 download");
 
     Object.keys(expect).forEach(function(field) {
-      is(msg.downloads[0][field], expect[field], `DownloadItem.${field} is correct"`);
+      equal(msg.downloads[0][field], expect[field], `DownloadItem.${field} is correct"`);
     });
   }
   yield checkDownloadItem(downloadIds.txt1, {
     url: TXT_URL,
     filename: downloadPath(TXT_FILE),
     mime: "text/plain",
     state: "complete",
     bytesReceived: TXT_LEN,
@@ -222,29 +211,29 @@ add_task(function* test_search() {
     bytesReceived: HTML_LEN,
     totalBytes: HTML_LEN,
     fileSize: HTML_LEN,
     exists: true,
   });
 
   function* checkSearch(query, expected, description, exact) {
     let msg = yield search(query);
-    is(msg.status, "success", "search() succeeded");
-    is(msg.downloads.length, expected.length, `search() for ${description} found exactly ${expected.length} downloads`);
+    equal(msg.status, "success", "search() succeeded");
+    equal(msg.downloads.length, expected.length, `search() for ${description} found exactly ${expected.length} downloads`);
 
     let receivedIds = msg.downloads.map(item => item.id);
     if (exact) {
       receivedIds.forEach((id, idx) => {
-        is(id, downloadIds[expected[idx]], `search() for ${description} returned ${expected[idx]} in position ${idx}`);
+        equal(id, downloadIds[expected[idx]], `search() for ${description} returned ${expected[idx]} in position ${idx}`);
       });
     } else {
       Object.keys(downloadIds).forEach(key => {
         const id = downloadIds[key];
         const thisExpected = expected.includes(key);
-        is(receivedIds.includes(id), thisExpected,
+        equal(receivedIds.includes(id), thisExpected,
            `search() for ${description} ${thisExpected ? "includes" : "does not include"} ${key}`);
       });
     }
   }
 
   // Check that search with an invalid id returns nothing.
   // NB: for now ids are not persistent and we start numbering them at 1
   //     so a sufficiently large number will be unused.
@@ -394,17 +383,17 @@ add_task(function* test_search() {
   yield checkSearch({orderBy: ["url", "-startTime"]}, ["html2", "html1", "txt2", "txt1"], "orderBy with multiple fields", true);
 
   // Check orderBy with limit.
   yield checkSearch({orderBy: ["url"], limit: 1}, ["html1"], "orderBy with limit", true);
 
   // Check bad arguments.
   function* checkBadSearch(query, pattern, description) {
     let msg = yield search(query);
-    is(msg.status, "error", "search() failed");
+    equal(msg.status, "error", "search() failed");
     ok(pattern.test(msg.errmsg), `error message for ${description} was correct (${msg.errmsg}).`);
   }
 
   yield checkBadSearch("myquery", /Incorrect argument type/, "query is not an object");
   yield checkBadSearch({bogus: "boo"}, /Unexpected property/, "query contains an unknown field");
   yield checkBadSearch({query: "query string"}, /Expected array/, "query.query is a string");
   yield checkBadSearch({startedBefore: "i am not a time"}, /Type error/, "query.startedBefore is not a valid time");
   yield checkBadSearch({startedAfter: "i am not a time"}, /Type error/, "query.startedAfter is not a valid time");
@@ -412,13 +401,8 @@ add_task(function* test_search() {
   yield checkBadSearch({endedAfter: "i am not a time"}, /Type error/, "query.endedAfter is not a valid time");
   yield checkBadSearch({urlRegex: "["}, /Invalid urlRegex/, "query.urlRegexp is not a valid regular expression");
   yield checkBadSearch({filenameRegex: "["}, /Invalid filenameRegex/, "query.filenameRegexp is not a valid regular expression");
   yield checkBadSearch({orderBy: "startTime"}, /Expected array/, "query.orderBy is not an array");
   yield checkBadSearch({orderBy: ["bogus"]}, /Invalid orderBy field/, "query.orderBy references a non-existent field");
 
   yield extension.unload();
 });
-
-</script>
-
-</body>
-</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_extension.js
@@ -0,0 +1,55 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* test_is_allowed_incognito_access() {
+  function background() {
+    browser.extension.isAllowedIncognitoAccess().then(isAllowedIncognitoAccess => {
+      browser.test.assertEq(true, isAllowedIncognitoAccess, "isAllowedIncognitoAccess is true");
+      browser.test.notifyPass("isAllowedIncognitoAccess");
+    });
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background,
+    manifest: {},
+  });
+
+  yield extension.startup();
+  yield extension.awaitFinish("isAllowedIncognitoAccess");
+  yield extension.unload();
+});
+
+add_task(function* test_in_incognito_context_false() {
+  function background() {
+    browser.test.assertEq(false, browser.extension.inIncognitoContext, "inIncognitoContext returned false");
+    browser.test.notifyPass("inIncognitoContext");
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background,
+    manifest: {},
+  });
+
+  yield extension.startup();
+  yield extension.awaitFinish("inIncognitoContext");
+  yield extension.unload();
+});
+
+add_task(function* test_is_allowed_file_scheme_access() {
+  function background() {
+    browser.extension.isAllowedFileSchemeAccess().then(isAllowedFileSchemeAccess => {
+      browser.test.assertEq(false, isAllowedFileSchemeAccess, "isAllowedFileSchemeAccess is false");
+      browser.test.notifyPass("isAllowedFileSchemeAccess");
+    });
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background,
+    manifest: {},
+  });
+
+  yield extension.startup();
+  yield extension.awaitFinish("isAllowedFileSchemeAccess");
+  yield extension.unload();
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_idle.js
@@ -0,0 +1,30 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* testIdle() {
+  function background() {
+    browser.idle.queryState(15).then(status => {
+      browser.test.assertEq("active", status, "Expected status");
+      browser.test.notifyPass("idle");
+    },
+    e => {
+      browser.test.fail(`Error: ${e} :: ${e.stack}`);
+      browser.test.notifyFail("idle");
+    });
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background,
+
+    manifest: {
+      permissions: ["idle"],
+    },
+  });
+
+  yield extension.startup();
+
+  yield extension.awaitFinish("idle");
+
+  yield extension.unload();
+});
rename from toolkit/components/extensions/test/mochitest/test_ext_localStorage.html
rename to toolkit/components/extensions/test/xpcshell/test_ext_localStorage.js
--- a/toolkit/components/extensions/test/mochitest/test_ext_localStorage.html
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_localStorage.js
@@ -1,21 +1,10 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>WebExtension test</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-
-<script type="text/javascript">
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 function backgroundScript() {
   let hasRun = localStorage.getItem("has-run");
   let result;
   if (!hasRun) {
     localStorage.setItem("has-run", "yup");
     localStorage.setItem("test-item", "item1");
@@ -33,28 +22,28 @@ function backgroundScript() {
       result = "cleared";
     }
   }
   browser.test.sendMessage("result", result);
   browser.test.notifyPass("localStorage");
 }
 
 let extensionData = {
-  background: "(" + backgroundScript.toString() + ")()",
+  background: backgroundScript,
 };
 
-add_task(function* test_contentscript() {
+add_task(function* test_localStorage() {
   let id = "test-webextension@mozilla.com";
   const RESULTS = ["item1", "item2", "deleted", "cleared", "item1"];
 
   for (let expected of RESULTS) {
     let extension = ExtensionTestUtils.loadExtension(extensionData, id);
-    let [, actual] = yield Promise.all([extension.startup(), extension.awaitMessage("result")]);
+
+    yield extension.startup();
+
+    let actual = yield extension.awaitMessage("result");
+
     yield extension.awaitFinish("localStorage");
     yield extension.unload();
 
-    is(actual, expected, "got expected localStorage data");
+    equal(actual, expected, "got expected localStorage data");
   }
 });
-</script>
-
-</body>
-</html>
--- a/toolkit/components/extensions/test/xpcshell/test_ext_manifest_content_security_policy.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_manifest_content_security_policy.js
@@ -1,26 +1,26 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 
 add_task(function* test_manifest_csp() {
-  let normalized = yield normalizeManifest({
+  let normalized = yield ExtensionTestUtils.normalizeManifest({
     "content_security_policy": "script-src 'self'; object-src 'none'",
   });
 
   equal(normalized.error, undefined, "Should not have an error");
   equal(normalized.errors.length, 0, "Should not have warnings");
   equal(normalized.value.content_security_policy,
         "script-src 'self'; object-src 'none'",
         "Should have the expected poilcy string");
 
 
-  normalized = yield normalizeManifest({
+  normalized = yield ExtensionTestUtils.normalizeManifest({
     "content_security_policy": "object-src 'none'",
   });
 
   equal(normalized.error, undefined, "Should not have an error");
 
   Assert.deepEqual(normalized.errors,
                    ["Error processing content_security_policy: SyntaxError: Policy is missing a required \u2018script-src\u2019 directive"],
                    "Should have the expected warning");
--- a/toolkit/components/extensions/test/xpcshell/test_ext_manifest_incognito.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_manifest_incognito.js
@@ -1,25 +1,25 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 
 add_task(function* test_manifest_incognito() {
-  let normalized = yield normalizeManifest({
+  let normalized = yield ExtensionTestUtils.normalizeManifest({
     "incognito": "spanning",
   });
 
   equal(normalized.error, undefined, "Should not have an error");
   equal(normalized.errors.length, 0, "Should not have warnings");
   equal(normalized.value.incognito,
         "spanning",
         "Should have the expected incognito string");
 
-  normalized = yield normalizeManifest({
+  normalized = yield ExtensionTestUtils.normalizeManifest({
     "incognito": "split",
   });
 
   equal(normalized.error, undefined, "Should not have an error");
   Assert.deepEqual(normalized.errors,
                    ['Error processing incognito: Invalid enumeration value "split"'],
                    "Should have the expected warning");
   equal(normalized.value.incognito, null,
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_onmessage_removelistener.js
@@ -0,0 +1,30 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+function backgroundScript() {
+  function listener() {
+    browser.test.notifyFail("listener should not be invoked");
+  }
+
+  browser.runtime.onMessage.addListener(listener);
+  browser.runtime.onMessage.removeListener(listener);
+  browser.runtime.sendMessage("hello");
+
+  // Make sure that, if we somehow fail to remove the listener, then we'll run
+  // the listener before the test is marked as passing.
+  setTimeout(function() {
+    browser.test.notifyPass("onmessage_removelistener");
+  }, 0);
+}
+
+let extensionData = {
+  background: backgroundScript,
+};
+
+add_task(function* test_contentscript() {
+  let extension = ExtensionTestUtils.loadExtension(extensionData);
+  yield extension.startup();
+  yield extension.awaitFinish("onmessage_removelistener");
+  yield extension.unload();
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_getPlatformInfo.js
@@ -0,0 +1,25 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+function backgroundScript() {
+  browser.runtime.getPlatformInfo(info => {
+    let validOSs = ["mac", "win", "android", "cros", "linux", "openbsd", "gonk"];
+    let validArchs = ["arm", "x86-32", "x86-64"];
+
+    browser.test.assertTrue(validOSs.indexOf(info.os) != -1, "OS is valid");
+    browser.test.assertTrue(validArchs.indexOf(info.arch) != -1, "Architecture is valid");
+    browser.test.notifyPass("runtime.getPlatformInfo");
+  });
+}
+
+let extensionData = {
+  background: backgroundScript,
+};
+
+add_task(function* test_contentscript() {
+  let extension = ExtensionTestUtils.loadExtension(extensionData);
+  yield extension.startup();
+  yield extension.awaitFinish("runtime.getPlatformInfo");
+  yield extension.unload();
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage.js
@@ -0,0 +1,70 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* tabsSendMessageReply() {
+  function background() {
+    browser.runtime.onMessage.addListener((msg, sender, respond) => {
+      if (msg == "respond-now") {
+        respond(msg);
+      } else if (msg == "respond-soon") {
+        setTimeout(() => { respond(msg); }, 0);
+        return true;
+      } else if (msg == "respond-promise") {
+        return Promise.resolve(msg);
+      } else if (msg == "respond-never") {
+        return;
+      } else if (msg == "respond-error") {
+        return Promise.reject(new Error(msg));
+      } else if (msg == "throw-error") {
+        throw new Error(msg);
+      }
+    });
+
+    browser.runtime.onMessage.addListener((msg, sender, respond) => {
+      if (msg == "respond-now") {
+        respond("hello");
+      } else if (msg == "respond-now-2") {
+        respond(msg);
+      }
+    });
+
+    browser.runtime.sendMessage("respond-never", response => {
+      browser.test.fail(`Got unexpected response callback: ${response}`);
+      browser.test.notifyFail("sendMessage");
+    });
+
+    Promise.all([
+      browser.runtime.sendMessage("respond-now"),
+      browser.runtime.sendMessage("respond-now-2"),
+      new Promise(resolve => browser.runtime.sendMessage("respond-soon", resolve)),
+      browser.runtime.sendMessage("respond-promise"),
+      browser.runtime.sendMessage("respond-never"),
+
+      browser.runtime.sendMessage("respond-error").catch(error => Promise.resolve({error})),
+      browser.runtime.sendMessage("throw-error").catch(error => Promise.resolve({error})),
+    ]).then(([respondNow, respondNow2, respondSoon, respondPromise, respondNever, respondError, throwError]) => {
+      browser.test.assertEq("respond-now", respondNow, "Got the expected immediate response");
+      browser.test.assertEq("respond-now-2", respondNow2, "Got the expected immediate response from the second listener");
+      browser.test.assertEq("respond-soon", respondSoon, "Got the expected delayed response");
+      browser.test.assertEq("respond-promise", respondPromise, "Got the expected promise response");
+      browser.test.assertEq(undefined, respondNever, "Got the expected no-response resolution");
+
+      browser.test.assertEq("respond-error", respondError.error.message, "Got the expected error response");
+      browser.test.assertEq("throw-error", throwError.error.message, "Got the expected thrown error response");
+
+      browser.test.notifyPass("sendMessage");
+    }).catch(e => {
+      browser.test.fail(`Error: ${e} :: ${e.stack}`);
+      browser.test.notifyFail("sendMessage");
+    });
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background,
+  });
+
+  yield extension.startup();
+  yield extension.awaitFinish("sendMessage");
+  yield extension.unload();
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_simple.js
@@ -0,0 +1,52 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(function* test_simple() {
+  let extensionData = {
+    manifest: {
+      "name": "Simple extension test",
+      "version": "1.0",
+      "manifest_version": 2,
+      "description": "",
+    },
+  };
+
+  let extension = ExtensionTestUtils.loadExtension(extensionData);
+  yield extension.startup();
+  yield extension.unload();
+});
+
+add_task(function* test_background() {
+  function background() {
+    browser.test.log("running background script");
+
+    browser.test.onMessage.addListener((x, y) => {
+      browser.test.assertEq(x, 10, "x is 10");
+      browser.test.assertEq(y, 20, "y is 20");
+
+      browser.test.notifyPass("background test passed");
+    });
+
+    browser.test.sendMessage("running", 1);
+  }
+
+  let extensionData = {
+    background,
+    manifest: {
+      "name": "Simple extension test",
+      "version": "1.0",
+      "manifest_version": 2,
+      "description": "",
+    },
+  };
+
+  let extension = ExtensionTestUtils.loadExtension(extensionData);
+
+  let [, x] = yield Promise.all([extension.startup(), extension.awaitMessage("running")]);
+  equal(x, 1, "got correct value from extension");
+
+  extension.sendMessage(10, 20);
+  yield extension.awaitFinish();
+  yield extension.unload();
+});
rename from toolkit/components/extensions/test/mochitest/test_ext_storage.html
rename to toolkit/components/extensions/test/xpcshell/test_ext_storage.js
--- a/toolkit/components/extensions/test/mochitest/test_ext_storage.html
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_storage.js
@@ -1,21 +1,10 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>WebExtension test</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-
-<script type="text/javascript">
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 function backgroundScript() {
   let storage = browser.storage.local;
   function check(prop, value) {
     return storage.get(null).then(data => {
       browser.test.assertEq(value, data[prop], "null getter worked for " + prop);
       return storage.get(prop);
@@ -166,31 +155,28 @@ function backgroundScript() {
     browser.test.notifyPass("storage");
   }).catch(e => {
     browser.test.fail(`Error: ${e} :: ${e.stack}`);
     browser.test.notifyFail("storage");
   });
 }
 
 let extensionData = {
-  background: "(" + backgroundScript.toString() + ")()",
+  background: backgroundScript,
   manifest: {
     permissions: ["storage"],
   },
 };
 
 add_task(function* test_backgroundScript() {
   let extension = ExtensionTestUtils.loadExtension(extensionData);
 
   yield extension.startup();
 
   yield extension.awaitMessage("invalidate");
-  SpecialPowers.invalidateExtensionStorageCache();
+
+  Services.obs.notifyObservers(null, "extension-invalidate-storage-cache", "");
+
   extension.sendMessage("invalidated");
 
   yield extension.awaitFinish("storage");
   yield extension.unload();
 });
-
-</script>
-
-</body>
-</html>
--- a/toolkit/components/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell.ini
@@ -1,18 +1,45 @@
 [DEFAULT]
 head = head.js
 tail =
 firefox-appdir = browser
 skip-if = toolkit == 'gonk' || appname == "thunderbird"
+support-files =
+  data/**
 
 [test_csp_custom_policies.js]
 [test_csp_validator.js]
-[test_locale_data.js]
-[test_locale_converter.js]
+[test_ext_alarms.js]
+[test_ext_alarms_does_not_fire.js]
+[test_ext_alarms_periodic.js]
+[test_ext_alarms_replaces.js]
+[test_ext_background_generated_load_events.js]
+[test_ext_background_generated_reload.js]
+[test_ext_background_runtime_connect_params.js]
+[test_ext_background_sub_windows.js]
+[test_ext_background_window_properties.js]
+skip-if = os == "android"
 [test_ext_contexts.js]
+[test_ext_downloads.js]
+[test_ext_downloads_download.js]
+skip-if = os == "android"
+[test_ext_downloads_misc.js]
+skip-if = os == "android"
+[test_ext_downloads_search.js]
+skip-if = os == "android"
+[test_ext_extension.js]
+[test_ext_idle.js]
 [test_ext_json_parser.js]
+[test_ext_localStorage.js]
 [test_ext_manifest_content_security_policy.js]
 [test_ext_manifest_incognito.js]
+[test_ext_onmessage_removelistener.js]
+[test_ext_runtime_getPlatformInfo.js]
+[test_ext_runtime_sendMessage.js]
 [test_ext_schemas.js]
+[test_ext_simple.js]
+[test_ext_storage.js]
 [test_getAPILevelForWindow.js]
+[test_locale_converter.js]
+[test_locale_data.js]
 [test_native_messaging.js]
 skip-if = os == "android"
--- a/toolkit/components/places/SQLFunctions.cpp
+++ b/toolkit/components/places/SQLFunctions.cpp
@@ -171,16 +171,27 @@ namespace {
         sourceStart = sourceNext;
       }
 
     } while (sourceStart < sourceEnd);
 
     return false;
   }
 
+  static
+  MOZ_ALWAYS_INLINE nsDependentCString
+  getSharedString(mozIStorageValueArray* aValues, uint32_t aIndex) {
+    uint32_t len;
+    const char* str = aValues->AsSharedUTF8String(aIndex, &len);
+    if (!str) {
+      return nsDependentCString("", (uint32_t)0);
+    }
+    return nsDependentCString(str, len);
+  }
+
 } // End anonymous namespace
 
 namespace mozilla {
 namespace places {
 
 ////////////////////////////////////////////////////////////////////////////////
 //// AutoComplete Matching Function
 
@@ -195,46 +206,50 @@ namespace places {
       NS_LITERAL_CSTRING("autocomplete_match"), kArgIndexLength, function
     );
     NS_ENSURE_SUCCESS(rv, rv);
 
     return NS_OK;
   }
 
   /* static */
-  void
-  MatchAutoCompleteFunction::fixupURISpec(const nsCString &aURISpec,
+  nsDependentCSubstring
+  MatchAutoCompleteFunction::fixupURISpec(const nsACString &aURISpec,
                                           int32_t aMatchBehavior,
-                                          nsCString &_fixedSpec)
+                                          nsACString &aSpecBuf)
   {
-    nsCString unescapedSpec;
-    (void)NS_UnescapeURL(aURISpec, esc_SkipControl | esc_AlwaysCopy,
-                         unescapedSpec);
+    nsDependentCSubstring fixedSpec;
 
-    // If this unescaped string is valid UTF-8, we'll use it.  Otherwise,
-    // we will simply use our original string.
-    NS_ASSERTION(_fixedSpec.IsEmpty(),
-                 "Passing a non-empty string as an out parameter!");
-    if (IsUTF8(unescapedSpec))
-      _fixedSpec.Assign(unescapedSpec);
-    else
-      _fixedSpec.Assign(aURISpec);
+    // Try to unescape the string.  If that succeeds and yields a different
+    // string which is also valid UTF-8, we'll use it.
+    // Otherwise, we will simply use our original string.
+    bool unescaped = NS_UnescapeURL(aURISpec.BeginReading(),
+      aURISpec.Length(), esc_SkipControl, aSpecBuf);
+    if (unescaped && IsUTF8(aSpecBuf)) {
+      fixedSpec.Rebind(aSpecBuf, 0);
+    } else {
+      fixedSpec.Rebind(aURISpec, 0);
+    }
 
     if (aMatchBehavior == mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED)
-      return;
+      return fixedSpec;
 
-    if (StringBeginsWith(_fixedSpec, NS_LITERAL_CSTRING("http://")))
-      _fixedSpec.Cut(0, 7);
-    else if (StringBeginsWith(_fixedSpec, NS_LITERAL_CSTRING("https://")))
-      _fixedSpec.Cut(0, 8);
-    else if (StringBeginsWith(_fixedSpec, NS_LITERAL_CSTRING("ftp://")))
-      _fixedSpec.Cut(0, 6);
+    if (StringBeginsWith(fixedSpec, NS_LITERAL_CSTRING("http://"))) {
+      fixedSpec.Rebind(fixedSpec, 7);
+    } else if (StringBeginsWith(fixedSpec, NS_LITERAL_CSTRING("https://"))) {
+      fixedSpec.Rebind(fixedSpec, 8);
+    } else if (StringBeginsWith(fixedSpec, NS_LITERAL_CSTRING("ftp://"))) {
+      fixedSpec.Rebind(fixedSpec, 6);
+    }
 
-    if (StringBeginsWith(_fixedSpec, NS_LITERAL_CSTRING("www.")))
-      _fixedSpec.Cut(0, 4);
+    if (StringBeginsWith(fixedSpec, NS_LITERAL_CSTRING("www."))) {
+      fixedSpec.Rebind(fixedSpec, 4);
+    }
+
+    return fixedSpec;
   }
 
   /* static */
   bool
   MatchAutoCompleteFunction::findAnywhere(const nsDependentCSubstring &aToken,
                                           const nsACString &aSourceString)
   {
     // We can't use FindInReadable here; it works only for ASCII.
@@ -327,38 +342,37 @@ namespace places {
                                             nsIVariant **_result)
   {
     // Macro to make the code a bit cleaner and easier to read.  Operates on
     // searchBehavior.
     int32_t searchBehavior = aArguments->AsInt32(kArgIndexSearchBehavior);
     #define HAS_BEHAVIOR(aBitName) \
       (searchBehavior & mozIPlacesAutoComplete::BEHAVIOR_##aBitName)
 
-    nsAutoCString searchString;
-    (void)aArguments->GetUTF8String(kArgSearchString, searchString);
-    nsCString url;
-    (void)aArguments->GetUTF8String(kArgIndexURL, url);
+    nsDependentCString searchString =
+      getSharedString(aArguments, kArgSearchString);
+    nsDependentCString url =
+      getSharedString(aArguments, kArgIndexURL);
 
     int32_t matchBehavior = aArguments->AsInt32(kArgIndexMatchBehavior);
 
     // We only want to filter javascript: URLs if we are not supposed to search
     // for them, and the search does not start with "javascript:".
     if (matchBehavior != mozIPlacesAutoComplete::MATCH_ANYWHERE_UNMODIFIED &&
+        StringBeginsWith(url, NS_LITERAL_CSTRING("javascript:")) &&
         !HAS_BEHAVIOR(JAVASCRIPT) &&
-        !StringBeginsWith(searchString, NS_LITERAL_CSTRING("javascript:")) &&
-        StringBeginsWith(url, NS_LITERAL_CSTRING("javascript:"))) {
+        !StringBeginsWith(searchString, NS_LITERAL_CSTRING("javascript:"))) {
       NS_ADDREF(*_result = new IntegerVariant(0));
       return NS_OK;
     }
 
     int32_t visitCount = aArguments->AsInt32(kArgIndexVisitCount);
     bool typed = aArguments->AsInt32(kArgIndexTyped) ? true : false;
     bool bookmark = aArguments->AsInt32(kArgIndexBookmark) ? true : false;
-    nsAutoCString tags;
-    (void)aArguments->GetUTF8String(kArgIndexTags, tags);
+    nsDependentCString tags = getSharedString(aArguments, kArgIndexTags);
     int32_t openPageCount = aArguments->AsInt32(kArgIndexOpenPageCount);
     bool matches = false;
     if (HAS_BEHAVIOR(RESTRICT)) {
       // Make sure we match all the filter requirements.  If a given restriction
       // is active, make sure the corresponding condition is not true.
       matches = (!HAS_BEHAVIOR(HISTORY) || visitCount > 0) &&
                 (!HAS_BEHAVIOR(TYPED) || typed) &&
                 (!HAS_BEHAVIOR(BOOKMARK) || bookmark) &&
@@ -378,24 +392,24 @@ namespace places {
       NS_ADDREF(*_result = new IntegerVariant(0));
       return NS_OK;
     }
 
     // Obtain our search function.
     searchFunctionPtr searchFunction = getSearchFunction(matchBehavior);
 
     // Clean up our URI spec and prepare it for searching.
-    nsCString fixedUrl;
-    fixupURISpec(url, matchBehavior, fixedUrl);
+    nsCString fixedUrlBuf;
+    nsDependentCSubstring fixedUrl =
+      fixupURISpec(url, matchBehavior, fixedUrlBuf);
     // Limit the number of chars we search through.
     const nsDependentCSubstring& trimmedUrl =
       Substring(fixedUrl, 0, MAX_CHARS_TO_SEARCH_THROUGH);
 
-    nsAutoCString title;
-    (void)aArguments->GetUTF8String(kArgIndexTitle, title);
+    nsDependentCString title = getSharedString(aArguments, kArgIndexTitle);
     // Limit the number of chars we search through.
     const nsDependentCSubstring& trimmedTitle =
       Substring(title, 0, MAX_CHARS_TO_SEARCH_THROUGH);
 
     // Determine if every token matches either the bookmark title, tags, page
     // title, or page URL.
     nsCWhitespaceTokenizer tokenizer(searchString);
     while (matches && tokenizer.hasMoreTokens()) {
--- a/toolkit/components/places/SQLFunctions.h
+++ b/toolkit/components/places/SQLFunctions.h
@@ -161,21 +161,23 @@ private:
    * unescaping escaped characters and removing certain specs that we do not
    * care to search for.
    *
    * @param aURISpec
    *        The spec of the URI to prepare for searching.
    * @param aMatchBehavior
    *        The matching behavior to use defined by one of the
    *        mozIPlacesAutoComplete::MATCH_* values.
-   * @param _fixedSpec
-   *        An out parameter that is the fixed up string.
+   * @param aSpecBuf
+   *        A string buffer that the returned slice can point into, if needed.
+   * @return the fixed up string.
    */
-  static void fixupURISpec(const nsCString &aURISpec, int32_t aMatchBehavior,
-                           nsCString &_fixedSpec);
+  static nsDependentCSubstring fixupURISpec(const nsACString &aURISpec,
+                                            int32_t aMatchBehavior,
+                                            nsACString &aSpecBuf);
 };
 
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Frecency Calculation Function
 
 /**
  * This function is used to calculate frecency for a page.
--- a/toolkit/components/places/UnifiedComplete.js
+++ b/toolkit/components/places/UnifiedComplete.js
@@ -104,26 +104,33 @@ const SQL_BOOKMARK_TAGS_FRAGMENT =
    ( SELECT GROUP_CONCAT(t.title, ', ')
      FROM moz_bookmarks b
      JOIN moz_bookmarks t ON t.id = +b.parent AND t.parent = :parent
      WHERE b.fk = h.id
    ) AS tags`;
 
 // TODO bug 412736: in case of a frecency tie, we might break it with h.typed
 // and h.visit_count.  That is slower though, so not doing it yet...
+// NB: as a slight performance optimization, we only evaluate the "btitle"
+// and "tags" queries for bookmarked entries.
 function defaultQuery(conditions = "") {
   let query =
     `SELECT :query_type, h.url, h.title, f.url, ${SQL_BOOKMARK_TAGS_FRAGMENT},
             h.visit_count, h.typed, h.id, t.open_count, h.frecency
      FROM moz_places h
      LEFT JOIN moz_favicons f ON f.id = h.favicon_id
      LEFT JOIN moz_openpages_temp t ON t.url = h.url
      WHERE h.frecency <> 0
        AND AUTOCOMPLETE_MATCH(:searchString, h.url,
-                              IFNULL(btitle, h.title), tags,
+                              CASE WHEN bookmarked THEN
+                                IFNULL(btitle, h.title)
+                              ELSE h.title END,
+                              CASE WHEN bookmarked THEN
+                                tags
+                              ELSE '' END,
                               h.visit_count, h.typed,
                               bookmarked, t.open_count,
                               :matchBehavior, :searchBehavior)
        ${conditions}
      ORDER BY h.frecency DESC, h.id DESC
      LIMIT :maxResults`;
   return query;
 }
--- a/toolkit/modules/AppConstants.jsm
+++ b/toolkit/modules/AppConstants.jsm
@@ -206,16 +206,23 @@ this.AppConstants = Object.freeze({
 
   DEBUG:
 #ifdef DEBUG
   true,
 #else
   false,
 #endif
 
+  ASAN:
+#ifdef MOZ_ASAN
+  true,
+#else
+  false,
+#endif
+
   MOZ_B2G_RIL:
 #ifdef MOZ_B2G_RIL
   true,
 #else
   false,
 #endif
 
   MOZ_GRAPHENE:
--- a/toolkit/modules/subprocess/subprocess_common.jsm
+++ b/toolkit/modules/subprocess/subprocess_common.jsm
@@ -10,16 +10,18 @@
 /* exported BaseProcess, PromiseWorker */
 
 var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.importGlobalProperties(["TextDecoder"]);
 
+XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
+                                  "resource://gre/modules/AsyncShutdown.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
                                   "resource://gre/modules/Timer.jsm");
 
 Services.scriptloader.loadSubScript("resource://gre/modules/subprocess/subprocess_shared.js", this);
 
 var EXPORTED_SYMBOLS = ["BaseProcess", "PromiseWorker", "SubprocessConstants"];
 
 const BUFFER_SIZE = 4096;
@@ -33,21 +35,35 @@ let nextResponseId = 0;
  */
 class PromiseWorker extends ChromeWorker {
   constructor(url) {
     super(url);
 
     this.listeners = new Map();
     this.pendingResponses = new Map();
 
+    this.addListener("close", this.onClose.bind(this));
     this.addListener("failure", this.onFailure.bind(this));
     this.addListener("success", this.onSuccess.bind(this));
     this.addListener("debug", this.onDebug.bind(this));
 
     this.addEventListener("message", this.onmessage);
+
+    this.shutdown = this.shutdown.bind(this);
+    AsyncShutdown.webWorkersShutdown.addBlocker(
+      "Subprocess.jsm: Shut down IO worker",
+      this.shutdown);
+  }
+
+  onClose() {
+    AsyncShutdown.webWorkersShutdown.removeBlocker(this.shutdown);
+  }
+
+  shutdown() {
+    return this.call("shutdown", []);
   }
 
   /**
    * Adds a listener for the given message from the worker. Any message received
    * from the worker with a `data.msg` property matching the given `msg`
    * parameter are passed to the given listener.
    *
    * @param {string} msg
@@ -610,25 +626,29 @@ class BaseProcess {
       return new this(worker, processId, fds, pid);
     });
   }
 
   static get WORKER_URL() {
     throw new Error("Not implemented");
   }
 
+  static get WorkerClass() {
+    return PromiseWorker;
+  }
+
   /**
    * Gets the current subprocess worker, or spawns a new one if it does not
    * currently exist.
    *
    * @returns {PromiseWorker}
    */
   static getWorker() {
     if (!this._worker) {
-      this._worker = new PromiseWorker(this.WORKER_URL);
+      this._worker = new this.WorkerClass(this.WORKER_URL);
     }
     return this._worker;
   }
 
   /**
    * Kills the process.
    *
    * @param {integer} [timeout=300]
--- a/toolkit/modules/subprocess/subprocess_shared_win.js
+++ b/toolkit/modules/subprocess/subprocess_shared_win.js
@@ -17,16 +17,17 @@ var win32 = {
   // On Windows 64, winapi_abi is an alias for default_abi.
   WINAPI: ctypes.winapi_abi,
 
   VOID: ctypes.void_t,
 
   BYTE: ctypes.uint8_t,
   WORD: ctypes.uint16_t,
   DWORD: ctypes.uint32_t,
+  LONG: ctypes.long,
 
   UINT: ctypes.unsigned_int,
   UCHAR: ctypes.unsigned_char,
 
   BOOL: ctypes.bool,
 
   HANDLE: ctypes.voidptr_t,
   PVOID: ctypes.voidptr_t,
@@ -214,16 +215,25 @@ var libc = new Library("libc", LIBC_CHOI
     win32.BOOL, /* bInheritHandle */
     win32.DWORD, /* dwCreationFlags */
     win32.LPVOID, /* opt lpEnvironment */
     win32.LPCWSTR, /* opt lpCurrentDirectory */
     win32.STARTUPINFOW.ptr, /* lpStartupInfo */
     win32.PROCESS_INFORMATION.ptr, /* out lpProcessInformation */
   ],
 
+  CreateSemaphoreW: [
+    win32.WINAPI,
+    win32.HANDLE,
+    win32.SECURITY_ATTRIBUTES.ptr, /* opt lpSemaphoreAttributes */
+    win32.LONG, /* lInitialCount */
+    win32.LONG, /* lMaximumCount */
+    win32.LPCWSTR, /* opt lpName */
+  ],
+
   DeleteProcThreadAttributeList: [
     win32.WINAPI,
     win32.VOID,
     win32.LPPROC_THREAD_ATTRIBUTE_LIST, /* in/out lpAttributeList */
   ],
 
   DuplicateHandle: [
     win32.WINAPI,
@@ -294,16 +304,24 @@ var libc = new Library("libc", LIBC_CHOI
     win32.BOOL,
     win32.HANDLE, /* hFile */
     win32.LPVOID, /* out lpBuffer */
     win32.DWORD, /* nNumberOfBytesToRead */
     win32.LPDWORD, /* opt out lpNumberOfBytesRead */
     win32.OVERLAPPED.ptr, /* opt in/out lpOverlapped */
   ],
 
+  ReleaseSemaphore: [
+    win32.WINAPI,
+    win32.BOOL,
+    win32.HANDLE, /* hSemaphore */
+    win32.LONG, /* lReleaseCount */
+    win32.LONG.ptr, /* opt out lpPreviousCount */
+  ],
+
   TerminateProcess: [
     win32.WINAPI,
     win32.BOOL,
     win32.HANDLE, /* hProcess */
     win32.UINT, /* uExitCode */
   ],
 
   UpdateProcThreadAttribute: [
--- a/toolkit/modules/subprocess/subprocess_unix.jsm
+++ b/toolkit/modules/subprocess/subprocess_unix.jsm
@@ -4,35 +4,81 @@
  * 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/. */
 "use strict";
 
 /* eslint-disable mozilla/balanced-listeners */
 
 /* exported SubprocessImpl */
 
-/* globals BaseProcess */
+/* globals BaseProcess, PromiseWorker */
 
 var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 var EXPORTED_SYMBOLS = ["SubprocessImpl"];
 
 Cu.import("resource://gre/modules/ctypes.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/subprocess/subprocess_common.jsm");
 
 Services.scriptloader.loadSubScript("resource://gre/modules/subprocess/subprocess_shared.js", this);
 Services.scriptloader.loadSubScript("resource://gre/modules/subprocess/subprocess_shared_unix.js", this);
 
+class UnixPromiseWorker extends PromiseWorker {
+  constructor(...args) {
+    super(...args);
+
+    let fds = ctypes.int.array(2)();
+    let res = libc.pipe(fds);
+    if (res == -1) {
+      throw new Error("Unable to create pipe");
+    }
+
+    this.signalFd = fds[1];
+
+    libc.fcntl(fds[0], LIBC.F_SETFL, LIBC.O_NONBLOCK);
+    libc.fcntl(fds[0], LIBC.F_SETFD, LIBC.FD_CLOEXEC);
+    libc.fcntl(fds[1], LIBC.F_SETFD, LIBC.FD_CLOEXEC);
+
+    this.call("init", [{signalFd: fds[0]}]);
+  }
+
+  closePipe() {
+    if (this.signalFd) {
+      libc.close(this.signalFd);
+      this.signalFd = null;
+    }
+  }
+
+  onClose() {
+    this.closePipe();
+    super.onClose();
+  }
+
+  signalWorker() {
+    libc.write(this.signalFd, new ArrayBuffer(1), 1);
+  }
+
+  postMessage(...args) {
+    this.signalWorker();
+    return super.postMessage(...args);
+  }
+}
+
+
 class Process extends BaseProcess {
   static get WORKER_URL() {
     return "resource://gre/modules/subprocess/subprocess_worker_unix.js";
   }
+
+  static get WorkerClass() {
+    return UnixPromiseWorker;
+  }
 }
 
 var SubprocessUnix = {
   Process,
 
   call(options) {
     return Process.create(options);
   },
--- a/toolkit/modules/subprocess/subprocess_win.jsm
+++ b/toolkit/modules/subprocess/subprocess_win.jsm
@@ -4,45 +4,69 @@
  * 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/. */
 "use strict";
 
 /* eslint-disable mozilla/balanced-listeners */
 
 /* exported SubprocessImpl */
 
-/* globals BaseProcess */
+/* globals BaseProcess, PromiseWorker */
 
 var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 var EXPORTED_SYMBOLS = ["SubprocessImpl"];
 
 Cu.import("resource://gre/modules/ctypes.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/subprocess/subprocess_common.jsm");
 
 Services.scriptloader.loadSubScript("resource://gre/modules/subprocess/subprocess_shared.js", this);
 Services.scriptloader.loadSubScript("resource://gre/modules/subprocess/subprocess_shared_win.js", this);
 
+class WinPromiseWorker extends PromiseWorker {
+  constructor(...args) {
+    super(...args);
+
+    this.signalEvent = libc.CreateSemaphoreW(null, 0, 32, null);
+
+    this.call("init", [{
+      signalEvent: String(ctypes.cast(this.signalEvent, ctypes.uintptr_t).value),
+    }]);
+  }
+
+  signalWorker() {
+    libc.ReleaseSemaphore(this.signalEvent, 1, null);
+  }
+
+  postMessage(...args) {
+    this.signalWorker();
+    return super.postMessage(...args);
+  }
+}
+
 class Process extends BaseProcess {
   static get WORKER_URL() {
     return "resource://gre/modules/subprocess/subprocess_worker_win.js";
   }
+
+  static get WorkerClass() {
+    return WinPromiseWorker;
+  }
 }
 
 var SubprocessWin = {
   Process,
 
   call(options) {
     return Process.create(options);
   },
 
-
   * getEnvironment() {
     let env = libc.GetEnvironmentStringsW();
     try {
       for (let p = env, q = env; ; p = p.increment()) {
         if (p.contents == "\0") {
           if (String(p) == String(q)) {
             break;
           }
--- a/toolkit/modules/subprocess/subprocess_worker_common.js
+++ b/toolkit/modules/subprocess/subprocess_worker_common.js
@@ -83,16 +83,28 @@ class BaseProcess {
     // by the GC if it runs before our process is started.
     this.stringArrays.push(cstrings);
 
     return result;
   }
 }
 
 let requests = {
+  init(details) {
+    io.init(details);
+
+    return {data: {}};
+  },
+
+  shutdown() {
+    io.shutdown();
+
+    return {data: {}};
+  },
+
   close(pipeId, force = false) {
     let pipe = io.getPipe(pipeId);
 
     return pipe.close(force).then(() => ({data: {}}));
   },
 
   spawn(options) {
     let process = new Process(options);
@@ -151,16 +163,18 @@ let requests = {
   },
 
   waitForNoProcesses() {
     return Promise.all(Array.from(io.processes.values(), proc => proc.exitPromise));
   },
 };
 
 onmessage = event => {
+  io.messageCount--;
+
   let {msg, msgId, args} = event.data;
 
   new Promise(resolve => {
     resolve(requests[msg](...args));
   }).then(result => {
     let response = {
       msg: "success",
       msgId,
@@ -190,8 +204,14 @@ onmessage = event => {
 
     self.postMessage({
       msg: "failure",
       msgId,
       error: {},
     });
   });
 };
+
+onclose = event => {
+  io.shutdown();
+
+  self.postMessage({msg: "close"});
+};
--- a/toolkit/modules/subprocess/subprocess_worker_unix.js
+++ b/toolkit/modules/subprocess/subprocess_worker_unix.js
@@ -7,18 +7,17 @@
 
 /* exported Process */
 /* globals BaseProcess, BasePipe */
 
 importScripts("resource://gre/modules/subprocess/subprocess_shared.js",
               "resource://gre/modules/subprocess/subprocess_shared_unix.js",
               "resource://gre/modules/subprocess/subprocess_worker_common.js");
 
-const POLL_INTERVAL = 50;
-const POLL_TIMEOUT = 0;
+const POLL_TIMEOUT = 5000;
 
 let io;
 
 let nextPipeId = 0;
 
 class Pipe extends BasePipe {
   constructor(process, fd) {
     super();
@@ -243,16 +242,50 @@ class OutputPipe extends Pipe {
     }
 
     if (writes.length == 0) {
       io.updatePollFds();
     }
   }
 }
 
+class Signal {
+  constructor(fd) {
+    this.fd = fd;
+  }
+
+  cleanup() {
+    libc.close(this.fd);
+    this.fd = null;
+  }
+
+  get pollEvents() {
+    return LIBC.POLLIN;
+  }
+
+  /**
+   * Called when an error occurred while polling our file descriptor.
+   */
+  onError() {
+    io.shutdown();
+  }
+
+  /**
+   * Called when one of the IO operations matching the `pollEvents` mask may be
+   * performed without blocking.
+   */
+  onReady() {
+    let buffer = new ArrayBuffer(16);
+    let count = +libc.read(this.fd, buffer, buffer.byteLength);
+    if (count > 0) {
+      io.messageCount += count;
+    }
+  }
+}
+
 class Process extends BaseProcess {
   /**
    * Each Process object opens an additional pipe from the target object, which
    * will be automatically closed when the process exits, but otherwise
    * carries no data.
    *
    * This property contains a bit mask of poll() events which we wish to be
    * notified of on this descriptor. We're not expecting any input from this
@@ -444,17 +477,37 @@ class Process extends BaseProcess {
 io = {
   pollFds: null,
   pollHandlers: null,
 
   pipes: new Map(),
 
   processes: new Map(),
 
-  interval: null,
+  messageCount: 0,
+
+  running: true,
+
+  init(details) {
+    this.signal = new Signal(details.signalFd);
+    this.updatePollFds();
+
+    setTimeout(this.loop.bind(this), 0);
+  },
+
+  shutdown() {
+    if (this.running) {
+      this.running = false;
+
+      this.signal.cleanup();
+      this.signal = null;
+
+      self.close();
+    }
+  },
 
   getPipe(pipeId) {
     let pipe = this.pipes.get(pipeId);
 
     if (!pipe) {
       let error = new Error("File closed");
       error.errorCode = SubprocessConstants.ERROR_END_OF_FILE;
       throw error;
@@ -467,47 +520,49 @@ io = {
 
     if (!process) {
       throw new Error(`Invalid process ID: ${processId}`);
     }
     return process;
   },
 
   updatePollFds() {
-    let handlers = [...this.pipes.values(),
+    let handlers = [this.signal,
+                    ...this.pipes.values(),
                     ...this.processes.values()];
 
     handlers = handlers.filter(handler => handler.pollEvents);
 
     let pollfds = unix.pollfd.array(handlers.length)();
 
     for (let [i, handler] of handlers.entries()) {
       let pollfd = pollfds[i];
 
       pollfd.fd = handler.fd;
       pollfd.events = handler.pollEvents;
       pollfd.revents = 0;
     }
 
     this.pollFds = pollfds;
     this.pollHandlers = handlers;
+  },
 
-    if (pollfds.length && !this.interval) {
-      this.interval = setInterval(this.poll.bind(this), POLL_INTERVAL);
-    } else if (!pollfds.length && this.interval) {
-      clearInterval(this.interval);
-      this.interval = null;
+  loop() {
+    this.poll();
+    if (this.running) {
+      setTimeout(this.loop.bind(this), 0);
     }
   },
 
   poll() {
     let handlers = this.pollHandlers;
     let pollfds = this.pollFds;
 
-    let count = libc.poll(pollfds, pollfds.length, POLL_TIMEOUT);
+    let timeout = this.messageCount > 0 ? 0 : POLL_TIMEOUT;
+    let count = libc.poll(pollfds, pollfds.length, timeout);
 
     for (let i = 0; count && i < pollfds.length; i++) {
       let pollfd = pollfds[i];
       if (pollfd.revents) {
         count--;
 
         let handler = handlers[i];
         try {
--- a/toolkit/modules/subprocess/subprocess_worker_win.js
+++ b/toolkit/modules/subprocess/subprocess_worker_win.js
@@ -7,18 +7,17 @@
 
 /* exported Process */
 /* globals BaseProcess, BasePipe, win32 */
 
 importScripts("resource://gre/modules/subprocess/subprocess_shared.js",
               "resource://gre/modules/subprocess/subprocess_shared_win.js",
               "resource://gre/modules/subprocess/subprocess_worker_common.js");
 
-const POLL_INTERVAL = 50;
-const POLL_TIMEOUT = 0;
+const POLL_TIMEOUT = 5000;
 
 // The exit code that we send when we forcibly terminate a process.
 const TERMINATE_EXIT_CODE = 0x7f;
 
 let io;
 
 let nextPipeId = 0;
 
@@ -293,16 +292,35 @@ class OutputPipe extends Pipe {
         this.writeNext();
       } else {
         io.updatePollEvents();
       }
     }
   }
 }
 
+class Signal {
+  constructor(event) {
+    this.event = event;
+  }
+
+  cleanup() {
+    libc.CloseHandle(this.event);
+    this.event = null;
+  }
+
+  onError() {
+    io.shutdown();
+  }
+
+  onReady() {
+    io.messageCount += 1;
+  }
+}
+
 class Process extends BaseProcess {
   constructor(...args) {
     super(...args);
 
     this.killed = false;
   }
 
   /**
@@ -538,17 +556,39 @@ class Process extends BaseProcess {
 io = {
   events: null,
   eventHandlers: null,
 
   pipes: new Map(),
 
   processes: new Map(),
 
-  interval: null,
+  messageCount: 0,
+
+  running: true,
+
+  init(details) {
+    let signalEvent = ctypes.cast(ctypes.uintptr_t(details.signalEvent),
+                                  win32.HANDLE);
+    this.signal = new Signal(signalEvent);
+    this.updatePollEvents();
+
+    setTimeout(this.loop.bind(this), 0);
+  },
+
+  shutdown() {
+    if (this.running) {
+      this.running = false;
+
+      this.signal.cleanup();
+      this.signal = null;
+
+      self.close();
+    }
+  },
 
   getPipe(pipeId) {
     let pipe = this.pipes.get(pipeId);
 
     if (!pipe) {
       let error = new Error("File closed");
       error.errorCode = SubprocessConstants.ERROR_END_OF_FILE;
       throw error;
@@ -561,41 +601,44 @@ io = {
 
     if (!process) {
       throw new Error(`Invalid process ID: ${processId}`);
     }
     return process;
   },
 
   updatePollEvents() {
-    let handlers = [...this.pipes.values(),
+    let handlers = [this.signal,
+                    ...this.pipes.values(),
                     ...this.processes.values()];
 
     handlers = handlers.filter(handler => handler.event);
 
     this.eventHandlers = handlers;
 
     let handles = handlers.map(handler => handler.event);
     this.events = win32.HANDLE.array()(handles);
+  },
 
-    if (handles.length && !this.interval) {
-      this.interval = setInterval(this.poll.bind(this), POLL_INTERVAL);
-    } else if (!handlers.length && this.interval) {
-      clearInterval(this.interval);
-      this.interval = null;
+  loop() {
+    this.poll();
+    if (this.running) {
+      setTimeout(this.loop.bind(this), 0);
     }
   },
 
+
   poll() {
-    for (;;) {
+    let timeout = this.messageCount > 0 ? 0 : POLL_TIMEOUT;
+    for (;; timeout = 0) {
       let events = this.events;
       let handlers = this.eventHandlers;
 
       let result = libc.WaitForMultipleObjects(events.length, events,
-                                               false, POLL_TIMEOUT);
+                                               false, timeout);
 
       if (result < handlers.length) {
         try {
           handlers[result].onReady();
         } catch (e) {
           console.error(e);
           debug(`Worker error: ${e} :: ${e.stack}`);
           handlers[result].onError();
--- a/toolkit/modules/subprocess/test/xpcshell/test_subprocess.js
+++ b/toolkit/modules/subprocess/test/xpcshell/test_subprocess.js
@@ -1,16 +1,19 @@
 "use strict";
 
+Cu.import("resource://gre/modules/AppConstants.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
 
 
 const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
 
+const MAX_ROUND_TRIP_TIME_MS = AppConstants.DEBUG || AppConstants.ASAN ? 18 : 9;
+
 let PYTHON;
 let PYTHON_BIN;
 let PYTHON_DIR;
 
 const TEST_SCRIPT = do_get_file("data_test_script.py").path;
 
 let read = pipe => {
   return pipe.readUint32().then(count => {
@@ -173,16 +176,52 @@ add_task(function* test_subprocess_huge(
 
 
   let {exitCode} = yield proc.wait();
 
   equal(exitCode, 0, "Got expected exit code");
 });
 
 
+add_task(function* test_subprocess_round_trip_perf() {
+  let proc = yield Subprocess.call({
+    command: PYTHON,
+    arguments: ["-u", TEST_SCRIPT, "echo"],
+  });
+
+
+  const LINE = "I'm a leaf on the wind.\n";
+
+  let now = Date.now();
+  const COUNT = 1000;
+  for (let i = 0; i < COUNT; i++) {
+    let [output] = yield Promise.all([
+      read(proc.stdout),
+      proc.stdin.write(LINE),
+    ]);
+
+    // We don't want to log this for every iteration, but we still need
+    // to fail if it goes wrong.
+    if (output !== LINE) {
+      equal(output, LINE, "Got expected output");
+    }
+  }
+
+  let roundTripTime = (Date.now() - now) / COUNT;
+  ok(roundTripTime <= MAX_ROUND_TRIP_TIME_MS,
+     `Expected round trip time (${roundTripTime}ms) to be less than ${MAX_ROUND_TRIP_TIME_MS}ms`);
+
+  yield proc.stdin.close();
+
+  let {exitCode} = yield proc.wait();
+
+  equal(exitCode, 0, "Got expected exit code");
+});
+
+
 add_task(function* test_subprocess_stderr_default() {
   const LINE1 = "I'm a leaf on the wind.\n";
   const LINE2 = "Watch how I soar.\n";
 
   let proc = yield Subprocess.call({
     command: PYTHON,