merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 22 Jun 2015 14:03:17 +0200
changeset 249958 be81b8d6fae99c89e8b14591b11dd26eec0a416e
parent 249846 cddf3b36b5e2c2d9553f71c83a3ae943da73006f (current diff)
parent 249957 026e77985e59fa57715aa30b3117091caeaba597 (diff)
child 249971 006adfdd7855a89e122e3018eb837e24e3accf4d
child 249993 53fd04b756f04ec78c059a6dae85b8559e2f3723
child 250010 4b47c3f074a36bb16295bf3127268094a64ed7db
push id28940
push usercbook@mozilla.com
push dateMon, 22 Jun 2015 12:03:34 +0000
treeherdermozilla-central@be81b8d6fae9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone41.0a1
first release with
nightly linux32
be81b8d6fae9 / 41.0a1 / 20150622052959 / files
nightly linux64
be81b8d6fae9 / 41.0a1 / 20150622052959 / files
nightly mac
be81b8d6fae9 / 41.0a1 / 20150622052959 / files
nightly win32
be81b8d6fae9 / 41.0a1 / 20150622052959 / files
nightly win64
be81b8d6fae9 / 41.0a1 / 20150622052959 / 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
b2g/app/b2g.js
browser/app/profile/firefox.js
caps/tests/mochitest/moz.build
dom/imptests/failures/webapps/XMLHttpRequest/tests/submissions/Ms2ger/mochitest.ini
dom/imptests/failures/webapps/XMLHttpRequest/tests/submissions/Ms2ger/test_interfaces.html.json
image/test/mochitest/test_bug512435.html
testing/web-platform/meta/IndexedDB/close-in-upgradeneeded.html.ini
testing/web-platform/meta/IndexedDB/idbcursor_delete_index4.htm.ini
testing/web-platform/meta/IndexedDB/idbcursor_delete_objectstore4.htm.ini
testing/web-platform/meta/IndexedDB/idbcursor_update_index4.htm.ini
testing/web-platform/meta/IndexedDB/idbcursor_update_objectstore5.htm.ini
testing/web-platform/meta/IndexedDB/idbindex_get6.htm.ini
testing/web-platform/meta/IndexedDB/idbindex_getKey6.htm.ini
testing/web-platform/meta/IndexedDB/idbindex_openCursor.htm.ini
testing/web-platform/meta/IndexedDB/idbindex_openKeyCursor2.htm.ini
testing/web-platform/meta/IndexedDB/idbobjectstore_add16.htm.ini
testing/web-platform/meta/IndexedDB/idbobjectstore_clear4.htm.ini
testing/web-platform/meta/IndexedDB/idbobjectstore_count4.htm.ini
testing/web-platform/meta/IndexedDB/idbobjectstore_delete7.htm.ini
testing/web-platform/meta/IndexedDB/idbobjectstore_deleted.htm.ini
testing/web-platform/meta/IndexedDB/idbobjectstore_put16.htm.ini
testing/web-platform/meta/workers/WorkerLocation_hash_encoding.htm.ini
toolkit/components/telemetry/Histograms.json
--- a/addon-sdk/source/lib/sdk/io/buffer.js
+++ b/addon-sdk/source/lib/sdk/io/buffer.js
@@ -242,21 +242,21 @@ Object.defineProperties(Buffer.prototype
 
       return buffer;
     }
   },
   write: {
     value: function(string, offset, length, encoding = 'utf8') {
       // write(string, encoding);
       if (typeof(offset) === 'string' && Number.isNaN(parseInt(offset))) {
-        ([offset, length, encoding]) = [0, null, offset];
+        [offset, length, encoding] = [0, null, offset];
       }
       // write(string, offset, encoding);
       else if (typeof(length) === 'string')
-        ([length, encoding]) = [null, length];
+        [length, encoding] = [null, length];
 
       if (offset < 0 || offset > this.length)
         throw new RangeError('offset is outside of valid range');
 
       offset = ~~offset;
 
       // Clamp length if it would overflow buffer, or if its
       // undefined
--- a/addon-sdk/source/lib/sdk/panel/utils.js
+++ b/addon-sdk/source/lib/sdk/panel/utils.js
@@ -124,33 +124,33 @@ function display(panel, options, anchor)
 
   if (!anchor) {
     // The XUL Panel doesn't have an arrow, so the margin needs to be reset
     // in order to, be positioned properly
     panel.style.margin = "0";
 
     let viewportRect = document.defaultView.gBrowser.getBoundingClientRect();
 
-    ({x, y, width, height}) = calculateRegion(options, viewportRect);
+    ({x, y, width, height} = calculateRegion(options, viewportRect));
   }
   else {
     // The XUL Panel has an arrow, so the margin needs to be reset
     // to the default value.
     panel.style.margin = "";
     let { CustomizableUI, window } = anchor.ownerDocument.defaultView;
 
     // In Australis, widgets may be positioned in an overflow panel or the
     // menu panel.
     // In such cases clicking this widget will hide the overflow/menu panel,
     // and the widget's panel will show instead.
     // If `CustomizableUI` is not available, it means the anchor is not in a
     // chrome browser window, and therefore there is no need for this check.
     if (CustomizableUI) {
       let node = anchor;
-      ({anchor}) = CustomizableUI.getWidget(anchor.id).forWindow(window);
+      ({anchor} = CustomizableUI.getWidget(anchor.id).forWindow(window));
 
       // if `node` is not the `anchor` itself, it means the widget is
       // positioned in a panel, therefore we have to hide it before show
       // the widget's panel in the same anchor
       if (node !== anchor)
         CustomizableUI.hidePanelForNode(anchor);
     }
 
--- a/addon-sdk/source/lib/sdk/util/sequence.js
+++ b/addon-sdk/source/lib/sdk/util/sequence.js
@@ -240,17 +240,17 @@ const map = (f, ...sequences) => seq(fun
     // Run loop yielding of applying `f` to the set of
     // items at each step until one of the `inputs` is
     // exhausted.
     let done = false;
     while (!done) {
       let index = 0;
       let value = void(0);
       while (index < count && !done) {
-        ({ done, value }) = inputs[index].next();
+        ({ done, value } = inputs[index].next());
 
         // If input is not exhausted yet store value in args.
         if (!done) {
           args[index] = value;
           index = index + 1;
         }
       }
 
@@ -268,20 +268,20 @@ exports.map = map;
 //
 // Implements clojure reductions:
 // http://clojuredocs.org/clojure_core/clojure.core/reductions
 const reductions = (...params) => {
   const count = params.length;
   let hasInitial = false;
   let f, initial, source;
   if (count === 2) {
-    ([f, source]) = params;
+    [f, source] = params;
   }
   else if (count === 3) {
-    ([f, initial, source]) = params;
+    [f, initial, source] = params;
     hasInitial = true;
   }
   else {
     throw Error("Invoked with wrong number of arguments: " + count);
   }
 
   const sequence = seq(source);
 
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -217,16 +217,20 @@ pref("content.sink.perf_parse_time", 500
 pref("dom.use_watchdog", false);
 
 // The slow script dialog can be triggered from inside the JS engine as well,
 // ensure that those calls don't accidentally trigger the dialog.
 pref("dom.max_script_run_time", 0);
 pref("dom.max_chrome_script_run_time", 0);
 pref("dom.max_child_script_run_time", 0);
 
+// Temporarily disable support for offsetX/Y to work around Google Maps bug
+// (bug 1150284)
+pref("dom.mouseEvent.offsetXY.enabled", false);
+
 // plugins
 pref("plugin.disable", true);
 pref("dom.ipc.plugins.enabled", true);
 
 // product URLs
 // The breakpad report server to link to in about:crashes
 pref("breakpad.reportURL", "https://crash-stats.mozilla.com/report/index/");
 pref("app.releaseNotesURL", "http://www.mozilla.com/%LOCALE%/b2g/%VERSION%/releasenotes/");
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -624,17 +624,17 @@ pref("mousewheel.with_meta.action", 1); 
 #endif
 pref("mousewheel.with_control.action",3);
 pref("mousewheel.with_win.action", 1);
 
 pref("browser.xul.error_pages.enabled", true);
 pref("browser.xul.error_pages.expert_bad_cert", false);
 
 // If true, network link events will change the value of navigator.onLine
-pref("network.manage-offline-status", false);
+pref("network.manage-offline-status", true);
 
 // We want to make sure mail URLs are handled externally...
 pref("network.protocol-handler.external.mailto", true); // for mail
 pref("network.protocol-handler.external.news", true);   // for news
 pref("network.protocol-handler.external.snews", true);  // for secure news
 pref("network.protocol-handler.external.nntp", true);   // also news
 #ifdef XP_WIN
 pref("network.protocol-handler.external.ms-windows-store", true);
@@ -1947,9 +1947,10 @@ pref("browser.pocket.oAuthConsumerKey", 
 pref("browser.pocket.useLocaleList", true);
 pref("browser.pocket.enabledLocales", "en-US de es-ES ja ja-JP-mac ru");
 
 pref("view_source.tab", true);
 
 // Enable Service Workers for desktop on non-release builds
 #ifndef RELEASE_BUILD
 pref("dom.serviceWorkers.enabled", true);
+pref("dom.serviceWorkers.interception.enabled", true);
 #endif
--- a/browser/base/content/pageinfo/permissions.js
+++ b/browser/base/content/pageinfo/permissions.js
@@ -201,16 +201,20 @@ function initIndexedDBRow()
 }
 
 function onIndexedDBClear()
 {
   Components.classes["@mozilla.org/dom/quota/manager;1"]
             .getService(nsIQuotaManager)
             .clearStoragesForURI(gPermURI);
 
+  Components.classes["@mozilla.org/serviceworkers/manager;1"]
+            .getService(Components.interfaces.nsIServiceWorkerManager)
+            .removeAndPropagate(gPermURI.host);
+
   SitePermissions.remove(gPermURI, "indexedDB");
   initIndexedDBRow();
 }
 
 function onIndexedDBUsageCallback(uri, usage, fileUsage)
 {
   if (!uri.equals(gPermURI)) {
     throw new Error("Callback received for bad URI: " + uri);
--- a/browser/devtools/storage/test/storage-complex-values.html
+++ b/browser/devtools/storage/test/storage-complex-values.html
@@ -1,9 +1,9 @@
-<!DOCTYPE HTML>
+<!DOCTYPE HTML>
 <html>
 <!--
 Bug 970517 - Storage inspector front end - tests
 -->
 <head>
   <meta charset="utf-8">
   <title>Storage inspector test for correct values in the sidebar</title>
 </head>
@@ -36,16 +36,17 @@ console.log("added cookies and stuff fro
 
 function success(event) {
   setupIDB.next(event);
 }
 
 window.idbGenerator = function*(callback) {
   let request = indexedDB.open("idb1", 1);
   request.onupgradeneeded = success;
+  request.onsuccess = success;
   request.onerror = function(e) {
     throw new Error("error opening db connection");
   };
   let event = yield undefined;
   let db = event.target.result;
   let store1 = db.createObjectStore("obj1", { keyPath: "id" });
   store1.createIndex("name", "name", { unique: false });
   store1.createIndex("email", "email", { unique: true });
@@ -55,28 +56,28 @@ window.idbGenerator = function*(callback
   yield undefined;
   store1.add({id: 2, name: "foo2", email: "foo2@bar.com"}).onsuccess = success;
   yield undefined;
   store1.add({id: 3, name: "foo2", email: "foo3@bar.com"}).onsuccess = success;
   yield undefined;
   store2.add({id2: 1, name: "foo", email: "foo@bar.com", extra: "baz"}).onsuccess = success;
   yield undefined;
 
-  store1.transaction.oncomplete = success;
   yield undefined;
   db.close();
 
   request = indexedDB.open("idb2", 1);
   request.onupgradeneeded = success;
+  request.onsuccess = success;
   event = yield undefined;
 
   let db2 = event.target.result;
   let store3 = db2.createObjectStore("obj3", { keyPath: "id3" });
   store3.createIndex("name2", "name2", { unique: true });
-  store3.transaction.oncomplete = success;
+
   yield undefined;
   db2.close();
   console.log("added cookies and stuff from main page");
   callback();
 }
 
 function successClear(event) {
   clearIterator.next(event);
--- a/browser/devtools/storage/test/storage-listings.html
+++ b/browser/devtools/storage/test/storage-listings.html
@@ -1,9 +1,9 @@
-<!DOCTYPE HTML>
+<!DOCTYPE HTML>
 <html>
 <!--
 Bug 970517 - Storage inspector front end - tests
 -->
 <head>
   <meta charset="utf-8">
   <title>Storage inspector test for listing hosts and storages</title>
 </head>
@@ -30,16 +30,17 @@ console.log("added cookies and stuff fro
 
 function success(event) {
   setupIDB.next(event);
 }
 
 window.idbGenerator = function*(callback) {
   let request = indexedDB.open("idb1", 1);
   request.onupgradeneeded = success;
+  request.onsuccess = success;
   request.onerror = function(e) {
     throw new Error("error opening db connection");
   };
   let event = yield undefined;
   let db = event.target.result;
   let store1 = db.createObjectStore("obj1", { keyPath: "id" });
   store1.createIndex("name", "name", { unique: false });
   store1.createIndex("email", "email", { unique: true });
@@ -49,28 +50,28 @@ window.idbGenerator = function*(callback
   yield undefined;
   store1.add({id: 2, name: "foo2", email: "foo2@bar.com"}).onsuccess = success;
   yield undefined;
   store1.add({id: 3, name: "foo2", email: "foo3@bar.com"}).onsuccess = success;
   yield undefined;
   store2.add({id2: 1, name: "foo", email: "foo@bar.com", extra: "baz"}).onsuccess = success;
   yield undefined;
 
-  store1.transaction.oncomplete = success;
   yield undefined;
   db.close();
 
   request = indexedDB.open("idb2", 1);
   request.onupgradeneeded = success;
+  request.onsuccess = success;
   event = yield undefined;
 
   let db2 = event.target.result;
   let store3 = db2.createObjectStore("obj3", { keyPath: "id3" });
   store3.createIndex("name2", "name2", { unique: true });
-  store3.transaction.oncomplete = success;
+
   yield undefined;
   db2.close();
   console.log("added cookies and stuff from main page");
   callback();
 }
 
 function successClear(event) {
   clearIterator.next(event);
--- a/browser/devtools/storage/test/storage-secured-iframe.html
+++ b/browser/devtools/storage/test/storage-secured-iframe.html
@@ -1,9 +1,9 @@
-<!DOCTYPE HTML>
+<!DOCTYPE HTML>
 <html>
 <!--
 Iframe for testing multiple host detetion in storage actor
 -->
 <head>
   <meta charset="utf-8">
 </head>
 <body>
@@ -15,41 +15,43 @@ sessionStorage.setItem("iframe-s-ss1", "
 
 function success(event) {
   setupIDB.next(event);
 }
 
 window.idbGenerator = function*(callback) {
   let request = indexedDB.open("idb-s1", 1);
   request.onupgradeneeded = success;
+  request.onsuccess = success;
   request.onerror = function(e) {
     throw new Error("error opening db connection");
   };
   let event = yield undefined;
   let db = event.target.result;
   let store1 = db.createObjectStore("obj-s1", { keyPath: "id" });
 
   store1.add({id: 6, name: "foo", email: "foo@bar.com"}).onsuccess = success;
   yield undefined;
   store1.add({id: 7, name: "foo2", email: "foo2@bar.com"}).onsuccess = success;
   yield undefined;
-  store1.transaction.oncomplete = success;
+
   yield undefined;
   db.close();
 
   request = indexedDB.open("idb-s2", 1);
   request.onupgradeneeded = success;
+  request.onsuccess = success;
   event = yield undefined;
 
   let db2 = event.target.result;
   let store3 = db2.createObjectStore("obj-s2", { keyPath: "id3", autoIncrement: true });
   store3.createIndex("name2", "name2", { unique: true });
   store3.add({id3: 16, name2: "foo", email: "foo@bar.com"}).onsuccess = success;
   yield undefined;
-  store3.transaction.oncomplete = success;
+
   yield undefined;
   db2.close();
   console.log("added cookies and stuff from secured iframe");
   callback();
 }
 
 function successClear(event) {
   clearIterator.next(event);
--- a/build/mobile/remoteautomation.py
+++ b/build/mobile/remoteautomation.py
@@ -7,17 +7,17 @@ import time
 import re
 import os
 import tempfile
 import shutil
 import subprocess
 import sys
 
 from automation import Automation
-from devicemanager import DMError
+from devicemanager import DMError, DeviceManager
 from mozlog.structured import get_default_logger
 import mozcrash
 
 # signatures for logcat messages that we don't care about much
 fennecLogcatFilters = [ "The character encoding of the HTML document was not declared",
                         "Use of Mutation Events is deprecated. Use MutationObserver instead.",
                         "Unexpected value from nativeGetEnabledTags: 0" ]
 
@@ -119,18 +119,20 @@ class RemoteAutomation(Automation):
 
         return status
 
     def deleteANRs(self):
         # empty ANR traces.txt file; usually need root permissions
         # we make it empty and writable so we can test the ANR reporter later
         traces = "/data/anr/traces.txt"
         try:
-            self._devicemanager.shellCheckOutput(['echo', '', '>', traces], root=True)
-            self._devicemanager.shellCheckOutput(['chmod', '666', traces], root=True)
+            self._devicemanager.shellCheckOutput(['echo', '', '>', traces], root=True,
+                                                 timeout=DeviceManager.short_timeout)
+            self._devicemanager.shellCheckOutput(['chmod', '666', traces], root=True,
+                                                 timeout=DeviceManager.short_timeout)
         except DMError:
             print "Error deleting %s" % traces
             pass
 
     def checkForANRs(self):
         traces = "/data/anr/traces.txt"
         if self._devicemanager.fileExists(traces):
             try:
@@ -145,33 +147,36 @@ class RemoteAutomation(Automation):
                 print "Error pulling %s" % traces
         else:
             print "%s not found" % traces
 
     def deleteTombstones(self):
         # delete any existing tombstone files from device
         remoteDir = "/data/tombstones"
         try:
-            self._devicemanager.shellCheckOutput(['rm', '-r', remoteDir], root=True)
+            self._devicemanager.shellCheckOutput(['rm', '-r', remoteDir], root=True,
+                                                 timeout=DeviceManager.short_timeout)
         except DMError:
             # This may just indicate that the tombstone directory is missing
             pass
 
     def checkForTombstones(self):
         # pull any tombstones from device and move to MOZ_UPLOAD_DIR
         remoteDir = "/data/tombstones"
         blobberUploadDir = os.environ.get('MOZ_UPLOAD_DIR', None)
         if blobberUploadDir:
             if not os.path.exists(blobberUploadDir):
                 os.mkdir(blobberUploadDir)
             if self._devicemanager.dirExists(remoteDir):
                 # copy tombstone files from device to local blobber upload directory
                 try:
-                    self._devicemanager.shellCheckOutput(['chmod', '777', remoteDir], root=True)
-                    self._devicemanager.shellCheckOutput(['chmod', '666', os.path.join(remoteDir, '*')], root=True)
+                    self._devicemanager.shellCheckOutput(['chmod', '777', remoteDir], root=True,
+                                                 timeout=DeviceManager.short_timeout)
+                    self._devicemanager.shellCheckOutput(['chmod', '666', os.path.join(remoteDir, '*')], root=True,
+                                                 timeout=DeviceManager.short_timeout)
                     self._devicemanager.getDirectory(remoteDir, blobberUploadDir, False)
                 except DMError:
                     # This may just indicate that no tombstone files are present
                     pass
                 self.deleteTombstones()
                 # add a .txt file extension to each tombstone file name, so
                 # that blobber will upload it
                 for f in glob.glob(os.path.join(blobberUploadDir, "tombstone_??")):
@@ -187,22 +192,17 @@ class RemoteAutomation(Automation):
                 print "%s does not exist; tombstone check skipped" % remoteDir
         else:
             print "MOZ_UPLOAD_DIR not defined; tombstone check skipped"
 
     def checkForCrashes(self, directory, symbolsPath):
         self.checkForANRs()
         self.checkForTombstones()
 
-        try:
-            logcat = self._devicemanager.getLogcat(filterOutRegexps=fennecLogcatFilters)
-        except DMError:
-            print "getLogcat threw DMError; re-trying just once..."
-            time.sleep(1)
-            logcat = self._devicemanager.getLogcat(filterOutRegexps=fennecLogcatFilters)
+        logcat = self._devicemanager.getLogcat(filterOutRegexps=fennecLogcatFilters)
 
         javaException = mozcrash.check_for_java_exception(logcat)
         if javaException:
             return True
 
         # If crash reporting is disabled (MOZ_CRASHREPORTER!=1), we can't say
         # anything.
         if not self.CRASHREPORTER:
--- a/caps/moz.build
+++ b/caps/moz.build
@@ -1,16 +1,16 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-TEST_DIRS += ['tests/mochitest']
-
+MOCHITEST_MANIFESTS += ['tests/mochitest/mochitest.ini']
+MOCHITEST_CHROME_MANIFESTS += ['tests/mochitest/chrome.ini']
 XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
 
 XPIDL_SOURCES += [
     'nsIDomainPolicy.idl',
     'nsIPrincipal.idl',
     'nsIScriptSecurityManager.idl',
 ]
 
deleted file mode 100644
--- a/caps/tests/mochitest/moz.build
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-MOCHITEST_MANIFESTS += ['mochitest.ini']
-MOCHITEST_CHROME_MANIFESTS += ['chrome.ini']
-
--- a/db/sqlite3/src/sqlite.def
+++ b/db/sqlite3/src/sqlite.def
@@ -102,16 +102,17 @@ EXPORTS
         sqlite3_realloc
         sqlite3_release_memory
         sqlite3_reset
         sqlite3_reset_auto_extension
         sqlite3_result_blob
         sqlite3_result_double
         sqlite3_result_error
         sqlite3_result_error16
+        sqlite3_result_error_code
         sqlite3_result_error_nomem
         sqlite3_result_int
         sqlite3_result_int64
         sqlite3_result_null
         sqlite3_result_text
         sqlite3_result_text16
         sqlite3_result_text16be
         sqlite3_result_text16le
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -219,20 +219,22 @@ static NS_DEFINE_CID(kAppShellCID, NS_AP
 #else
 #include <unistd.h> // for getpid()
 #endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using mozilla::dom::workers::ServiceWorkerManager;
 
-// True means sUseErrorPages has been added to preferences var cache.
+// True means sUseErrorPages and sInterceptionEnabled has been added to
+// preferences var cache.
 static bool gAddedPreferencesVarCache = false;
 
 bool nsDocShell::sUseErrorPages = false;
+bool nsDocShell::sInterceptionEnabled = false;
 
 // Number of documents currently loading
 static int32_t gNumberOfDocumentsLoading = 0;
 
 // Global count of existing docshells.
 static int32_t gDocShellCount = 0;
 
 // Global count of docshells with the private attribute set
@@ -5736,16 +5738,19 @@ nsDocShell::Create()
   // Should we use XUL error pages instead of alerts if possible?
   mUseErrorPages =
     Preferences::GetBool("browser.xul.error_pages.enabled", mUseErrorPages);
 
   if (!gAddedPreferencesVarCache) {
     Preferences::AddBoolVarCache(&sUseErrorPages,
                                  "browser.xul.error_pages.enabled",
                                  mUseErrorPages);
+    Preferences::AddBoolVarCache(&sInterceptionEnabled,
+                                 "dom.serviceWorkers.interception.enabled",
+                                 false);
     gAddedPreferencesVarCache = true;
   }
 
   mDeviceSizeIsPageSize =
     Preferences::GetBool("docshell.device_size_is_page_size",
                          mDeviceSizeIsPageSize);
 
   nsCOMPtr<nsIObserverService> serv = services::GetObserverService();
@@ -14042,16 +14047,21 @@ nsDocShell::MaybeNotifyKeywordSearchLoad
 #endif
 }
 
 NS_IMETHODIMP
 nsDocShell::ShouldPrepareForIntercept(nsIURI* aURI, bool aIsNavigate,
                                       bool* aShouldIntercept)
 {
   *aShouldIntercept = false;
+  // Preffed off.
+  if (!sInterceptionEnabled) {
+    return NS_OK;
+  }
+
   if (mSandboxFlags) {
     // If we're sandboxed, don't intercept.
     return NS_OK;
   }
 
   nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
   if (!swm) {
     return NS_OK;
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -898,16 +898,19 @@ protected:
     PARENT_ALLOWS,
     PARENT_PROHIBITS
   };
   FullscreenAllowedState mFullscreenAllowed;
 
   // Cached value of the "browser.xul.error_pages.enabled" preference.
   static bool sUseErrorPages;
 
+  // Cached value of the "dom.serviceWorkers.interception.enabled" preference.
+  static bool sInterceptionEnabled;
+
   bool mCreated;
   bool mAllowSubframes;
   bool mAllowPlugins;
   bool mAllowJavascript;
   bool mAllowMetaRedirects;
   bool mAllowImages;
   bool mAllowMedia;
   bool mAllowDNSPrefetch;
--- a/dom/base/Link.cpp
+++ b/dom/base/Link.cpp
@@ -439,17 +439,17 @@ Link::GetHash(nsAString &_hash, ErrorRes
     // string.
     return;
   }
 
   nsAutoCString ref;
   nsresult rv = uri->GetRef(ref);
   if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) {
     _hash.Assign(char16_t('#'));
-    if (nsContentUtils::EncodeDecodeURLHash()) {
+    if (nsContentUtils::GettersDecodeURLHash()) {
       NS_UnescapeURL(ref); // XXX may result in random non-ASCII bytes!
     }
     AppendUTF8toUTF16(ref, _hash);
   }
 }
 
 void
 Link::ResetLinkState(bool aNotify, bool aHasHref)
--- a/dom/base/URL.cpp
+++ b/dom/base/URL.cpp
@@ -520,17 +520,17 @@ void
 URL::GetHash(nsAString& aHash, ErrorResult& aRv) const
 {
   aHash.Truncate();
 
   nsAutoCString ref;
   nsresult rv = mURI->GetRef(ref);
   if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) {
     aHash.Assign(char16_t('#'));
-    if (nsContentUtils::EncodeDecodeURLHash()) {
+    if (nsContentUtils::GettersDecodeURLHash()) {
       NS_UnescapeURL(ref); // XXX may result in random non-ASCII bytes!
     }
     AppendUTF8toUTF16(ref, aHash);
   }
 }
 
 void
 URL::SetHash(const nsAString& aHash, ErrorResult& aRv)
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -251,16 +251,17 @@ bool nsContentUtils::sInitialized = fals
 bool nsContentUtils::sIsFullScreenApiEnabled = false;
 bool nsContentUtils::sTrustedFullScreenOnly = true;
 bool nsContentUtils::sIsCutCopyAllowed = true;
 bool nsContentUtils::sIsPerformanceTimingEnabled = false;
 bool nsContentUtils::sIsResourceTimingEnabled = false;
 bool nsContentUtils::sIsUserTimingLoggingEnabled = false;
 bool nsContentUtils::sIsExperimentalAutocompleteEnabled = false;
 bool nsContentUtils::sEncodeDecodeURLHash = false;
+bool nsContentUtils::sGettersDecodeURLHash = false;
 bool nsContentUtils::sPrivacyResistFingerprinting = false;
 
 uint32_t nsContentUtils::sHandlingInputTimeout = 1000;
 
 nsHtml5StringParser* nsContentUtils::sHTMLFragmentParser = nullptr;
 nsIParser* nsContentUtils::sXMLFragmentParser = nullptr;
 nsIFragmentContentSink* nsContentUtils::sXMLFragmentSink = nullptr;
 bool nsContentUtils::sFragmentParsingActive = false;
@@ -533,16 +534,19 @@ nsContentUtils::Init()
                                "dom.performance.enable_user_timing_logging", false);
 
   Preferences::AddBoolVarCache(&sIsExperimentalAutocompleteEnabled,
                                "dom.forms.autocomplete.experimental", false);
 
   Preferences::AddBoolVarCache(&sEncodeDecodeURLHash,
                                "dom.url.encode_decode_hash", false);
 
+  Preferences::AddBoolVarCache(&sGettersDecodeURLHash,
+                               "dom.url.getters_decode_hash", false);
+
   Preferences::AddBoolVarCache(&sPrivacyResistFingerprinting,
                                "privacy.resistFingerprinting", false);
 
   Preferences::AddUintVarCache(&sHandlingInputTimeout,
                                "dom.event.handling-user-input-time-limit",
                                1000);
 
 #if !(defined(DEBUG) || defined(MOZ_ENABLE_JS_DUMP))
@@ -6308,20 +6312,17 @@ nsContentUtils::FlushLayoutForTree(nsIDO
     nsCOMPtr<nsPIDOMWindow> piWin = do_QueryInterface(aWindow);
     if (!piWin)
         return;
 
     // Note that because FlushPendingNotifications flushes parents, this
     // is O(N^2) in docshell tree depth.  However, the docshell tree is
     // usually pretty shallow.
 
-    nsCOMPtr<nsIDOMDocument> domDoc;
-    aWindow->GetDocument(getter_AddRefs(domDoc));
-    nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
-    if (doc) {
+    if (nsCOMPtr<nsIDocument> doc = piWin->GetDoc()) {
         doc->FlushPendingNotifications(Flush_Layout);
     }
 
     nsCOMPtr<nsIDocShell> docShell = piWin->GetDocShell();
     if (docShell) {
         int32_t i = 0, i_end;
         docShell->GetChildCount(&i_end);
         for (; i < i_end; ++i) {
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -1944,16 +1944,24 @@ public:
    * and getters should return the percent decoded value of the segment
    */
   static bool EncodeDecodeURLHash()
   {
     return sEncodeDecodeURLHash;
   }
 
   /*
+   * Returns true if URL getters should percent decode the value of the segment
+   */
+  static bool GettersDecodeURLHash()
+  {
+    return sGettersDecodeURLHash && sEncodeDecodeURLHash;
+  }
+
+  /*
    * Returns true if the browser should attempt to prevent content scripts
    * from collecting distinctive information about the browser that could
    * be used to "fingerprint" and track the user across websites.
    */
   static bool ResistFingerprinting()
   {
     return sPrivacyResistFingerprinting;
   }
@@ -2494,16 +2502,17 @@ private:
   static bool sTrustedFullScreenOnly;
   static bool sIsCutCopyAllowed;
   static uint32_t sHandlingInputTimeout;
   static bool sIsPerformanceTimingEnabled;
   static bool sIsResourceTimingEnabled;
   static bool sIsUserTimingLoggingEnabled;
   static bool sIsExperimentalAutocompleteEnabled;
   static bool sEncodeDecodeURLHash;
+  static bool sGettersDecodeURLHash;
   static bool sPrivacyResistFingerprinting;
 
   static nsHtml5StringParser* sHTMLFragmentParser;
   static nsIParser* sXMLFragmentParser;
   static nsIFragmentContentSink* sXMLFragmentSink;
 
   /**
    * True if there's a fragment parser activation on the stack.
--- a/dom/base/nsFrameMessageManager.cpp
+++ b/dom/base/nsFrameMessageManager.cpp
@@ -1780,18 +1780,17 @@ nsMessageManagerScriptExecutor::TryCache
     JS::Rooted<JSScript*> script(cx);
 
     if (aRunInGlobalScope) {
       if (!JS::Compile(cx, options, srcBuf, &script)) {
         return;
       }
     } else {
       // We're going to run these against some non-global scope.
-      options.setHasPollutedScope(true);
-      if (!JS::Compile(cx, options, srcBuf, &script)) {
+      if (!JS::CompileForNonSyntacticScope(cx, options, srcBuf, &script)) {
         return;
       }
     }
 
     aScriptp.set(script);
 
     nsAutoCString scheme;
     uri->GetScheme(scheme);
--- a/dom/base/nsImageLoadingContent.cpp
+++ b/dom/base/nsImageLoadingContent.cpp
@@ -82,17 +82,16 @@ nsImageLoadingContent::nsImageLoadingCon
     mImageBlockingStatus(nsIContentPolicy::ACCEPT),
     mLoadingEnabled(true),
     mIsImageStateForced(false),
     mLoading(false),
     // mBroken starts out true, since an image without a URI is broken....
     mBroken(true),
     mUserDisabled(false),
     mSuppressed(false),
-    mFireEventsOnDecode(false),
     mNewRequestsWillNeedAnimationReset(false),
     mStateChangerDepth(0),
     mCurrentRequestRegistered(false),
     mPendingRequestRegistered(false),
     mFrameCreateCalled(false),
     mVisibleCount(0)
 {
   if (!nsContentUtils::GetImgLoaderForChannel(nullptr, nullptr)) {
@@ -181,28 +180,16 @@ nsImageLoadingContent::Notify(imgIReques
       }
     }
     nsresult status =
         reqStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
     return OnLoadComplete(aRequest, status);
   }
 
   if (aType == imgINotificationObserver::DECODE_COMPLETE) {
-    if (mFireEventsOnDecode) {
-      mFireEventsOnDecode = false;
-
-      uint32_t reqStatus;
-      aRequest->GetImageStatus(&reqStatus);
-      if (reqStatus & imgIRequest::STATUS_ERROR) {
-        FireEvent(NS_LITERAL_STRING("error"));
-      } else {
-        FireEvent(NS_LITERAL_STRING("load"));
-      }
-    }
-
     UpdateImageState(true);
   }
 
   return NS_OK;
 }
 
 nsresult
 nsImageLoadingContent::OnLoadComplete(imgIRequest* aRequest, nsresult aStatus)
@@ -272,33 +259,21 @@ nsImageLoadingContent::OnLoadComplete(im
       // visible.
       if (!mFrameCreateCalled || (f->GetStateBits() & NS_FRAME_FIRST_REFLOW) ||
           mVisibleCount > 0 || shell->AssumeAllImagesVisible()) {
         mCurrentRequest->StartDecoding();
       }
     }
   }
 
-  // We want to give the decoder a chance to find errors. If we haven't found
-  // an error yet and we've started decoding, either from the above
-  // StartDecoding or from some other place, we must only fire these events
-  // after we finish decoding.
-  uint32_t reqStatus;
-  aRequest->GetImageStatus(&reqStatus);
-  if (NS_SUCCEEDED(aStatus) && !(reqStatus & imgIRequest::STATUS_ERROR) &&
-      (reqStatus & imgIRequest::STATUS_DECODE_STARTED) &&
-      !(reqStatus & imgIRequest::STATUS_DECODE_COMPLETE)) {
-    mFireEventsOnDecode = true;
+  // Fire the appropriate DOM event.
+  if (NS_SUCCEEDED(aStatus)) {
+    FireEvent(NS_LITERAL_STRING("load"));
   } else {
-    // Fire the appropriate DOM event.
-    if (NS_SUCCEEDED(aStatus)) {
-      FireEvent(NS_LITERAL_STRING("load"));
-    } else {
-      FireEvent(NS_LITERAL_STRING("error"));
-    }
+    FireEvent(NS_LITERAL_STRING("error"));
   }
 
   nsCOMPtr<nsINode> thisNode = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
   nsSVGEffects::InvalidateDirectRenderingObservers(thisNode->AsElement());
 
   return NS_OK;
 }
 
--- a/dom/base/nsImageLoadingContent.h
+++ b/dom/base/nsImageLoadingContent.h
@@ -405,17 +405,16 @@ private:
   /**
    * The state we had the last time we checked whether we needed to notify the
    * document of a state change.  These are maintained by UpdateImageState.
    */
   bool mLoading : 1;
   bool mBroken : 1;
   bool mUserDisabled : 1;
   bool mSuppressed : 1;
-  bool mFireEventsOnDecode : 1;
 
 protected:
   /**
    * A hack to get animations to reset, see bug 594771. On requests
    * that originate from setting .src, we mark them for needing their animation
    * reset when they are ready. mNewRequestsWillNeedAnimationReset is set to
    * true while preparing such requests (as a hack around needing to change an
    * interface), and the other two booleans store which of the current
--- a/dom/base/nsLocation.cpp
+++ b/dom/base/nsLocation.cpp
@@ -294,17 +294,17 @@ nsLocation::GetHash(nsAString& aHash)
     return rv;
   }
 
   nsAutoCString ref;
   nsAutoString unicodeRef;
 
   rv = uri->GetRef(ref);
 
-  if (nsContentUtils::EncodeDecodeURLHash()) {
+  if (nsContentUtils::GettersDecodeURLHash()) {
     if (NS_SUCCEEDED(rv)) {
       nsCOMPtr<nsITextToSubURI> textToSubURI(
           do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv));
 
       if (NS_SUCCEEDED(rv)) {
         nsAutoCString charset;
         uri->GetOriginCharset(charset);
 
--- a/dom/base/test/test_url.html
+++ b/dom/base/test/test_url.html
@@ -111,17 +111,17 @@
       port: '',
       pathname: '/',
       search: '?test',
       hash: ''
     },
     { url: 'http://example.com/carrot#question%3f',
       base: undefined,
       error: false,
-      hash: '#question?'
+      hash: '#question%3f'
     },
     { url: 'https://example.com:4443?',
       base: undefined,
       error: false,
       protocol: 'https:',
       port: '4443',
       pathname: '/',
       hash: '',
--- a/dom/events/test/mochitest.ini
+++ b/dom/events/test/mochitest.ini
@@ -184,11 +184,12 @@ support-files =
 support-files =
   bug989198_embedded.html
   bug989198_helper.js
 skip-if = buildapp == 'b2g' || e10s
 [test_bug1096146.html]
 support-files =
   bug1096146_embedded.html
 [test_offsetxy.html]
+skip-if = toolkit == 'android' || buildapp == 'b2g' || buildapp == 'mulet'
 [test_eventhandler_scoping.html]
 [test_bug1013412.html]
 skip-if = buildapp == 'b2g' # no wheel events on b2g
--- a/dom/imptests/WebIDLParser.js
+++ b/dom/imptests/WebIDLParser.js
@@ -915,10 +915,10 @@
             parse:  function (str, opt) {
                 if (!opt) opt = {};
                 var tokens = tokenise(str);
                 return parse(tokens, opt);
             }
     };
 
     if (inNode) module.exports = obj;
-    else        window.WebIDL2 = obj;
+    else        self.WebIDL2 = obj;
 }());
--- a/dom/imptests/failures/html/dom/test_interfaces.html.json
+++ b/dom/imptests/failures/html/dom/test_interfaces.html.json
@@ -1,42 +1,35 @@
 {
   "DOMException exception: existence and properties of exception interface prototype object's \"name\" property": true,
-  "CustomEvent interface: existence and properties of interface object": true,
   "EventListener interface: existence and properties of interface prototype object": true,
   "EventListener interface: existence and properties of interface prototype object's \"constructor\" property": true,
   "EventListener interface: operation handleEvent(Event)": true,
   "MutationObserver interface: operation observe(Node,MutationObserverInit)": true,
-  "Node interface: existence and properties of interface object": true,
-  "Document interface: existence and properties of interface object": true,
   "Document interface: operation prepend([object Object],[object Object])": true,
   "Document interface: operation append([object Object],[object Object])": true,
-  "XMLDocument interface: existence and properties of interface object": true,
   "Document interface: xmlDoc must inherit property \"prepend\" with the proper type (28)": true,
   "Document interface: calling prepend([object Object],[object Object]) on xmlDoc with too few arguments must throw TypeError": true,
   "Document interface: xmlDoc must inherit property \"append\" with the proper type (29)": true,
   "Document interface: calling append([object Object],[object Object]) on xmlDoc with too few arguments must throw TypeError": true,
-  "DocumentFragment interface: existence and properties of interface object": true,
   "DocumentFragment interface: operation prepend([object Object],[object Object])": true,
   "DocumentFragment interface: operation append([object Object],[object Object])": true,
   "DocumentFragment interface: document.createDocumentFragment() must inherit property \"prepend\" with the proper type (4)": true,
   "DocumentFragment interface: calling prepend([object Object],[object Object]) on document.createDocumentFragment() with too few arguments must throw TypeError": true,
   "DocumentFragment interface: document.createDocumentFragment() must inherit property \"append\" with the proper type (5)": true,
   "DocumentFragment interface: calling append([object Object],[object Object]) on document.createDocumentFragment() with too few arguments must throw TypeError": true,
-  "DocumentType interface: existence and properties of interface object": true,
   "DocumentType interface: operation before([object Object],[object Object])": true,
   "DocumentType interface: operation after([object Object],[object Object])": true,
   "DocumentType interface: operation replace([object Object],[object Object])": true,
   "DocumentType interface: document.doctype must inherit property \"before\" with the proper type (3)": true,
   "DocumentType interface: calling before([object Object],[object Object]) on document.doctype with too few arguments must throw TypeError": true,
   "DocumentType interface: document.doctype must inherit property \"after\" with the proper type (4)": true,
   "DocumentType interface: calling after([object Object],[object Object]) on document.doctype with too few arguments must throw TypeError": true,
   "DocumentType interface: document.doctype must inherit property \"replace\" with the proper type (5)": true,
   "DocumentType interface: calling replace([object Object],[object Object]) on document.doctype with too few arguments must throw TypeError": true,
-  "Element interface: existence and properties of interface object": true,
   "Element interface: attribute namespaceURI": true,
   "Element interface: attribute prefix": true,
   "Element interface: attribute localName": true,
   "Element interface: operation prepend([object Object],[object Object])": true,
   "Element interface: operation append([object Object],[object Object])": true,
   "Element interface: operation before([object Object],[object Object])": true,
   "Element interface: operation after([object Object],[object Object])": true,
   "Element interface: operation replace([object Object],[object Object])": true,
@@ -47,41 +40,53 @@
   "Element interface: element must inherit property \"before\" with the proper type (25)": true,
   "Element interface: calling before([object Object],[object Object]) on element with too few arguments must throw TypeError": true,
   "Element interface: element must inherit property \"after\" with the proper type (26)": true,
   "Element interface: calling after([object Object],[object Object]) on element with too few arguments must throw TypeError": true,
   "Element interface: element must inherit property \"replace\" with the proper type (27)": true,
   "Element interface: calling replace([object Object],[object Object]) on element with too few arguments must throw TypeError": true,
   "Attr interface: existence and properties of interface object": true,
   "Attr interface: existence and properties of interface prototype object": true,
-  "CharacterData interface: existence and properties of interface object": true,
   "CharacterData interface: operation before([object Object],[object Object])": true,
   "CharacterData interface: operation after([object Object],[object Object])": true,
   "CharacterData interface: operation replace([object Object],[object Object])": true,
-  "Text interface: existence and properties of interface object": true,
   "CharacterData interface: document.createTextNode(\"abc\") must inherit property \"before\" with the proper type (7)": true,
   "CharacterData interface: calling before([object Object],[object Object]) on document.createTextNode(\"abc\") with too few arguments must throw TypeError": true,
   "CharacterData interface: document.createTextNode(\"abc\") must inherit property \"after\" with the proper type (8)": true,
   "CharacterData interface: calling after([object Object],[object Object]) on document.createTextNode(\"abc\") with too few arguments must throw TypeError": true,
   "CharacterData interface: document.createTextNode(\"abc\") must inherit property \"replace\" with the proper type (9)": true,
   "CharacterData interface: calling replace([object Object],[object Object]) on document.createTextNode(\"abc\") with too few arguments must throw TypeError": true,
-  "ProcessingInstruction interface: existence and properties of interface object": true,
   "CharacterData interface: xmlDoc.createProcessingInstruction(\"abc\", \"def\") must inherit property \"before\" with the proper type (7)": true,
   "CharacterData interface: calling before([object Object],[object Object]) on xmlDoc.createProcessingInstruction(\"abc\", \"def\") with too few arguments must throw TypeError": true,
   "CharacterData interface: xmlDoc.createProcessingInstruction(\"abc\", \"def\") must inherit property \"after\" with the proper type (8)": true,
   "CharacterData interface: calling after([object Object],[object Object]) on xmlDoc.createProcessingInstruction(\"abc\", \"def\") with too few arguments must throw TypeError": true,
   "CharacterData interface: xmlDoc.createProcessingInstruction(\"abc\", \"def\") must inherit property \"replace\" with the proper type (9)": true,
   "CharacterData interface: calling replace([object Object],[object Object]) on xmlDoc.createProcessingInstruction(\"abc\", \"def\") with too few arguments must throw TypeError": true,
-  "Comment interface: existence and properties of interface object": true,
   "CharacterData interface: document.createComment(\"abc\") must inherit property \"before\" with the proper type (7)": true,
   "CharacterData interface: calling before([object Object],[object Object]) on document.createComment(\"abc\") with too few arguments must throw TypeError": true,
   "CharacterData interface: document.createComment(\"abc\") must inherit property \"after\" with the proper type (8)": true,
   "CharacterData interface: calling after([object Object],[object Object]) on document.createComment(\"abc\") with too few arguments must throw TypeError": true,
   "CharacterData interface: document.createComment(\"abc\") must inherit property \"replace\" with the proper type (9)": true,
   "CharacterData interface: calling replace([object Object],[object Object]) on document.createComment(\"abc\") with too few arguments must throw TypeError": true,
-  "NodeFilter interface: existence and properties of interface object": true,
-  "NodeList interface: existence and properties of interface prototype object": true,
-  "DOMTokenList interface: operation add(DOMString)": true,
-  "DOMTokenList interface: operation remove(DOMString)": true,
-  "DOMTokenList interface: calling add(DOMString) on document.body.classList with too few arguments must throw TypeError": true,
-  "DOMTokenList interface: calling remove(DOMString) on document.body.classList with too few arguments must throw TypeError": true,
-  "DOMSettableTokenList interface: existence and properties of interface object": true
+  "NodeFilter interface: existence and properties of interface prototype object": true,
+  "NodeFilter interface: existence and properties of interface prototype object": true,
+  "NodeFilter interface: existence and properties of interface prototype object's \"constructor\" property": true,
+  "EventListener interface: existence and properties of interface object": true,
+  "EventListener interface object length": true,
+  "NodeFilter interface: constant FILTER_ACCEPT on interface prototype object": true,
+  "NodeFilter interface: constant FILTER_REJECT on interface prototype object": true,
+  "NodeFilter interface: constant FILTER_SKIP on interface prototype object": true,
+  "NodeFilter interface: constant SHOW_ALL on interface prototype object": true,
+  "NodeFilter interface: constant SHOW_ELEMENT on interface prototype object": true,
+  "NodeFilter interface: constant SHOW_ATTRIBUTE on interface prototype object": true,
+  "NodeFilter interface: constant SHOW_TEXT on interface prototype object": true,
+  "NodeFilter interface: constant SHOW_CDATA_SECTION on interface prototype object": true,
+  "NodeFilter interface: constant SHOW_ENTITY_REFERENCE on interface prototype object": true,
+  "NodeFilter interface: constant SHOW_ENTITY on interface prototype object": true,
+  "NodeFilter interface: constant SHOW_PROCESSING_INSTRUCTION on interface prototype object": true,
+  "NodeFilter interface: constant SHOW_COMMENT on interface prototype object": true,
+  "NodeFilter interface: constant SHOW_DOCUMENT on interface prototype object": true,
+  "NodeFilter interface: constant SHOW_DOCUMENT_TYPE on interface prototype object": true,
+  "NodeFilter interface: constant SHOW_DOCUMENT_FRAGMENT on interface prototype object": true,
+  "NodeFilter interface: constant SHOW_NOTATION on interface prototype object": true,
+  "NodeFilter interface: operation acceptNode(Node)": true,
+  "NodeList interface: existence and properties of interface prototype object": true
 }
deleted file mode 100644
--- a/dom/imptests/failures/webapps/XMLHttpRequest/tests/submissions/Ms2ger/mochitest.ini
+++ /dev/null
@@ -1,6 +0,0 @@
-# THIS FILE IS AUTOGENERATED BY parseFailures.py - DO NOT EDIT
-[DEFAULT]
-support-files =
-
-
-[test_interfaces.html.json]
deleted file mode 100644
--- a/dom/imptests/failures/webapps/XMLHttpRequest/tests/submissions/Ms2ger/test_interfaces.html.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-  "XMLHttpRequestUpload interface: existence and properties of interface object": true,
-  "XMLHttpRequest interface: existence and properties of interface object": true
-}
--- a/dom/imptests/html/dom/test_interfaces.html
+++ b/dom/imptests/html/dom/test_interfaces.html
@@ -5,45 +5,16 @@
 <script src=/resources/testharnessreport.js></script>
 <script src=/resources/WebIDLParser.js></script>
 <script src=/resources/idlharness.js></script>
 
 <h1>DOM IDL tests</h1>
 <div id=log></div>
 
 <script type=text/plain>
-exception DOMException {
-  const unsigned short INDEX_SIZE_ERR = 1;
-  const unsigned short DOMSTRING_SIZE_ERR = 2; // historical
-  const unsigned short HIERARCHY_REQUEST_ERR = 3;
-  const unsigned short WRONG_DOCUMENT_ERR = 4;
-  const unsigned short INVALID_CHARACTER_ERR = 5;
-  const unsigned short NO_DATA_ALLOWED_ERR = 6; // historical
-  const unsigned short NO_MODIFICATION_ALLOWED_ERR = 7;
-  const unsigned short NOT_FOUND_ERR = 8;
-  const unsigned short NOT_SUPPORTED_ERR = 9;
-  const unsigned short INUSE_ATTRIBUTE_ERR = 10; // historical
-  const unsigned short INVALID_STATE_ERR = 11;
-  const unsigned short SYNTAX_ERR = 12;
-  const unsigned short INVALID_MODIFICATION_ERR = 13;
-  const unsigned short NAMESPACE_ERR = 14;
-  const unsigned short INVALID_ACCESS_ERR = 15;
-  const unsigned short VALIDATION_ERR = 16; // historical
-  const unsigned short TYPE_MISMATCH_ERR = 17; // historical; use TypeError instead
-  const unsigned short SECURITY_ERR = 18;
-  const unsigned short NETWORK_ERR = 19;
-  const unsigned short ABORT_ERR = 20;
-  const unsigned short URL_MISMATCH_ERR = 21;
-  const unsigned short QUOTA_EXCEEDED_ERR = 22;
-  const unsigned short TIMEOUT_ERR = 23;
-  const unsigned short INVALID_NODE_TYPE_ERR = 24;
-  const unsigned short DATA_CLONE_ERR = 25;
-  unsigned short code;
-};
-
 [Constructor(DOMString name)]
 interface DOMError {
   readonly attribute DOMString name;
 };
 
 [Constructor(DOMString type, optional EventInit eventInitDict)]
 interface Event {
   readonly attribute DOMString type;
@@ -446,30 +417,28 @@ interface DOMTokenList {
 };
 
 interface DOMSettableTokenList : DOMTokenList {
             attribute DOMString value;
 };
 </script>
 <script>
 "use strict";
-var xmlDoc, domException, detachedRange, element;
+var xmlDoc, detachedRange, element;
 var idlArray;
 setup(function() {
 	xmlDoc = document.implementation.createDocument(null, "", null);
-	try { document.appendChild(document); } catch(e) { domException = e; }
 	detachedRange = document.createRange();
 	detachedRange.detach();
 	element = xmlDoc.createElementNS(null, "test");
 	element.setAttribute("bar", "baz");
 
 	idlArray = new IdlArray();
 	idlArray.add_idls(document.querySelector("script[type=text\\/plain]").textContent);
 	idlArray.add_objects({
-		DOMException: ['domException'],
 		Event: ['document.createEvent("Event")', 'new Event("foo")'],
 		CustomEvent: ['new CustomEvent("foo")'],
 		XMLDocument: ['xmlDoc'],
 		DOMImplementation: ['document.implementation'],
 		DocumentFragment: ['document.createDocumentFragment()'],
 		DocumentType: ['document.doctype'],
 		Element: ['element'],
 		Attr: ['document.querySelector("[id]").attributes[0]'],
--- a/dom/imptests/idlharness.js
+++ b/dom/imptests/idlharness.js
@@ -51,19 +51,32 @@ policies and contribution forms [3].
 /// Helpers ///
 function constValue (cnt) {
     if (cnt.type === "null") return null;
     if (cnt.type === "NaN") return NaN;
     if (cnt.type === "Infinity") return cnt.negative ? -Infinity : Infinity;
     return cnt.value;
 }
 
+function minOverloadLength(overloads) {
+    if (!overloads.length) {
+        return 0;
+    }
+
+    return overloads.map(function(attr) {
+        return attr.arguments ? attr.arguments.filter(function(arg) {
+            return !arg.optional && !arg.variadic;
+        }).length : 0;
+    })
+    .reduce(function(m, n) { return Math.min(m, n); });
+}
+
 /// IdlArray ///
 // Entry point
-window.IdlArray = function()
+self.IdlArray = function()
 //@{
 {
     /**
      * A map from strings to the corresponding named IdlObject, such as
      * IdlInterface or IdlException.  These are the things that test() will run
      * tests on.
      */
     this.members = {};
@@ -161,21 +174,18 @@ IdlArray.prototype.internal_add_idls = f
         parsed_idl.array = this;
         if (parsed_idl.name in this.members)
         {
             throw "Duplicate identifier " + parsed_idl.name;
         }
         switch(parsed_idl.type)
         {
         case "interface":
-            this.members[parsed_idl.name] = new IdlInterface(parsed_idl);
-            break;
-
-        case "exception":
-            this.members[parsed_idl.name] = new IdlException(parsed_idl);
+            this.members[parsed_idl.name] =
+                new IdlInterface(parsed_idl, /* is_callback = */ false);
             break;
 
         case "dictionary":
             // Nothing to test, but we need the dictionary info around for type
             // checks
             this.members[parsed_idl.name] = new IdlDictionary(parsed_idl);
             break;
 
@@ -188,18 +198,18 @@ IdlArray.prototype.internal_add_idls = f
             console.log("callback not yet supported");
             break;
 
         case "enum":
             this.members[parsed_idl.name] = new IdlEnum(parsed_idl);
             break;
 
         case "callback interface":
-            // TODO
-            console.log("callback interface not yet supported");
+            this.members[parsed_idl.name] =
+                new IdlInterface(parsed_idl, /* is_callback = */ true);
             break;
 
         default:
             throw parsed_idl.name + ": " + parsed_idl.type + " not yet supported";
         }
     }.bind(this));
 };
 
@@ -408,22 +418,24 @@ IdlArray.prototype.assert_type_is = func
             assert_true(0 <= value && value <= 4294967295, "unsigned long " + value + " not in range [0, 4294967295]");
             return;
 
         case "long long":
             assert_equals(typeof value, "number");
             return;
 
         case "unsigned long long":
+        case "DOMTimeStamp":
             assert_equals(typeof value, "number");
             assert_true(0 <= value, "unsigned long long is negative");
             return;
 
         case "float":
         case "double":
+        case "DOMHighResTimeStamp":
         case "unrestricted float":
         case "unrestricted double":
             // TODO: distinguish these cases
             assert_equals(typeof value, "number");
             return;
 
         case "DOMString":
         case "ByteString":
@@ -443,23 +455,23 @@ IdlArray.prototype.assert_type_is = func
     }
 
     if (this.members[type] instanceof IdlInterface)
     {
         // We don't want to run the full
         // IdlInterface.prototype.test_instance_of, because that could result
         // in an infinite loop.  TODO: This means we don't have tests for
         // NoInterfaceObject interfaces, and we also can't test objects that
-        // come from another window.
+        // come from another self.
         assert_true(typeof value == "object" || typeof value == "function", "wrong type: not object or function");
         if (value instanceof Object
         && !this.members[type].has_extended_attribute("NoInterfaceObject")
-        && type in window)
+        && type in self)
         {
-            assert_true(value instanceof window[type], "not instanceof " + type);
+            assert_true(value instanceof self[type], "not instanceof " + type);
         }
     }
     else if (this.members[type] instanceof IdlEnum)
     {
         assert_equals(typeof value, "string");
     }
     else if (this.members[type] instanceof IdlDictionary)
     {
@@ -524,21 +536,18 @@ function IdlDictionary(obj)
      * if there is none.
      */
     this.base = obj.inheritance;
 }
 
 //@}
 IdlDictionary.prototype = Object.create(IdlObject.prototype);
 
-/// IdlExceptionOrInterface ///
-// Code sharing!
-function IdlExceptionOrInterface(obj)
-//@{
-{
+/// IdlInterface ///
+function IdlInterface(obj, is_callback) {
     /**
      * obj is an object produced by the WebIDLParser.js "exception" or
      * "interface" production, as appropriate.
      */
 
     /** Self-explanatory. */
     this.name = obj.name;
 
@@ -552,27 +561,58 @@ function IdlExceptionOrInterface(obj)
      */
     this.untested = obj.untested;
 
     /** An array of objects produced by the "ExtAttr" production. */
     this.extAttrs = obj.extAttrs;
 
     /** An array of IdlInterfaceMembers. */
     this.members = obj.members.map(function(m){return new IdlInterfaceMember(m); });
+    if (this.has_extended_attribute("Unforgeable")) {
+        this.members
+            .filter(function(m) { return !m["static"] && (m.type == "attribute" || m.type == "operation"); })
+            .forEach(function(m) { return m.isUnforgeable = true; });
+    }
 
     /**
      * The name (as a string) of the type we inherit from, or null if there is
      * none.
      */
     this.base = obj.inheritance;
+
+    this._is_callback = is_callback;
 }
- 
+IdlInterface.prototype = Object.create(IdlObject.prototype);
+IdlInterface.prototype.is_callback = function()
+//@{
+{
+    return this._is_callback;
+};
 //@}
-IdlExceptionOrInterface.prototype = Object.create(IdlObject.prototype);
-IdlExceptionOrInterface.prototype.test = function()
+
+IdlInterface.prototype.has_constants = function()
+//@{
+{
+    return this.members.some(function(member) {
+        return member.type === "const";
+    });
+};
+//@}
+
+IdlInterface.prototype.is_global = function()
+//@{
+{
+    return this.extAttrs.some(function(attribute) {
+        return attribute.name === "Global" ||
+               attribute.name === "PrimaryGlobal";
+    });
+};
+//@}
+
+IdlInterface.prototype.test = function()
 //@{
 {
     if (this.has_extended_attribute("NoInterfaceObject"))
     {
         // No tests to do without an instance.  TODO: We should still be able
         // to run tests on the prototype object, if we obtain one through some
         // other means.
         return;
@@ -588,853 +628,604 @@ IdlExceptionOrInterface.prototype.test =
     // operations, . . .).  These are run even if .untested is true, because
     // members might themselves be marked as .untested.  This might happen to
     // interfaces if the interface itself is untested but a partial interface
     // that extends it is tested -- then the interface itself and its initial
     // members will be marked as untested, but the members added by the partial
     // interface are still tested.
     this.test_members();
 };
-
-//@}
-
-/// IdlException ///
-function IdlException(obj) { IdlExceptionOrInterface.call(this, obj); }
-IdlException.prototype = Object.create(IdlExceptionOrInterface.prototype);
-IdlException.prototype.test_self = function()
-//@{
-{
-    test(function()
-    {
-        // "For every exception that is not declared with the
-        // [NoInterfaceObject] extended attribute, a corresponding property
-        // must exist on the exception’s relevant namespace object. The name of
-        // the property is the identifier of the exception, and its value is an
-        // object called the exception interface object, which provides access
-        // to any constants that have been associated with the exception. The
-        // property has the attributes { [[Writable]]: true, [[Enumerable]]:
-        // false, [[Configurable]]: true }."
-        assert_own_property(window, this.name,
-                            "window does not have own property " + format_value(this.name));
-        var desc = Object.getOwnPropertyDescriptor(window, this.name);
-        assert_false("get" in desc, "window's property " + format_value(this.name) + " has getter");
-        assert_false("set" in desc, "window's property " + format_value(this.name) + " has setter");
-        assert_true(desc.writable, "window's property " + format_value(this.name) + " is not writable");
-        assert_false(desc.enumerable, "window's property " + format_value(this.name) + " is enumerable");
-        assert_true(desc.configurable, "window's property " + format_value(this.name) + " is not configurable");
-
-        // "The exception interface object for a given exception must be a
-        // function object."
-        // "If an object is defined to be a function object, then it has
-        // characteristics as follows:"
-        // "Its [[Prototype]] internal property is the Function prototype
-        // object."
-        // Note: This doesn't match browsers as of December 2011, see
-        // http://www.w3.org/Bugs/Public/show_bug.cgi?id=14813
-        assert_equals(Object.getPrototypeOf(window[this.name]), Function.prototype,
-                      "prototype of window's property " + format_value(this.name) + " is not Function.prototype");
-        // "Its [[Get]] internal property is set as described in ECMA-262
-        // section 15.3.5.4."
-        // Not much to test for this.
-        // "Its [[Construct]] internal property is set as described in ECMA-262
-        // section 13.2.2."
-        // Tested below.
-        // "Its [[HasInstance]] internal property is set as described in
-        // ECMA-262 section 15.3.5.3, unless otherwise specified."
-        // TODO
-        // "Its [[Class]] internal property is “Function”."
-        // String() returns something implementation-dependent, because it
-        // calls Function#toString.
-        assert_class_string(window[this.name], "Function",
-                            "class string of " + this.name);
-
-        // TODO: Test 4.9.1.1. Exception interface object [[Call]] method
-        // (which does not match browsers:
-        // http://www.w3.org/Bugs/Public/show_bug.cgi?id=14885)
-    }.bind(this), this.name + " exception: existence and properties of exception interface object");
-
-    test(function()
-    {
-        assert_own_property(window, this.name,
-                            "window does not have own property " + format_value(this.name));
-
-        // "The exception interface object must also have a property named
-        // “prototype” with attributes { [[Writable]]: false, [[Enumerable]]:
-        // false, [[Configurable]]: false } whose value is an object called the
-        // exception interface prototype object. This object also provides
-        // access to the constants that are declared on the exception."
-        assert_own_property(window[this.name], "prototype",
-                            'exception "' + this.name + '" does not have own property "prototype"');
-        var desc = Object.getOwnPropertyDescriptor(window[this.name], "prototype");
-        assert_false("get" in desc, this.name + ".prototype has getter");
-        assert_false("set" in desc, this.name + ".prototype has setter");
-        assert_false(desc.writable, this.name + ".prototype is writable");
-        assert_false(desc.enumerable, this.name + ".prototype is enumerable");
-        assert_false(desc.configurable, this.name + ".prototype is configurable");
-
-        // "The exception interface prototype object for a given exception must
-        // have an internal [[Prototype]] property whose value is as follows:
-        //
-        // "If the exception is declared to inherit from another exception,
-        // then the value of the internal [[Prototype]] property is the
-        // exception interface prototype object for the inherited exception.
-        // "Otherwise, the exception is not declared to inherit from another
-        // exception. The value of the internal [[Prototype]] property is the
-        // Error prototype object ([ECMA-262], section 15.11.3.1)."
-        //
-        // Note: This doesn't match browsers as of December 2011, see
-        // https://www.w3.org/Bugs/Public/show_bug.cgi?id=14887.
-        var inherit_exception = this.base ? this.base : "Error";
-        assert_own_property(window, inherit_exception,
-                            'should inherit from ' + inherit_exception + ', but window has no such property');
-        assert_own_property(window[inherit_exception], "prototype",
-                            'should inherit from ' + inherit_exception + ', but that object has no "prototype" property');
-        assert_equals(Object.getPrototypeOf(window[this.name].prototype),
-                      window[inherit_exception].prototype,
-                      'prototype of ' + this.name + '.prototype is not ' + inherit_exception + '.prototype');
-
-        // "The class string of an exception interface prototype object is the
-        // concatenation of the exception’s identifier and the string
-        // “Prototype”."
-        assert_class_string(window[this.name].prototype, this.name + "Prototype",
-                            "class string of " + this.name + ".prototype");
-        // TODO: Test String(), based on ES definition of
-        // Error.prototype.toString?
-    }.bind(this), this.name + " exception: existence and properties of exception interface prototype object");
-
-    test(function()
-    {
-        assert_own_property(window, this.name,
-                            "window does not have own property " + format_value(this.name));
-        assert_own_property(window[this.name], "prototype",
-                            'interface "' + this.name + '" does not have own property "prototype"');
-
-        // "There must be a property named “name” on the exception interface
-        // prototype object with attributes { [[Writable]]: true,
-        // [[Enumerable]]: false, [[Configurable]]: true } and whose value is
-        // the identifier of the exception."
-        assert_own_property(window[this.name].prototype, "name",
-                'prototype object does not have own property "name"');
-        var desc = Object.getOwnPropertyDescriptor(window[this.name].prototype, "name");
-        assert_false("get" in desc, this.name + ".prototype.name has getter");
-        assert_false("set" in desc, this.name + ".prototype.name has setter");
-        assert_true(desc.writable, this.name + ".prototype.name is not writable");
-        assert_false(desc.enumerable, this.name + ".prototype.name is enumerable");
-        assert_true(desc.configurable, this.name + ".prototype.name is not configurable");
-        assert_equals(desc.value, this.name, this.name + ".prototype.name has incorrect value");
-    }.bind(this), this.name + " exception: existence and properties of exception interface prototype object's \"name\" property");
-
-    test(function()
-    {
-        assert_own_property(window, this.name,
-                            "window does not have own property " + format_value(this.name));
-        assert_own_property(window[this.name], "prototype",
-                            'interface "' + this.name + '" does not have own property "prototype"');
-
-        // "If the [NoInterfaceObject] extended attribute was not specified on
-        // the exception, then there must also be a property named
-        // “constructor” on the exception interface prototype object with
-        // attributes { [[Writable]]: true, [[Enumerable]]: false,
-        // [[Configurable]]: true } and whose value is a reference to the
-        // exception interface object for the exception."
-        assert_own_property(window[this.name].prototype, "constructor",
-                            this.name + '.prototype does not have own property "constructor"');
-        var desc = Object.getOwnPropertyDescriptor(window[this.name].prototype, "constructor");
-        assert_false("get" in desc, this.name + ".prototype.constructor has getter");
-        assert_false("set" in desc, this.name + ".prototype.constructor has setter");
-        assert_true(desc.writable, this.name + ".prototype.constructor is not writable");
-        assert_false(desc.enumerable, this.name + ".prototype.constructor is enumerable");
-        assert_true(desc.configurable, this.name + ".prototype.constructor in not configurable");
-        assert_equals(window[this.name].prototype.constructor, window[this.name],
-                      this.name + '.prototype.constructor is not the same object as ' + this.name);
-    }.bind(this), this.name + " exception: existence and properties of exception interface prototype object's \"constructor\" property");
-};
-
-//@}
-IdlException.prototype.test_members = function()
-//@{
-{
-    for (var i = 0; i < this.members.length; i++)
-    {
-        var member = this.members[i];
-        if (member.untested)
-        {
-            continue;
-        }
-        if (member.type == "const" && member.name != "prototype")
-        {
-            test(function()
-            {
-                assert_own_property(window, this.name,
-                                    "window does not have own property " + format_value(this.name));
-
-                // "For each constant defined on the exception, there must be a
-                // corresponding property on the exception interface object, if
-                // it exists, if the identifier of the constant is not
-                // “prototype”."
-                assert_own_property(window[this.name], member.name);
-                // "The value of the property is the ECMAScript value that is
-                // equivalent to the constant’s IDL value, according to the
-                // rules in section 4.2 above."
-                assert_equals(window[this.name][member.name], constValue(member.value),
-                              "property has wrong value");
-                // "The property has attributes { [[Writable]]: false,
-                // [[Enumerable]]: true, [[Configurable]]: false }."
-                var desc = Object.getOwnPropertyDescriptor(window[this.name], member.name);
-                assert_false("get" in desc, "property has getter");
-                assert_false("set" in desc, "property has setter");
-                assert_false(desc.writable, "property is writable");
-                assert_true(desc.enumerable, "property is not enumerable");
-                assert_false(desc.configurable, "property is configurable");
-            }.bind(this), this.name + " exception: constant " + member.name + " on exception interface object");
-            // "In addition, a property with the same characteristics must
-            // exist on the exception interface prototype object."
-            test(function()
-            {
-                assert_own_property(window, this.name,
-                                    "window does not have own property " + format_value(this.name));
-                assert_own_property(window[this.name], "prototype",
-                                    'exception "' + this.name + '" does not have own property "prototype"');
-
-                assert_own_property(window[this.name].prototype, member.name);
-                assert_equals(window[this.name].prototype[member.name], constValue(member.value),
-                              "property has wrong value");
-                var desc = Object.getOwnPropertyDescriptor(window[this.name].prototype, member.name);
-                assert_false("get" in desc, "property has getter");
-                assert_false("set" in desc, "property has setter");
-                assert_false(desc.writable, "property is writable");
-                assert_true(desc.enumerable, "property is not enumerable");
-                assert_false(desc.configurable, "property is configurable");
-            }.bind(this), this.name + " exception: constant " + member.name + " on exception interface prototype object");
-        }
-        else if (member.type == "field")
-        {
-            test(function()
-            {
-                assert_own_property(window, this.name,
-                                    "window does not have own property " + format_value(this.name));
-                assert_own_property(window[this.name], "prototype",
-                                    'exception "' + this.name + '" does not have own property "prototype"');
-
-                // "For each exception field, there must be a corresponding
-                // property on the exception interface prototype object, whose
-                // characteristics are as follows:
-                // "The name of the property is the identifier of the exception
-                // field."
-                assert_own_property(window[this.name].prototype, member.name);
-                // "The property has attributes { [[Get]]: G, [[Enumerable]]:
-                // true, [[Configurable]]: true }, where G is the exception
-                // field getter, defined below."
-                var desc = Object.getOwnPropertyDescriptor(window[this.name].prototype, member.name);
-                assert_false("value" in desc, "property descriptor has value but is supposed to be accessor");
-                assert_false("writable" in desc, 'property descriptor has "writable" field but is supposed to be accessor');
-                // TODO: ES5 doesn't seem to say whether desc should have a
-                // .set property.
-                assert_true(desc.enumerable, "property is not enumerable");
-                assert_true(desc.configurable, "property is not configurable");
-                // "The exception field getter is a Function object whose
-                // behavior when invoked is as follows:"
-                assert_equals(typeof desc.get, "function", "typeof getter");
-                // "The value of the Function object’s “length” property is the
-                // Number value 0."
-                // This test is before the TypeError tests so that it's easiest
-                // to see that Firefox 11a1 only fails one assert in this test.
-                assert_equals(desc.get.length, 0, "getter length");
-                // "Let O be the result of calling ToObject on the this value.
-                // "If O is not a platform object representing an exception for
-                // the exception on which the exception field was declared,
-                // then throw a TypeError."
-                // TODO: Test on a platform object representing an exception.
-                assert_throws(new TypeError(), function()
-                {
-                    window[this.name].prototype[member.name];
-                }.bind(this), "getting property on prototype object must throw TypeError");
-                assert_throws(new TypeError(), function()
-                {
-                    desc.get.call({});
-                }.bind(this), "calling getter on wrong object type must throw TypeError");
-            }.bind(this), this.name + " exception: field " + member.name + " on exception interface prototype object");
-        }
-    }
-};
-
-//@}
-IdlException.prototype.test_object = function(desc)
-//@{
-{
-    var obj, exception = null;
-    try
-    {
-        obj = eval(desc);
-    }
-    catch(e)
-    {
-        exception = e;
-    }
-
-    test(function()
-    {
-        assert_equals(exception, null, "Unexpected exception when evaluating object");
-        assert_equals(typeof obj, "object", "wrong typeof object");
-
-        // We can't easily test that its prototype is correct if there's no
-        // interface object, or the object is from a different global
-        // environment (not instanceof Object).  TODO: test in this case that
-        // its prototype at least looks correct, even if we can't test that
-        // it's actually correct.
-        if (!this.has_extended_attribute("NoInterfaceObject")
-        && (typeof obj != "object" || obj instanceof Object))
-        {
-            assert_own_property(window, this.name,
-                                "window does not have own property " + format_value(this.name));
-            assert_own_property(window[this.name], "prototype",
-                                'exception "' + this.name + '" does not have own property "prototype"');
-
-            // "The value of the internal [[Prototype]] property of the
-            // exception object must be the exception interface prototype
-            // object from the global environment the exception object is
-            // associated with."
-            assert_equals(Object.getPrototypeOf(obj),
-                          window[this.name].prototype,
-                          desc + "'s prototype is not " + this.name + ".prototype");
-        }
-
-        // "The class string of the exception object must be the identifier of
-        // the exception."
-        assert_class_string(obj, this.name, "class string of " + desc);
-        // Stringifier is not defined for DOMExceptions, because message isn't
-        // defined.
-    }.bind(this), this.name + " must be represented by " + desc);
-
-    for (var i = 0; i < this.members.length; i++)
-    {
-        var member = this.members[i];
-        test(function()
-        {
-            assert_equals(exception, null, "Unexpected exception when evaluating object");
-            assert_equals(typeof obj, "object", "wrong typeof object");
-            assert_inherits(obj, member.name);
-            if (member.type == "const")
-            {
-                assert_equals(obj[member.name], constValue(member.value));
-            }
-            if (member.type == "field")
-            {
-                this.array.assert_type_is(obj[member.name], member.idlType);
-            }
-        }.bind(this), this.name + " exception: " + desc + ' must inherit property "' + member.name + '" with the proper type');
-    }
-};
-//@}
-
-/// IdlInterface ///
-function IdlInterface(obj) { IdlExceptionOrInterface.call(this, obj); }
-IdlInterface.prototype = Object.create(IdlExceptionOrInterface.prototype);
-IdlInterface.prototype.is_callback = function()
-//@{
-{
-    return this.has_extended_attribute("Callback");
-};
-//@}
-
-IdlInterface.prototype.has_constants = function()
-//@{
-{
-    return this.members.some(function(member) {
-        return member.type === "const";
-    });
-};
 //@}
 
 IdlInterface.prototype.test_self = function()
 //@{
 {
     test(function()
     {
-        // This function tests WebIDL as of 2012-11-28.
+        // This function tests WebIDL as of 2015-01-13.
+        // TODO: Consider [Exposed].
 
-        // "For every interface that:
+        // "For every interface that is exposed in a given ECMAScript global
+        // environment and:
         // * is a callback interface that has constants declared on it, or
         // * is a non-callback interface that is not declared with the
         //   [NoInterfaceObject] extended attribute,
         // a corresponding property MUST exist on the ECMAScript global object.
         // The name of the property is the identifier of the interface, and its
         // value is an object called the interface object.
         // The property has the attributes { [[Writable]]: true,
         // [[Enumerable]]: false, [[Configurable]]: true }."
         if (this.is_callback() && !this.has_constants()) {
             return;
         }
 
         // TODO: Should we test here that the property is actually writable
         // etc., or trust getOwnPropertyDescriptor?
-        assert_own_property(window, this.name,
-                            "window does not have own property " + format_value(this.name));
-        var desc = Object.getOwnPropertyDescriptor(window, this.name);
-        assert_false("get" in desc, "window's property " + format_value(this.name) + " has getter");
-        assert_false("set" in desc, "window's property " + format_value(this.name) + " has setter");
-        assert_true(desc.writable, "window's property " + format_value(this.name) + " is not writable");
-        assert_false(desc.enumerable, "window's property " + format_value(this.name) + " is enumerable");
-        assert_true(desc.configurable, "window's property " + format_value(this.name) + " is not configurable");
+        assert_own_property(self, this.name,
+                            "self does not have own property " + format_value(this.name));
+        var desc = Object.getOwnPropertyDescriptor(self, this.name);
+        assert_false("get" in desc, "self's property " + format_value(this.name) + " has getter");
+        assert_false("set" in desc, "self's property " + format_value(this.name) + " has setter");
+        assert_true(desc.writable, "self's property " + format_value(this.name) + " is not writable");
+        assert_false(desc.enumerable, "self's property " + format_value(this.name) + " is enumerable");
+        assert_true(desc.configurable, "self's property " + format_value(this.name) + " is not configurable");
 
         if (this.is_callback()) {
             // "The internal [[Prototype]] property of an interface object for
             // a callback interface MUST be the Object.prototype object."
-            assert_equals(Object.getPrototypeOf(window[this.name]), Object.prototype,
-                          "prototype of window's property " + format_value(this.name) + " is not Object.prototype");
+            assert_equals(Object.getPrototypeOf(self[this.name]), Object.prototype,
+                          "prototype of self's property " + format_value(this.name) + " is not Object.prototype");
 
             return;
         }
 
         // "The interface object for a given non-callback interface is a
         // function object."
         // "If an object is defined to be a function object, then it has
         // characteristics as follows:"
 
-        // "* Its [[Prototype]] internal property is the Function prototype
-        //    object."
-        assert_equals(Object.getPrototypeOf(window[this.name]), Function.prototype,
-                      "prototype of window's property " + format_value(this.name) + " is not Function.prototype");
+        // Its [[Prototype]] internal property is otherwise specified (see
+        // below).
 
         // "* Its [[Get]] internal property is set as described in ECMA-262
-        //    section 15.3.5.4."
+        //    section 9.1.8."
         // Not much to test for this.
 
         // "* Its [[Construct]] internal property is set as described in
-        //    ECMA-262 section 13.2.2."
+        //    ECMA-262 section 19.2.2.3."
         // Tested below if no constructor is defined.  TODO: test constructors
         // if defined.
 
-        // "* Its [[HasInstance]] internal property is set as described in
-        //    ECMA-262 section 15.3.5.3, unless otherwise specified."
+        // "* Its @@hasInstance property is set as described in ECMA-262
+        //    section 19.2.3.8, unless otherwise specified."
         // TODO
 
-        // "* Its [[NativeBrand]] internal property is “Function”."
-        // String() returns something implementation-dependent, because it calls
-        // Function#toString.
-        assert_class_string(window[this.name], "Function", "class string of " + this.name);
+        // ES6 (rev 30) 19.1.3.6:
+        // "Else, if O has a [[Call]] internal method, then let builtinTag be
+        // "Function"."
+        assert_class_string(self[this.name], "Function", "class string of " + this.name);
+
+        // "The [[Prototype]] internal property of an interface object for a
+        // non-callback interface is determined as follows:"
+        var prototype = Object.getPrototypeOf(self[this.name]);
+        if (this.base) {
+            // "* If the interface inherits from some other interface, the
+            //    value of [[Prototype]] is the interface object for that other
+            //    interface."
+            var has_interface_object =
+                !this.array
+                     .members[this.base]
+                     .has_extended_attribute("NoInterfaceObject");
+            if (has_interface_object) {
+                assert_own_property(self, this.base,
+                                    'should inherit from ' + this.base +
+                                    ', but self has no such property');
+                assert_equals(prototype, self[this.base],
+                              'prototype of ' + this.name + ' is not ' +
+                              this.base);
+            }
+        } else {
+            // "If the interface doesn't inherit from any other interface, the
+            // value of [[Prototype]] is %FunctionPrototype% ([ECMA-262],
+            // section 6.1.7.4)."
+            assert_equals(prototype, Function.prototype,
+                          "prototype of self's property " + format_value(this.name) + " is not Function.prototype");
+        }
 
         if (!this.has_extended_attribute("Constructor")) {
             // "The internal [[Call]] method of the interface object behaves as
             // follows . . .
             //
             // "If I was not declared with a [Constructor] extended attribute,
             // then throw a TypeError."
             assert_throws(new TypeError(), function() {
-                window[this.name]();
+                self[this.name]();
             }.bind(this), "interface object didn't throw TypeError when called as a function");
             assert_throws(new TypeError(), function() {
-                new window[this.name]();
+                new self[this.name]();
             }.bind(this), "interface object didn't throw TypeError when called as a constructor");
         }
     }.bind(this), this.name + " interface: existence and properties of interface object");
 
     if (!this.is_callback()) {
         test(function() {
             // This function tests WebIDL as of 2014-10-25.
             // https://heycam.github.io/webidl/#es-interface-call
 
-            assert_own_property(window, this.name,
-                                "window does not have own property " + format_value(this.name));
+            assert_own_property(self, this.name,
+                                "self does not have own property " + format_value(this.name));
 
             // "Interface objects for non-callback interfaces MUST have a
             // property named “length” with attributes { [[Writable]]: false,
             // [[Enumerable]]: false, [[Configurable]]: true } whose value is
             // a Number."
-            assert_own_property(window[this.name], "length");
-            var desc = Object.getOwnPropertyDescriptor(window[this.name], "length");
+            assert_own_property(self[this.name], "length");
+            var desc = Object.getOwnPropertyDescriptor(self[this.name], "length");
             assert_false("get" in desc, this.name + ".length has getter");
             assert_false("set" in desc, this.name + ".length has setter");
             assert_false(desc.writable, this.name + ".length is writable");
             assert_false(desc.enumerable, this.name + ".length is enumerable");
             assert_true(desc.configurable, this.name + ".length is not configurable");
 
             var constructors = this.extAttrs
                 .filter(function(attr) { return attr.name == "Constructor"; });
-            var expected_length;
-            if (!constructors.length) {
-                // "If the [Constructor] extended attribute, does not appear on
-                // the interface definition, then the value is 0."
-                expected_length = 0;
-            } else {
-                // "Otherwise, the value is determined as follows: . . .
-                // "Return the length of the shortest argument list of the
-                // entries in S."
-                expected_length = constructors.map(function(attr) {
-                    return attr.arguments ? attr.arguments.filter(function(arg) {
-                        return !arg.optional;
-                    }).length : 0;
-                })
-                .reduce(function(m, n) { return Math.min(m, n); });
-            }
-            assert_equals(window[this.name].length, expected_length, "wrong value for " + this.name + ".length");
+            var expected_length = minOverloadLength(constructors);
+            assert_equals(self[this.name].length, expected_length, "wrong value for " + this.name + ".length");
         }.bind(this), this.name + " interface object length");
     }
 
     // TODO: Test named constructors if I find any interfaces that have them.
 
     test(function()
     {
-        assert_own_property(window, this.name,
-                            "window does not have own property " + format_value(this.name));
+        // This function tests WebIDL as of 2015-01-21.
+        // https://heycam.github.io/webidl/#interface-object
 
-        if (this.has_extended_attribute("Callback")) {
-            assert_false("prototype" in window[this.name],
+        if (this.is_callback() && !this.has_constants()) {
+            return;
+        }
+
+        assert_own_property(self, this.name,
+                            "self does not have own property " + format_value(this.name));
+
+        if (this.is_callback()) {
+            assert_false("prototype" in self[this.name],
                          this.name + ' should not have a "prototype" property');
             return;
         }
 
-        // "The interface object must also have a property named “prototype”
-        // with attributes { [[Writable]]: false, [[Enumerable]]: false,
-        // [[Configurable]]: false } whose value is an object called the
-        // interface prototype object. This object has properties that
-        // correspond to the attributes and operations defined on the
-        // interface, and is described in more detail in section 4.5.3 below."
-        assert_own_property(window[this.name], "prototype",
+        // "An interface object for a non-callback interface must have a
+        // property named “prototype” with attributes { [[Writable]]: false,
+        // [[Enumerable]]: false, [[Configurable]]: false } whose value is an
+        // object called the interface prototype object. This object has
+        // properties that correspond to the regular attributes and regular
+        // operations defined on the interface, and is described in more detail
+        // in section 4.5.4 below."
+        assert_own_property(self[this.name], "prototype",
                             'interface "' + this.name + '" does not have own property "prototype"');
-        var desc = Object.getOwnPropertyDescriptor(window[this.name], "prototype");
+        var desc = Object.getOwnPropertyDescriptor(self[this.name], "prototype");
         assert_false("get" in desc, this.name + ".prototype has getter");
         assert_false("set" in desc, this.name + ".prototype has setter");
         assert_false(desc.writable, this.name + ".prototype is writable");
         assert_false(desc.enumerable, this.name + ".prototype is enumerable");
         assert_false(desc.configurable, this.name + ".prototype is configurable");
 
         // Next, test that the [[Prototype]] of the interface prototype object
         // is correct. (This is made somewhat difficult by the existence of
         // [NoInterfaceObject].)
         // TODO: Aryeh thinks there's at least other place in this file where
         //       we try to figure out if an interface prototype object is
         //       correct. Consolidate that code.
 
         // "The interface prototype object for a given interface A must have an
-        // internal [[Prototype]] property whose value is as follows:
-        // "If A is not declared to inherit from another interface, then the
-        // value of the internal [[Prototype]] property of A is the Array
-        // prototype object ([ECMA-262], section 15.4.4) if the interface was
-        // declared with ArrayClass, or the Object prototype object otherwise
+        // internal [[Prototype]] property whose value is returned from the
+        // following steps:
+        // "If A is declared with the [Global] or [PrimaryGlobal] extended
+        // attribute, and A supports named properties, then return the named
+        // properties object for A, as defined in section 4.5.5 below.
+        // "Otherwise, if A is declared to inherit from another interface, then
+        // return the interface prototype object for the inherited interface.
+        // "Otherwise, if A is declared with the [ArrayClass] extended
+        // attribute, then return %ArrayPrototype% ([ECMA-262], section
+        // 6.1.7.4).
+        // "Otherwise, return %ObjectPrototype% ([ECMA-262], section 6.1.7.4).
         // ([ECMA-262], section 15.2.4).
-        // "Otherwise, A does inherit from another interface. The value of the
-        // internal [[Prototype]] property of A is the interface prototype
-        // object for the inherited interface."
-        var inherit_interface, inherit_interface_has_interface_object;
-        if (this.base) {
-            inherit_interface = this.base;
-            inherit_interface_has_interface_object =
-                !this.array
-                     .members[inherit_interface]
-                     .has_extended_attribute("NoInterfaceObject");
-        } else if (this.has_extended_attribute('ArrayClass')) {
-            inherit_interface = 'Array';
-            inherit_interface_has_interface_object = true;
+        if (this.name === "Window") {
+            assert_class_string(Object.getPrototypeOf(self[this.name].prototype),
+                                'WindowProperties',
+                                'Class name for prototype of Window' +
+                                '.prototype is not "WindowProperties"');
         } else {
-            inherit_interface = 'Object';
-            inherit_interface_has_interface_object = true;
-        }
-        if (inherit_interface_has_interface_object) {
-            assert_own_property(window, inherit_interface,
-                                'should inherit from ' + inherit_interface + ', but window has no such property');
-            assert_own_property(window[inherit_interface], 'prototype',
-                                'should inherit from ' + inherit_interface + ', but that object has no "prototype" property');
-            assert_equals(Object.getPrototypeOf(window[this.name].prototype),
-                          window[inherit_interface].prototype,
-                          'prototype of ' + this.name + '.prototype is not ' + inherit_interface + '.prototype');
-        } else {
-            // We can't test that we get the correct object, because this is the
-            // only way to get our hands on it. We only test that its class
-            // string, at least, is correct.
-            assert_class_string(Object.getPrototypeOf(window[this.name].prototype),
-                                inherit_interface + 'Prototype',
-                                'Class name for prototype of ' + this.name +
-                                '.prototype is not "' + inherit_interface + 'Prototype"');
+            var inherit_interface, inherit_interface_has_interface_object;
+            if (this.base) {
+                inherit_interface = this.base;
+                inherit_interface_has_interface_object =
+                    !this.array
+                         .members[inherit_interface]
+                         .has_extended_attribute("NoInterfaceObject");
+            } else if (this.has_extended_attribute('ArrayClass')) {
+                inherit_interface = 'Array';
+                inherit_interface_has_interface_object = true;
+            } else {
+                inherit_interface = 'Object';
+                inherit_interface_has_interface_object = true;
+            }
+            if (inherit_interface_has_interface_object) {
+                assert_own_property(self, inherit_interface,
+                                    'should inherit from ' + inherit_interface + ', but self has no such property');
+                assert_own_property(self[inherit_interface], 'prototype',
+                                    'should inherit from ' + inherit_interface + ', but that object has no "prototype" property');
+                assert_equals(Object.getPrototypeOf(self[this.name].prototype),
+                              self[inherit_interface].prototype,
+                              'prototype of ' + this.name + '.prototype is not ' + inherit_interface + '.prototype');
+            } else {
+                // We can't test that we get the correct object, because this is the
+                // only way to get our hands on it. We only test that its class
+                // string, at least, is correct.
+                assert_class_string(Object.getPrototypeOf(self[this.name].prototype),
+                                    inherit_interface + 'Prototype',
+                                    'Class name for prototype of ' + this.name +
+                                    '.prototype is not "' + inherit_interface + 'Prototype"');
+            }
         }
 
         // "The class string of an interface prototype object is the
         // concatenation of the interface’s identifier and the string
         // “Prototype”."
-        assert_class_string(window[this.name].prototype, this.name + "Prototype",
+        assert_class_string(self[this.name].prototype, this.name + "Prototype",
                             "class string of " + this.name + ".prototype");
         // String() should end up calling {}.toString if nothing defines a
         // stringifier.
         if (!this.has_stringifier()) {
-            assert_equals(String(window[this.name].prototype), "[object " + this.name + "Prototype]",
+            assert_equals(String(self[this.name].prototype), "[object " + this.name + "Prototype]",
                     "String(" + this.name + ".prototype)");
         }
     }.bind(this), this.name + " interface: existence and properties of interface prototype object");
 
     test(function()
     {
-        assert_own_property(window, this.name,
-                            "window does not have own property " + format_value(this.name));
+        if (this.is_callback() && !this.has_constants()) {
+            return;
+        }
 
-        if (this.has_extended_attribute("Callback")) {
-            assert_false("prototype" in window[this.name],
+        assert_own_property(self, this.name,
+                            "self does not have own property " + format_value(this.name));
+
+        if (this.is_callback()) {
+            assert_false("prototype" in self[this.name],
                          this.name + ' should not have a "prototype" property');
             return;
         }
 
-        assert_own_property(window[this.name], "prototype",
+        assert_own_property(self[this.name], "prototype",
                             'interface "' + this.name + '" does not have own property "prototype"');
 
         // "If the [NoInterfaceObject] extended attribute was not specified on
         // the interface, then the interface prototype object must also have a
         // property named “constructor” with attributes { [[Writable]]: true,
         // [[Enumerable]]: false, [[Configurable]]: true } whose value is a
         // reference to the interface object for the interface."
-        assert_own_property(window[this.name].prototype, "constructor",
+        assert_own_property(self[this.name].prototype, "constructor",
                             this.name + '.prototype does not have own property "constructor"');
-        var desc = Object.getOwnPropertyDescriptor(window[this.name].prototype, "constructor");
+        var desc = Object.getOwnPropertyDescriptor(self[this.name].prototype, "constructor");
         assert_false("get" in desc, this.name + ".prototype.constructor has getter");
         assert_false("set" in desc, this.name + ".prototype.constructor has setter");
         assert_true(desc.writable, this.name + ".prototype.constructor is not writable");
         assert_false(desc.enumerable, this.name + ".prototype.constructor is enumerable");
         assert_true(desc.configurable, this.name + ".prototype.constructor in not configurable");
-        assert_equals(window[this.name].prototype.constructor, window[this.name],
+        assert_equals(self[this.name].prototype.constructor, self[this.name],
                       this.name + '.prototype.constructor is not the same object as ' + this.name);
     }.bind(this), this.name + ' interface: existence and properties of interface prototype object\'s "constructor" property');
 };
 
 //@}
 IdlInterface.prototype.test_member_const = function(member)
 //@{
 {
     test(function()
     {
-        assert_own_property(window, this.name,
-                            "window does not have own property " + format_value(this.name));
+        if (this.is_callback() && !this.has_constants()) {
+            return;
+        }
+
+        assert_own_property(self, this.name,
+                            "self does not have own property " + format_value(this.name));
 
         // "For each constant defined on an interface A, there must be
         // a corresponding property on the interface object, if it
         // exists."
-        assert_own_property(window[this.name], member.name);
+        assert_own_property(self[this.name], member.name);
         // "The value of the property is that which is obtained by
         // converting the constant’s IDL value to an ECMAScript
         // value."
-        assert_equals(window[this.name][member.name], constValue(member.value),
+        assert_equals(self[this.name][member.name], constValue(member.value),
                       "property has wrong value");
         // "The property has attributes { [[Writable]]: false,
         // [[Enumerable]]: true, [[Configurable]]: false }."
-        var desc = Object.getOwnPropertyDescriptor(window[this.name], member.name);
+        var desc = Object.getOwnPropertyDescriptor(self[this.name], member.name);
         assert_false("get" in desc, "property has getter");
         assert_false("set" in desc, "property has setter");
         assert_false(desc.writable, "property is writable");
         assert_true(desc.enumerable, "property is not enumerable");
         assert_false(desc.configurable, "property is configurable");
     }.bind(this), this.name + " interface: constant " + member.name + " on interface object");
     // "In addition, a property with the same characteristics must
     // exist on the interface prototype object."
     test(function()
     {
-        assert_own_property(window, this.name,
-                            "window does not have own property " + format_value(this.name));
+        if (this.is_callback() && !this.has_constants()) {
+            return;
+        }
 
-        if (this.has_extended_attribute("Callback")) {
-            assert_false("prototype" in window[this.name],
+        assert_own_property(self, this.name,
+                            "self does not have own property " + format_value(this.name));
+
+        if (this.is_callback()) {
+            assert_false("prototype" in self[this.name],
                          this.name + ' should not have a "prototype" property');
             return;
         }
 
-        assert_own_property(window[this.name], "prototype",
+        assert_own_property(self[this.name], "prototype",
                             'interface "' + this.name + '" does not have own property "prototype"');
 
-        assert_own_property(window[this.name].prototype, member.name);
-        assert_equals(window[this.name].prototype[member.name], constValue(member.value),
+        assert_own_property(self[this.name].prototype, member.name);
+        assert_equals(self[this.name].prototype[member.name], constValue(member.value),
                       "property has wrong value");
-        var desc = Object.getOwnPropertyDescriptor(window[this.name], member.name);
+        var desc = Object.getOwnPropertyDescriptor(self[this.name], member.name);
         assert_false("get" in desc, "property has getter");
         assert_false("set" in desc, "property has setter");
         assert_false(desc.writable, "property is writable");
         assert_true(desc.enumerable, "property is not enumerable");
         assert_false(desc.configurable, "property is configurable");
     }.bind(this), this.name + " interface: constant " + member.name + " on interface prototype object");
 };
 
 
 //@}
 IdlInterface.prototype.test_member_attribute = function(member)
 //@{
 {
     test(function()
     {
-        assert_own_property(window, this.name,
-                            "window does not have own property " + format_value(this.name));
-        assert_own_property(window[this.name], "prototype",
+        if (this.is_callback() && !this.has_constants()) {
+            return;
+        }
+
+        assert_own_property(self, this.name,
+                            "self does not have own property " + format_value(this.name));
+        assert_own_property(self[this.name], "prototype",
                             'interface "' + this.name + '" does not have own property "prototype"');
 
         if (member["static"]) {
-            assert_own_property(window[this.name], member.name,
+            assert_own_property(self[this.name], member.name,
                 "The interface object must have a property " +
                 format_value(member.name));
-        }
-        else
-        {
-            assert_true(member.name in window[this.name].prototype,
+        } else if (this.is_global()) {
+            assert_own_property(self, member.name,
+                "The global object must have a property " +
+                format_value(member.name));
+            assert_false(member.name in self[this.name].prototype,
+                "The prototype object must not have a property " +
+                format_value(member.name));
+
+            // Try/catch around the get here, since it can legitimately throw.
+            // If it does, we obviously can't check for equality with direct
+            // invocation of the getter.
+            var gotValue;
+            var propVal;
+            try {
+                propVal = self[member.name];
+                gotValue = true;
+            } catch (e) {
+                gotValue = false;
+            }
+            if (gotValue) {
+                var getter = Object.getOwnPropertyDescriptor(self, member.name).get;
+                assert_equals(typeof(getter), "function",
+                              format_value(member.name) + " must have a getter");
+                assert_equals(propVal, getter.call(undefined),
+                              "Gets on a global should not require an explicit this");
+            }
+            this.do_interface_attribute_asserts(self, member);
+        } else {
+            assert_true(member.name in self[this.name].prototype,
                 "The prototype object must have a property " +
                 format_value(member.name));
 
             if (!member.has_extended_attribute("LenientThis")) {
                 assert_throws(new TypeError(), function() {
-                    window[this.name].prototype[member.name];
+                    self[this.name].prototype[member.name];
                 }.bind(this), "getting property on prototype object must throw TypeError");
             } else {
-                assert_equals(window[this.name].prototype[member.name], undefined,
+                assert_equals(self[this.name].prototype[member.name], undefined,
                               "getting property on prototype object must return undefined");
             }
-            do_interface_attribute_asserts(window[this.name].prototype, member);
+            this.do_interface_attribute_asserts(self[this.name].prototype, member);
         }
     }.bind(this), this.name + " interface: attribute " + member.name);
 };
 
 //@}
 IdlInterface.prototype.test_member_operation = function(member)
 //@{
 {
     test(function()
     {
-        assert_own_property(window, this.name,
-                            "window does not have own property " + format_value(this.name));
+        if (this.is_callback() && !this.has_constants()) {
+            return;
+        }
 
-        if (this.has_extended_attribute("Callback")) {
-            assert_false("prototype" in window[this.name],
+        assert_own_property(self, this.name,
+                            "self does not have own property " + format_value(this.name));
+
+        if (this.is_callback()) {
+            assert_false("prototype" in self[this.name],
                          this.name + ' should not have a "prototype" property');
             return;
         }
 
-        assert_own_property(window[this.name], "prototype",
+        assert_own_property(self[this.name], "prototype",
                             'interface "' + this.name + '" does not have own property "prototype"');
 
         // "For each unique identifier of an operation defined on the
         // interface, there must be a corresponding property on the
         // interface prototype object (if it is a regular operation) or
         // the interface object (if it is a static operation), unless
         // the effective overload set for that identifier and operation
         // and with an argument count of 0 (for the ECMAScript language
         // binding) has no entries."
         //
-        var prototypeOrInterfaceObject;
+        var memberHolderObject;
         if (member["static"]) {
-            assert_own_property(window[this.name], member.name,
-                    "interface prototype object missing static operation");
-            prototypeOrInterfaceObject = window[this.name];
-        }
-        else
-        {
-            assert_own_property(window[this.name].prototype, member.name,
+            assert_own_property(self[this.name], member.name,
+                    "interface object missing static operation");
+            memberHolderObject = self[this.name];
+        } else if (this.is_global()) {
+            assert_own_property(self, member.name,
+                    "global object missing non-static operation");
+            memberHolderObject = self;
+        } else {
+            assert_own_property(self[this.name].prototype, member.name,
                     "interface prototype object missing non-static operation");
-            prototypeOrInterfaceObject = window[this.name].prototype;
+            memberHolderObject = self[this.name].prototype;
         }
 
-        var desc = Object.getOwnPropertyDescriptor(prototypeOrInterfaceObject, member.name);
-        // "The property has attributes { [[Writable]]: true,
-        // [[Enumerable]]: true, [[Configurable]]: true }."
-        assert_false("get" in desc, "property has getter");
-        assert_false("set" in desc, "property has setter");
-        assert_true(desc.writable, "property is not writable");
-        assert_true(desc.enumerable, "property is not enumerable");
-        assert_true(desc.configurable, "property is not configurable");
-        // "The value of the property is a Function object whose
-        // behavior is as follows . . ."
-        assert_equals(typeof prototypeOrInterfaceObject[member.name], "function",
-                      "property must be a function");
-        // "The value of the Function object’s “length” property is
-        // a Number determined as follows:
-        // ". . .
-        // "Return the length of the shortest argument list of the
-        // entries in S."
-        //
-        // TODO: Doesn't handle overloading or variadic arguments.
-        assert_equals(prototypeOrInterfaceObject[member.name].length,
-            member.arguments.filter(function(arg) {
-                return !arg.optional;
-            }).length,
-            "property has wrong .length");
+        this.do_member_operation_asserts(memberHolderObject, member);
+    }.bind(this), this.name + " interface: operation " + member.name +
+    "(" + member.arguments.map(function(m) { return m.idlType.idlType; }) +
+    ")");
+};
 
-        // Make some suitable arguments
-        var args = member.arguments.map(function(arg) {
-            return create_suitable_object(arg.idlType);
-        });
+//@}
+IdlInterface.prototype.do_member_operation_asserts = function(memberHolderObject, member)
+//@{
+{
+    var operationUnforgeable = member.isUnforgeable;
+    var desc = Object.getOwnPropertyDescriptor(memberHolderObject, member.name);
+    // "The property has attributes { [[Writable]]: B,
+    // [[Enumerable]]: true, [[Configurable]]: B }, where B is false if the
+    // operation is unforgeable on the interface, and true otherwise".
+    assert_false("get" in desc, "property has getter");
+    assert_false("set" in desc, "property has setter");
+    assert_equals(desc.writable, !operationUnforgeable,
+                  "property should be writable if and only if not unforgeable");
+    assert_true(desc.enumerable, "property is not enumerable");
+    assert_equals(desc.configurable, !operationUnforgeable,
+                  "property should be configurable if and only if not unforgeable");
+    // "The value of the property is a Function object whose
+    // behavior is as follows . . ."
+    assert_equals(typeof memberHolderObject[member.name], "function",
+                  "property must be a function");
+    // "The value of the Function object’s “length” property is
+    // a Number determined as follows:
+    // ". . .
+    // "Return the length of the shortest argument list of the
+    // entries in S."
+    assert_equals(memberHolderObject[member.name].length,
+        minOverloadLength(this.members.filter(function(m) {
+            return m.type == "operation" && m.name == member.name;
+        })),
+        "property has wrong .length");
 
-        // "Let O be a value determined as follows:
-        // ". . .
-        // "Otherwise, throw a TypeError."
-        // This should be hit if the operation is not static, there is
-        // no [ImplicitThis] attribute, and the this value is null.
-        //
-        // TODO: We currently ignore the [ImplicitThis] case.
-        if (!member["static"]) {
+    // Make some suitable arguments
+    var args = member.arguments.map(function(arg) {
+        return create_suitable_object(arg.idlType);
+    });
+
+    // "Let O be a value determined as follows:
+    // ". . .
+    // "Otherwise, throw a TypeError."
+    // This should be hit if the operation is not static, there is
+    // no [ImplicitThis] attribute, and the this value is null.
+    //
+    // TODO: We currently ignore the [ImplicitThis] case.  Except we manually
+    // check for globals, since otherwise we'll invoke window.close().  And we
+    // have to skip this test for anything that on the proto chain of "self",
+    // since that does in fact have implicit-this behavior.
+    if (!member["static"]) {
+        if (!this.is_global() &&
+            memberHolderObject[member.name] != self[member.name])
+        {
             assert_throws(new TypeError(), function() {
-                window[this.name].prototype[member.name].apply(null, args);
+                memberHolderObject[member.name].apply(null, args);
             }, "calling operation with this = null didn't throw TypeError");
         }
 
         // ". . . If O is not null and is also not a platform object
         // that implements interface I, throw a TypeError."
         //
         // TODO: Test a platform object that implements some other
         // interface.  (Have to be sure to get inheritance right.)
         assert_throws(new TypeError(), function() {
-            window[this.name].prototype[member.name].apply({}, args);
+            memberHolderObject[member.name].apply({}, args);
         }, "calling operation with this = {} didn't throw TypeError");
-    }.bind(this), this.name + " interface: operation " + member.name +
-    "(" + member.arguments.map(function(m) { return m.idlType.idlType; }) +
-    ")");
-};
+    }
+}
 
 //@}
 IdlInterface.prototype.test_member_stringifier = function(member)
 //@{
 {
     test(function()
     {
-        assert_own_property(window, this.name,
-                            "window does not have own property " + format_value(this.name));
+        if (this.is_callback() && !this.has_constants()) {
+            return;
+        }
 
-        if (this.has_extended_attribute("Callback")) {
-            assert_false("prototype" in window[this.name],
+        assert_own_property(self, this.name,
+                            "self does not have own property " + format_value(this.name));
+
+        if (this.is_callback()) {
+            assert_false("prototype" in self[this.name],
                          this.name + ' should not have a "prototype" property');
             return;
         }
 
-        assert_own_property(window[this.name], "prototype",
+        assert_own_property(self[this.name], "prototype",
                             'interface "' + this.name + '" does not have own property "prototype"');
 
         // ". . . the property exists on the interface prototype object."
-        var interfacePrototypeObject = window[this.name].prototype;
-        assert_own_property(window[this.name].prototype, "toString",
+        var interfacePrototypeObject = self[this.name].prototype;
+        assert_own_property(self[this.name].prototype, "toString",
                 "interface prototype object missing non-static operation");
 
+        var stringifierUnforgeable = member.isUnforgeable;
         var desc = Object.getOwnPropertyDescriptor(interfacePrototypeObject, "toString");
         // "The property has attributes { [[Writable]]: B,
         // [[Enumerable]]: true, [[Configurable]]: B }, where B is false if the
         // stringifier is unforgeable on the interface, and true otherwise."
         assert_false("get" in desc, "property has getter");
         assert_false("set" in desc, "property has setter");
-        assert_true(desc.writable, "property is not writable");
+        assert_equals(desc.writable, !stringifierUnforgeable,
+                      "property should be writable if and only if not unforgeable");
         assert_true(desc.enumerable, "property is not enumerable");
-        assert_true(desc.configurable, "property is not configurable");
+        assert_equals(desc.configurable, !stringifierUnforgeable,
+                      "property should be configurable if and only if not unforgeable");
         // "The value of the property is a Function object, which behaves as
         // follows . . ."
         assert_equals(typeof interfacePrototypeObject.toString, "function",
                       "property must be a function");
         // "The value of the Function object’s “length” property is the Number
         // value 0."
         assert_equals(interfacePrototypeObject.toString.length, 0,
             "property has wrong .length");
 
         // "Let O be the result of calling ToObject on the this value."
         assert_throws(new TypeError(), function() {
-            window[this.name].prototype.toString.apply(null, []);
+            self[this.name].prototype.toString.apply(null, []);
         }, "calling stringifier with this = null didn't throw TypeError");
 
         // "If O is not an object that implements the interface on which the
         // stringifier was declared, then throw a TypeError."
         //
         // TODO: Test a platform object that implements some other
         // interface.  (Have to be sure to get inheritance right.)
         assert_throws(new TypeError(), function() {
-            window[this.name].prototype.toString.apply({}, []);
+            self[this.name].prototype.toString.apply({}, []);
         }, "calling stringifier with this = {} didn't throw TypeError");
     }.bind(this), this.name + " interface: stringifier");
 };
 
 //@}
 IdlInterface.prototype.test_members = function()
 //@{
 {
@@ -1448,26 +1239,32 @@ IdlInterface.prototype.test_members = fu
         switch (member.type) {
         case "const":
             this.test_member_const(member);
             break;
 
         case "attribute":
             // For unforgeable attributes, we do the checks in
             // test_interface_of instead.
-            if (!member.has_extended_attribute("Unforgeable")) {
+            if (!member.isUnforgeable)
+            {
                 this.test_member_attribute(member);
             }
             break;
 
         case "operation":
             // TODO: Need to correctly handle multiple operations with the same
             // identifier.
+            // For unforgeable operations, we do the checks in
+            // test_interface_of instead.
             if (member.name) {
-                this.test_member_operation(member);
+                if (!member.isUnforgeable)
+                {
+                    this.test_member_operation(member);
+                }
             } else if (member.stringifier) {
                 this.test_member_stringifier(member);
             }
             break;
 
         default:
             // TODO: check more member types.
             break;
@@ -1526,27 +1323,27 @@ IdlInterface.prototype.test_primary_inte
     // least looks correct, even if we can't test that it's actually correct.
     if (!this.has_extended_attribute("NoInterfaceObject")
     && (typeof obj != expected_typeof || obj instanceof Object))
     {
         test(function()
         {
             assert_equals(exception, null, "Unexpected exception when evaluating object");
             assert_equals(typeof obj, expected_typeof, "wrong typeof object");
-            assert_own_property(window, this.name,
-                                "window does not have own property " + format_value(this.name));
-            assert_own_property(window[this.name], "prototype",
+            assert_own_property(self, this.name,
+                                "self does not have own property " + format_value(this.name));
+            assert_own_property(self[this.name], "prototype",
                                 'interface "' + this.name + '" does not have own property "prototype"');
 
             // "The value of the internal [[Prototype]] property of the
             // platform object is the interface prototype object of the primary
             // interface from the platform object’s associated global
             // environment."
             assert_equals(Object.getPrototypeOf(obj),
-                          window[this.name].prototype,
+                          self[this.name].prototype,
                           desc + "'s prototype is not " + this.name + ".prototype");
         }.bind(this), this.name + " must be primary interface of " + desc);
     }
 
     // "The class string of a platform object that implements one or more
     // interfaces must be the identifier of the primary interface of the
     // platform object."
     test(function()
@@ -1566,36 +1363,54 @@ IdlInterface.prototype.test_interface_of
 //@{
 {
     // TODO: Indexed and named properties, more checks on interface members
     this.already_tested = true;
 
     for (var i = 0; i < this.members.length; i++)
     {
         var member = this.members[i];
-        if (member.has_extended_attribute("Unforgeable"))
+        if (member.type == "attribute" && member.isUnforgeable)
         {
             test(function()
             {
                 assert_equals(exception, null, "Unexpected exception when evaluating object");
                 assert_equals(typeof obj, expected_typeof, "wrong typeof object");
-                do_interface_attribute_asserts(obj, member);
+                this.do_interface_attribute_asserts(obj, member);
+            }.bind(this), this.name + " interface: " + desc + ' must have own property "' + member.name + '"');
+        }
+        else if (member.type == "operation" &&
+                 member.name &&
+                 member.isUnforgeable)
+        {
+            test(function()
+            {
+                assert_equals(exception, null, "Unexpected exception when evaluating object");
+                assert_equals(typeof obj, expected_typeof, "wrong typeof object");
+                assert_own_property(obj, member.name,
+                                    "Doesn't have the unforgeable operation property");
+                this.do_member_operation_asserts(obj, member);
             }.bind(this), this.name + " interface: " + desc + ' must have own property "' + member.name + '"');
         }
         else if ((member.type == "const"
         || member.type == "attribute"
         || member.type == "operation")
         && member.name)
         {
             test(function()
             {
                 assert_equals(exception, null, "Unexpected exception when evaluating object");
                 assert_equals(typeof obj, expected_typeof, "wrong typeof object");
                 if (!member["static"]) {
-                    assert_inherits(obj, member.name);
+                    if (!this.is_global()) {
+                        assert_inherits(obj, member.name);
+                    } else {
+                        assert_own_property(obj, member.name);
+                    }
+
                     if (member.type == "const")
                     {
                         assert_equals(obj[member.name], constValue(member.value));
                     }
                     if (member.type == "attribute")
                     {
                         // Attributes are accessor properties, so they might
                         // legitimately throw an exception rather than returning
@@ -1626,29 +1441,32 @@ IdlInterface.prototype.test_interface_of
         // TODO: Test passing arguments of the wrong type.
         if (member.type == "operation" && member.name && member.arguments.length)
         {
             test(function()
             {
                 assert_equals(exception, null, "Unexpected exception when evaluating object");
                 assert_equals(typeof obj, expected_typeof, "wrong typeof object");
                 if (!member["static"]) {
-                    assert_inherits(obj, member.name);
+                    if (!this.is_global() && !member.isUnforgeable) {
+                        assert_inherits(obj, member.name);
+                    } else {
+                        assert_own_property(obj, member.name);
+                    }
                 }
                 else
                 {
                     assert_false(member.name in obj);
                 }
+
+                var minLength = minOverloadLength(this.members.filter(function(m) {
+                    return m.type == "operation" && m.name == member.name;
+                }));
                 var args = [];
-                for (var i = 0; i < member.arguments.length; i++)
-                {
-                    if (member.arguments[i].optional)
-                    {
-                        break;
-                    }
+                for (var i = 0; i < minLength; i++) {
                     assert_throws(new TypeError(), function()
                     {
                         obj[member.name].apply(obj, args);
                     }.bind(this), "Called with " + i + " arguments");
 
                     args.push(create_suitable_object(member.arguments[i].idlType));
                 }
             }.bind(this), this.name + " interface: calling " + member.name +
@@ -1668,92 +1486,117 @@ IdlInterface.prototype.has_stringifier =
     if (this.base &&
         this.array.members[this.base].has_stringifier()) {
         return true;
     }
     return false;
 };
 
 //@}
-function do_interface_attribute_asserts(obj, member)
+IdlInterface.prototype.do_interface_attribute_asserts = function(obj, member)
 //@{
 {
-    // "For each attribute defined on the interface, there must exist a
-    // corresponding property. If the attribute was declared with the
-    // [Unforgeable] extended attribute, then the property exists on every
-    // object that implements the interface.  Otherwise, it exists on the
-    // interface’s interface prototype object."
-    //
-    // This is called by test_self() with the prototype as obj, and by
-    // test_interface_of() with the object as obj.
+    // This function tests WebIDL as of 2015-01-27.
+    // TODO: Consider [Exposed].
+
+    // This is called by test_member_attribute() with the prototype as obj if
+    // it is not a global, and the global otherwise, and by test_interface_of()
+    // with the object as obj.
+
+    // "For each exposed attribute of the interface, whether it was declared on
+    // the interface itself or one of its consequential interfaces, there MUST
+    // exist a corresponding property. The characteristics of this property are
+    // as follows:"
+
+    // "The name of the property is the identifier of the attribute."
     assert_own_property(obj, member.name);
 
     // "The property has attributes { [[Get]]: G, [[Set]]: S, [[Enumerable]]:
     // true, [[Configurable]]: configurable }, where:
     // "configurable is false if the attribute was declared with the
     // [Unforgeable] extended attribute and true otherwise;
     // "G is the attribute getter, defined below; and
     // "S is the attribute setter, also defined below."
     var desc = Object.getOwnPropertyDescriptor(obj, member.name);
     assert_false("value" in desc, 'property descriptor has value but is supposed to be accessor');
     assert_false("writable" in desc, 'property descriptor has "writable" field but is supposed to be accessor');
     assert_true(desc.enumerable, "property is not enumerable");
-    if (member.has_extended_attribute("Unforgeable"))
+    if (member.isUnforgeable)
     {
         assert_false(desc.configurable, "[Unforgeable] property must not be configurable");
     }
     else
     {
         assert_true(desc.configurable, "property must be configurable");
     }
 
+
     // "The attribute getter is a Function object whose behavior when invoked
-    // is as follows:
-    // "...
+    // is as follows:"
+    assert_equals(typeof desc.get, "function", "getter must be Function");
+
+    // "If the attribute is a regular attribute, then:"
+    if (!member["static"]) {
+        // "If O is not a platform object that implements I, then:
+        // "If the attribute was specified with the [LenientThis] extended
+        // attribute, then return undefined.
+        // "Otherwise, throw a TypeError."
+        if (!member.has_extended_attribute("LenientThis")) {
+            assert_throws(new TypeError(), function() {
+                desc.get.call({});
+            }.bind(this), "calling getter on wrong object type must throw TypeError");
+        } else {
+            assert_equals(desc.get.call({}), undefined,
+                          "calling getter on wrong object type must return undefined");
+        }
+    }
+
     // "The value of the Function object’s “length” property is the Number
     // value 0."
-    assert_equals(typeof desc.get, "function", "getter must be Function");
     assert_equals(desc.get.length, 0, "getter length must be 0");
-    if (!member.has_extended_attribute("LenientThis")) {
-        assert_throws(new TypeError(), function() {
-            desc.get.call({});
-        }.bind(this), "calling getter on wrong object type must throw TypeError");
-    } else {
-        assert_equals(desc.get.call({}), undefined,
-                      "calling getter on wrong object type must return undefined");
-    }
+
 
     // TODO: Test calling setter on the interface prototype (should throw
     // TypeError in most cases).
-    //
-    // "The attribute setter is undefined if the attribute is declared readonly
-    // and has neither a [PutForwards] nor a [Replaceable] extended attribute
-    // declared on it.  Otherwise, it is a Function object whose behavior when
-    // invoked is as follows:
-    // "...
-    // "The value of the Function object’s “length” property is the Number
-    // value 1."
     if (member.readonly
     && !member.has_extended_attribute("PutForwards")
     && !member.has_extended_attribute("Replaceable"))
     {
+        // "The attribute setter is undefined if the attribute is declared
+        // readonly and has neither a [PutForwards] nor a [Replaceable]
+        // extended attribute declared on it."
         assert_equals(desc.set, undefined, "setter must be undefined for readonly attributes");
     }
     else
     {
+        // "Otherwise, it is a Function object whose behavior when
+        // invoked is as follows:"
         assert_equals(typeof desc.set, "function", "setter must be function for PutForwards, Replaceable, or non-readonly attributes");
+
+        // "If the attribute is a regular attribute, then:"
+        if (!member["static"]) {
+            // "If /validThis/ is false and the attribute was not specified
+            // with the [LenientThis] extended attribute, then throw a
+            // TypeError."
+            // "If the attribute is declared with a [Replaceable] extended
+            // attribute, then: ..."
+            // "If validThis is false, then return."
+            if (!member.has_extended_attribute("LenientThis")) {
+                assert_throws(new TypeError(), function() {
+                    desc.set.call({});
+                }.bind(this), "calling setter on wrong object type must throw TypeError");
+            } else {
+                assert_equals(desc.set.call({}), undefined,
+                              "calling setter on wrong object type must return undefined");
+            }
+        }
+
+        // "The value of the Function object’s “length” property is the Number
+        // value 1."
         assert_equals(desc.set.length, 1, "setter length must be 1");
-        if (!member.has_extended_attribute("LenientThis")) {
-            assert_throws(new TypeError(), function() {
-                desc.set.call({});
-            }.bind(this), "calling setter on wrong object type must throw TypeError");
-        } else {
-            assert_equals(desc.set.call({}), undefined,
-                          "calling setter on wrong object type must return undefined");
-        }
     }
 }
 //@}
 
 /// IdlInterfaceMember ///
 function IdlInterfaceMember(obj)
 //@{
 {
@@ -1765,16 +1608,18 @@ function IdlInterfaceMember(obj)
     for (var k in obj)
     {
         this[k] = obj[k];
     }
     if (!("extAttrs" in this))
     {
         this.extAttrs = [];
     }
+
+    this.isUnforgeable = this.has_extended_attribute("Unforgeable");
 }
 
 //@}
 IdlInterfaceMember.prototype = Object.create(IdlObject.prototype);
 
 /// Internal helper functions ///
 function create_suitable_object(type)
 //@{
--- a/dom/imptests/moz.build
+++ b/dom/imptests/moz.build
@@ -27,10 +27,9 @@ MOCHITEST_MANIFESTS += [
     'failures/html/html/semantics/forms/the-select-element/mochitest.ini',
     'failures/html/html/semantics/scripting-1/the-script-element/mochitest.ini',
     'failures/html/html/semantics/tabular-data/the-table-element/mochitest.ini',
     'failures/html/html/webappapis/atob/mochitest.ini',
     'failures/html/js/builtins/mochitest.ini',
     'failures/html/microdata/microdata-dom-api/mochitest.ini',
     'failures/html/typedarrays/mochitest.ini',
     'failures/webapps/WebStorage/tests/submissions/Infraware/mochitest.ini',
-    'failures/webapps/XMLHttpRequest/tests/submissions/Ms2ger/mochitest.ini',
 ]
--- a/dom/imptests/testharness.css
+++ b/dom/imptests/testharness.css
@@ -9,21 +9,16 @@ html {
 }
 
 #log .error,
 #log .error a {
   color: white;
   background: red;
 }
 
-#log pre {
-  border: 1px solid black;
-  padding: 1em;
-}
-
 section#summary {
     margin-bottom:1em;
 }
 
 table#results {
     border-collapse:collapse;
     table-layout:fixed;
     width:100%;
--- a/dom/imptests/testharness.js
+++ b/dom/imptests/testharness.js
@@ -17,17 +17,18 @@ policies and contribution forms [3].
     var debug = false;
     // default timeout is 10 seconds, test can override if needed
     var settings = {
         output:true,
         harness_timeout:{
             "normal":10000,
             "long":60000
         },
-        test_timeout:null
+        test_timeout:null,
+        message_events: ["start", "test_state", "result", "completion"]
     };
 
     var xhtml_ns = "http://www.w3.org/1999/xhtml";
 
     /*
      * TestEnvironment is an abstraction for the environment in which the test
      * harness is used. Each implementation of a test environment has to provide
      * the following interface:
@@ -59,30 +60,73 @@ policies and contribution forms [3].
      * apisample11.html and apisample12.html.
      */
     function WindowTestEnvironment() {
         this.name_counter = 0;
         this.window_cache = null;
         this.output_handler = null;
         this.all_loaded = false;
         var this_obj = this;
+        this.message_events = [];
+
+        this.message_functions = {
+            start: [add_start_callback, remove_start_callback,
+                    function (properties) {
+                        this_obj._dispatch("start_callback", [properties],
+                                           {type: "start", properties: properties});
+                    }],
+
+            test_state: [add_test_state_callback, remove_test_state_callback,
+                         function(test) {
+                             this_obj._dispatch("test_state_callback", [test],
+                                                {type: "test_state",
+                                                 test: test.structured_clone()});
+                         }],
+            result: [add_result_callback, remove_result_callback,
+                     function (test) {
+                         this_obj.output_handler.show_status();
+                         this_obj._dispatch("result_callback", [test],
+                                            {type: "result",
+                                             test: test.structured_clone()});
+                     }],
+            completion: [add_completion_callback, remove_completion_callback,
+                         function (tests, harness_status) {
+                             var cloned_tests = map(tests, function(test) {
+                                 return test.structured_clone();
+                             });
+                             this_obj._dispatch("completion_callback", [tests, harness_status],
+                                                {type: "complete",
+                                                 tests: cloned_tests,
+                                                 status: harness_status.structured_clone()});
+                         }]
+        }
+
         on_event(window, 'load', function() {
             this_obj.all_loaded = true;
         });
     }
 
     WindowTestEnvironment.prototype._dispatch = function(selector, callback_args, message_arg) {
         this._forEach_windows(
-                function(w, is_same_origin) {
-                    if (is_same_origin && selector in w) {
+                function(w, same_origin) {
+                    if (same_origin) {
                         try {
-                            w[selector].apply(undefined, callback_args);
-                        } catch (e) {
-                            if (debug) {
-                                throw e;
+                            var has_selector = selector in w;
+                        } catch(e) {
+                            // If document.domain was set at some point same_origin can be
+                            // wrong and the above will fail.
+                            has_selector = false;
+                        }
+                        if (has_selector) {
+                            try {
+                                w[selector].apply(undefined, callback_args);
+                            } catch (e) {
+                                if (debug) {
+                                    throw e;
+                                }
                             }
                         }
                     }
                     if (supports_post_message(w) && w !== self) {
                         w.postMessage(message_arg, "*");
                     }
                 });
     };
@@ -136,51 +180,63 @@ policies and contribution forms [3].
                 });
     };
 
     WindowTestEnvironment.prototype.on_tests_ready = function() {
         var output = new Output();
         this.output_handler = output;
 
         var this_obj = this;
+
         add_start_callback(function (properties) {
             this_obj.output_handler.init(properties);
-            this_obj._dispatch("start_callback", [properties],
-                           { type: "start", properties: properties });
         });
+
         add_test_state_callback(function(test) {
             this_obj.output_handler.show_status();
-            this_obj._dispatch("test_state_callback", [test],
-                               { type: "test_state", test: test.structured_clone() });
         });
+
         add_result_callback(function (test) {
             this_obj.output_handler.show_status();
-            this_obj._dispatch("result_callback", [test],
-                               { type: "result", test: test.structured_clone() });
         });
+
         add_completion_callback(function (tests, harness_status) {
             this_obj.output_handler.show_results(tests, harness_status);
-            var cloned_tests = map(tests, function(test) { return test.structured_clone(); });
-            this_obj._dispatch("completion_callback", [tests, harness_status],
-                               { type: "complete", tests: cloned_tests,
-                                 status: harness_status.structured_clone() });
         });
+        this.setup_messages(settings.message_events);
     };
 
+    WindowTestEnvironment.prototype.setup_messages = function(new_events) {
+        var this_obj = this;
+        forEach(settings.message_events, function(x) {
+            var current_dispatch = this_obj.message_events.indexOf(x) !== -1;
+            var new_dispatch = new_events.indexOf(x) !== -1;
+            if (!current_dispatch && new_dispatch) {
+                this_obj.message_functions[x][0](this_obj.message_functions[x][2]);
+            } else if (current_dispatch && !new_dispatch) {
+                this_obj.message_functions[x][1](this_obj.message_functions[x][2]);
+            }
+        });
+        this.message_events = new_events;
+    }
+
     WindowTestEnvironment.prototype.next_default_test_name = function() {
         //Don't use document.title to work around an Opera bug in XHTML documents
         var title = document.getElementsByTagName("title")[0];
         var prefix = (title && title.firstChild && title.firstChild.data) || "Untitled";
         var suffix = this.name_counter > 0 ? " " + this.name_counter : "";
         this.name_counter++;
         return prefix + suffix;
     };
 
     WindowTestEnvironment.prototype.on_new_harness_properties = function(properties) {
         this.output_handler.setup(properties);
+        if (properties.hasOwnProperty("message_events")) {
+            this.setup_messages(properties.message_events);
+        }
     };
 
     WindowTestEnvironment.prototype.add_on_loaded_callback = function(callback) {
         on_event(window, 'load', callback);
     };
 
     WindowTestEnvironment.prototype.test_timeout = function() {
         var metas = document.getElementsByTagName("meta");
@@ -354,18 +410,30 @@ policies and contribution forms [3].
     function ServiceWorkerTestEnvironment() {
         WorkerTestEnvironment.call(this);
         this.all_loaded = false;
         this.on_loaded_callback = null;
         var this_obj = this;
         self.addEventListener("message",
                 function(event) {
                     if (event.data.type && event.data.type === "connect") {
-                        this_obj._add_message_port(event.ports[0]);
-                        event.ports[0].start();
+                        if (event.ports && event.ports[0]) {
+                            // If a MessageChannel was passed, then use it to
+                            // send results back to the main window.  This
+                            // allows the tests to work even if the browser
+                            // does not fully support MessageEvent.source in
+                            // ServiceWorkers yet.
+                            this_obj._add_message_port(event.ports[0]);
+                            event.ports[0].start();
+                        } else {
+                            // If there is no MessageChannel, then attempt to
+                            // use the MessageEvent.source to send results
+                            // back to the main window.
+                            this_obj._add_message_port(event.source);
+                        }
                     }
                 });
 
         // The oninstall event is received after the service worker script and
         // all imported scripts have been fetched and executed. It's the
         // equivalent of an onload event for a document. All tests should have
         // been added by the time this event is received, thus it's not
         // necessary to wait until the onactivate event.
@@ -459,16 +527,22 @@ policies and contribution forms [3].
                     if (value instanceof AssertionError) {
                         throw value;
                     }
                     assert(false, "promise_test", null,
                            "Unhandled rejection with value: ${value}", {value:value});
                 }));
     }
 
+    function promise_rejects(test, expected, promise) {
+        return promise.then(test.unreached_func("Should have rejected.")).catch(function(e) {
+            assert_throws(expected, function() { throw e });
+        });
+    }
+
     /**
      * This constructor helper allows DOM events to be handled using Promises,
      * which can make it a lot easier to test a very specific series of events,
      * including ensuring that unexpected events are not fired at any point.
      */
     function EventWatcher(test, watchedNode, eventTypes)
     {
         if (typeof eventTypes == 'string') {
@@ -574,16 +648,17 @@ policies and contribution forms [3].
     function on_event(object, event, callback)
     {
         object.addEventListener(event, callback, false);
     }
 
     expose(test, 'test');
     expose(async_test, 'async_test');
     expose(promise_test, 'promise_test');
+    expose(promise_rejects, 'promise_rejects');
     expose(generate_tests, 'generate_tests');
     expose(setup, 'setup');
     expose(done, 'done');
     expose(on_event, 'on_event');
 
     /*
      * Return a string truncated to the given length, with ... added at the end
      * if it was longer.
@@ -837,17 +912,17 @@ policies and contribution forms [3].
         assert(actual.length === expected.length,
                "assert_array_equals", description,
                "lengths differ, expected ${expected} got ${actual}",
                {expected:expected.length, actual:actual.length});
 
         for (var i = 0; i < actual.length; i++) {
             assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i),
                    "assert_array_equals", description,
-                   "property ${i}, property expected to be $expected but was $actual",
+                   "property ${i}, property expected to be ${expected} but was ${actual}",
                    {i:i, expected:expected.hasOwnProperty(i) ? "present" : "missing",
                    actual:actual.hasOwnProperty(i) ? "present" : "missing"});
             assert(same_value(expected[i], actual[i]),
                    "assert_array_equals", description,
                    "property ${i}, expected ${expected} but got ${actual}",
                    {i:i, expected:expected[i], actual:actual[i]});
         }
     }
@@ -928,17 +1003,17 @@ policies and contribution forms [3].
          * Test if a primitive number is less than or equal to another
          */
         assert(typeof actual === "number",
                "assert_less_than_equal", description,
                "expected a number but got a ${type_actual}",
                {type_actual:typeof actual});
 
         assert(actual <= expected,
-               "assert_less_than", description,
+               "assert_less_than_equal", description,
                "expected a number less than or equal to ${expected} but got ${actual}",
                {expected:expected, actual:actual});
     }
     expose(assert_less_than_equal, "assert_less_than_equal");
 
     function assert_greater_than_equal(actual, expected, description)
     {
         /*
@@ -1120,32 +1195,38 @@ policies and contribution forms [3].
                 NetworkError: 19,
                 AbortError: 20,
                 URLMismatchError: 21,
                 QuotaExceededError: 22,
                 TimeoutError: 23,
                 InvalidNodeTypeError: 24,
                 DataCloneError: 25,
 
+                EncodingError: 0,
+                NotReadableError: 0,
                 UnknownError: 0,
                 ConstraintError: 0,
                 DataError: 0,
                 TransactionInactiveError: 0,
                 ReadOnlyError: 0,
-                VersionError: 0
+                VersionError: 0,
+                OperationError: 0,
             };
 
             if (!(name in name_code_map)) {
                 throw new AssertionError('Test bug: unrecognized DOMException code "' + code + '" passed to assert_throws()');
             }
 
             var required_props = { code: name_code_map[name] };
 
             if (required_props.code === 0 ||
-               ("name" in e && e.name !== e.name.toUpperCase() && e.name !== "DOMException")) {
+               (typeof e == "object" &&
+                "name" in e &&
+                e.name !== e.name.toUpperCase() &&
+                e.name !== "DOMException")) {
                 // New style exception: also test the name property.
                 required_props.name = name;
             }
 
             //We'd like to test that e instanceof the appropriate interface,
             //but we can't, because we don't know what window it was created
             //in.  It might be an instanceof the appropriate interface on some
             //unknown other window.  TODO: Work around this somehow?
@@ -1209,16 +1290,17 @@ policies and contribution forms [3].
         var timeout = properties.timeout ? properties.timeout : settings.test_timeout;
         if (timeout !== null) {
             this.timeout_length = timeout * tests.timeout_multiplier;
         } else {
             this.timeout_length = null;
         }
 
         this.message = null;
+        this.stack = null;
 
         this.steps = [];
 
         this.cleanup_callbacks = [];
 
         tests.push(this);
     }
 
@@ -1245,16 +1327,17 @@ policies and contribution forms [3].
             msg = msg ? String(msg) : msg;
             this._structured_clone = merge({
                 name:String(this.name),
                 properties:merge({}, this.properties),
             }, Test.statuses);
         }
         this._structured_clone.status = this.status;
         this._structured_clone.message = this.message;
+        this._structured_clone.stack = this.stack;
         this._structured_clone.index = this.index;
         return this._structured_clone;
     };
 
     Test.prototype.step = function(func, this_obj)
     {
         if (this.phase > this.phases.STARTED) {
             return;
@@ -1277,25 +1360,20 @@ policies and contribution forms [3].
         }
 
         try {
             return func.apply(this_obj, Array.prototype.slice.call(arguments, 2));
         } catch (e) {
             if (this.phase >= this.phases.HAS_RESULT) {
                 return;
             }
-            var message = (typeof e === "object" && e !== null) ? e.message : e;
-            if (typeof e.stack != "undefined" && typeof e.message == "string") {
-                //Try to make it more informative for some exceptions, at least
-                //in Gecko and WebKit.  This results in a stack dump instead of
-                //just errors like "Cannot read property 'parentNode' of null"
-                //or "root is null".  Makes it a lot longer, of course.
-                message += "(stack: " + e.stack + ")";
-            }
-            this.set_status(this.FAIL, message);
+            var message = String((typeof e === "object" && e !== null) ? e.message : e);
+            var stack = e.stack ? e.stack : null;
+
+            this.set_status(this.FAIL, message, stack);
             this.phase = this.phases.HAS_RESULT;
             this.done();
         }
     };
 
     Test.prototype.step_func = function(func, this_obj)
     {
         var test_this = this;
@@ -1351,20 +1429,21 @@ policies and contribution forms [3].
             var this_obj = this;
             this.timeout_id = setTimeout(function()
                                          {
                                              this_obj.timeout();
                                          }, this.timeout_length);
         }
     };
 
-    Test.prototype.set_status = function(status, message)
+    Test.prototype.set_status = function(status, message, stack)
     {
         this.status = status;
         this.message = message;
+        this.stack = stack ? stack : null;
     };
 
     Test.prototype.timeout = function()
     {
         this.timeout_id = null;
         this.set_status(this.TIMEOUT, "Test timed out");
         this.phase = this.phases.HAS_RESULT;
         this.done();
@@ -1411,32 +1490,33 @@ policies and contribution forms [3].
         this.phase = this.phases.INITIAL;
         this.update_state_from(clone);
         tests.push(this);
     }
 
     RemoteTest.prototype.structured_clone = function() {
         var clone = {};
         Object.keys(this).forEach(
-                function(key) {
+                (function(key) {
                     if (typeof(this[key]) === "object") {
                         clone[key] = merge({}, this[key]);
                     } else {
                         clone[key] = this[key];
                     }
-                });
+                }).bind(this));
         clone.phases = merge({}, this.phases);
         return clone;
     };
 
     RemoteTest.prototype.cleanup = function() {};
     RemoteTest.prototype.phases = Test.prototype.phases;
     RemoteTest.prototype.update_state_from = function(clone) {
         this.status = clone.status;
         this.message = clone.message;
+        this.stack = clone.stack;
         if (this.phase === this.phases.INITIAL) {
             this.phase = this.phases.STARTED;
         }
     };
     RemoteTest.prototype.done = function() {
         this.phase = this.phases.COMPLETE;
     }
 
@@ -1450,25 +1530,34 @@ policies and contribution forms [3].
         this.tests = new Array();
 
         var this_obj = this;
         worker.onerror = function(error) { this_obj.worker_error(error); };
 
         var message_port;
 
         if (is_service_worker(worker)) {
-            // The ServiceWorker's implicit MessagePort is currently not
-            // reliably accessible from the ServiceWorkerGlobalScope due to
-            // Blink setting MessageEvent.source to null for messages sent via
-            // ServiceWorker.postMessage(). Until that's resolved, create an
-            // explicit MessageChannel and pass one end to the worker.
-            var message_channel = new MessageChannel();
-            message_port = message_channel.port1;
-            message_port.start();
-            worker.postMessage({type: "connect"}, [message_channel.port2]);
+            if (window.MessageChannel) {
+                // The ServiceWorker's implicit MessagePort is currently not
+                // reliably accessible from the ServiceWorkerGlobalScope due to
+                // Blink setting MessageEvent.source to null for messages sent
+                // via ServiceWorker.postMessage(). Until that's resolved,
+                // create an explicit MessageChannel and pass one end to the
+                // worker.
+                var message_channel = new MessageChannel();
+                message_port = message_channel.port1;
+                message_port.start();
+                worker.postMessage({type: "connect"}, [message_channel.port2]);
+            } else {
+                // If MessageChannel is not available, then try the
+                // ServiceWorker.postMessage() approach using MessageEvent.source
+                // on the other end.
+                message_port = navigator.serviceWorker;
+                worker.postMessage({type: "connect"});
+            }
         } else if (is_shared_worker(worker)) {
             message_port = worker.port;
         } else {
             message_port = worker;
         }
 
         // Keeping a reference to the worker until worker_done() is seen
         // prevents the Worker object and its MessageChannel from going away
@@ -1486,17 +1575,18 @@ policies and contribution forms [3].
     RemoteWorker.prototype.worker_error = function(error) {
         var message = error.message || String(error);
         var filename = (error.filename ? " " + error.filename: "");
         // FIXME: Display worker error states separately from main document
         // error state.
         this.worker_done({
             status: {
                 status: tests.status.ERROR,
-                message: "Error in worker" + filename + ": " + message
+                message: "Error in worker" + filename + ": " + message,
+                stack: error.stack
             }
         });
         error.preventDefault();
     };
 
     RemoteWorker.prototype.test_state = function(data) {
         var remote_test = this.tests[data.test.index];
         if (!remote_test) {
@@ -1514,16 +1604,17 @@ policies and contribution forms [3].
         tests.result(remote_test);
     };
 
     RemoteWorker.prototype.worker_done = function(data) {
         if (tests.status.status === null &&
             data.status.status !== data.status.OK) {
             tests.status.status = data.status.status;
             tests.status.message = data.status.message;
+            tests.status.stack = data.status.stack;
         }
         this.running = false;
         this.worker = null;
         if (tests.all_done()) {
             tests.complete();
         }
     };
 
@@ -1536,16 +1627,17 @@ policies and contribution forms [3].
     /*
      * Harness
      */
 
     function TestsStatus()
     {
         this.status = null;
         this.message = null;
+        this.stack = null;
     }
 
     TestsStatus.statuses = {
         OK:0,
         ERROR:1,
         TIMEOUT:2
     };
 
@@ -1553,17 +1645,18 @@ policies and contribution forms [3].
 
     TestsStatus.prototype.structured_clone = function()
     {
         if (!this._structured_clone) {
             var msg = this.message;
             msg = msg ? String(msg) : msg;
             this._structured_clone = merge({
                 status:this.status,
-                message:msg
+                message:msg,
+                stack:this.stack
             }, TestsStatus.statuses);
         }
         return this._structured_clone;
     };
 
     function Tests()
     {
         this.tests = [];
@@ -1643,16 +1736,17 @@ policies and contribution forms [3].
         }
 
         if (func) {
             try {
                 func();
             } catch (e) {
                 this.status.status = this.status.ERROR;
                 this.status.message = String(e);
+                this.status.stack = e.stack ? e.stack : null;
             }
         }
         this.set_timeout();
     };
 
     Tests.prototype.set_file_is_test = function() {
         if (this.tests.length > 0) {
             throw new Error("Tried to set file as test after creating a test");
@@ -1806,31 +1900,52 @@ policies and contribution forms [3].
     function add_start_callback(callback) {
         tests.start_callbacks.push(callback);
     }
 
     function add_test_state_callback(callback) {
         tests.test_state_callbacks.push(callback);
     }
 
-    function add_result_callback(callback)
-    {
+    function add_result_callback(callback) {
         tests.test_done_callbacks.push(callback);
     }
 
-    function add_completion_callback(callback)
-    {
-       tests.all_done_callbacks.push(callback);
+    function add_completion_callback(callback) {
+        tests.all_done_callbacks.push(callback);
     }
 
     expose(add_start_callback, 'add_start_callback');
     expose(add_test_state_callback, 'add_test_state_callback');
     expose(add_result_callback, 'add_result_callback');
     expose(add_completion_callback, 'add_completion_callback');
 
+    function remove(array, item) {
+        var index = array.indexOf(item);
+        if (index > -1) {
+            array.splice(index, 1);
+        }
+    }
+
+    function remove_start_callback(callback) {
+        remove(tests.start_callbacks, callback);
+    }
+
+    function remove_test_state_callback(callback) {
+        remove(tests.test_state_callbacks, callback);
+    }
+
+    function remove_result_callback(callback) {
+        remove(tests.test_done_callbacks, callback);
+    }
+
+    function remove_completion_callback(callback) {
+       remove(tests.all_done_callbacks, callback);
+    }
+
     /*
      * Output listener
     */
 
     function Output() {
         this.output_document = document;
         this.output_node = null;
         this.enabled = settings.output;
@@ -2000,16 +2115,19 @@ policies and contribution forms [3].
                                                 ["span", {"class":status_class(status)},
                                                  status
                                                 ],
                                                ]
                                               ]];
 
                                     if (harness_status.status === harness_status.ERROR) {
                                         rv[0].push(["pre", {}, harness_status.message]);
+                                        if (harness_status.stack) {
+                                            rv[0].push(["pre", {}, harness_status.stack]);
+                                        }
                                     }
                                     return rv;
                                 },
                                 ["p", {}, "Found ${num_tests} tests"],
                                 function() {
                                     var rv = [["div", {}]];
                                     var i = 0;
                                     while (status_text.hasOwnProperty(i)) {
@@ -2097,16 +2215,19 @@ policies and contribution forms [3].
                 escape_html(status_class(status_text[tests[i].status])) +
                 '"><td>' +
                 escape_html(status_text[tests[i].status]) +
                 "</td><td>" +
                 escape_html(tests[i].name) +
                 "</td><td>" +
                 (assertions ? escape_html(get_assertion(tests[i])) + "</td><td>" : "") +
                 escape_html(tests[i].message ? tests[i].message : " ") +
+                (tests[i].stack ? "<pre>" +
+                 escape_html(tests[i].stack) +
+                 "</pre>": "") +
                 "</td></tr>";
         }
         html += "</tbody></table>";
         try {
             log.lastChild.innerHTML = html;
         } catch (e) {
             log.appendChild(document.createElementNS(xhtml_ns, "p"))
                .textContent = "Setting innerHTML for the log threw an exception.";
@@ -2292,21 +2413,44 @@ policies and contribution forms [3].
                                    error, substitutions);
             throw new AssertionError(msg);
         }
     }
 
     function AssertionError(message)
     {
         this.message = message;
+        this.stack = this.get_stack();
     }
 
-    AssertionError.prototype.toString = function() {
-        return this.message;
-    };
+    AssertionError.prototype = Object.create(Error.prototype);
+
+    AssertionError.prototype.get_stack = function() {
+        var stack = new Error().stack;
+        if (!stack) {
+            try {
+                throw new Error();
+            } catch (e) {
+                stack = e.stack;
+            }
+        }
+        var lines = stack.split("\n");
+        var rv = [];
+        var re = /\/resources\/testharness\.js/;
+        var i = 0;
+        // Fire remove any preamble that doesn't match the regexp
+        while (!re.test(lines[i])) {
+            i++
+        }
+        // Then remove top frames in testharness.js itself
+        while (re.test(lines[i])) {
+            i++
+        }
+        return lines.slice(i).join("\n");
+    }
 
     function make_message(function_name, description, error, substitutions)
     {
         for (var p in substitutions) {
             if (substitutions.hasOwnProperty(p)) {
                 substitutions[p] = format_value(substitutions[p]);
             }
         }
@@ -2435,23 +2579,23 @@ policies and contribution forms [3].
     var tests = new Tests();
 
     addEventListener("error", function(e) {
         if (tests.file_is_test) {
             var test = tests.tests[0];
             if (test.phase >= test.phases.HAS_RESULT) {
                 return;
             }
-            var message = e.message;
-            test.set_status(test.FAIL, message);
+            test.set_status(test.FAIL, e.message, e.stack);
             test.phase = test.phases.HAS_RESULT;
             test.done();
             done();
         } else if (!tests.allow_uncaught_exception) {
             tests.status.status = tests.status.ERROR;
             tests.status.message = e.message;
+            tests.status.stack = e.stack;
         }
     });
 
     test_environment.on_tests_ready();
 
 })();
 // vim: set expandtab shiftwidth=4 tabstop=4:
--- a/dom/indexedDB/ActorsChild.cpp
+++ b/dom/indexedDB/ActorsChild.cpp
@@ -1257,24 +1257,36 @@ BackgroundFactoryRequestChild::HandleRes
   AssertIsOnOwningThread();
 
   mRequest->Reset();
 
   auto databaseActor =
     static_cast<BackgroundDatabaseChild*>(aResponse.databaseChild());
   MOZ_ASSERT(databaseActor);
 
-  databaseActor->EnsureDOMObject();
-
   IDBDatabase* database = databaseActor->GetDOMObject();
-  MOZ_ASSERT(database);
-
-  ResultHelper helper(mRequest, nullptr, database);
-
-  DispatchSuccessEvent(&helper);
+  if (!database) {
+    databaseActor->EnsureDOMObject();
+
+    database = databaseActor->GetDOMObject();
+    MOZ_ASSERT(database);
+
+    MOZ_ASSERT(!database->IsClosed());
+  }
+
+  if (database->IsClosed()) {
+    // If the database was closed already, which is only possible if we fired an
+    // "upgradeneeded" event, then we shouldn't fire a "success" event here.
+    // Instead we fire an error event with AbortErr.
+    DispatchErrorEvent(mRequest, NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
+  } else {
+    ResultHelper helper(mRequest, nullptr, database);
+
+    DispatchSuccessEvent(&helper);
+  }
 
   databaseActor->ReleaseDOMObject();
 
   return true;
 }
 
 bool
 BackgroundFactoryRequestChild::HandleResponse(
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -5338,21 +5338,20 @@ protected:
                                             const OptionalKeyRange& aKeyRange);
 
   static nsresult
   UpdateIndexValues(DatabaseConnection* aConnection,
                     const int64_t aObjectStoreId,
                     const Key& aObjectStoreKey,
                     const FallibleTArray<IndexDataValue>& aIndexValues);
 
-#ifdef DEBUG
-  static bool
+  static nsresult
   ObjectStoreHasIndexes(DatabaseConnection* aConnection,
-                        const int64_t aObjectStoreId);
-#endif
+                        const int64_t aObjectStoreId,
+                        bool* aHasIndexes);
 
 private:
   template <typename T>
   static nsresult
   GetStructuredCloneReadInfoFromSource(T* aSource,
                                        uint32_t aDataIndex,
                                        uint32_t aFileIdsIndex,
                                        FileManager* aFileManager,
@@ -6936,27 +6935,25 @@ private:
 
 class DeleteObjectStoreOp final
   : public VersionChangeTransactionOp
 {
   friend class VersionChangeTransaction;
 
   const nsRefPtr<FullObjectStoreMetadata> mMetadata;
   const bool mIsLastObjectStore;
-  const bool mObjectStoreHasIndexes;
 
 private:
   // Only created by VersionChangeTransaction.
   DeleteObjectStoreOp(VersionChangeTransaction* aTransaction,
                       FullObjectStoreMetadata* const aMetadata,
                       const bool aIsLastObjectStore)
     : VersionChangeTransactionOp(aTransaction)
     , mMetadata(aMetadata)
     , mIsLastObjectStore(aIsLastObjectStore)
-    , mObjectStoreHasIndexes(aMetadata->HasLiveIndexes())
   {
     MOZ_ASSERT(aMetadata->mCommonMetadata.id());
   }
 
   ~DeleteObjectStoreOp()
   { }
 
   virtual nsresult
@@ -7133,16 +7130,25 @@ protected:
     : TransactionDatabaseOperationBase(aTransaction)
     , mResponseSent(false)
   { }
 
   virtual
   ~NormalTransactionOp()
   { }
 
+  // An overload of DatabaseOperationBase's function that can avoid doing extra
+  // work on non-versionchange transactions.
+  static nsresult
+  ObjectStoreHasIndexes(NormalTransactionOp* aOp,
+                        DatabaseConnection* aConnection,
+                        const int64_t aObjectStoreId,
+                        const bool aMayHaveIndexes,
+                        bool* aHasIndexes);
+
   // Subclasses use this override to set the IPDL response value.
   virtual void
   GetResponse(RequestResponse& aResponse) = 0;
 
 private:
   virtual nsresult
   SendSuccessResult() override;
 
@@ -7174,17 +7180,17 @@ class ObjectStoreAddOrPutRequestOp final
 
   nsRefPtr<FileManager> mFileManager;
 
   Key mResponse;
   const nsCString mGroup;
   const nsCString mOrigin;
   const PersistenceType mPersistenceType;
   const bool mOverwrite;
-  const bool mObjectStoreHasIndexes;
+  const bool mObjectStoreMayHaveIndexes;
 
 private:
   // Only created by TransactionBase.
   ObjectStoreAddOrPutRequestOp(TransactionBase* aTransaction,
                                const RequestParams& aParams);
 
   ~ObjectStoreAddOrPutRequestOp()
   { }
@@ -7292,17 +7298,17 @@ private:
 
 class ObjectStoreDeleteRequestOp final
   : public NormalTransactionOp
 {
   friend class TransactionBase;
 
   const ObjectStoreDeleteParams mParams;
   ObjectStoreDeleteResponse mResponse;
-  const bool mObjectStoreHasIndexes;
+  const bool mObjectStoreMayHaveIndexes;
 
 private:
   ObjectStoreDeleteRequestOp(TransactionBase* aTransaction,
                              const ObjectStoreDeleteParams& aParams);
 
   ~ObjectStoreDeleteRequestOp()
   { }
 
@@ -7318,17 +7324,17 @@ private:
 
 class ObjectStoreClearRequestOp final
   : public NormalTransactionOp
 {
   friend class TransactionBase;
 
   const ObjectStoreClearParams mParams;
   ObjectStoreClearResponse mResponse;
-  const bool mObjectStoreHasIndexes;
+  const bool mObjectStoreMayHaveIndexes;
 
 private:
   ObjectStoreClearRequestOp(TransactionBase* aTransaction,
                             const ObjectStoreClearParams& aParams);
 
   ~ObjectStoreClearRequestOp()
   { }
 
@@ -13055,22 +13061,22 @@ TransactionBase::GetMetadataForObjectSto
   MOZ_ASSERT(aObjectStoreId);
 
   if (!aObjectStoreId) {
     return nullptr;
   }
 
   nsRefPtr<FullObjectStoreMetadata> metadata;
   if (!mDatabase->Metadata()->mObjectStores.Get(aObjectStoreId,
-                                                getter_AddRefs(metadata))) {
+                                                getter_AddRefs(metadata)) ||
+      metadata->mDeleted) {
     return nullptr;
   }
 
   MOZ_ASSERT(metadata->mCommonMetadata.id() == aObjectStoreId);
-  MOZ_ASSERT(!metadata->mDeleted);
 
   return metadata.forget();
 }
 
 already_AddRefed<FullIndexMetadata>
 TransactionBase::GetMetadataForIndexId(
                             FullObjectStoreMetadata* const aObjectStoreMetadata,
                             int64_t aIndexId) const
@@ -13078,22 +13084,22 @@ TransactionBase::GetMetadataForIndexId(
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aIndexId);
 
   if (!aIndexId) {
     return nullptr;
   }
 
   nsRefPtr<FullIndexMetadata> metadata;
-  if (!aObjectStoreMetadata->mIndexes.Get(aIndexId, getter_AddRefs(metadata))) {
+  if (!aObjectStoreMetadata->mIndexes.Get(aIndexId, getter_AddRefs(metadata)) ||
+      metadata->mDeleted) {
     return nullptr;
   }
 
   MOZ_ASSERT(metadata->mCommonMetadata.id() == aIndexId);
-  MOZ_ASSERT(!metadata->mDeleted);
 
   return metadata.forget();
 }
 
 void
 TransactionBase::NoteModifiedAutoIncrementObjectStore(
                                              FullObjectStoreMetadata* aMetadata)
 {
@@ -18058,18 +18064,26 @@ nsresult
 DatabaseOperationBase::DeleteObjectStoreDataTableRowsWithIndexes(
                                               DatabaseConnection* aConnection,
                                               const int64_t aObjectStoreId,
                                               const OptionalKeyRange& aKeyRange)
 {
   MOZ_ASSERT(aConnection);
   aConnection->AssertIsOnConnectionThread();
   MOZ_ASSERT(aObjectStoreId);
-  MOZ_ASSERT(ObjectStoreHasIndexes(aConnection, aObjectStoreId),
-             "Don't use this slow method if there are no indexes!");
+
+#ifdef DEBUG
+  {
+    bool hasIndexes = false;
+    MOZ_ASSERT(NS_SUCCEEDED(
+      ObjectStoreHasIndexes(aConnection, aObjectStoreId, &hasIndexes)));
+    MOZ_ASSERT(hasIndexes,
+               "Don't use this slow method if there are no indexes!");
+  }
+#endif
 
   PROFILER_LABEL("IndexedDB",
                  "DatabaseOperationBase::"
                  "DeleteObjectStoreDataTableRowsWithIndexes",
                  js::ProfileEntry::Category::STORAGE);
 
   const bool singleRowOnly =
     aKeyRange.type() == OptionalKeyRange::TSerializedKeyRange &&
@@ -18267,47 +18281,54 @@ DatabaseOperationBase::UpdateIndexValues
   rv = updateStmt->Execute();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
-#ifdef DEBUG
-
 // static
-bool
+nsresult
 DatabaseOperationBase::ObjectStoreHasIndexes(DatabaseConnection* aConnection,
-                                             const int64_t aObjectStoreId)
+                                             const int64_t aObjectStoreId,
+                                             bool* aHasIndexes)
 {
   MOZ_ASSERT(aConnection);
   aConnection->AssertIsOnConnectionThread();
   MOZ_ASSERT(aObjectStoreId);
+  MOZ_ASSERT(aHasIndexes);
 
   DatabaseConnection::CachedStatement stmt;
 
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-    aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
-      "SELECT id "
-        "FROM object_store_index "
-        "WHERE object_store_id = :object_store_id;"),
-      &stmt)));
-
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-    stmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"),
-                          aObjectStoreId)));
+  nsresult rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
+    "SELECT id "
+      "FROM object_store_index "
+      "WHERE object_store_id = :object_store_id "
+      "LIMIT 1;"),
+    &stmt);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"),
+                             aObjectStoreId);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
   bool hasResult;
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)));
-
-  return hasResult;
-}
-
-#endif // DEBUG
+  rv = stmt->ExecuteStep(&hasResult);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  *aHasIndexes = hasResult;
+  return NS_OK;
+}
 
 NS_IMPL_ISUPPORTS_INHERITED(DatabaseOperationBase,
                             nsRunnable,
                             mozIStorageProgressHandler)
 
 NS_IMETHODIMP
 DatabaseOperationBase::OnProgress(mozIStorageConnection* aConnection,
                                   bool* _retval)
@@ -19664,17 +19685,28 @@ OpenDatabaseOp::BeginVersionChange()
   mState = State_WaitingForOtherDatabasesToClose;
   return NS_OK;
 }
 
 void
 OpenDatabaseOp::NoteDatabaseClosed(Database* aDatabase)
 {
   AssertIsOnOwningThread();
-  MOZ_ASSERT(mState == State_WaitingForOtherDatabasesToClose);
+  MOZ_ASSERT(aDatabase);
+  MOZ_ASSERT(mState == State_WaitingForOtherDatabasesToClose ||
+             mState == State_DatabaseWorkVersionChange);
+
+  if (mState == State_DatabaseWorkVersionChange) {
+    MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
+    MOZ_ASSERT(mRequestedVersion >
+                 aDatabase->Metadata()->mCommonMetadata.version(),
+               "Must only be closing databases for a previous version!");
+    return;
+  }
+
   MOZ_ASSERT(!mMaybeBlockedDatabases.IsEmpty());
 
   bool actorDestroyed = IsActorDestroyed() || mDatabase->IsActorDestroyed();
 
   nsresult rv;
   if (actorDestroyed) {
     IDB_REPORT_INTERNAL_ERR();
     rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
@@ -21630,21 +21662,16 @@ DeleteObjectStoreOp::DoDatabaseWork(Data
         foundOtherObjectStore = true;
       }
     }
 
     MOZ_ASSERT_IF(mIsLastObjectStore,
                   foundThisObjectStore && !foundOtherObjectStore);
     MOZ_ASSERT_IF(!mIsLastObjectStore,
                   foundThisObjectStore && foundOtherObjectStore);
-
-    // Make sure |hasIndexes| is telling the truth.
-    MOZ_ASSERT(mObjectStoreHasIndexes ==
-                 ObjectStoreHasIndexes(aConnection,
-                                       mMetadata->mCommonMetadata.id()));
   }
 #endif
 
   DatabaseConnection::AutoSavepoint autoSave;
   nsresult rv = autoSave.Start(Transaction());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -21707,17 +21734,25 @@ DeleteObjectStoreOp::DoDatabaseWork(Data
       return rv;
     }
 
     rv = stmt->Execute();
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   } else {
-    if (mObjectStoreHasIndexes) {
+    bool hasIndexes;
+    rv = ObjectStoreHasIndexes(aConnection,
+                               mMetadata->mCommonMetadata.id(),
+                               &hasIndexes);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    if (hasIndexes) {
       rv = DeleteObjectStoreDataTableRowsWithIndexes(
         aConnection,
         mMetadata->mCommonMetadata.id(),
         void_t());
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
@@ -22785,16 +22820,58 @@ DeleteIndexOp::DoDatabaseWork(DatabaseCo
   rv = autoSave.Commit();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
+// static
+nsresult
+NormalTransactionOp::ObjectStoreHasIndexes(NormalTransactionOp* aOp,
+                                           DatabaseConnection* aConnection,
+                                           const int64_t aObjectStoreId,
+                                           const bool aMayHaveIndexes,
+                                           bool* aHasIndexes)
+{
+  MOZ_ASSERT(aOp);
+  MOZ_ASSERT(aConnection);
+  aConnection->AssertIsOnConnectionThread();
+  MOZ_ASSERT(aObjectStoreId);
+  MOZ_ASSERT(aHasIndexes);
+
+  bool hasIndexes;
+  if (aOp->Transaction()->GetMode() == IDBTransaction::VERSION_CHANGE &&
+      aMayHaveIndexes) {
+    // If this is a version change transaction then mObjectStoreMayHaveIndexes
+    // could be wrong (e.g. if a unique index failed to be created due to a
+    // constraint error). We have to check on this thread by asking the database
+    // directly.
+    nsresult rv =
+      DatabaseOperationBase::ObjectStoreHasIndexes(aConnection,
+                                                   aObjectStoreId,
+                                                   &hasIndexes);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  } else {
+    MOZ_ASSERT(NS_SUCCEEDED(
+      DatabaseOperationBase::ObjectStoreHasIndexes(aConnection,
+                                                   aObjectStoreId,
+                                                   &hasIndexes)));
+    MOZ_ASSERT(aMayHaveIndexes == hasIndexes);
+
+    hasIndexes = aMayHaveIndexes;
+  }
+
+  *aHasIndexes = hasIndexes;
+  return NS_OK;
+}
+
 nsresult
 NormalTransactionOp::SendSuccessResult()
 {
   AssertIsOnOwningThread();
 
   if (!IsActorDestroyed()) {
     RequestResponse response;
     GetResponse(response);
@@ -22861,37 +22938,48 @@ ObjectStoreAddOrPutRequestOp::ObjectStor
   : NormalTransactionOp(aTransaction)
   , mParams(aParams.type() == RequestParams::TObjectStoreAddParams ?
               aParams.get_ObjectStoreAddParams().commonParams() :
               aParams.get_ObjectStorePutParams().commonParams())
   , mGroup(aTransaction->GetDatabase()->Group())
   , mOrigin(aTransaction->GetDatabase()->Origin())
   , mPersistenceType(aTransaction->GetDatabase()->Type())
   , mOverwrite(aParams.type() == RequestParams::TObjectStorePutParams)
-  , mObjectStoreHasIndexes(false)
+  , mObjectStoreMayHaveIndexes(false)
 {
   MOZ_ASSERT(aParams.type() == RequestParams::TObjectStoreAddParams ||
              aParams.type() == RequestParams::TObjectStorePutParams);
 
   mMetadata =
     aTransaction->GetMetadataForObjectStoreId(mParams.objectStoreId());
   MOZ_ASSERT(mMetadata);
 
-  const_cast<bool&>(mObjectStoreHasIndexes) = mMetadata->HasLiveIndexes();
+  const_cast<bool&>(mObjectStoreMayHaveIndexes) = mMetadata->HasLiveIndexes();
 }
 
 nsresult
 ObjectStoreAddOrPutRequestOp::RemoveOldIndexDataValues(
                                                 DatabaseConnection* aConnection)
 {
   AssertIsOnConnectionThread();
   MOZ_ASSERT(aConnection);
   MOZ_ASSERT(mOverwrite);
   MOZ_ASSERT(!mResponse.IsUnset());
-  MOZ_ASSERT(mObjectStoreHasIndexes);
+
+#ifdef DEBUG
+  {
+    bool hasIndexes = false;
+    MOZ_ASSERT(NS_SUCCEEDED(
+      DatabaseOperationBase::ObjectStoreHasIndexes(aConnection,
+                                                   mParams.objectStoreId(),
+                                                   &hasIndexes)));
+    MOZ_ASSERT(hasIndexes,
+               "Don't use this slow method if there are no indexes!");
+  }
+#endif
 
   DatabaseConnection::CachedStatement indexValuesStmt;
   nsresult rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
     "SELECT index_data_values "
       "FROM object_data "
       "WHERE object_store_id = :object_store_id "
       "AND key = :key;"),
     &indexValuesStmt);
@@ -23093,44 +23181,52 @@ ObjectStoreAddOrPutRequestOp::Init(Trans
 
 nsresult
 ObjectStoreAddOrPutRequestOp::DoDatabaseWork(DatabaseConnection* aConnection)
 {
   MOZ_ASSERT(aConnection);
   aConnection->AssertIsOnConnectionThread();
   MOZ_ASSERT(aConnection->GetStorageConnection());
   MOZ_ASSERT_IF(mFileManager, !mStoredFileInfos.IsEmpty());
-  MOZ_ASSERT(mObjectStoreHasIndexes ==
-               ObjectStoreHasIndexes(aConnection, mParams.objectStoreId()));
 
   PROFILER_LABEL("IndexedDB",
                  "ObjectStoreAddOrPutRequestOp::DoDatabaseWork",
                  js::ProfileEntry::Category::STORAGE);
 
   if (NS_WARN_IF(IndexedDatabaseManager::InLowDiskSpaceMode())) {
     return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
   }
 
   DatabaseConnection::AutoSavepoint autoSave;
   nsresult rv = autoSave.Start(Transaction());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  bool objectStoreHasIndexes;
+  rv = ObjectStoreHasIndexes(this,
+                             aConnection,
+                             mParams.objectStoreId(),
+                             mObjectStoreMayHaveIndexes,
+                             &objectStoreHasIndexes);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
   // This will be the final key we use.
   Key& key = mResponse;
   key = mParams.key();
 
   const bool keyUnset = key.IsUnset();
   const int64_t osid = mParams.objectStoreId();
   const KeyPath& keyPath = mMetadata->mCommonMetadata.keyPath();
 
   // First delete old index_data_values if we're overwriting something and we
   // have indexes.
-  if (mOverwrite && !keyUnset && mObjectStoreHasIndexes) {
+  if (mOverwrite && !keyUnset && objectStoreHasIndexes) {
     rv = RemoveOldIndexDataValues(aConnection);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   // The "|| keyUnset" here is mostly a debugging tool. If a key isn't
   // specified we should never have a collision and so it shouldn't matter
@@ -23774,47 +23870,54 @@ ObjectStoreGetAllKeysRequestOp::GetRespo
   }
 }
 
 ObjectStoreDeleteRequestOp::ObjectStoreDeleteRequestOp(
                                          TransactionBase* aTransaction,
                                          const ObjectStoreDeleteParams& aParams)
   : NormalTransactionOp(aTransaction)
   , mParams(aParams)
-  , mObjectStoreHasIndexes(false)
+  , mObjectStoreMayHaveIndexes(false)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aTransaction);
 
   nsRefPtr<FullObjectStoreMetadata> metadata =
     aTransaction->GetMetadataForObjectStoreId(mParams.objectStoreId());
   MOZ_ASSERT(metadata);
 
-  const_cast<bool&>(mObjectStoreHasIndexes) = metadata->HasLiveIndexes();
+  const_cast<bool&>(mObjectStoreMayHaveIndexes) = metadata->HasLiveIndexes();
 }
 
 nsresult
 ObjectStoreDeleteRequestOp::DoDatabaseWork(DatabaseConnection* aConnection)
 {
   MOZ_ASSERT(aConnection);
   aConnection->AssertIsOnConnectionThread();
-  MOZ_ASSERT(mObjectStoreHasIndexes ==
-               ObjectStoreHasIndexes(aConnection, mParams.objectStoreId()));
-
   PROFILER_LABEL("IndexedDB",
                  "ObjectStoreDeleteRequestOp::DoDatabaseWork",
                  js::ProfileEntry::Category::STORAGE);
 
   DatabaseConnection::AutoSavepoint autoSave;
   nsresult rv = autoSave.Start(Transaction());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  if (mObjectStoreHasIndexes) {
+  bool objectStoreHasIndexes;
+  rv = ObjectStoreHasIndexes(this,
+                             aConnection,
+                             mParams.objectStoreId(),
+                             mObjectStoreMayHaveIndexes,
+                             &objectStoreHasIndexes);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (objectStoreHasIndexes) {
     rv = DeleteObjectStoreDataTableRowsWithIndexes(aConnection,
                                                    mParams.objectStoreId(),
                                                    mParams.keyRange());
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   } else {
     NS_NAMED_LITERAL_CSTRING(objectStoreIdString, "object_store_id");
@@ -23859,47 +23962,55 @@ ObjectStoreDeleteRequestOp::DoDatabaseWo
   return NS_OK;
 }
 
 ObjectStoreClearRequestOp::ObjectStoreClearRequestOp(
                                           TransactionBase* aTransaction,
                                           const ObjectStoreClearParams& aParams)
   : NormalTransactionOp(aTransaction)
   , mParams(aParams)
-  , mObjectStoreHasIndexes(false)
+  , mObjectStoreMayHaveIndexes(false)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aTransaction);
 
   nsRefPtr<FullObjectStoreMetadata> metadata =
     aTransaction->GetMetadataForObjectStoreId(mParams.objectStoreId());
   MOZ_ASSERT(metadata);
 
-  const_cast<bool&>(mObjectStoreHasIndexes) = metadata->HasLiveIndexes();
+  const_cast<bool&>(mObjectStoreMayHaveIndexes) = metadata->HasLiveIndexes();
 }
 
 nsresult
 ObjectStoreClearRequestOp::DoDatabaseWork(DatabaseConnection* aConnection)
 {
   MOZ_ASSERT(aConnection);
   aConnection->AssertIsOnConnectionThread();
-  MOZ_ASSERT(mObjectStoreHasIndexes ==
-               ObjectStoreHasIndexes(aConnection, mParams.objectStoreId()));
 
   PROFILER_LABEL("IndexedDB",
                  "ObjectStoreClearRequestOp::DoDatabaseWork",
                  js::ProfileEntry::Category::STORAGE);
 
   DatabaseConnection::AutoSavepoint autoSave;
   nsresult rv = autoSave.Start(Transaction());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  if (mObjectStoreHasIndexes) {
+  bool objectStoreHasIndexes;
+  rv = ObjectStoreHasIndexes(this,
+                             aConnection,
+                             mParams.objectStoreId(),
+                             mObjectStoreMayHaveIndexes,
+                             &objectStoreHasIndexes);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (objectStoreHasIndexes) {
     rv = DeleteObjectStoreDataTableRowsWithIndexes(aConnection,
                                                    mParams.objectStoreId(),
                                                    void_t());
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   } else {
     DatabaseConnection::CachedStatement stmt;
--- a/dom/indexedDB/IDBDatabase.cpp
+++ b/dom/indexedDB/IDBDatabase.cpp
@@ -515,25 +515,27 @@ already_AddRefed<IDBObjectStore>
 IDBDatabase::CreateObjectStore(
                             const nsAString& aName,
                             const IDBObjectStoreParameters& aOptionalParameters,
                             ErrorResult& aRv)
 {
   AssertIsOnOwningThread();
 
   IDBTransaction* transaction = IDBTransaction::GetCurrent();
-
   if (!transaction ||
       transaction->Database() != this ||
       transaction->GetMode() != IDBTransaction::VERSION_CHANGE) {
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
     return nullptr;
   }
 
-  MOZ_ASSERT(transaction->IsOpen());
+  if (!transaction->IsOpen()) {
+    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
+    return nullptr;
+  }
 
   KeyPath keyPath(0);
   if (NS_FAILED(KeyPath::Parse(aOptionalParameters.mKeyPath, &keyPath))) {
     aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
     return nullptr;
   }
 
   nsTArray<ObjectStoreSpec>& objectStores = mSpec->objectStores();
@@ -591,25 +593,27 @@ IDBDatabase::CreateObjectStore(
 }
 
 void
 IDBDatabase::DeleteObjectStore(const nsAString& aName, ErrorResult& aRv)
 {
   AssertIsOnOwningThread();
 
   IDBTransaction* transaction = IDBTransaction::GetCurrent();
-
   if (!transaction ||
       transaction->Database() != this ||
       transaction->GetMode() != IDBTransaction::VERSION_CHANGE) {
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
     return;
   }
 
-  MOZ_ASSERT(transaction->IsOpen());
+  if (!transaction->IsOpen()) {
+    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
+    return;
+  }
 
   nsTArray<ObjectStoreSpec>& specArray = mSpec->objectStores();
 
   int64_t objectStoreId = 0;
 
   for (uint32_t specCount = specArray.Length(), specIndex = 0;
        specIndex < specCount;
        specIndex++) {
--- a/dom/indexedDB/IDBIndex.cpp
+++ b/dom/indexedDB/IDBIndex.cpp
@@ -218,16 +218,21 @@ IDBIndex::GetKeyPath(JSContext* aCx,
 already_AddRefed<IDBRequest>
 IDBIndex::GetInternal(bool aKeyOnly,
                       JSContext* aCx,
                       JS::Handle<JS::Value> aKey,
                       ErrorResult& aRv)
 {
   AssertIsOnOwningThread();
 
+  if (mDeletedMetadata) {
+    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
+    return nullptr;
+  }
+
   IDBTransaction* transaction = mObjectStore->Transaction();
   if (!transaction->IsOpen()) {
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
     return nullptr;
   }
 
   nsRefPtr<IDBKeyRange> keyRange;
   aRv = IDBKeyRange::FromJSVal(aCx, aKey, getter_AddRefs(keyRange));
@@ -301,16 +306,21 @@ already_AddRefed<IDBRequest>
 IDBIndex::GetAllInternal(bool aKeysOnly,
                          JSContext* aCx,
                          JS::Handle<JS::Value> aKey,
                          const Optional<uint32_t>& aLimit,
                          ErrorResult& aRv)
 {
   AssertIsOnOwningThread();
 
+  if (mDeletedMetadata) {
+    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
+    return nullptr;
+  }
+
   IDBTransaction* transaction = mObjectStore->Transaction();
   if (!transaction->IsOpen()) {
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
     return nullptr;
   }
 
   nsRefPtr<IDBKeyRange> keyRange;
   aRv = IDBKeyRange::FromJSVal(aCx, aKey, getter_AddRefs(keyRange));
@@ -382,16 +392,21 @@ already_AddRefed<IDBRequest>
 IDBIndex::OpenCursorInternal(bool aKeysOnly,
                              JSContext* aCx,
                              JS::Handle<JS::Value> aRange,
                              IDBCursorDirection aDirection,
                              ErrorResult& aRv)
 {
   AssertIsOnOwningThread();
 
+  if (mDeletedMetadata) {
+    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
+    return nullptr;
+  }
+
   IDBTransaction* transaction = mObjectStore->Transaction();
   if (!transaction->IsOpen()) {
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
     return nullptr;
   }
 
   nsRefPtr<IDBKeyRange> keyRange;
   aRv = IDBKeyRange::FromJSVal(aCx, aRange, getter_AddRefs(keyRange));
@@ -478,16 +493,21 @@ IDBIndex::OpenCursorInternal(bool aKeysO
 
 already_AddRefed<IDBRequest>
 IDBIndex::Count(JSContext* aCx,
                 JS::Handle<JS::Value> aKey,
                 ErrorResult& aRv)
 {
   AssertIsOnOwningThread();
 
+  if (mDeletedMetadata) {
+    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
+    return nullptr;
+  }
+
   IDBTransaction* transaction = mObjectStore->Transaction();
   if (!transaction->IsOpen()) {
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
     return nullptr;
   }
 
   nsRefPtr<IDBKeyRange> keyRange;
   aRv = IDBKeyRange::FromJSVal(aCx, aKey, getter_AddRefs(keyRange));
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -1155,16 +1155,21 @@ IDBObjectStore::AddOrPut(JSContext* aCx,
                          bool aOverwrite,
                          bool aFromCursor,
                          ErrorResult& aRv)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aCx);
   MOZ_ASSERT_IF(aFromCursor, aOverwrite);
 
+  if (mDeletedSpec) {
+    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
+    return nullptr;
+  }
+
   if (!mTransaction->IsOpen()) {
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
     return nullptr;
   }
 
   if (!mTransaction->IsWriteAllowed()) {
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR);
     return nullptr;
@@ -1309,16 +1314,21 @@ already_AddRefed<IDBRequest>
 IDBObjectStore::GetAllInternal(bool aKeysOnly,
                                JSContext* aCx,
                                JS::Handle<JS::Value> aKey,
                                const Optional<uint32_t>& aLimit,
                                ErrorResult& aRv)
 {
   AssertIsOnOwningThread();
 
+  if (mDeletedSpec) {
+    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
+    return nullptr;
+  }
+
   if (!mTransaction->IsOpen()) {
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
     return nullptr;
   }
 
   nsRefPtr<IDBKeyRange> keyRange;
   aRv = IDBKeyRange::FromJSVal(aCx, aKey, getter_AddRefs(keyRange));
   if (NS_WARN_IF(aRv.Failed())) {
@@ -1381,16 +1391,21 @@ IDBObjectStore::GetAllInternal(bool aKey
   return request.forget();
 }
 
 already_AddRefed<IDBRequest>
 IDBObjectStore::Clear(ErrorResult& aRv)
 {
   AssertIsOnOwningThread();
 
+  if (mDeletedSpec) {
+    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
+    return nullptr;
+  }
+
   if (!mTransaction->IsOpen()) {
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
     return nullptr;
   }
 
   if (!mTransaction->IsWriteAllowed()) {
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR);
     return nullptr;
@@ -1567,16 +1582,21 @@ IDBObjectStore::IndexNames()
 
 already_AddRefed<IDBRequest>
 IDBObjectStore::Get(JSContext* aCx,
                     JS::Handle<JS::Value> aKey,
                     ErrorResult& aRv)
 {
   AssertIsOnOwningThread();
 
+  if (mDeletedSpec) {
+    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
+    return nullptr;
+  }
+
   if (!mTransaction->IsOpen()) {
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
     return nullptr;
   }
 
   nsRefPtr<IDBKeyRange> keyRange;
   aRv = IDBKeyRange::FromJSVal(aCx, aKey, getter_AddRefs(keyRange));
   if (aRv.Failed()) {
@@ -1615,16 +1635,21 @@ IDBObjectStore::Get(JSContext* aCx,
 already_AddRefed<IDBRequest>
 IDBObjectStore::DeleteInternal(JSContext* aCx,
                                JS::Handle<JS::Value> aKey,
                                bool aFromCursor,
                                ErrorResult& aRv)
 {
   AssertIsOnOwningThread();
 
+  if (mDeletedSpec) {
+    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
+    return nullptr;
+  }
+
   if (!mTransaction->IsOpen()) {
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
     return nullptr;
   }
 
   if (!mTransaction->IsWriteAllowed()) {
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR);
     return nullptr;
@@ -1708,22 +1733,25 @@ already_AddRefed<IDBIndex>
 IDBObjectStore::CreateIndexInternal(
                                   const nsAString& aName,
                                   const KeyPath& aKeyPath,
                                   const IDBIndexParameters& aOptionalParameters,
                                   ErrorResult& aRv)
 {
   AssertIsOnOwningThread();
 
-  IDBTransaction* transaction = IDBTransaction::GetCurrent();
+  if (mTransaction->GetMode() != IDBTransaction::VERSION_CHANGE ||
+      mDeletedSpec) {
+    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
+    return nullptr;
+  }
 
-  if (!transaction ||
-      transaction != mTransaction ||
-      mTransaction->GetMode() != IDBTransaction::VERSION_CHANGE) {
-    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
+  IDBTransaction* transaction = IDBTransaction::GetCurrent();
+  if (!transaction || transaction != mTransaction) {
+    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
     return nullptr;
   }
 
   MOZ_ASSERT(transaction->IsOpen());
 
   auto& indexes = const_cast<nsTArray<IndexMetadata>&>(mSpec->indexes());
   for (uint32_t count = indexes.Length(), index = 0;
        index < count;
@@ -1788,22 +1816,25 @@ IDBObjectStore::CreateIndexInternal(
   return index.forget();
 }
 
 void
 IDBObjectStore::DeleteIndex(const nsAString& aName, ErrorResult& aRv)
 {
   AssertIsOnOwningThread();
 
-  IDBTransaction* transaction = IDBTransaction::GetCurrent();
+  if (mTransaction->GetMode() != IDBTransaction::VERSION_CHANGE ||
+      mDeletedSpec) {
+    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
+    return;
+  }
 
-  if (!transaction ||
-      transaction != mTransaction ||
-      mTransaction->GetMode() != IDBTransaction::VERSION_CHANGE) {
-    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
+  IDBTransaction* transaction = IDBTransaction::GetCurrent();
+  if (!transaction || transaction != mTransaction) {
+    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
     return;
   }
 
   MOZ_ASSERT(transaction->IsOpen());
 
   auto& metadataArray = const_cast<nsTArray<IndexMetadata>&>(mSpec->indexes());
 
   int64_t foundId = 0;
@@ -1861,16 +1892,23 @@ IDBObjectStore::DeleteIndex(const nsAStr
   transaction->DeleteIndex(this, foundId);
 }
 
 already_AddRefed<IDBRequest>
 IDBObjectStore::Count(JSContext* aCx,
                       JS::Handle<JS::Value> aKey,
                       ErrorResult& aRv)
 {
+  AssertIsOnOwningThread();
+
+  if (mDeletedSpec) {
+    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
+    return nullptr;
+  }
+
   if (!mTransaction->IsOpen()) {
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
     return nullptr;
   }
 
   nsRefPtr<IDBKeyRange> keyRange;
   aRv = IDBKeyRange::FromJSVal(aCx, aKey, getter_AddRefs(keyRange));
   if (aRv.Failed()) {
@@ -1911,16 +1949,21 @@ already_AddRefed<IDBRequest>
 IDBObjectStore::OpenCursorInternal(bool aKeysOnly,
                                    JSContext* aCx,
                                    JS::Handle<JS::Value> aRange,
                                    IDBCursorDirection aDirection,
                                    ErrorResult& aRv)
 {
   AssertIsOnOwningThread();
 
+  if (mDeletedSpec) {
+    aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
+    return nullptr;
+  }
+
   if (!mTransaction->IsOpen()) {
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR);
     return nullptr;
   }
 
   nsRefPtr<IDBKeyRange> keyRange;
   aRv = IDBKeyRange::FromJSVal(aCx, aRange, getter_AddRefs(keyRange));
   if (NS_WARN_IF(aRv.Failed())) {
--- a/dom/indexedDB/test/error_events_abort_transactions_iframe.html
+++ b/dom/indexedDB/test/error_events_abort_transactions_iframe.html
@@ -53,16 +53,19 @@
 
       let request = indexedDB.open(window.location.pathname, 1);
       request.onsuccess = unexpectedSuccessHandler;
       request.onerror = grabEventAndContinueHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
       let event = yield undefined;
 
       let db = event.target.result;
+      db.onversionchange = function(event) {
+        event.target.close();
+      };
 
       is(db.version, 1, "Correct version");
       is(db.objectStoreNames.length, 0, "Correct objectStoreNames length");
 
       let trans = event.target.transaction;
 
       trans.oncomplete = unexpectedSuccessHandler;
       trans.onabort = grabEventAndContinueHandler;
@@ -99,16 +102,19 @@
       is(originalRequest.transaction, null, "request.transaction should now be null");
 
       request = indexedDB.open(window.location.pathname, 1);
       request.onerror = errorHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
       event = yield undefined;
 
       db = event.target.result;
+      db.onversionchange = function(event) {
+        event.target.close();
+      };
 
       event.target.transaction.oncomplete = grabEventAndContinueHandler;
       event.target.transaction.onabort = unexpectedSuccessHandler;
 
       is(db.version, "1", "Correct version");
       is(db.objectStoreNames.length, 0, "Correct objectStoreNames length");
 
       objectStore = db.createObjectStore("foo");
@@ -148,24 +154,25 @@
 
       is(event.type, "complete", "Got a transaction complete event");
 
       is(db.version, "1", "Correct version");
       is(db.objectStoreNames.length, 2, "Correct objectStoreNames length");
       ok(db.objectStoreNames.contains("foo"), "Has correct objectStore");
       ok(db.objectStoreNames.contains("bar"), "Has correct objectStore");
 
-      db.close();
-
       request = indexedDB.open(window.location.pathname, 2);
       request.onerror = errorHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
       event = yield undefined;
 
       db = event.target.result;
+      db.onversionchange = function(event) {
+        event.target.close();
+      };
 
       trans = event.target.transaction;
       trans.oncomplete = unexpectedSuccessHandler;
 
       is(db.version, "2", "Correct version");
       is(db.objectStoreNames.length, 2, "Correct objectStoreNames length");
       ok(db.objectStoreNames.contains("foo"), "Has correct objectStore");
       ok(db.objectStoreNames.contains("bar"), "Has correct objectStore");
--- a/dom/indexedDB/test/unit/test_add_twice_failure.js
+++ b/dom/indexedDB/test/unit/test_add_twice_failure.js
@@ -7,16 +7,17 @@ var testGenerator = testSteps();
 
 function testSteps()
 {
   const name = this.window ? window.location.pathname : "Splendid Test";
 
   let request = indexedDB.open(name, 1);
   request.onerror = errorHandler;
   request.onupgradeneeded = grabEventAndContinueHandler;
+  request.onsuccess = grabEventAndContinueHandler;
   let event = yield undefined;
 
   let db = request.result;
 
   ok(event.target === request, "Good event target");
 
   let objectStore = db.createObjectStore("foo", { keyPath: null });
   let key = 10;
@@ -28,12 +29,15 @@ function testSteps()
 
   is(request.result, key, "Correct key");
 
   request = objectStore.add({}, key);
   request.addEventListener("error", new ExpectError("ConstraintError", true));
   request.onsuccess = unexpectedSuccessHandler;
   yield undefined;
 
+  // Wait for success.
+  yield undefined;
+
   finishTest();
   yield undefined;
 }
 
--- a/dom/indexedDB/test/unit/test_cursor_update_updates_indexes.js
+++ b/dom/indexedDB/test/unit/test_cursor_update_updates_indexes.js
@@ -24,16 +24,17 @@ function testSteps()
   for (let i = 0; i < objectStoreInfo.length; i++) {
     // Create our object stores.
     let info = objectStoreInfo[i];
 
     ok(true, "1");
     request = indexedDB.open(name, i + 1);
     request.onerror = errorHandler;
     request.onupgradeneeded = grabEventAndContinueHandler;
+    request.onsuccess = grabEventAndContinueHandler;
     event = yield undefined;
 
     let db = event.target.result;
 
     ok(true, "2");
     let objectStore = info.hasOwnProperty("options") ?
                       db.createObjectStore(info.name, info.options) :
                       db.createObjectStore(info.name);
@@ -80,15 +81,19 @@ function testSteps()
     request = uniqueIndex.get(END_DATA);
     request.onerror = errorHandler;
     request.onsuccess = grabEventAndContinueHandler;
     event = yield undefined;
 
     ok(true, "7");
     ok(obj.data, event.target.result.data,
                   "Unique index was properly updated.");
+
+    // Wait for success
+    yield undefined;
+
     db.close();
   }
 
   finishTest();
   yield undefined;
 }
 
--- a/dom/indexedDB/test/unit/test_cursors.js
+++ b/dom/indexedDB/test/unit/test_cursors.js
@@ -11,16 +11,17 @@ function testSteps()
   const keys = [1, -1, 0, 10, 2000, "q", "z", "two", "b", "a"];
   const sortedKeys = [-1, 0, 1, 10, 2000, "a", "b", "q", "two", "z"];
 
   is(keys.length, sortedKeys.length, "Good key setup");
 
   let request = indexedDB.open(name, 1);
   request.onerror = errorHandler;
   request.onupgradeneeded = grabEventAndContinueHandler;
+  request.onsuccess = grabEventAndContinueHandler;
   let event = yield undefined;
 
   let db = event.target.result;
 
   let objectStore = db.createObjectStore("autoIncrement",
                                          { autoIncrement: true });
 
   request = objectStore.openCursor();
@@ -367,11 +368,16 @@ function testSteps()
       ok(storedCursor.value === undefined, "The cursor's value should be undefined.");
       testGenerator.next();
     }
   }
   yield undefined;
 
   is(keyIndex, -1, "Saw all added items");
 
+  // Wait for success
+  yield undefined;
+
+  db.close();
+
   finishTest();
   yield undefined;
 }
--- a/dom/indexedDB/test/unit/test_event_source.js
+++ b/dom/indexedDB/test/unit/test_event_source.js
@@ -8,25 +8,29 @@ var testGenerator = testSteps();
 function testSteps()
 {
   const name = this.window ? window.location.pathname : "Splendid Test";
   const objectStoreName = "Objects";
 
   var request = indexedDB.open(name, 1);
   request.onerror = errorHandler;
   request.onupgradeneeded = grabEventAndContinueHandler;
+  request.onsuccess = grabEventAndContinueHandler;
   var event = yield undefined;
 
   is(event.target.source, null, "correct event.target.source");
 
   var db = event.target.result;
   var objectStore = db.createObjectStore(objectStoreName,
                                          { autoIncrement: true });
   request = objectStore.add({});
   request.onerror = errorHandler;
   request.onsuccess = grabEventAndContinueHandler;
   event = yield undefined;
 
   ok(event.target.source === objectStore, "correct event.source");
 
+  // Wait for success
+  yield undefined;
+
   finishTest();
   yield undefined;
 }
--- a/dom/indexedDB/test/unit/test_key_requirements.js
+++ b/dom/indexedDB/test/unit/test_key_requirements.js
@@ -7,16 +7,17 @@ var testGenerator = testSteps();
 
 function testSteps()
 {
   const name = this.window ? window.location.pathname : "Splendid Test";
 
   let request = indexedDB.open(name, 1);
   request.onerror = errorHandler;
   request.onupgradeneeded = grabEventAndContinueHandler;
+  request.onsuccess = grabEventAndContinueHandler;
   let event = yield undefined;
 
   let db = event.target.result;
   db.addEventListener("error", function(event) {
     event.preventDefault();
   }, false);
 
   let objectStore = db.createObjectStore("foo", { autoIncrement: true });
@@ -271,11 +272,14 @@ function testSteps()
     ok(true, "add with inline key and passed key threw");
   }
 
   request = objectStore.delete(key2);
   request.onerror = errorHandler;
   request.onsuccess = grabEventAndContinueHandler;
   event = yield undefined;
 
+  // Wait for success
+  yield undefined;
+
   finishTest();
   yield undefined;
-}
\ No newline at end of file
+}
--- a/dom/indexedDB/test/unit/test_objectCursors.js
+++ b/dom/indexedDB/test/unit/test_objectCursors.js
@@ -22,16 +22,19 @@ function testSteps()
   var j = 0;
   for (let i in objectStores) {
     let request = indexedDB.open(name, ++j);
     request.onerror = errorHandler;
     request.onupgradeneeded = grabEventAndContinueHandler;
     let event = yield undefined;
 
     let db = event.target.result;
+    db.onversionchange = function(event) {
+      event.target.close();
+    };
 
     let objectStore =
       db.createObjectStore(objectStores[i].name,
                            { keyPath: "id",
                              autoIncrement: objectStores[i].autoIncrement });
 
     for (let j in indexes) {
       objectStore.createIndex(indexes[j].name, "name", indexes[j].options);
@@ -43,17 +46,16 @@ function testSteps()
     }
 
     request = objectStore.add(data);
     request.onerror = errorHandler;
     request.onsuccess = grabEventAndContinueHandler;
     event = yield undefined;
 
     ok(event.target.result == 1 || event.target.result == 2, "Good id");
-    db.close();
   }
 
   executeSoon(function() { testGenerator.next(); });
   yield undefined;
 
   let request = indexedDB.open(name, j);
   request.onerror = errorHandler;
   request.onsuccess = grabEventAndContinueHandler;
--- a/dom/indexedDB/test/unit/test_objectStore_inline_autoincrement_key_added_on_put.js
+++ b/dom/indexedDB/test/unit/test_objectStore_inline_autoincrement_key_added_on_put.js
@@ -7,16 +7,17 @@ var testGenerator = testSteps();
 
 function testSteps()
 {
   const name = this.window ? window.location.pathname : "Splendid Test";
 
   var request = indexedDB.open(name, 1);
   request.onerror = errorHandler;
   request.onupgradeneeded = grabEventAndContinueHandler;
+  request.onsuccess = grabEventAndContinueHandler;
   var event = yield undefined;
 
   var db = event.target.result;
 
   var test = {
     name: "inline key; key generator",
     autoIncrement: true,
     storedObject: {name: "Lincoln"},
@@ -40,12 +41,15 @@ function testSteps()
 
   // Sanity check!
   is(event.target.result.name, test.storedObject.name,
      "The correct object was stored.");
 
   // Ensure that the id was also stored on the object.
   is(event.target.result.id, id, "The object had the id stored on it.");
 
+  // Wait for success
+  yield undefined;
+
   finishTest();
   yield undefined;
 }
 
--- a/dom/indexedDB/test/unit/test_objectStore_remove_values.js
+++ b/dom/indexedDB/test/unit/test_objectStore_remove_values.js
@@ -37,19 +37,23 @@ function testSteps()
   ];
 
   for (let i = 0; i < data.length; i++) {
     let test = data[i];
 
     let request = indexedDB.open(name, i+1);
     request.onerror = errorHandler;
     request.onupgradeneeded = grabEventAndContinueHandler;
+    request.onsuccess = grabEventAndContinueHandler;
     let event = yield undefined;
 
     let db = event.target.result;
+    db.onversionchange = function(event) {
+      event.target.close();
+    };
 
     let objectStore = db.createObjectStore(test.name,
                                            { keyPath: test.keyName,
                                              autoIncrement: test.autoIncrement });
 
     request = objectStore.add(test.storedObject, test.keyValue);
     request.onerror = errorHandler;
     request.onsuccess = grabEventAndContinueHandler;
@@ -72,15 +76,17 @@ function testSteps()
 
     // Make sure it was removed.
     request = objectStore.get(id);
     request.onerror = errorHandler;
     request.onsuccess = grabEventAndContinueHandler;
     event = yield undefined;
 
     ok(event.target.result === undefined, "Object was deleted");
-    db.close();
+
+    // Wait for success
+    yield undefined;
   }
 
   finishTest();
   yield undefined;
 }
 
--- a/dom/indexedDB/test/unit/test_put_get_values.js
+++ b/dom/indexedDB/test/unit/test_put_get_values.js
@@ -11,16 +11,17 @@ function testSteps()
   const objectStoreName = "Objects";
 
   let testString = { key: 0, value: "testString" };
   let testInt = { key: 1, value: 1002 };
 
   let request = indexedDB.open(name, 1);
   request.onerror = errorHandler;
   request.onupgradeneeded = grabEventAndContinueHandler;
+  request.onsuccess = grabEventAndContinueHandler;
   let event = yield undefined;
 
   let db = event.target.result;
 
   let objectStore = db.createObjectStore(objectStoreName,
                                          { autoIncrement: 0 });
 
   request = objectStore.add(testString.value, testString.key);
@@ -37,15 +38,18 @@ function testSteps()
   request = objectStore.add(testInt.value, testInt.key);
   request.onerror = errorHandler;
   request.onsuccess = function(event) {
     is(event.target.result, testInt.key, "Got the right key");
     request = objectStore.get(testInt.key);
     request.onerror = errorHandler;
     request.onsuccess = function(event) {
       is(event.target.result, testInt.value, "Got the right value");
-      finishTest();
     };
   }
 
+  // Wait for success
+  yield undefined;
+
+  finishTest();
   yield undefined;
 }
 
--- a/dom/indexedDB/test/unit/test_put_get_values_autoIncrement.js
+++ b/dom/indexedDB/test/unit/test_put_get_values_autoIncrement.js
@@ -11,16 +11,17 @@ function testSteps()
   const objectStoreName = "Objects";
 
   let testString = { value: "testString" };
   let testInt = { value: 1002 };
 
   let request = indexedDB.open(name, 1);
   request.onerror = errorHandler;
   request.onupgradeneeded = grabEventAndContinueHandler;
+  request.onsuccess = grabEventAndContinueHandler;
   let event = yield undefined;
 
   let db = event.target.result;
 
   let objectStore = db.createObjectStore(objectStoreName,
                                          { autoIncrement: 1 });
 
   request = objectStore.put(testString.value);
@@ -37,14 +38,17 @@ function testSteps()
   request = objectStore.put(testInt.value);
   request.onerror = errorHandler;
   request.onsuccess = function(event) {
     testInt.key = event.target.result;
     request = objectStore.get(testInt.key);
     request.onerror = errorHandler;
     request.onsuccess = function(event) {
       is(event.target.result, testInt.value, "Got the right value");
-      finishTest();
     };
   }
 
+  // Wait for success
   yield undefined;
-}
\ No newline at end of file
+
+  finishTest();
+  yield undefined;
+}
--- a/dom/indexedDB/test/unit/test_remove_objectStore.js
+++ b/dom/indexedDB/test/unit/test_remove_objectStore.js
@@ -8,18 +8,22 @@ var testGenerator = testSteps();
 function testSteps()
 {
   const name = this.window ? window.location.pathname : "Splendid Test";
   const objectStoreName = "Objects";
 
   let request = indexedDB.open(name, 1);
   request.onerror = errorHandler;
   request.onupgradeneeded = grabEventAndContinueHandler;
+  request.onsuccess = unexpectedSuccessHandler;
   let event = yield undefined;
 
+  request.onupgradeneeded = unexpectedSuccessHandler;
+  request.onsuccess = grabEventAndContinueHandler;
+
   let db = event.target.result;
   is(db.objectStoreNames.length, 0, "Correct objectStoreNames list");
 
   let objectStore = db.createObjectStore(objectStoreName,
                                          { keyPath: "foo" });
 
   let addedCount = 0;
 
@@ -35,23 +39,30 @@ function testSteps()
   yield undefined;
 
   is(db.objectStoreNames.length, 1, "Correct objectStoreNames list");
   is(db.objectStoreNames.item(0), objectStoreName, "Correct name");
 
   event.target.transaction.oncomplete = grabEventAndContinueHandler;
   event = yield undefined;
 
+  // Wait for success.
+  event = yield undefined;
+
   db.close();
 
   request = indexedDB.open(name, 2);
   request.onerror = errorHandler;
   request.onupgradeneeded = grabEventAndContinueHandler;
+  request.onsuccess = unexpectedSuccessHandler;
   event = yield undefined;
 
+  request.onupgradeneeded = unexpectedSuccessHandler;
+  request.onsuccess = grabEventAndContinueHandler;
+
   db = event.target.result;
   let trans = event.target.transaction;
 
   let oldObjectStore = trans.objectStore(objectStoreName);
   isnot(oldObjectStore, null, "Correct object store prior to deleting");
   db.deleteObjectStore(objectStoreName);
   is(db.objectStoreNames.length, 0, "Correct objectStores list");
   try {
@@ -82,16 +93,19 @@ function testSteps()
   is(db.objectStoreNames.length, 0, "Correct objectStores list");
 
   continueToNextStep();
   yield undefined;
 
   trans.oncomplete = grabEventAndContinueHandler;
   event = yield undefined;
 
+  // Wait for success.
+  event = yield undefined;
+
   db.close();
 
   request = indexedDB.open(name, 3);
   request.onerror = errorHandler;
   request.onupgradeneeded = grabEventAndContinueHandler;
   event = yield undefined;
 
   db = event.target.result;
--- a/dom/indexedDB/test/unit/test_request_readyState.js
+++ b/dom/indexedDB/test/unit/test_request_readyState.js
@@ -9,16 +9,17 @@ function testSteps()
 {
   const name = this.window ? window.location.pathname : "Splendid Test";
 
   let request = indexedDB.open(name, 1);
   is(request.readyState, "pending", "Correct readyState");
 
   request.onerror = errorHandler;
   request.onupgradeneeded = grabEventAndContinueHandler;
+  request.onsuccess = grabEventAndContinueHandler;
   let event = yield undefined;
 
   is(request.readyState, "done", "Correct readyState");
 
   let db = event.target.result;
 
   let objectStore = db.createObjectStore("foo");
   let key = 10;
@@ -37,11 +38,14 @@ function testSteps()
   request.onerror = errorHandler;
   request.onsuccess = grabEventAndContinueHandler;
   is(request.readyState, "pending", "Correct readyState");
   event = yield undefined;
 
   ok(event.target.result, "Got something");
   is(request.readyState, "done", "Correct readyState");
 
+  // Wait for success
+  yield undefined;
+
   finishTest();
   yield undefined;
 }
--- a/dom/indexedDB/test/unit/test_setVersion.js
+++ b/dom/indexedDB/test/unit/test_setVersion.js
@@ -10,41 +10,42 @@ function testSteps()
   const name = this.window ? window.location.pathname : "Splendid Test";
 
   let request = indexedDB.open(name, 1);
   request.onerror = errorHandler;
   request.onsuccess = grabEventAndContinueHandler;
   let event = yield undefined;
 
   let db = event.target.result;
+  db.close();
 
   // Check default state.
   is(db.version, 1, "Correct default version for a new database.");
 
   const versions = [
     7,
     42,
   ];
 
-  db.close();
-
   for (let i = 0; i < versions.length; i++) {
     let version = versions[i];
 
     let request = indexedDB.open(name, version);
     request.onerror = errorHandler;
     request.onupgradeneeded = grabEventAndContinueHandler;
+    request.onsuccess = grabEventAndContinueHandler;
     let event = yield undefined;
 
     let db = event.target.result;
 
     is(db.version, version, "Database version number updated correctly");
     is(event.target.transaction.mode, "versionchange", "Correct mode");
 
-    executeSoon(function() { testGenerator.next(); });
+    // Wait for success
     yield undefined;
+
     db.close();
   }
 
   finishTest();
   yield undefined;
 }
 
--- a/dom/interfaces/base/nsIServiceWorkerManager.idl
+++ b/dom/interfaces/base/nsIServiceWorkerManager.idl
@@ -28,17 +28,17 @@ interface nsIServiceWorkerInfo : nsISupp
   readonly attribute DOMString scope;
   readonly attribute DOMString scriptSpec;
   readonly attribute DOMString currentWorkerURL;
 
   readonly attribute DOMString activeCacheName;
   readonly attribute DOMString waitingCacheName;
 };
 
-[scriptable, builtinclass, uuid(5e112a42-df4c-4ae9-bc71-e6e681ab5f38)]
+[scriptable, builtinclass, uuid(aee94712-9adb-4c0b-80a7-a8df34dfa2e8)]
 interface nsIServiceWorkerManager : nsISupports
 {
   /**
    * Registers a ServiceWorker with script loaded from `aScriptURI` to act as
    * the ServiceWorker for aScope.  Requires a valid entry settings object on
    * the stack. This means you must call this from content code 'within'
    * a window.
    *
@@ -100,22 +100,17 @@ interface nsIServiceWorkerManager : nsIS
    * Clears ServiceWorker registrations from memory and disk for the specified
    * host.
    * - All ServiceWorker instances change their state to redundant.
    * - Existing ServiceWorker instances handling fetches will keep running.
    * - All documents will immediately stop being controlled.
    * - Unregister jobs will be queued for all registrations.
    *   This eventually results in the registration being deleted from disk too.
    */
-  void remove(in AUTF8String aHost);
-
-  /*
-   * Clear all registrations for all hosts. See remove().
-   */
-  void removeAll();
+  void removeAndPropagate(in AUTF8String aHost);
 
   // Testing
   DOMString getScopeForUrl(in nsIPrincipal aPrincipal, in DOMString aPath);
 
   // Note: This is meant to be used only by about:serviceworkers.
   //It returns an array of nsIServiceWorkerInfo.
   nsIArray getAllRegistrations();
 
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -1239,36 +1239,16 @@ ContentChild::RecvUpdateServiceWorkerReg
 {
     nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager();
     if (swm) {
         swm->UpdateAllRegistrations();
     }
     return true;
 }
 
-bool
-ContentChild::RecvRemoveServiceWorkerRegistrationsForDomain(const nsString& aDomain)
-{
-    nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager();
-    if (swm) {
-        swm->Remove(NS_ConvertUTF16toUTF8(aDomain));
-    }
-    return true;
-}
-
-bool
-ContentChild::RecvRemoveServiceWorkerRegistrations()
-{
-    nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager();
-    if (swm) {
-        swm->RemoveAll();
-    }
-    return true;
-}
-
 static CancelableTask* sFirstIdleTask;
 
 static void FirstIdle(void)
 {
     MOZ_ASSERT(sFirstIdleTask);
     sFirstIdleTask = nullptr;
     ContentChild::GetSingleton()->SendFirstIdle();
 }
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -300,20 +300,16 @@ public:
     virtual bool RecvSetConnectivity(const bool& connectivity) override;
 
     virtual bool RecvSpeakerManagerNotify() override;
 
     virtual bool RecvBidiKeyboardNotify(const bool& isLangRTL) override;
 
     virtual bool RecvUpdateServiceWorkerRegistrations() override;
 
-    virtual bool RecvRemoveServiceWorkerRegistrationsForDomain(const nsString& aDomain) override;
-
-    virtual bool RecvRemoveServiceWorkerRegistrations() override;
-
     virtual bool RecvNotifyVisited(const URIParams& aURI) override;
     // auto remove when alertfinished is received.
     nsresult AddRemoteAlertObserver(const nsString& aData, nsIObserver* aObserver);
 
     virtual bool RecvSystemMemoryAvailable(const uint64_t& aGetterId,
                                            const uint32_t& aMemoryAvailable) override;
 
     virtual bool RecvPreferenceUpdate(const PrefSetting& aPref) override;
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -501,20 +501,16 @@ child:
     /**
      * Communication between the PuppetBidiKeyboard and the actual
      * BidiKeyboard hosted by the parent
      */
     async BidiKeyboardNotify(bool isLangRTL);
 
     async UpdateServiceWorkerRegistrations();
 
-    async RemoveServiceWorkerRegistrationsForDomain(nsString aDomain);
-
-    async RemoveServiceWorkerRegistrations();
-
     async DataStoreNotify(uint32_t aAppId, nsString aName,
                           nsString aManifestURL);
 
     /**
      * Dump this process's GC and CC logs to the provided files.
      *
      * For documentation on the other args, see dumpGCAndCCLogsToFile in
      * nsIMemoryInfoDumper.idl
--- a/dom/ipc/ProcessHangMonitor.cpp
+++ b/dom/ipc/ProcessHangMonitor.cpp
@@ -151,17 +151,17 @@ public:
   NS_IMETHOD BeginStartingDebugger() override;
   NS_IMETHOD EndStartingDebugger() override;
   NS_IMETHOD TerminatePlugin() override;
   NS_IMETHOD TerminateProcess() override;
   NS_IMETHOD UserCanceled() override;
 
   NS_IMETHOD IsReportForBrowser(nsIFrameLoader* aFrameLoader, bool* aResult) override;
 
-  // Called on xpcom shutdown
+  // Called when a content process shuts down.
   void Clear() {
     mContentParent = nullptr;
     mActor = nullptr;
   }
 
   void SetHangData(const HangData& aHangData) { mHangData = aHangData; }
   void SetBrowserDumpId(nsAutoString& aId) {
     mBrowserDumpId = aId;
@@ -451,21 +451,35 @@ HangMonitorParent::HangMonitorParent(Pro
    mMonitor("HangMonitorParent lock"),
    mShutdownDone(false),
    mBrowserCrashDumpHashLock("mBrowserCrashDumpIds lock")
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   mReportHangs = mozilla::Preferences::GetBool("dom.ipc.reportProcessHangs", false);
 }
 
+static PLDHashOperator
+DeleteMinidump(const uint32_t& aPluginId, nsString aCrashId, void* aUserData)
+{
+#ifdef MOZ_CRASHREPORTER
+  if (!aCrashId.IsEmpty()) {
+    CrashReporter::DeleteMinidumpFilesForID(aCrashId);
+  }
+#endif
+  return PL_DHASH_NEXT;
+}
+
 HangMonitorParent::~HangMonitorParent()
 {
   // For some reason IPDL doesn't autmatically delete the channel for a
   // bridged protocol (bug 1090570). So we have to do it ourselves.
   XRE_GetIOMessageLoop()->PostTask(FROM_HERE, new DeleteTask<Transport>(GetTransport()));
+
+  MutexAutoLock lock(mBrowserCrashDumpHashLock);
+  mBrowserCrashDumpIds.EnumerateRead(DeleteMinidump, nullptr);
 }
 
 void
 HangMonitorParent::Shutdown()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   MonitorAutoLock lock(mMonitor);
@@ -800,30 +814,32 @@ HangMonitoredProcess::TerminatePlugin()
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   if (mHangData.type() != HangData::TPluginHangData) {
     return NS_ERROR_UNEXPECTED;
   }
 
   uint32_t id = mHangData.get_PluginHangData().pluginId();
   plugins::TerminatePlugin(id, mBrowserDumpId);
 
-  mActor->CleanupPluginHang(id, false);
+  if (mActor) {
+    mActor->CleanupPluginHang(id, false);
+  }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HangMonitoredProcess::TerminateProcess()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   if (!mContentParent) {
     return NS_ERROR_UNEXPECTED;
   }
 
-  if (mHangData.type() == HangData::TPluginHangData) {
+  if (mActor && mHangData.type() == HangData::TPluginHangData) {
     uint32_t id = mHangData.get_PluginHangData().pluginId();
     mActor->CleanupPluginHang(id, true);
   }
 
   mContentParent->KillHard("HangMonitor");
   return NS_OK;
 }
 
@@ -850,18 +866,20 @@ HangMonitoredProcess::IsReportForBrowser
 NS_IMETHODIMP
 HangMonitoredProcess::UserCanceled()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   if (mHangData.type() != HangData::TPluginHangData) {
     return NS_OK;
   }
 
-  uint32_t id = mHangData.get_PluginHangData().pluginId();
-  mActor->CleanupPluginHang(id, true);
+  if (mActor) {
+    uint32_t id = mHangData.get_PluginHangData().pluginId();
+    mActor->CleanupPluginHang(id, true);
+  }
   return NS_OK;
 }
 
 ProcessHangMonitor* ProcessHangMonitor::sInstance;
 
 ProcessHangMonitor::ProcessHangMonitor()
  : mCPOWTimeout(false)
 {
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -2141,16 +2141,23 @@ TabChild::RecvHandleLongTap(const CSSPoi
 }
 
 bool
 TabChild::RecvNotifyAPZStateChange(const ViewID& aViewId,
                                    const APZStateChange& aChange,
                                    const int& aArg)
 {
   mAPZEventState->ProcessAPZStateChange(GetDocument(), aViewId, aChange, aArg);
+  if (aChange == APZStateChange::TransformEnd) {
+    // This is used by tests to determine when the APZ is done doing whatever
+    // it's doing. XXX generify this as needed when writing additional tests.
+    DispatchMessageManagerMessage(
+      NS_LITERAL_STRING("APZ:TransformEnd"),
+      NS_LITERAL_STRING("{}"));
+  }
   return true;
 }
 
 bool
 TabChild::RecvNotifyFlushComplete()
 {
   APZCCallbackHelper::NotifyFlushComplete();
   return true;
--- a/dom/media/mediasource/SourceBuffer.cpp
+++ b/dom/media/mediasource/SourceBuffer.cpp
@@ -483,21 +483,23 @@ SourceBuffer::AppendDataCompletedWithSuc
       mActive = true;
       mMediaSource->SourceBufferIsActive(this);
       mMediaSource->QueueInitializationEvent();
       if (mIsUsingFormatReader) {
         mMediaSource->GetDecoder()->NotifyWaitingForResourcesStatusChanged();
       }
     }
   }
-  if (mActive) {
+  if (mActive && mIsUsingFormatReader) {
     // Tell our parent decoder that we have received new data.
     // The information provided do not matter much so long as it is monotonically
     // increasing.
     mMediaSource->GetDecoder()->NotifyDataArrived(nullptr, 1, mReportedOffset++);
+    // Send progress event.
+    mMediaSource->GetDecoder()->NotifyBytesDownloaded();
   }
 
   CheckEndTime();
 
   StopUpdating();
 }
 
 void
--- a/dom/plugins/base/PluginPRLibrary.h
+++ b/dom/plugins/base/PluginPRLibrary.h
@@ -118,16 +118,17 @@ public:
 #endif
     virtual nsresult SetBackgroundUnknown(NPP instance) override;
     virtual nsresult BeginUpdateBackground(NPP instance,
                                            const nsIntRect&, gfxContext** aCtx) override;
     virtual nsresult EndUpdateBackground(NPP instance,
                                          gfxContext* aCtx, const nsIntRect&) override;
     virtual void GetLibraryPath(nsACString& aPath) { aPath.Assign(mFilePath); }
     virtual nsresult GetRunID(uint32_t* aRunID) override { return NS_ERROR_NOT_IMPLEMENTED; }
+    virtual void SetHasLocalInstance() override { }
 
 private:
     NP_InitializeFunc mNP_Initialize;
     NP_ShutdownFunc mNP_Shutdown;
     NP_GetMIMEDescriptionFunc mNP_GetMIMEDescription;
 #if defined(XP_UNIX) && !defined(XP_MACOSX)
     NP_GetValueFunc mNP_GetValue;
 #endif
--- a/dom/plugins/base/nsPluginHost.cpp
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -950,16 +950,18 @@ nsPluginHost::TrySetUpPluginInstance(con
   if (!plugin) {
     return NS_ERROR_FAILURE;
   }
 
   nsPluginTag* pluginTag = FindNativePluginForType(aMimeType, true);
 
   NS_ASSERTION(pluginTag, "Must have plugin tag here!");
 
+  plugin->GetLibrary()->SetHasLocalInstance();
+
 #if defined(MOZ_WIDGET_ANDROID) && defined(MOZ_CRASHREPORTER)
   if (pluginTag->mIsFlashPlugin) {
     CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("FlashVersion"), pluginTag->mVersion);
   }
 #endif
 
   nsRefPtr<nsNPAPIPluginInstance> instance = new nsNPAPIPluginInstance();
 
--- a/dom/plugins/base/nsPluginTags.cpp
+++ b/dom/plugins/base/nsPluginTags.cpp
@@ -143,16 +143,17 @@ GetStatePrefNameForPlugin(nsPluginTag* a
 
 uint32_t nsPluginTag::sNextId;
 
 nsPluginTag::nsPluginTag(nsPluginInfo* aPluginInfo,
                          int64_t aLastModifiedTime,
                          bool fromExtension)
   : mId(sNextId++),
     mContentProcessRunningCount(0),
+    mHadLocalInstance(false),
     mName(aPluginInfo->fName),
     mDescription(aPluginInfo->fDescription),
     mLibrary(nullptr),
     mIsJavaPlugin(false),
     mIsFlashPlugin(false),
     mFileName(aPluginInfo->fFileName),
     mFullPath(aPluginInfo->fFullPath),
     mVersion(aPluginInfo->fVersion),
--- a/dom/plugins/base/nsPluginTags.h
+++ b/dom/plugins/base/nsPluginTags.h
@@ -85,16 +85,20 @@ public:
 
   bool IsFromExtension() const;
 
   nsRefPtr<nsPluginTag> mNext;
   uint32_t      mId;
 
   // Number of PluginModuleParents living in all content processes.
   size_t        mContentProcessRunningCount;
+
+  // True if we've ever created an instance of this plugin in the current process.
+  bool          mHadLocalInstance;
+
   nsCString     mName; // UTF-8
   nsCString     mDescription; // UTF-8
   nsTArray<nsCString> mMimeTypes; // UTF-8
   nsTArray<nsCString> mMimeDescriptions; // UTF-8
   nsTArray<nsCString> mExtensions; // UTF-8
   PRLibrary     *mLibrary;
   nsRefPtr<nsNPAPIPlugin> mPlugin;
   bool          mIsJavaPlugin;
--- a/dom/plugins/ipc/PluginHangUIParent.cpp
+++ b/dom/plugins/ipc/PluginHangUIParent.cpp
@@ -348,18 +348,17 @@ PluginHangUIParent::RecvUserResponse(con
   }
   mLastUserResponse = aResponse;
   mResponseTicks = ::GetTickCount();
   mIsShowing = false;
   // responseCode: 1 = Stop, 2 = Continue, 3 = Cancel
   int responseCode;
   if (aResponse & HANGUI_USER_RESPONSE_STOP) {
     // User clicked Stop
-    nsString dummy;
-    mModule->TerminateChildProcess(mMainThreadMessageLoop, &dummy);
+    mModule->TerminateChildProcess(mMainThreadMessageLoop, EmptyString());
     responseCode = 1;
   } else if(aResponse & HANGUI_USER_RESPONSE_CONTINUE) {
     mModule->OnHangUIContinue();
     // User clicked Continue
     responseCode = 2;
   } else {
     // Dialog was cancelled
     responseCode = 3;
--- a/dom/plugins/ipc/PluginLibrary.h
+++ b/dom/plugins/ipc/PluginLibrary.h
@@ -80,14 +80,15 @@ public:
    * API.
    */
   virtual nsresult SetBackgroundUnknown(NPP instance) = 0;
   virtual nsresult BeginUpdateBackground(NPP instance,
                                          const nsIntRect&, gfxContext**) = 0;
   virtual nsresult EndUpdateBackground(NPP instance,
                                        gfxContext*, const nsIntRect&) = 0;
   virtual nsresult GetRunID(uint32_t* aRunID) = 0;
+  virtual void SetHasLocalInstance() = 0;
 };
 
 
 } // namespace mozilla
 
 #endif  // ifndef mozilla_PluginLibrary_h
--- a/dom/plugins/ipc/PluginModuleChild.cpp
+++ b/dom/plugins/ipc/PluginModuleChild.cpp
@@ -62,17 +62,16 @@ using mozilla::dom::PCrashReporterChild;
 #if defined(XP_WIN)
 const wchar_t * kFlashFullscreenClass = L"ShockwaveFlashFullScreen";
 const wchar_t * kMozillaWindowClass = L"MozillaWindowClass";
 #endif
 
 namespace {
 // see PluginModuleChild::GetChrome()
 PluginModuleChild* gChromeInstance = nullptr;
-nsTArray<PluginModuleChild*>* gAllInstances;
 }
 
 #ifdef MOZ_WIDGET_QT
 typedef void (*_gtk_init_fn)(int argc, char **argv);
 static _gtk_init_fn s_gtk_init = nullptr;
 static PRLibrary *sGtkLib = nullptr;
 #endif
 
@@ -122,42 +121,38 @@ PluginModuleChild::CreateForContentProce
     return child;
 }
 
 PluginModuleChild::PluginModuleChild(bool aIsChrome)
   : mLibrary(0)
   , mPluginFilename("")
   , mQuirks(QUIRKS_NOT_INITIALIZED)
   , mIsChrome(aIsChrome)
+  , mHasShutdown(false)
   , mTransport(nullptr)
   , mShutdownFunc(0)
   , mInitializeFunc(0)
 #if defined(OS_WIN) || defined(OS_MACOSX)
   , mGetEntryPointsFunc(0)
 #elif defined(MOZ_WIDGET_GTK)
   , mNestedLoopTimerId(0)
 #elif defined(MOZ_WIDGET_QT)
   , mNestedLoopTimerObject(0)
 #endif
 #ifdef OS_WIN
   , mNestedEventHook(nullptr)
   , mGlobalCallWndProcHook(nullptr)
 #endif
 {
-    if (!gAllInstances) {
-        gAllInstances = new nsTArray<PluginModuleChild*>(1);
-    }
-    gAllInstances->AppendElement(this);
-
     memset(&mFunctions, 0, sizeof(mFunctions));
     if (mIsChrome) {
         MOZ_ASSERT(!gChromeInstance);
         gChromeInstance = this;
     }
-    mUserAgent.SetIsVoid(true);
+
 #ifdef XP_MACOSX
     if (aIsChrome) {
       mac_plugin_interposing::child::SetUpCocoaInterposing();
     }
 #endif
 }
 
 PluginModuleChild::~PluginModuleChild()
@@ -165,23 +160,16 @@ PluginModuleChild::~PluginModuleChild()
     if (mTransport) {
         // For some reason IPDL doesn't autmatically delete the channel for a
         // bridged protocol (bug 1090570). So we have to do it ourselves. This
         // code is only invoked for PluginModuleChild instances created via
         // bridging; otherwise mTransport is null.
         XRE_GetIOMessageLoop()->PostTask(FROM_HERE, new DeleteTask<Transport>(mTransport));
     }
 
-    gAllInstances->RemoveElement(this);
-    MOZ_ASSERT_IF(mIsChrome, gAllInstances->Length() == 0);
-    if (gAllInstances->IsEmpty()) {
-        delete gAllInstances;
-        gAllInstances = nullptr;
-    }
-
     if (mIsChrome) {
         MOZ_ASSERT(gChromeInstance == this);
 
         // We don't unload the plugin library in case it uses atexit handlers or
         // other similar hooks.
 
         DeinitGraphics();
         PluginScriptableObjectChild::ClearIdentifiers();
@@ -689,40 +677,53 @@ PluginModuleChild::DeinitGraphics()
 #if defined(MOZ_X11) && defined(NS_FREE_PERMANENT_DATA)
     // We free some data off of XDisplay close hooks, ensure they're
     // run.  Closing the display is pretty scary, so we only do it to
     // silence leak checkers.
     XCloseDisplay(DefaultXDisplay());
 #endif
 }
 
-bool
-PluginModuleChild::AnswerNP_Shutdown(NPError *rv)
+NPError
+PluginModuleChild::NP_Shutdown()
 {
     AssertPluginThread();
     MOZ_ASSERT(mIsChrome);
 
+    if (mHasShutdown) {
+        return NPERR_NO_ERROR;
+    }
+
 #if defined XP_WIN
     mozilla::widget::StopAudioSession();
 #endif
 
     // the PluginModuleParent shuts down this process after this interrupt
     // call pops off its stack
 
-    *rv = mShutdownFunc ? mShutdownFunc() : NPERR_NO_ERROR;
+    NPError rv = mShutdownFunc ? mShutdownFunc() : NPERR_NO_ERROR;
 
     // weakly guard against re-entry after NP_Shutdown
     memset(&mFunctions, 0, sizeof(mFunctions));
 
 #ifdef OS_WIN
     ResetEventHooks();
 #endif
 
     GetIPCChannel()->SetAbortOnError(false);
 
+    mHasShutdown = true;
+
+    return rv;
+}
+
+bool
+PluginModuleChild::AnswerNP_Shutdown(NPError *rv)
+{
+    *rv = NP_Shutdown();
     return true;
 }
 
 bool
 PluginModuleChild::AnswerOptionalFunctionsSupported(bool *aURLRedirectNotify,
                                                     bool *aClearSiteData,
                                                     bool *aGetSitesWithData)
 {
@@ -834,16 +835,21 @@ PluginModuleChild::ActorDestroy(ActorDes
         return;
     }
 
     if (AbnormalShutdown == why) {
         NS_WARNING("shutting down early because of crash!");
         QuickExit();
     }
 
+    if (!mHasShutdown) {
+        MOZ_ASSERT(gChromeInstance == this);
+        NP_Shutdown();
+    }
+
     // doesn't matter why we're being destroyed; it's up to us to
     // initiate (clean) shutdown
     XRE_ShutdownChildProcess();
 }
 
 void
 PluginModuleChild::CleanUp()
 {
--- a/dom/plugins/ipc/PluginModuleChild.h
+++ b/dom/plugins/ipc/PluginModuleChild.h
@@ -172,16 +172,18 @@ public:
                         IPC::Channel* aChannel);
 
     static PluginModuleChild*
     CreateForContentProcess(mozilla::ipc::Transport* aTransport,
                             base::ProcessId aOtherProcess);
 
     void CleanUp();
 
+    NPError NP_Shutdown();
+
     const char* GetUserAgent();
 
     static const NPNetscapeFuncs sBrowserFuncs;
 
     static PluginModuleChild* GetChrome();
 
     /**
      * The child implementation of NPN_CreateObject.
@@ -318,20 +320,20 @@ private:
 #elif defined(MOZ_WIDGET_QT)
 
     virtual void EnteredCxxStack() override;
     virtual void ExitedCxxStack() override;
 #endif
 
     PRLibrary* mLibrary;
     nsCString mPluginFilename; // UTF8
-    nsCString mUserAgent;
     int mQuirks;
 
     bool mIsChrome;
+    bool mHasShutdown; // true if NP_Shutdown has run
     Transport* mTransport;
 
     // we get this from the plugin
     NP_PLUGINSHUTDOWN mShutdownFunc;
 #if defined(OS_LINUX) || defined(OS_BSD)
     NP_PLUGINUNIXINIT mInitializeFunc;
 #elif defined(OS_WIN) || defined(OS_MACOSX)
     NP_PLUGININIT mInitializeFunc;
--- a/dom/plugins/ipc/PluginModuleParent.cpp
+++ b/dom/plugins/ipc/PluginModuleParent.cpp
@@ -354,20 +354,19 @@ mozilla::plugins::TerminatePlugin(uint32
 {
     MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
 
     nsRefPtr<nsPluginHost> host = nsPluginHost::GetInst();
     nsPluginTag* pluginTag = host->PluginWithId(aPluginId);
     if (!pluginTag || !pluginTag->mPlugin) {
         return;
     }
-    nsAutoString dumpId(aBrowserDumpId);
     nsRefPtr<nsNPAPIPlugin> plugin = pluginTag->mPlugin;
     PluginModuleChromeParent* chromeParent = static_cast<PluginModuleChromeParent*>(plugin->GetLibrary());
-    chromeParent->TerminateChildProcess(MessageLoop::current(), &dumpId);
+    chromeParent->TerminateChildProcess(MessageLoop::current(), aBrowserDumpId);
 }
 
 /* static */ PluginLibrary*
 PluginModuleContentParent::LoadModule(uint32_t aPluginId)
 {
     PluginModuleMapping::NotifyLoadingModule loadingModule;
     nsAutoPtr<PluginModuleMapping> mapping(new PluginModuleMapping(aPluginId));
 
@@ -617,16 +616,17 @@ PluginModuleChromeParent::WaitForIPCConn
         return false;
     }
     return true;
 }
 
 PluginModuleParent::PluginModuleParent(bool aIsChrome)
     : mIsChrome(aIsChrome)
     , mShutdown(false)
+    , mHadLocalInstance(false)
     , mClearSiteDataSupported(false)
     , mGetSitesWithDataSupported(false)
     , mNPNIface(nullptr)
     , mNPPIface(nullptr)
     , mPlugin(nullptr)
     , mTaskFactory(this)
     , mIsFlashPlugin(false)
     , mIsStartingAsync(false)
@@ -1149,18 +1149,17 @@ PluginModuleChromeParent::ShouldContinue
 #ifdef XP_WIN
     if (LaunchHangUI()) {
         return true;
     }
     // If LaunchHangUI returned false then we should proceed with the 
     // original plugin hang behaviour and kill the plugin container.
     FinishHangUI();
 #endif // XP_WIN
-    nsString dummy;
-    TerminateChildProcess(MessageLoop::current(), &dummy);
+    TerminateChildProcess(MessageLoop::current(), EmptyString());
     GetIPCChannel()->CloseWithTimeout();
     return false;
 }
 
 bool
 PluginModuleContentParent::ShouldContinueFromReplyTimeout()
 {
     nsRefPtr<ProcessHangMonitor> monitor = ProcessHangMonitor::Get();
@@ -1174,17 +1173,17 @@ PluginModuleContentParent::ShouldContinu
 void
 PluginModuleContentParent::OnExitedSyncSend()
 {
     ProcessHangMonitor::ClearHang();
 }
 
 void
 PluginModuleChromeParent::TerminateChildProcess(MessageLoop* aMsgLoop,
-                                                nsAString* aBrowserDumpId)
+                                                const nsAString& aBrowserDumpId)
 {
 #ifdef MOZ_CRASHREPORTER
 #ifdef XP_WIN
     mozilla::MutexAutoLock lock(mCrashReporterMutex);
     CrashReporterParent* crashReporter = mCrashReporter;
     if (!crashReporter) {
         // If mCrashReporter is null then the hang has ended, the plugin module
         // is shutting down. There's nothing to do here.
@@ -1210,28 +1209,28 @@ PluginModuleChromeParent::TerminateChild
     bool reportsReady = false;
 
     // Check to see if we already have a browser dump id - with e10s plugin
     // hangs we take this earlier (see ProcessHangMonitor) from a background
     // thread. We do this before we message the main thread about the hang
     // since the posted message will trash our browser stack state.
     bool exists;
     nsCOMPtr<nsIFile> browserDumpFile;
-    if (aBrowserDumpId && !aBrowserDumpId->IsEmpty() &&
-        CrashReporter::GetMinidumpForID(*aBrowserDumpId, getter_AddRefs(browserDumpFile)) &&
+    if (!aBrowserDumpId.IsEmpty() &&
+        CrashReporter::GetMinidumpForID(aBrowserDumpId, getter_AddRefs(browserDumpFile)) &&
         browserDumpFile &&
         NS_SUCCEEDED(browserDumpFile->Exists(&exists)) && exists)
     {
         // We have a single browser report, generate a new plugin process parent
         // report and pair it up with the browser report handed in.
         reportsReady = crashReporter->GenerateMinidumpAndPair(this, browserDumpFile,
                                                               NS_LITERAL_CSTRING("browser"));
         if (!reportsReady) {
           browserDumpFile = nullptr;
-          CrashReporter::DeleteMinidumpFilesForID(*aBrowserDumpId);
+          CrashReporter::DeleteMinidumpFilesForID(aBrowserDumpId);
         }
     }
 
     // Generate crash report including plugin and browser process minidumps.
     // The plugin process is the parent report with additional dumps including
     // the browser process, content process when running under e10s, and
     // various flash subprocesses if we're the flash module.
     if (!reportsReady) {
@@ -2382,17 +2381,22 @@ PluginModuleParent::NP_Shutdown(NPError*
 
     return NS_OK;
 }
 
 bool
 PluginModuleParent::DoShutdown(NPError* error)
 {
     bool ok = true;
-    if (IsChrome()) {
+    if (IsChrome() && mHadLocalInstance) {
+        // We synchronously call NP_Shutdown if the chrome process was using
+        // plugins itself. That way we can service any requests the plugin
+        // makes. If we're in e10s, though, the content processes will have
+        // already shut down and there's no one to talk to. So we shut down
+        // asynchronously in PluginModuleChild::ActorDestroy.
         ok = CallNP_Shutdown(error);
     }
 
     // if NP_Shutdown() is nested within another interrupt call, this will
     // break things.  but lord help us if we're doing that anyway; the
     // plugin dso will have been unloaded on the other side by the
     // CallNP_Shutdown() message
     Close();
--- a/dom/plugins/ipc/PluginModuleParent.h
+++ b/dom/plugins/ipc/PluginModuleParent.h
@@ -128,16 +128,19 @@ public:
 
     virtual bool WaitForIPCConnection() { return true; }
 
     nsCString GetHistogramKey() const {
         return mPluginName + mPluginVersion;
     }
 
     virtual nsresult GetRunID(uint32_t* aRunID) override;
+    virtual void SetHasLocalInstance() override {
+        mHadLocalInstance = true;
+    }
 
 protected:
     virtual mozilla::ipc::RacyInterruptPolicy
     MediateInterruptRace(const Message& parent, const Message& child) override
     {
         return MediateRace(parent, child);
     }
 
@@ -279,16 +282,17 @@ protected:
     bool MaybeRunDeferredShutdown();
     bool DoShutdown(NPError* error);
 
     bool GetSetting(NPNVariable aVariable);
     void GetSettings(PluginSettings* aSettings);
 
     bool mIsChrome;
     bool mShutdown;
+    bool mHadLocalInstance;
     bool mClearSiteDataSupported;
     bool mGetSitesWithDataSupported;
     NPNetscapeFuncs* mNPNIface;
     NPPluginFuncs* mNPPIface;
     nsNPAPIPlugin* mPlugin;
     TaskFactory<PluginModuleParent> mTaskFactory;
     nsString mPluginDumpID;
     nsString mBrowserDumpID;
@@ -382,17 +386,17 @@ class PluginModuleChromeParent
      *
      * @param aMsgLoop the main message pump associated with the module
      *   protocol.
      * @param aBrowserDumpId (optional) previously taken browser dump id. If
      *   provided TerminateChildProcess will use this browser dump file in
      *   generating a multi-process crash report. If not provided a browser
      *   dump will be taken at the time of this call.
      */
-    void TerminateChildProcess(MessageLoop* aMsgLoop, nsAString* aBrowserDumpId);
+    void TerminateChildProcess(MessageLoop* aMsgLoop, const nsAString& aBrowserDumpId);
 
 #ifdef XP_WIN
     /**
      * Called by Plugin Hang UI to notify that the user has clicked continue.
      * Used for chrome hang annotations.
      */
     void
     OnHangUIContinue();
--- a/dom/webidl/MouseEvent.webidl
+++ b/dom/webidl/MouseEvent.webidl
@@ -11,17 +11,19 @@
  */
 
 [Constructor(DOMString typeArg, optional MouseEventInit mouseEventInitDict)]
 interface MouseEvent : UIEvent {
   readonly attribute long           screenX;
   readonly attribute long           screenY;
   readonly attribute long           clientX;
   readonly attribute long           clientY;
+[Pref="dom.mouseEvent.offsetXY.enabled"]
   readonly attribute long           offsetX;
+[Pref="dom.mouseEvent.offsetXY.enabled"]
   readonly attribute long           offsetY;
   readonly attribute boolean        ctrlKey;
   readonly attribute boolean        shiftKey;
   readonly attribute boolean        altKey;
   readonly attribute boolean        metaKey;
   readonly attribute short          button;
   readonly attribute unsigned short buttons;
   readonly attribute EventTarget?   relatedTarget;
--- a/dom/webidl/ServiceWorkerGlobalScope.webidl
+++ b/dom/webidl/ServiceWorkerGlobalScope.webidl
@@ -16,16 +16,18 @@ interface ServiceWorkerGlobalScope : Wor
   readonly attribute Clients clients;
   readonly attribute ServiceWorkerRegistration registration;
 
   [Throws]
   Promise<boolean> skipWaiting();
 
   attribute EventHandler oninstall;
   attribute EventHandler onactivate;
+
+  [Func="mozilla::dom::workers::ServiceWorkerGlobalScope::InterceptionEnabled"]
   attribute EventHandler onfetch;
   attribute EventHandler onbeforeevicted;
   attribute EventHandler onevicted;
 
   // The event.source of these MessageEvents are instances of Client
   attribute EventHandler onmessage;
 };
 
--- a/dom/workers/PServiceWorkerManager.ipdl
+++ b/dom/workers/PServiceWorkerManager.ipdl
@@ -20,20 +20,26 @@ parent:
   Register(ServiceWorkerRegistrationData data);
 
   Unregister(PrincipalInfo principalInfo, nsString scope);
 
   PropagateSoftUpdate(OriginAttributes originAttributes,
                       nsString scope);
   PropagateUnregister(PrincipalInfo principalInfo, nsString scope);
 
+  PropagateRemove(nsCString host);
+
+  PropagateRemoveAll();
+
   Shutdown();
 
 child:
   NotifyRegister(ServiceWorkerRegistrationData data);
   NotifySoftUpdate(OriginAttributes originAttributes, nsString scope);
   NotifyUnregister(PrincipalInfo principalInfo, nsString scope);
+  NotifyRemove(nsCString host);
+  NotifyRemoveAll();
 
   __delete__();
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -156,16 +156,17 @@ static_assert(MAX_WORKERS_PER_DOMAIN >= 
 #define DUMP_CONTROLLED_BY_PREF 1
 #define PREF_DOM_WINDOW_DUMP_ENABLED "browser.dom.window.dump.enabled"
 #endif
 
 #define PREF_DOM_CACHES_ENABLED        "dom.caches.enabled"
 #define PREF_WORKERS_LATEST_JS_VERSION "dom.workers.latestJSVersion"
 #define PREF_INTL_ACCEPT_LANGUAGES     "intl.accept_languages"
 #define PREF_SERVICEWORKERS_ENABLED    "dom.serviceWorkers.enabled"
+#define PREF_INTERCEPTION_ENABLED      "dom.serviceWorkers.interception.enabled"
 
 namespace {
 
 const uint32_t kNoIndex = uint32_t(-1);
 
 const JS::ContextOptions kRequiredContextOptions =
   JS::ContextOptions().setDontReportUncaught(true);
 
@@ -1900,16 +1901,20 @@ RuntimeService::Init()
       NS_FAILED(Preferences::RegisterCallbackAndCall(
                                   WorkerPrefChanged,
                                   PREF_DOM_CACHES_ENABLED,
                                   reinterpret_cast<void *>(WORKERPREF_DOM_CACHES))) ||
       NS_FAILED(Preferences::RegisterCallbackAndCall(
                                   WorkerPrefChanged,
                                   PREF_SERVICEWORKERS_ENABLED,
                                   reinterpret_cast<void *>(WORKERPREF_SERVICEWORKERS))) ||
+      NS_FAILED(Preferences::RegisterCallbackAndCall(
+                                  WorkerPrefChanged,
+                                  PREF_INTERCEPTION_ENABLED,
+                                  reinterpret_cast<void *>(WORKERPREF_INTERCEPTION_ENABLED))) ||
       NS_FAILED(Preferences::RegisterCallback(LoadRuntimeOptions,
                                               PREF_JS_OPTIONS_PREFIX,
                                               nullptr)) ||
       NS_FAILED(Preferences::RegisterCallbackAndCall(
                                                    LoadRuntimeOptions,
                                                    PREF_WORKERS_OPTIONS_PREFIX,
                                                    nullptr)) ||
       NS_FAILED(Preferences::RegisterCallbackAndCall(PrefLanguagesChanged,
@@ -2097,16 +2102,20 @@ RuntimeService::Cleanup()
         NS_FAILED(Preferences::UnregisterCallback(LoadRuntimeOptions,
                                                   PREF_JS_OPTIONS_PREFIX,
                                                   nullptr)) ||
         NS_FAILED(Preferences::UnregisterCallback(LoadRuntimeOptions,
                                                   PREF_WORKERS_OPTIONS_PREFIX,
                                                   nullptr)) ||
         NS_FAILED(Preferences::UnregisterCallback(
                                   WorkerPrefChanged,
+                                  PREF_INTERCEPTION_ENABLED,
+                                  reinterpret_cast<void *>(WORKERPREF_INTERCEPTION_ENABLED))) ||
+        NS_FAILED(Preferences::UnregisterCallback(
+                                  WorkerPrefChanged,
                                   PREF_SERVICEWORKERS_ENABLED,
                                   reinterpret_cast<void *>(WORKERPREF_SERVICEWORKERS))) ||
         NS_FAILED(Preferences::UnregisterCallback(
                                   WorkerPrefChanged,
                                   PREF_DOM_CACHES_ENABLED,
                                   reinterpret_cast<void *>(WORKERPREF_DOM_CACHES))) ||
 #if DUMP_CONTROLLED_BY_PREF
         NS_FAILED(Preferences::UnregisterCallback(
@@ -2651,16 +2660,20 @@ RuntimeService::WorkerPrefChanged(const 
   if (key == WORKERPREF_DOM_CACHES) {
     key = WORKERPREF_DOM_CACHES;
     sDefaultPreferences[WORKERPREF_DOM_CACHES] =
       Preferences::GetBool(PREF_DOM_CACHES_ENABLED, false);
   } else if (key == WORKERPREF_SERVICEWORKERS) {
     key = WORKERPREF_SERVICEWORKERS;
     sDefaultPreferences[WORKERPREF_SERVICEWORKERS] =
       Preferences::GetBool(PREF_SERVICEWORKERS_ENABLED, false);
+  } else if (key == WORKERPREF_INTERCEPTION_ENABLED) {
+    key = WORKERPREF_INTERCEPTION_ENABLED;
+    sDefaultPreferences[key] =
+      Preferences::GetBool(PREF_INTERCEPTION_ENABLED, false);
   }
   // This function should never be registered as a callback for a preference it
   // does not handle.
   MOZ_ASSERT(key != WORKERPREF_COUNT);
 
   RuntimeService* rts = RuntimeService::GetService();
   if (rts) {
     rts->UpdateAllWorkerPreference(key, sDefaultPreferences[key]);
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -735,17 +735,17 @@ public:
 
   NS_IMETHOD Run() override
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
     MOZ_ASSERT(swm);
 
-    swm->PropagateSoftUpdate(mOriginAttributes,mScope);
+    swm->PropagateSoftUpdate(mOriginAttributes, mScope);
     return NS_OK;
   }
 
 private:
   ~PropagateSoftUpdateRunnable()
   {}
 
   const OriginAttributes mOriginAttributes;
@@ -784,16 +784,86 @@ private:
   ~PropagateUnregisterRunnable()
   {}
 
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCOMPtr<nsIServiceWorkerUnregisterCallback> mCallback;
   const nsString mScope;
 };
 
+class RemoveRunnable final : public nsRunnable
+{
+public:
+  explicit RemoveRunnable(const nsACString& aHost)
+  {}
+
+  NS_IMETHOD Run() override
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+    MOZ_ASSERT(swm);
+
+    swm->Remove(mHost);
+    return NS_OK;
+  }
+
+private:
+  ~RemoveRunnable()
+  {}
+
+  const nsCString mHost;
+};
+
+class PropagateRemoveRunnable final : public nsRunnable
+{
+public:
+  explicit PropagateRemoveRunnable(const nsACString& aHost)
+  {}
+
+  NS_IMETHOD Run() override
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+    MOZ_ASSERT(swm);
+
+    swm->PropagateRemove(mHost);
+    return NS_OK;
+  }
+
+private:
+  ~PropagateRemoveRunnable()
+  {}
+
+  const nsCString mHost;
+};
+
+class PropagateRemoveAllRunnable final : public nsRunnable
+{
+public:
+  PropagateRemoveAllRunnable()
+  {}
+
+  NS_IMETHOD Run() override
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+    MOZ_ASSERT(swm);
+
+    swm->PropagateRemoveAll();
+    return NS_OK;
+  }
+
+private:
+  ~PropagateRemoveAllRunnable()
+  {}
+};
+
 } // anonymous namespace
 
 class ServiceWorkerRegisterJob final : public ServiceWorkerJob,
                                        public serviceWorkerScriptCache::CompareCallback
 {
   friend class ContinueInstallTask;
 
   nsCString mScope;
@@ -2308,16 +2378,19 @@ private:
     PrincipalInfo principalInfo;
     if (NS_WARN_IF(NS_FAILED(PrincipalToPrincipalInfo(mPrincipal,
                                                       &principalInfo)))) {
       return mCallback ? mCallback->UnregisterSucceeded(false) : NS_OK;
     }
 
     nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
 
+    MOZ_ASSERT(swm->mActor);
+    swm->mActor->SendUnregister(principalInfo, NS_ConvertUTF8toUTF16(mScope));
+
     nsAutoCString scopeKey;
     nsresult rv = swm->PrincipalToScopeKey(mPrincipal, scopeKey);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return mCallback ? mCallback->UnregisterSucceeded(false) : NS_OK;
     }
 
     // "Let registration be the result of running [[Get Registration]]
     // algorithm passing scope as the argument."
@@ -2350,19 +2423,16 @@ private:
         return NS_OK;
       }
 
       // "Invoke [[Clear Registration]]..."
       registration->Clear();
       swm->RemoveRegistration(registration);
     }
 
-    MOZ_ASSERT(swm->mActor);
-    swm->mActor->SendUnregister(principalInfo, NS_ConvertUTF8toUTF16(mScope));
-
     return NS_OK;
   }
 
   // The unregister job is done irrespective of success or failure of any sort.
   void
   UnregisterAndDone()
   {
     nsresult rv = Unregister();
@@ -4317,30 +4387,74 @@ ServiceWorkerManager::ForceUnregister(Re
     queue->CancelJobs();
   }
 
   // Since Unregister is async, it is ok to call it in an enumeration.
   Unregister(aRegistration->mPrincipal, nullptr, NS_ConvertUTF8toUTF16(aRegistration->mScope));
 }
 
 NS_IMETHODIMP
+ServiceWorkerManager::RemoveAndPropagate(const nsACString& aHost)
+{
+  Remove(aHost);
+  PropagateRemove(aHost);
+  return NS_OK;
+}
+
+void
 ServiceWorkerManager::Remove(const nsACString& aHost)
 {
   AssertIsOnMainThread();
+
+  // We need to postpone this operation in case we don't have an actor because
+  // this is needed by the ForceUnregister.
+  if (!mActor) {
+    nsRefPtr<nsIRunnable> runnable = new RemoveRunnable(aHost);
+    AppendPendingOperation(runnable);
+    return;
+  }
+
   mRegistrationInfos.EnumerateRead(UnregisterIfMatchesHostPerPrincipal,
                                    &const_cast<nsACString&>(aHost));
-  return NS_OK;
 }
 
-NS_IMETHODIMP
+void
+ServiceWorkerManager::PropagateRemove(const nsACString& aHost)
+{
+  AssertIsOnMainThread();
+
+  if (!mActor) {
+    nsRefPtr<nsIRunnable> runnable = new PropagateRemoveRunnable(aHost);
+    AppendPendingOperation(runnable);
+    return;
+  }
+
+  mActor->SendPropagateRemove(nsCString(aHost));
+}
+
+void
 ServiceWorkerManager::RemoveAll()
 {
   AssertIsOnMainThread();
   mRegistrationInfos.EnumerateRead(UnregisterIfMatchesHostPerPrincipal, nullptr);
-  return NS_OK;
+}
+
+void
+ServiceWorkerManager::PropagateRemoveAll()
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+
+  if (!mActor) {
+    nsRefPtr<nsIRunnable> runnable = new PropagateRemoveAllRunnable();
+    AppendPendingOperation(runnable);
+    return;
+  }
+
+  mActor->SendPropagateRemoveAll();
 }
 
 static PLDHashOperator
 UpdateEachRegistration(const nsACString& aKey,
                        ServiceWorkerRegistrationInfo* aInfo,
                        void* aUserArg) {
   auto This = static_cast<ServiceWorkerManager*>(aUserArg);
   MOZ_ASSERT(!aInfo->mScope.IsEmpty());
@@ -4380,33 +4494,29 @@ ServiceWorkerManager::UpdateAllRegistrat
 
 NS_IMETHODIMP
 ServiceWorkerManager::Observe(nsISupports* aSubject,
                               const char* aTopic,
                               const char16_t* aData)
 {
   MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
 
-  nsAutoTArray<ContentParent*,1> children;
-  ContentParent::GetAll(children);
-
   if (strcmp(aTopic, PURGE_SESSION_HISTORY) == 0) {
-    for (uint32_t i = 0; i < children.Length(); i++) {
-      unused << children[i]->SendRemoveServiceWorkerRegistrations();
-    }
-
     RemoveAll();
-  } else if (strcmp(aTopic, PURGE_DOMAIN_DATA) == 0) {
+    PropagateRemoveAll();
+    return NS_OK;
+  }
+
+  if (strcmp(aTopic, PURGE_DOMAIN_DATA) == 0) {
     nsAutoString domain(aData);
-    for (uint32_t i = 0; i < children.Length(); i++) {
-      unused << children[i]->SendRemoveServiceWorkerRegistrationsForDomain(domain);
-    }
-
-    Remove(NS_ConvertUTF16toUTF8(domain));
-  } else if (strcmp(aTopic, WEBAPPS_CLEAR_DATA) == 0) {
+    RemoveAndPropagate(NS_ConvertUTF16toUTF8(domain));
+    return NS_OK;
+  }
+
+  if (strcmp(aTopic, WEBAPPS_CLEAR_DATA) == 0) {
     nsCOMPtr<mozIApplicationClearPrivateDataParams> params =
       do_QueryInterface(aSubject);
     if (NS_WARN_IF(!params)) {
       return NS_OK;
     }
 
     uint32_t appId;
     nsresult rv = params->GetAppId(&appId);
--- a/dom/workers/ServiceWorkerManager.h
+++ b/dom/workers/ServiceWorkerManager.h
@@ -303,16 +303,28 @@ public:
 
   void
   SoftUpdate(const OriginAttributes& aOriginAttributes, const nsACString& aScope);
 
   void
   PropagateSoftUpdate(const OriginAttributes& aOriginAttributes,
                       const nsAString& aScope);
 
+  void
+  PropagateRemove(const nsACString& aHost);
+
+  void
+  Remove(const nsACString& aHost);
+
+  void
+  PropagateRemoveAll();
+
+  void
+  RemoveAll();
+
   already_AddRefed<ServiceWorkerRegistrationInfo>
   GetRegistration(nsIPrincipal* aPrincipal, const nsACString& aScope) const;
 
   ServiceWorkerRegistrationInfo*
   CreateNewRegistration(const nsCString& aScope, nsIPrincipal* aPrincipal);
 
   void
   RemoveRegistration(ServiceWorkerRegistrationInfo* aRegistration);
--- a/dom/workers/ServiceWorkerManagerChild.cpp
+++ b/dom/workers/ServiceWorkerManagerChild.cpp
@@ -62,11 +62,39 @@ ServiceWorkerManagerChild::RecvNotifyUnr
     return true;
   }
 
   nsresult rv = swm->Unregister(principal, nullptr, aScope);
   unused << NS_WARN_IF(NS_FAILED(rv));
   return true;
 }
 
+bool
+ServiceWorkerManagerChild::RecvNotifyRemove(const nsCString& aHost)
+{
+  if (mShuttingDown) {
+    return true;
+  }
+
+  nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+  MOZ_ASSERT(swm);
+
+  swm->Remove(aHost);
+  return true;
+}
+
+bool
+ServiceWorkerManagerChild::RecvNotifyRemoveAll()
+{
+  if (mShuttingDown) {
+    return true;
+  }
+
+  nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+  MOZ_ASSERT(swm);
+
+  swm->RemoveAll();
+  return true;
+}
+
 } // workers namespace
 } // dom namespace
 } // mozilla namespace
--- a/dom/workers/ServiceWorkerManagerChild.h
+++ b/dom/workers/ServiceWorkerManagerChild.h
@@ -37,16 +37,20 @@ public:
                                                                        override;
 
   virtual bool RecvNotifySoftUpdate(const OriginAttributes& aOriginAttributes,
                                     const nsString& aScope) override;
 
   virtual bool RecvNotifyUnregister(const PrincipalInfo& aPrincipalInfo,
                                     const nsString& aScope) override;
 
+  virtual bool RecvNotifyRemove(const nsCString& aHost) override;
+
+  virtual bool RecvNotifyRemoveAll() override;
+
 private:
   ServiceWorkerManagerChild()
     : mShuttingDown(false)
   {}
 
   ~ServiceWorkerManagerChild() {}
 
   bool mShuttingDown;
--- a/dom/workers/ServiceWorkerManagerParent.cpp
+++ b/dom/workers/ServiceWorkerManagerParent.cpp
@@ -252,16 +252,42 @@ ServiceWorkerManagerParent::RecvPropagat
     return false;
   }
 
   mService->PropagateUnregister(mID, aPrincipalInfo, aScope);
   return true;
 }
 
 bool
+ServiceWorkerManagerParent::RecvPropagateRemove(const nsCString& aHost)
+{
+  AssertIsOnBackgroundThread();
+
+  if (NS_WARN_IF(!mService)) {
+    return false;
+  }
+
+  mService->PropagateRemove(mID, aHost);
+  return true;
+}
+
+bool
+ServiceWorkerManagerParent::RecvPropagateRemoveAll()
+{
+  AssertIsOnBackgroundThread();
+
+  if (NS_WARN_IF(!mService)) {
+    return false;
+  }
+
+  mService->PropagateRemoveAll(mID);
+  return true;
+}
+
+bool
 ServiceWorkerManagerParent::RecvShutdown()
 {
   AssertIsOnBackgroundThread();
 
   if (NS_WARN_IF(!mService)) {
     return false;
   }
 
--- a/dom/workers/ServiceWorkerManagerParent.h
+++ b/dom/workers/ServiceWorkerManagerParent.h
@@ -43,16 +43,20 @@ private:
                               const nsString& aScope) override;
 
   virtual bool RecvPropagateSoftUpdate(const OriginAttributes& aOriginAttributes,
                                        const nsString& aScope) override;
 
   virtual bool RecvPropagateUnregister(const PrincipalInfo& aPrincipalInfo,
                                        const nsString& aScope) override;
 
+  virtual bool RecvPropagateRemove(const nsCString& aHost) override;
+
+  virtual bool RecvPropagateRemoveAll() override;
+
   virtual bool RecvShutdown() override;
 
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
 
   nsRefPtr<ServiceWorkerManagerService> mService;
 
   // We use this ID in the Service in order to avoid the sending of messages to
   // ourself.
--- a/dom/workers/ServiceWorkerManagerService.cpp
+++ b/dom/workers/ServiceWorkerManagerService.cpp
@@ -222,16 +222,103 @@ UnregisterEnumerator(nsPtrHashKey<Servic
   } else {
     data->mParentFound = true;
 #endif
   }
 
   return PL_DHASH_NEXT;
 }
 
+struct MOZ_STACK_CLASS RemoveAllData final
+{
+  explicit RemoveAllData(uint64_t aParentID)
+    : mParentID(aParentID)
+#ifdef DEBUG
+    , mParentFound(false)
+#endif
+  {
+    MOZ_COUNT_CTOR(RemoveAllData);
+  }
+
+  ~RemoveAllData()
+  {
+    MOZ_COUNT_DTOR(RemoveAllData);
+  }
+
+  const uint64_t mParentID;
+#ifdef DEBUG
+  bool mParentFound;
+#endif
+};
+
+PLDHashOperator
+RemoveAllEnumerator(nsPtrHashKey<ServiceWorkerManagerParent>* aKey, void* aPtr)
+{
+  AssertIsOnBackgroundThread();
+
+  auto* data = static_cast<RemoveAllData*>(aPtr);
+  ServiceWorkerManagerParent* parent = aKey->GetKey();
+  MOZ_ASSERT(parent);
+
+  if (parent->ID() != data->mParentID) {
+    unused << parent->SendNotifyRemoveAll();
+#ifdef DEBUG
+  } else {
+    data->mParentFound = true;
+#endif
+  }
+
+  return PL_DHASH_NEXT;
+}
+
+struct MOZ_STACK_CLASS RemoveData final
+{
+  RemoveData(const nsACString& aHost,
+             uint64_t aParentID)
+    : mHost(aHost)
+    , mParentID(aParentID)
+#ifdef DEBUG
+    , mParentFound(false)
+#endif
+  {
+    MOZ_COUNT_CTOR(RemoveData);
+  }
+
+  ~RemoveData()
+  {
+    MOZ_COUNT_DTOR(RemoveData);
+  }
+
+  const nsCString mHost;
+  const uint64_t mParentID;
+#ifdef DEBUG
+  bool mParentFound;
+#endif
+};
+
+PLDHashOperator
+RemoveEnumerator(nsPtrHashKey<ServiceWorkerManagerParent>* aKey, void* aPtr)
+{
+  AssertIsOnBackgroundThread();
+
+  auto* data = static_cast<RemoveData*>(aPtr);
+  ServiceWorkerManagerParent* parent = aKey->GetKey();
+  MOZ_ASSERT(parent);
+
+  if (parent->ID() != data->mParentID) {
+    unused << parent->SendNotifyRemove(data->mHost);
+#ifdef DEBUG
+  } else {
+    data->mParentFound = true;
+#endif
+  }
+
+  return PL_DHASH_NEXT;
+}
+
 } // anonymous namespce
 
 void
 ServiceWorkerManagerService::PropagateRegistration(
                                            uint64_t aParentID,
                                            ServiceWorkerRegistrationData& aData)
 {
   AssertIsOnBackgroundThread();
@@ -280,11 +367,44 @@ ServiceWorkerManagerService::PropagateUn
   UnregisterData data(aPrincipalInfo, aScope, aParentID);
   mAgents.EnumerateEntries(UnregisterEnumerator, &data);
 
 #ifdef DEBUG
   MOZ_ASSERT(data.mParentFound);
 #endif
 }
 
+void
+ServiceWorkerManagerService::PropagateRemove(uint64_t aParentID,
+                                             const nsACString& aHost)
+{
+  AssertIsOnBackgroundThread();
+
+  RemoveData data(aHost, aParentID);
+  mAgents.EnumerateEntries(RemoveEnumerator, &data);
+
+#ifdef DEBUG
+  MOZ_ASSERT(data.mParentFound);
+#endif
+}
+
+void
+ServiceWorkerManagerService::PropagateRemoveAll(uint64_t aParentID)
+{
+  AssertIsOnBackgroundThread();
+
+  nsRefPtr<dom::ServiceWorkerRegistrar> service =
+    dom::ServiceWorkerRegistrar::Get();
+  MOZ_ASSERT(service);
+
+  service->RemoveAll();
+
+  RemoveAllData data(aParentID);
+  mAgents.EnumerateEntries(RemoveAllEnumerator, &data);
+
+#ifdef DEBUG
+  MOZ_ASSERT(data.mParentFound);
+#endif
+}
+
 } // workers namespace
 } // dom namespace
 } // mozilla namespace
--- a/dom/workers/ServiceWorkerManagerService.h
+++ b/dom/workers/ServiceWorkerManagerService.h
@@ -44,16 +44,20 @@ public:
   void PropagateSoftUpdate(uint64_t aParentID,
                            const OriginAttributes& aOriginAttributes,
                            const nsAString& aScope);
 
   void PropagateUnregister(uint64_t aParentID,
                            const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
                            const nsAString& aScope);
 
+  void PropagateRemove(uint64_t aParentID, const nsACString& aHost);
+
+  void PropagateRemoveAll(uint64_t aParentID);
+
 private:
   ServiceWorkerManagerService();
   ~ServiceWorkerManagerService();
 
   nsTHashtable<nsPtrHashKey<ServiceWorkerManagerParent>> mAgents;
 };
 
 } // workers namespace
--- a/dom/workers/ServiceWorkerRegistrar.cpp
+++ b/dom/workers/ServiceWorkerRegistrar.cpp
@@ -209,16 +209,41 @@ ServiceWorkerRegistrar::UnregisterServic
   }
 
   if (deleted) {
     ScheduleSaveData();
   }
 }
 
 void
+ServiceWorkerRegistrar::RemoveAll()
+{
+  AssertIsOnBackgroundThread();
+
+  if (mShuttingDown) {
+    NS_WARNING("Failed to remove all the serviceWorkers during shutting down.");
+    return;
+  }
+
+  bool deleted = false;
+
+  {
+    MonitorAutoLock lock(mMonitor);
+    MOZ_ASSERT(mDataLoaded);
+
+    deleted = !mData.IsEmpty();
+    mData.Clear();
+  }
+
+  if (deleted) {
+    ScheduleSaveData();
+  }
+}
+
+void
 ServiceWorkerRegistrar::LoadData()
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(!mDataLoaded);
 
   nsresult rv = ReadData();
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
--- a/dom/workers/ServiceWorkerRegistrar.h
+++ b/dom/workers/ServiceWorkerRegistrar.h
@@ -49,16 +49,17 @@ public:
 
   static already_AddRefed<ServiceWorkerRegistrar> Get();
 
   void GetRegistrations(nsTArray<ServiceWorkerRegistrationData>& aValues);
 
   void RegisterServiceWorker(const ServiceWorkerRegistrationData& aData);
   void UnregisterServiceWorker(const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
                                const nsACString& aScope);
+  void RemoveAll();
 
 protected:
   // These methods are protected because we test this class using gTest
   // subclassing it.
   void LoadData();
   void SaveData();
 
   nsresult ReadData();
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -4090,17 +4090,17 @@ WorkerPrivateParent<Derived>::SetBaseURI
   if (url && NS_SUCCEEDED(url->GetQuery(temp)) && !temp.IsEmpty()) {
     mLocationInfo.mSearch.Assign('?');
     mLocationInfo.mSearch.Append(temp);
   }
 
   if (NS_SUCCEEDED(aBaseURI->GetRef(temp)) && !temp.IsEmpty()) {
     nsCOMPtr<nsITextToSubURI> converter =
       do_GetService(NS_ITEXTTOSUBURI_CONTRACTID);
-    if (converter && nsContentUtils::EncodeDecodeURLHash()) {
+    if (converter && nsContentUtils::GettersDecodeURLHash()) {
       nsCString charset;
       nsAutoString unicodeRef;
       if (NS_SUCCEEDED(aBaseURI->GetOriginCharset(charset)) &&
           NS_SUCCEEDED(converter->UnEscapeURIForUI(charset, temp,
                                                    unicodeRef))) {
         mLocationInfo.mHash.Assign('#');
         mLocationInfo.mHash.Append(NS_ConvertUTF16toUTF8(unicodeRef));
       }
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -1235,16 +1235,23 @@ public:
   bool
   ServiceWorkersEnabled() const
   {
     AssertIsOnWorkerThread();
     return mPreferences[WORKERPREF_SERVICEWORKERS];
   }
 
   bool
+  InterceptionEnabled() const
+  {
+    AssertIsOnWorkerThread();
+    return mPreferences[WORKERPREF_INTERCEPTION_ENABLED];
+  }
+
+  bool
   OnLine() const
   {
     AssertIsOnWorkerThread();
     return mOnLine;
   }
 
   void
   StopSyncLoop(nsIEventTarget* aSyncLoopTarget, bool aResult);
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -616,16 +616,26 @@ ServiceWorkerGlobalScope::SkipWaiting(Er
   aRv = NS_DispatchToMainThread(runnable);
   if (NS_WARN_IF(aRv.Failed())) {
     promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
   }
 
   return promise.forget();
 }
 
+// static
+bool
+ServiceWorkerGlobalScope::InterceptionEnabled(JSContext* aCx, JSObject* aObj)
+{
+  WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+  MOZ_ASSERT(worker);
+  worker->AssertIsOnWorkerThread();
+  return worker->InterceptionEnabled();
+}
+
 WorkerDebuggerGlobalScope::WorkerDebuggerGlobalScope(
                                                   WorkerPrivate* aWorkerPrivate)
 : mWorkerPrivate(aWorkerPrivate)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 }
 
 WorkerDebuggerGlobalScope::~WorkerDebuggerGlobalScope()
--- a/dom/workers/WorkerScope.h
+++ b/dom/workers/WorkerScope.h
@@ -210,16 +210,19 @@ public:
                                            WorkerGlobalScope)
 
   ServiceWorkerGlobalScope(WorkerPrivate* aWorkerPrivate, const nsACString& aScope);
 
   virtual bool
   WrapGlobalObject(JSContext* aCx,
                    JS::MutableHandle<JSObject*> aReflector) override;
 
+  static bool
+  InterceptionEnabled(JSContext* aCx, JSObject* aObj);
+
   void
   GetScope(nsString& aScope) const
   {
     aScope = mScope;
   }
 
   ServiceWorkerClients*
   Clients();
--- a/dom/workers/Workers.h
+++ b/dom/workers/Workers.h
@@ -193,16 +193,17 @@ struct JSSettings
   }
 };
 
 enum WorkerPreference
 {
   WORKERPREF_DUMP = 0, // browser.dom.window.dump.enabled
   WORKERPREF_DOM_CACHES, // dom.caches.enabled
   WORKERPREF_SERVICEWORKERS, // dom.serviceWorkers.enabled
+  WORKERPREF_INTERCEPTION_ENABLED, // dom.serviceWorkers.interception.enabled
   WORKERPREF_COUNT
 };
 
 // Implemented in WorkerPrivate.cpp
 
 struct WorkerLoadInfo
 {
   // All of these should be released in WorkerPrivateParent::ForgetMainThreadObjects.
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/interception_featuredetect.js
@@ -0,0 +1,4 @@
+// Only succeeds if onfetch is available.
+if (!("onfetch" in self)) {
+  throw new Error("Not capable of interception");
+}
--- a/dom/workers/test/serviceworkers/mochitest.ini
+++ b/dom/workers/test/serviceworkers/mochitest.ini
@@ -124,73 +124,76 @@ support-files =
   sw_clients/refresher_compressed.html
   sw_clients/refresher_compressed.html^headers^
   sw_clients/refresher_cached.html
   sw_clients/refresher_cached_compressed.html
   sw_clients/refresher_cached_compressed.html^headers^
   strict_mode_error.js
   skip_waiting_installed_worker.js
   skip_waiting_scope/index.html
+  interception_featuredetect.js
   thirdparty/iframe1.html
   thirdparty/iframe2.html
   thirdparty/register.html
   thirdparty/unregister.html
   thirdparty/sw.js
   register_https.html
   gzip_redirect_worker.js
   sw_clients/navigator.html
   eval_worker.js
   test_eval_not_allowed.html^headers^
 
-[test_unregister.html]
-[test_installation_simple.html]
+[test_app_protocol.html]
+[test_bug1151916.html]
+[test_claim.html]
+[test_claim_fetch.html]
+[test_claim_oninstall.html]
+[test_client_focus.html]
+[test_close.html]
+[test_controller.html]
+[test_cross_origin_url_after_redirect.html]
+[test_empty_serviceworker.html]
+[test_eval_allowed.html]
+[test_eval_not_allowed.html]
 [test_fetch_event.html]
+[test_force_refresh.html]
+[test_gzip_redirect.html]
 [test_https_fetch.html]
 [test_https_fetch_cloned_response.html]
+[test_https_origin_after_redirect.html]
+[test_https_origin_after_redirect_cached.html]
 [test_https_synth_fetch_from_cached_sw.html]
+[test_importscript.html]
+[test_install_event.html]
+[test_installation_simple.html]
+[test_interception_featuredetect.html]
 [test_match_all.html]
 [test_match_all_advanced.html]
-[test_install_event.html]
-[test_navigator.html]
-[test_scopes.html]
-[test_controller.html]
-[test_workerUnregister.html]
-[test_workerUpdate.html]
-[test_post_message.html]
-[test_post_message_advanced.html]
-[test_post_message_source.html]
+[test_match_all_client_id.html]
 [test_match_all_client_properties.html]
-[test_close.html]
-[test_serviceworker_interfaces.html]
-[test_serviceworker_not_sharedworker.html]
-[test_match_all_client_id.html]
-[test_sandbox_intercept.html]
-[test_request_context.html]
-[test_importscript.html]
-[test_client_focus.html]
-[test_bug1151916.html]
-[test_workerupdatefoundevent.html]
-[test_empty_serviceworker.html]
-[test_periodic_update.html]
-[test_claim_oninstall.html]
-[test_claim.html]
-[test_periodic_https_update.html]
-[test_sanitize.html]
-[test_sanitize_domain.html]
-[test_service_worker_allowed.html]
-[test_app_protocol.html]
-[test_third_party_iframes.html]
-[test_claim_fetch.html]
-[test_force_refresh.html]
-[test_skip_waiting.html]
-[test_strict_mode_error.html]
-[test_cross_origin_url_after_redirect.html]
+[test_navigator.html]
 [test_origin_after_redirect.html]
 [test_origin_after_redirect_cached.html]
 [test_origin_after_redirect_to_https.html]
 [test_origin_after_redirect_to_https_cached.html]
-[test_https_origin_after_redirect.html]
-[test_https_origin_after_redirect_cached.html]
-[test_gzip_redirect.html]
+[test_periodic_https_update.html]
+[test_periodic_update.html]
+[test_post_message.html]
+[test_post_message_advanced.html]
+[test_post_message_source.html]
 [test_register_base.html]
 [test_register_https_in_http.html]
-[test_eval_allowed.html]
-[test_eval_not_allowed.html]
+[test_request_context.html]
+skip-if = toolkit == 'android' # Bug 1163410
+[test_scopes.html]
+[test_sandbox_intercept.html]
+[test_sanitize.html]
+[test_sanitize_domain.html]
+[test_service_worker_allowed.html]
+[test_serviceworker_interfaces.html]
+[test_serviceworker_not_sharedworker.html]
+[test_skip_waiting.html]
+[test_strict_mode_error.html]
+[test_third_party_iframes.html]
+[test_unregister.html]
+[test_workerUnregister.html]
+[test_workerUpdate.html]
+[test_workerupdatefoundevent.html]
--- a/dom/workers/test/serviceworkers/periodic_update_test.js
+++ b/dom/workers/test/serviceworkers/periodic_update_test.js
@@ -60,14 +60,15 @@ function unregisterSW() {
   return testFrame(gPrefix + "periodic/unregister.html");
 }
 
 function runTheTest() {
   SimpleTest.waitForExplicitFinish();
 
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true],
   ]}, function() {
     start();
   });
 }
--- a/dom/workers/test/serviceworkers/test_app_protocol.html
+++ b/dom/workers/test/serviceworkers/test_app_protocol.html
@@ -22,16 +22,17 @@ const appManifestURL =
 let gApp;
 
 function setup() {
   return new Promise((resolve, reject) => {
     SpecialPowers.setAllAppsLaunchable(true);
     SpecialPowers.pushPrefEnv({'set': [
       ['dom.mozBrowserFramesEnabled', true],
       ['dom.serviceWorkers.exemptFromPerDomainMax', true],
+      ["dom.serviceWorkers.interception.enabled", true],
       ['dom.serviceWorkers.enabled', true],
       ['dom.serviceWorkers.testing.enabled', true],
       ['dom.caches.enabled', true],
     ]}, () => {
       SpecialPowers.pushPermissions([
         { 'type': 'webapps-manage', 'allow': 1, 'context': document },
         { 'type': 'browser', 'allow': 1, 'context': document },
         { 'type': 'embed-apps', 'allow': 1, 'context': document }
--- a/dom/workers/test/serviceworkers/test_bug1151916.html
+++ b/dom/workers/test/serviceworkers/test_bug1151916.html
@@ -89,16 +89,17 @@
       .catch(function(e) {
         ok(false, "Some test failed with error " + e);
       }).then(SimpleTest.finish);
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true],
     ["dom.caches.enabled", true],
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_claim.html
+++ b/dom/workers/test/serviceworkers/test_claim.html
@@ -157,16 +157,17 @@
       .catch(function(e) {
         ok(false, "Some test failed with error " + e);
       }).then(SimpleTest.finish);
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true]
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
 
--- a/dom/workers/test/serviceworkers/test_claim_fetch.html
+++ b/dom/workers/test/serviceworkers/test_claim_fetch.html
@@ -83,16 +83,17 @@
       .catch(function(e) {
         ok(false, "Some test failed with error " + e);
       }).then(SimpleTest.finish);
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true]
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
 
--- a/dom/workers/test/serviceworkers/test_claim_oninstall.html
+++ b/dom/workers/test/serviceworkers/test_claim_oninstall.html
@@ -58,16 +58,17 @@
       .catch(function(e) {
         ok(false, "Some test failed with error " + e);
       }).then(SimpleTest.finish);
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true]
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
 
--- a/dom/workers/test/serviceworkers/test_client_focus.html
+++ b/dom/workers/test/serviceworkers/test_client_focus.html
@@ -81,16 +81,17 @@
       .catch(function(e) {
         ok(false, "Some test failed with error " + e);
       }).then(SimpleTest.finish);
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true],
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
 
--- a/dom/workers/test/serviceworkers/test_close.html
+++ b/dom/workers/test/serviceworkers/test_close.html
@@ -49,16 +49,17 @@
       }
     };
   }
 
   SimpleTest.waitForExplicitFinish();
   onload = function() {
     SpecialPowers.pushPrefEnv({"set": [
       ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+      ["dom.serviceWorkers.interception.enabled", true],
       ["dom.serviceWorkers.enabled", true],
       ["dom.serviceWorkers.testing.enabled", true],
     ]}, runTest);
   };
 </script>
 </pre>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_controller.html
+++ b/dom/workers/test/serviceworkers/test_controller.html
@@ -69,16 +69,17 @@
         ok(false, "Some test failed with error " + e);
         SimpleTest.finish();
       });
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true]
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
 
--- a/dom/workers/test/serviceworkers/test_cross_origin_url_after_redirect.html
+++ b/dom/workers/test/serviceworkers/test_cross_origin_url_after_redirect.html
@@ -35,16 +35,17 @@
       }
     };
   }
 
   SimpleTest.waitForExplicitFinish();
   onload = function() {
     SpecialPowers.pushPrefEnv({"set": [
       ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+      ["dom.serviceWorkers.interception.enabled", true],
       ["dom.serviceWorkers.enabled", true],
       ["dom.serviceWorkers.testing.enabled", true],
     ]}, runTest);
   };
 </script>
 </pre>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_empty_serviceworker.html
+++ b/dom/workers/test/serviceworkers/test_empty_serviceworker.html
@@ -31,16 +31,17 @@
       SimpleTest.finish();
     });
   }
 
   SimpleTest.waitForExplicitFinish();
   onload = function() {
     SpecialPowers.pushPrefEnv({"set": [
       ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+      ["dom.serviceWorkers.interception.enabled", true],
       ["dom.serviceWorkers.enabled", true],
       ["dom.serviceWorkers.testing.enabled", true]
     ]}, runTest);
   };
 </script>
 </pre>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_fetch_event.html
+++ b/dom/workers/test/serviceworkers/test_fetch_event.html
@@ -68,16 +68,17 @@
         ok(false, "Some test failed with error " + e);
         SimpleTest.finish();
       });
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true],
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
 
--- a/dom/workers/test/serviceworkers/test_force_refresh.html
+++ b/dom/workers/test/serviceworkers/test_force_refresh.html
@@ -69,16 +69,17 @@
       .catch(function(e) {
         ok(false, "Some test failed with error " + e);
       }).then(SimpleTest.finish);
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true],
     ["dom.caches.enabled", true],
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_https_fetch.html
+++ b/dom/workers/test/serviceworkers/test_https_fetch.html
@@ -42,16 +42,17 @@
       }
     };
   }
 
   SimpleTest.waitForExplicitFinish();
   onload = function() {
     SpecialPowers.pushPrefEnv({"set": [
       ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+      ["dom.serviceWorkers.interception.enabled", true],
       ["dom.serviceWorkers.enabled", true],
       ["dom.serviceWorkers.testing.enabled", true],
       ["dom.caches.enabled", true]
     ]}, runTest);
   };
 </script>
 </pre>
 </body>
--- a/dom/workers/test/serviceworkers/test_https_fetch_cloned_response.html
+++ b/dom/workers/test/serviceworkers/test_https_fetch_cloned_response.html
@@ -40,16 +40,17 @@
       }
     };
   }
 
   SimpleTest.waitForExplicitFinish();
   onload = function() {
     SpecialPowers.pushPrefEnv({"set": [
       ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+      ["dom.serviceWorkers.interception.enabled", true],
       ["dom.serviceWorkers.enabled", true],
       ["dom.serviceWorkers.testing.enabled", true],
       ["dom.caches.enabled", true]
     ]}, runTest);
   };
 </script>
 </pre>
 </body>
--- a/dom/workers/test/serviceworkers/test_https_synth_fetch_from_cached_sw.html
+++ b/dom/workers/test/serviceworkers/test_https_synth_fetch_from_cached_sw.html
@@ -53,16 +53,17 @@
       }
     };
   }
 
   SimpleTest.waitForExplicitFinish();
   onload = function() {
     SpecialPowers.pushPrefEnv({"set": [
       ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+      ["dom.serviceWorkers.interception.enabled", true],
       ["dom.serviceWorkers.enabled", true],
       ["dom.serviceWorkers.testing.enabled", true],
       ["dom.caches.enabled", true]
     ]}, runTest);
   };
 </script>
 </pre>
 </body>
--- a/dom/workers/test/serviceworkers/test_importscript.html
+++ b/dom/workers/test/serviceworkers/test_importscript.html
@@ -58,15 +58,16 @@
       .catch(function(e) {
         ok(false, "Some test failed with error " + e);
       }).then(SimpleTest.finish);
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true]
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_install_event.html
+++ b/dom/workers/test/serviceworkers/test_install_event.html
@@ -98,16 +98,17 @@
         ok(false, "Some test failed with error " + e);
         SimpleTest.finish();
       });
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true]
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
 
--- a/dom/workers/test/serviceworkers/test_installation_simple.html
+++ b/dom/workers/test/serviceworkers/test_installation_simple.html
@@ -196,16 +196,17 @@
         ok(false, "Some test failed with error " + e);
         SimpleTest.finish();
       });
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["dom.messageChannel.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true]
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_interception_featuredetect.html
@@ -0,0 +1,96 @@
+
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 1173389 - Test fetch interception feature detection.</title>
+  <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"></pre>
+<script class="testbody" type="text/javascript">
+
+  function register() {
+    // Randomness required to prevent reusing existing registration.
+    return navigator.serviceWorker.register("interception_featuredetect.js",
+                                            { scope: "featuredetect/" + Math.random() });
+  }
+
+  function registerWithPrefDisabled() {
+    return new Promise(function(resolve, reject) {
+      SpecialPowers.pushPrefEnv(
+        {"set": [["dom.serviceWorkers.interception.enabled", false]]},
+        function() {
+          register().then(function(v) {
+              ok(false, "Registration should fail when interception is disabled.");
+              reject();
+            }, function(e) {
+              ok(true, "Registration should fail when interception is disabled.");
+              resolve();
+            });
+        });
+    });
+  }
+
+  function registerWithPrefEnabled() {
+    return new Promise(function(resolve, reject) {
+      SpecialPowers.pushPrefEnv(
+        {"set": [["dom.serviceWorkers.interception.enabled", true]]},
+        function() {
+          register().then(function(v) {
+              ok(true, "Registration should succeed when interception is enabled.");
+              resolve();
+            }, function(e) {
+              ok(false, "Registration should succeed when interception is enabled.");
+              reject()
+            });
+        });
+    });
+  }
+
+  function unregister() {
+    return navigator.serviceWorker.getRegistrations().then(function(regs) {
+      var unregs = [];
+      regs.forEach(function(reg) {
+        if (reg.scope.indexOf("featuredetect") > -1) {
+          unregs.push(reg.unregister());
+        }
+      })
+
+      return Promise.all(unregs);
+    });
+  }
+
+  function runTest() {
+    Promise.resolve()
+      .then(registerWithPrefDisabled)
+      .then(registerWithPrefEnabled)
+      .then(registerWithPrefDisabled)
+      .then(unregister)
+      // put more tests here.
+      .then(function() {
+        SimpleTest.finish();
+      }).catch(function(e) {
+        ok(false, "Some test failed with error " + e);
+        SimpleTest.finish();
+      });
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  SpecialPowers.pushPrefEnv({"set": [
+    ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", false],
+    ["dom.serviceWorkers.enabled", true],
+    ["dom.serviceWorkers.testing.enabled", true]
+  ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
+
--- a/dom/workers/test/serviceworkers/test_match_all.html
+++ b/dom/workers/test/serviceworkers/test_match_all.html
@@ -65,16 +65,17 @@
         ok(true, "Didn't crash on resolving matchAll promises while worker shuts down.");
         SimpleTest.finish();
       });
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true]
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
 
--- a/dom/workers/test/serviceworkers/test_match_all_advanced.html
+++ b/dom/workers/test/serviceworkers/test_match_all_advanced.html
@@ -85,16 +85,17 @@
         SimpleTest.finish();
       });
 
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true]
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
 
--- a/dom/workers/test/serviceworkers/test_match_all_client_id.html
+++ b/dom/workers/test/serviceworkers/test_match_all_client_id.html
@@ -76,15 +76,16 @@
       .catch(function(e) {
         ok(false, "Some test failed with error " + e);
       }).then(SimpleTest.finish);
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true]
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_match_all_client_properties.html
+++ b/dom/workers/test/serviceworkers/test_match_all_client_properties.html
@@ -83,15 +83,16 @@
       .catch(function(e) {
         ok(false, "Some test failed with error " + e);
       }).then(SimpleTest.finish);
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true]
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_navigator.html
+++ b/dom/workers/test/serviceworkers/test_navigator.html
@@ -23,16 +23,17 @@
     ok(navigator.serviceWorker.ready instanceof Promise, "navigator.serviceWorker.ready should be a Promise.");
     ok(navigator.serviceWorker.controller === null, "There should be no controller worker for an uncontrolled document.");
   }
 
   SimpleTest.waitForExplicitFinish();
 
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["dom.serviceWorkers.enabled", true]
   ]}, function() {
     checkEnabled();
     SimpleTest.finish();
   });
 </script>
 </pre>
 </body>
--- a/dom/workers/test/serviceworkers/test_post_message.html
+++ b/dom/workers/test/serviceworkers/test_post_message.html
@@ -64,16 +64,17 @@
       .catch(function(e) {
         ok(false, "Some test failed with error " + e);
       }).then(SimpleTest.finish);
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true]
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
 
--- a/dom/workers/test/serviceworkers/test_post_message_advanced.html
+++ b/dom/workers/test/serviceworkers/test_post_message_advanced.html
@@ -93,16 +93,17 @@
       .catch(function(e) {
         ok(false, "Some test failed with error " + e);
       }).then(SimpleTest.finish);
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true]
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
 
--- a/dom/workers/test/serviceworkers/test_post_message_source.html
+++ b/dom/workers/test/serviceworkers/test_post_message_source.html
@@ -52,16 +52,17 @@
       .catch(function(e) {
         ok(false, "Some test failed with error " + e);
       }).then(SimpleTest.finish);
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true]
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
 
--- a/dom/workers/test/serviceworkers/test_request_context.html
+++ b/dom/workers/test/serviceworkers/test_request_context.html
@@ -83,16 +83,17 @@
     SpecialPowers.pushPrefEnv({"set": [
       ["beacon.enabled", true],
       ["browser.send_pings", true],
       ["browser.send_pings.max_per_link", -1],
       ["dom.caches.enabled", true],
       ["dom.image.picture.enabled", true],
       ["dom.image.srcset.enabled", true],
       ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+      ["dom.serviceWorkers.interception.enabled", true],
       ["dom.serviceWorkers.enabled", true],
       ["dom.serviceWorkers.testing.enabled", true],
     ]}, runTest);
   };
 </script>
 </pre>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_sandbox_intercept.html
+++ b/dom/workers/test/serviceworkers/test_sandbox_intercept.html
@@ -35,16 +35,17 @@
       }
     };
   }
 
   SimpleTest.waitForExplicitFinish();
   onload = function() {
     SpecialPowers.pushPrefEnv({"set": [
       ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+      ["dom.serviceWorkers.interception.enabled", true],
       ["dom.serviceWorkers.enabled", true],
       ["dom.serviceWorkers.testing.enabled", true],
     ]}, runTest);
   };
 </script>
 </pre>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_sanitize.html
+++ b/dom/workers/test/serviceworkers/test_sanitize.html
@@ -70,16 +70,17 @@
   function registerSW() {
     return testFrame("sanitize/register.html");
   }
 
   SimpleTest.waitForExplicitFinish();
 
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true],
   ]}, function() {
     start();
   });
 </script>
 </pre>
 </body>
--- a/dom/workers/test/serviceworkers/test_sanitize_domain.html
+++ b/dom/workers/test/serviceworkers/test_sanitize_domain.html
@@ -73,16 +73,17 @@
               return testFrame("http://prefixexample.com/tests/dom/workers/test/serviceworkers/sanitize/register.html");
             });
   }
 
   SimpleTest.waitForExplicitFinish();
 
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true],
   ]}, function() {
     start();
   });
 </script>
 </pre>
 </body>
--- a/dom/workers/test/serviceworkers/test_scopes.html
+++ b/dom/workers/test/serviceworkers/test_scopes.html
@@ -106,16 +106,17 @@
         ok(false, "Some test failed with error " + e);
         SimpleTest.finish();
       });
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true]
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
 
--- a/dom/workers/test/serviceworkers/test_service_worker_allowed.html
+++ b/dom/workers/test/serviceworkers/test_service_worker_allowed.html
@@ -62,13 +62,14 @@
     });
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true],
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_serviceworker_interfaces.html
+++ b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.html
@@ -99,16 +99,17 @@
     // if service workers are disabled by default we want to force on both
     // service workers and "dom.caches.enabled".  But if service workers are
     // enabled by default, we do not want to mess with the "dom.caches.enabled"
     // value, since that would defeat the purpose of the test.  Use a subframe
     // to decide whether service workers are enabled by default, so we don't
     // force creation of our own Navigator object before our prefs are set.
     var prefs = [
       ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+      ["dom.serviceWorkers.interception.enabled", true],
       ["dom.serviceWorkers.enabled", true],
       ["dom.serviceWorkers.testing.enabled", true],
     ];
 
     var subframe = document.createElement("iframe");
     document.body.appendChild(subframe);
     if (!("serviceWorker" in subframe.contentWindow.navigator)) {
 	prefs.push(["dom.caches.enabled", true]);
--- a/dom/workers/test/serviceworkers/test_serviceworker_not_sharedworker.html
+++ b/dom/workers/test/serviceworkers/test_serviceworker_not_sharedworker.html
@@ -51,16 +51,17 @@
       sw.postMessage({msg: "whoareyou"});
     };
   }
 
   SimpleTest.waitForExplicitFinish();
   onload = function() {
     SpecialPowers.pushPrefEnv({"set": [
       ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+      ["dom.serviceWorkers.interception.enabled", true],
       ["dom.serviceWorkers.enabled", true],
       ["dom.serviceWorkers.testing.enabled", true]
     ]}, runTest);
   };
 </script>
 </pre>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_skip_waiting.html
+++ b/dom/workers/test/serviceworkers/test_skip_waiting.html
@@ -81,15 +81,16 @@
       .catch(function(e) {
         ok(false, "Some test failed with error " + e);
       }).then(SimpleTest.finish);
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true]
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_strict_mode_error.html
+++ b/dom/workers/test/serviceworkers/test_strict_mode_error.html
@@ -24,16 +24,17 @@
         SimpleTest.finish();
       });
   }
 
   SimpleTest.waitForExplicitFinish();
   onload = function() {
     SpecialPowers.pushPrefEnv({"set": [
       ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+      ["dom.serviceWorkers.interception.enabled", true],
       ["dom.serviceWorkers.enabled", true],
       ["dom.serviceWorkers.testing.enabled", true],
     ]}, runTest);
   };
 </script>
 </pre>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_unregister.html
+++ b/dom/workers/test/serviceworkers/test_unregister.html
@@ -123,16 +123,17 @@
         ok(false, "Some test failed with error " + e);
         SimpleTest.finish();
       });
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true]
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
 
--- a/dom/workers/test/serviceworkers/test_workerUnregister.html
+++ b/dom/workers/test/serviceworkers/test_workerUnregister.html
@@ -67,16 +67,17 @@
     }).then(function() {
       SimpleTest.finish();
     });
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true]
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
 
--- a/dom/workers/test/serviceworkers/test_workerUpdate.html
+++ b/dom/workers/test/serviceworkers/test_workerUpdate.html
@@ -40,16 +40,17 @@
     }).then(function() {
       SimpleTest.finish();
     });
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true]
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
 
--- a/dom/workers/test/serviceworkers/test_workerupdatefoundevent.html
+++ b/dom/workers/test/serviceworkers/test_workerupdatefoundevent.html
@@ -70,16 +70,17 @@
       .catch(function(e) {
         ok(false, "Some test failed with error " + e);
       }).then(SimpleTest.finish);
   }
 
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({"set": [
     ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+    ["dom.serviceWorkers.interception.enabled", true],
     ["dom.serviceWorkers.enabled", true],
     ["dom.serviceWorkers.testing.enabled", true],
   ]}, runTest);
 </script>
 </pre>
 </body>
 </html>
 
--- a/dom/workers/test/urlApi_worker.js
+++ b/dom/workers/test/urlApi_worker.js
@@ -106,17 +106,17 @@ onmessage = function() {
       port: '',
       pathname: '/',
       search: '?test',
       hash: ''
     },
     { url: 'http://example.com/carrot#question%3f',
       base: undefined,
       error: false,
-      hash: '#question?'
+      hash: '#question%3f'
     },
     { url: 'https://example.com:4443?',
       base: undefined,
       error: false,
       protocol: 'https:',
       port: '4443',
       pathname: '/',
       hash: '',
--- a/dom/xbl/nsXBLMaybeCompiled.h
+++ b/dom/xbl/nsXBLMaybeCompiled.h
@@ -87,29 +87,29 @@ namespace js {
 
 template <class UncompiledT>
 struct GCMethods<nsXBLMaybeCompiled<UncompiledT> >
 {
   typedef struct GCMethods<JSObject *> Base;
 
   static nsXBLMaybeCompiled<UncompiledT> initial() { return nsXBLMaybeCompiled<UncompiledT>(); }
 
-  static bool needsPostBarrier(nsXBLMaybeCompiled<UncompiledT> function)
-  {
-    return function.IsCompiled() && Base::needsPostBarrier(function.GetJSFunction());
-  }
-
-  static void postBarrier(nsXBLMaybeCompiled<UncompiledT>* functionp)
+  static void postBarrier(nsXBLMaybeCompiled<UncompiledT>* functionp,
+                          nsXBLMaybeCompiled<UncompiledT> prev,
+                          nsXBLMaybeCompiled<UncompiledT> next)
   {
-    Base::postBarrier(&functionp->UnsafeGetJSFunction());
-  }
-
-  static void relocate(nsXBLMaybeCompiled<UncompiledT>* functionp)
-  {
-    Base::relocate(&functionp->UnsafeGetJSFunction());
+    if (next.IsCompiled()) {
+      Base::postBarrier(&functionp->UnsafeGetJSFunction(),
+                        prev.IsCompiled() ? prev.UnsafeGetJSFunction() : nullptr,
+                        next.UnsafeGetJSFunction());
+    } else if (prev.IsCompiled()) {
+      Base::postBarrier(&prev.UnsafeGetJSFunction(),
+                        prev.UnsafeGetJSFunction(),
+                        nullptr);
+    }
   }
 };
 
 template <class UncompiledT>
 class HeapBase<nsXBLMaybeCompiled<UncompiledT> >
 {
   const JS::Heap<nsXBLMaybeCompiled<UncompiledT> >& wrapper() const {
     return *static_cast<const JS::Heap<nsXBLMaybeCompiled<UncompiledT> >*>(this);
--- a/dom/xbl/nsXBLProtoImplField.cpp
+++ b/dom/xbl/nsXBLProtoImplField.cpp
@@ -240,22 +240,17 @@ FieldGetterImpl(JSContext *cx, JS::CallA
     return false;
   }
 
   if (!installed) {
     args.rval().setUndefined();
     return true;
   }
 
-  JS::Rooted<JS::Value> v(cx);
-  if (!JS_GetPropertyById(cx, thisObj, id, &v)) {
-    return false;
-  }
-  args.rval().set(v);
-  return true;
+  return JS_GetPropertyById(cx, thisObj, id, args.rval());
 }
 
 static bool
 FieldGetter(JSContext *cx, unsigned argc, JS::Value *vp)
 {
   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
   return JS::CallNonGenericMethod<ValueHasISupportsPrivate, FieldGetterImpl>
                                  (cx, args);
--- a/gfx/2d/Matrix.h
+++ b/gfx/2d/Matrix.h
@@ -8,16 +8,17 @@
 
 #include "Types.h"
 #include "Rect.h"
 #include "Point.h"
 #include <iosfwd>
 #include <math.h>
 #include "mozilla/Attributes.h"
 #include "mozilla/DebugOnly.h"
+#include "mozilla/FloatingPoint.h"
 
 namespace mozilla {
 namespace gfx {
 
 class Quaternion;
 
 static bool FuzzyEqual(Float aV1, Float aV2) {
   // XXX - Check if fabs does the smart thing and just negates the sign bit.
@@ -852,16 +853,36 @@ public:
            gfx::FuzzyEqual(_21, o._21) && gfx::FuzzyEqual(_22, o._22) &&
            gfx::FuzzyEqual(_23, o._23) && gfx::FuzzyEqual(_24, o._24) &&
            gfx::FuzzyEqual(_31, o._31) && gfx::FuzzyEqual(_32, o._32) &&
            gfx::FuzzyEqual(_33, o._33) && gfx::FuzzyEqual(_34, o._34) &&
            gfx::FuzzyEqual(_41, o._41) && gfx::FuzzyEqual(_42, o._42) &&
            gfx::FuzzyEqual(_43, o._43) && gfx::FuzzyEqual(_44, o._44);
   }
 
+  bool FuzzyEqualsMultiplicative(const Matrix4x4& o) const
+  {
+    return ::mozilla::FuzzyEqualsMultiplicative(_11, o._11) &&
+           ::mozilla::FuzzyEqualsMultiplicative(_12, o._12) &&
+           ::mozilla::FuzzyEqualsMultiplicative(_13, o._13) &&
+           ::mozilla::FuzzyEqualsMultiplicative(_14, o._14) &&
+           ::mozilla::FuzzyEqualsMultiplicative(_21, o._21) &&
+           ::mozilla::FuzzyEqualsMultiplicative(_22, o._22) &&
+           ::mozilla::FuzzyEqualsMultiplicative(_23, o._23) &&
+           ::mozilla::FuzzyEqualsMultiplicative(_24, o._24) &&
+           ::mozilla::FuzzyEqualsMultiplicative(_31, o._31) &&
+           ::mozilla::FuzzyEqualsMultiplicative(_32, o._32) &&
+           ::mozilla::FuzzyEqualsMultiplicative(_33, o._33) &&
+           ::mozilla::FuzzyEqualsMultiplicative(_34, o._34) &&
+           ::mozilla::FuzzyEqualsMultiplicative(_41, o._41) &&
+           ::mozilla::FuzzyEqualsMultiplicative(_42, o._42) &&
+           ::mozilla::FuzzyEqualsMultiplicative(_43, o._43) &&
+           ::mozilla::FuzzyEqualsMultiplicative(_44, o._44);
+  }
+
   bool IsBackfaceVisible() const
   {
     // Inverse()._33 < 0;
     Float det = Determinant();
     Float __33 = _12*_24*_41 - _14*_22*_41 +
                 _14*_21*_42 - _11*_24*_42 -
                 _12*_21*_44 + _11*_22*_44;
     return (__33 * det) < 0;
@@ -884,16 +905,34 @@ public:
     static const float error = 1e-5f;
     NudgeToInteger(&_41, error);
     NudgeToInteger(&_42, error);
     NudgeToInteger(&_43, error);
     NudgeToInteger(&_44, error);
     return *this;
   }
 
+  // Nudge the 3D components to integer so that this matrix will become 2D if
+  // it's very close to already being 2D.
+  // This doesn't change the _41 and _42 components.
+  Matrix4x4 &NudgeTo2D()
+  {
+    NudgeToInteger(&_13);
+    NudgeToInteger(&_14);
+    NudgeToInteger(&_23);
+    NudgeToInteger(&_24);
+    NudgeToInteger(&_31);
+    NudgeToInteger(&_32);
+    NudgeToInteger(&_33);
+    NudgeToInteger(&_34);
+    NudgeToInteger(&_43);
+    NudgeToInteger(&_44);
+    return *this;
+  }
+
   Point4D TransposedVector(int aIndex) const
   {
       MOZ_ASSERT(aIndex >= 0 && aIndex <= 3, "Invalid matrix array index");
       return Point4D(*((&_11)+aIndex), *((&_21)+aIndex), *((&_31)+aIndex), *((&_41)+aIndex));
   }
 
   void SetTransposedVector(int aIndex, Point4D &aVector)
   {
new file mode 100644
--- /dev/null
+++ b/gfx/docs/index.rst
@@ -0,0 +1,9 @@
+========
+Graphics
+========
+
+The graphics team's documentation is currently using doxygen. We're tracking the work to integrate it better at https://bugzilla.mozilla.org/show_bug.cgi?id=1150232.
+
+For now you can read the graphics source code documentation here:
+
+http://people.mozilla.org/~bgirard/doxygen/gfx/
--- a/gfx/layers/LayerTreeInvalidation.cpp
+++ b/gfx/layers/LayerTreeInvalidation.cpp
@@ -131,17 +131,17 @@ struct LayerPropertiesBase : public Laye
                                          NotifySubDocInvalidationFunc aCallback,
                                          bool* aGeometryChanged);
 
   virtual void MoveBy(const IntPoint& aOffset);
 
   nsIntRegion ComputeChange(NotifySubDocInvalidationFunc aCallback,
                             bool& aGeometryChanged)
   {
-    bool transformChanged = !mTransform.FuzzyEqual(mLayer->GetLocalTransform()) ||
+    bool transformChanged = !mTransform.FuzzyEqualsMultiplicative(mLayer->GetLocalTransform()) ||
                             mLayer->GetPostXScale() != mPostXScale ||
                             mLayer->GetPostYScale() != mPostYScale;
     Layer* otherMask = mLayer->GetMaskLayer();
     const Maybe<ParentLayerIntRect>& otherClip = mLayer->GetClipRect();
     nsIntRegion result;
     if ((mMaskLayer ? mMaskLayer->mLayer : nullptr) != otherMask ||
         (mUseClipRect != !!otherClip) ||
         mLayer->GetLocalOpacity() != mOpacity ||
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -3118,16 +3118,17 @@ AsyncPanZoomController::HasReadyTouchBlo
 
 void AsyncPanZoomController::SetState(PanZoomState aNewState)
 {
   PanZoomState oldState;
 
   // Intentional scoping for mutex
   {
     ReentrantMonitorAutoEnter lock(mMonitor);
+    APZC_LOG("%p changing from state %d to %d\n", this, mState, aNewState);
     oldState = mState;
     mState = aNewState;
   }
 
   DispatchStateChangeNotification(oldState, aNewState);
 }
 
 void AsyncPanZoomController::DispatchStateChangeNotification(PanZoomState aOldState,
--- a/gfx/layers/apz/test/apz_test_native_event_utils.js
+++ b/gfx/layers/apz/test/apz_test_native_event_utils.js
@@ -119,8 +119,35 @@ function synthesizeNativeMouseMove(aElem
 function synthesizeNativeMouseMoveAndWaitForMoveEvent(aElement, aX, aY, aCallback) {
   var targetWindow = aElement.ownerDocument.defaultView;
   targetWindow.addEventListener("mousemove", function mousemoveWaiter(e) {
     targetWindow.removeEventListener("mousemove", mousemoveWaiter);
     setTimeout(aCallback, 0);
   });
   return synthesizeNativeMouseMove(aElement, aX, aY);
 }
+
+// Synthesizes a native touch event and dispatches it. aX and aY in CSS pixels
+// relative to the top-left of |aElement|'s bounding rect.
+function synthesizeNativeTouch(aElement, aX, aY, aType, aObserver = null, aTouchId = 0) {
+  var targetWindow = aElement.ownerDocument.defaultView;
+
+  var scale = targetWindow.devicePixelRatio;
+  var rect = aElement.getBoundingClientRect();
+  var x = targetWindow.mozInnerScreenX + ((rect.left + aX) * scale);
+  var y = targetWindow.mozInnerScreenY + ((rect.top + aY) * scale);
+
+  var utils = SpecialPowers.getDOMWindowUtils(targetWindow);
+  utils.sendNativeTouchPoint(aTouchId, aType, x, y, 1, 90, aObserver);
+  return true;
+}
+
+function synthesizeNativeDrag(aElement, aX, aY, aDeltaX, aDeltaY, aObserver = null, aTouchId = 0) {
+  synthesizeNativeTouch(aElement, aX, aY, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, null, aTouchId);
+  var steps = Math.max(Math.abs(aDeltaX), Math.abs(aDeltaY));
+  for (var i = 1; i < steps; i++) {
+    var dx = i * (aDeltaX / steps);
+    var dy = i * (aDeltaY / steps);
+    synthesizeNativeTouch(aElement, aX + dx, aY + dy, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, null, aTouchId);
+  }
+  synthesizeNativeTouch(aElement, aX + aDeltaX, aY + aDeltaY, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, null, aTouchId);
+  return synthesizeNativeTouch(aElement, aX + aDeltaX, aY + aDeltaY, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE, aObserver, aTouchId);
+}
--- a/gfx/layers/apz/test/apz_test_utils.js
+++ b/gfx/layers/apz/test/apz_test_utils.js
@@ -94,8 +94,25 @@ function buildApzcTree(paint) {
     if ("hasNoParentWithSameLayersId" in paint[scrollId]) {
       addRoot(root, scrollId);
     } else if ("parentScrollId" in paint[scrollId]) {
       addLink(root, scrollId, paint[scrollId]["parentScrollId"]);
     }
   }
   return root;
 }
+
+function flushApzRepaints(aCallback, aWindow = window) {
+  if (!aCallback) {
+    throw "A callback must be provided!";
+  }
+  var repaintDone = function() {
+    SpecialPowers.Services.obs.removeObserver(repaintDone, "apz-repaints-flushed", false);
+    setTimeout(aCallback, 0);
+  };
+  SpecialPowers.Services.obs.addObserver(repaintDone, "apz-repaints-flushed", false);
+  if (SpecialPowers.getDOMWindowUtils(aWindow).flushApzRepaints()) {
+    dump("Flushed APZ repaints, waiting for callback...\n");
+  } else {
+    dump("Flushing APZ repaints was a no-op, triggering callback directly...\n");
+    repaintDone();
+  }
+}
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/helper_basic_pan.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width; initial-scale=1.0">
+  <title>Sanity panning test</title>
+  <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+  <script type="application/javascript" src="apz_test_utils.js"></script>
+  <script type="application/javascript">
+
+function scrollPage() {
+  SpecialPowers._addMessageListener("APZ:TransformEnd", function() {
+    dump("Transform complete; flushing repaints...\n");
+    flushApzRepaints(checkScroll);
+  });
+
+  const TOUCH_SLOP = 1;
+  synthesizeNativeDrag(document.body, 10, 100, 0, -(50 + TOUCH_SLOP));
+  dump("Finished native drag, waiting for transform-end observer...\n");
+}
+
+function checkScroll() {
+  window.opener.is(window.scrollY, 50, "check that the window scrolled");
+  window.opener.testDone();
+}
+
+window.onload = function() {
+  setTimeout(scrollPage, 0);
+}
+
+  </script>
+</head>
+<body>
+ <div style="height: 5000px; background-color: lightgreen;">
+  This div makes the page scrollable.
+ </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/helper_div_pan.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width; initial-scale=1.0">
+  <title>Sanity panning test for scrollable div</title>
+  <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+  <script type="application/javascript" src="apz_test_utils.js"></script>
+  <script type="application/javascript">
+
+function scrollOuter() {
+  SpecialPowers._addMessageListener("APZ:TransformEnd", function() {
+    dump("Transform complete; flushing repaints...\n");
+    flushApzRepaints(checkScroll);
+  });
+
+  const TOUCH_SLOP = 1;
+  synthesizeNativeDrag(document.getElementById('outer'), 10, 100, 0, -(50 + TOUCH_SLOP));
+  dump("Finished native drag, waiting for transform-end observer...\n");
+}
+
+function checkScroll() {
+  var outerScroll = document.getElementById('outer').scrollTop;
+  window.opener.is(outerScroll, 50, "check that the div scrolled");
+  window.opener.testDone();
+}
+
+window.onload = function() {
+  setTimeout(scrollOuter, 0);
+}
+
+  </script>
+</head>
+<body>
+ <div id="outer" style="height: 250px; border: solid 1px black; overflow:scroll">
+  <div style="height: 5000px; background-color: lightblue">
+    This div makes the |outer| div scrollable.
+  </div>
+ </div>
+ <div style="height: 5000px; background-color: lightgreen;">
+  This div makes the top-level page scrollable.
+ </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/helper_iframe_pan.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width; initial-scale=1.0">
+  <title>Sanity panning test for scrollable div</title>
+  <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+  <script type="application/javascript" src="apz_test_utils.js"></script>
+  <script type="application/javascript">
+
+function scrollOuter() {
+  var outer = document.getElementById('outer');
+  SpecialPowers._addMessageListener("APZ:TransformEnd", function() {
+    dump("Transform complete; flushing repaints...\n");
+    flushApzRepaints(checkScroll, outer.contentWindow);
+  });
+
+  const TOUCH_SLOP = 1;
+  synthesizeNativeDrag(outer.contentDocument.body, 10, 100, 0, -(50 + TOUCH_SLOP));
+  dump("Finished native drag, waiting for transform-end observer...\n");
+}
+
+function checkScroll() {
+  var outerScroll = document.getElementById('outer').contentWindow.scrollY;
+  window.opener.is(outerScroll, 50, "check that the iframe scrolled");
+  window.opener.testDone();
+}
+
+window.onload = function() {
+  setTimeout(scrollOuter, 0);
+}
+
+  </script>
+</head>
+<body>
+ <iframe id="outer" style="height: 250px; border: solid 1px black" src="data:text/html,<body style='height:5000px'>"></iframe>
+ <div style="height: 5000px; background-color: lightgreen;">
+  This div makes the top-level page scrollable.
+ </div>
+</body>
+</html>
--- a/gfx/layers/apz/test/mochitest.ini
+++ b/gfx/layers/apz/test/mochitest.ini
@@ -2,19 +2,24 @@
 support-files =
   apz_test_utils.js
   apz_test_native_event_utils.js
   helper_bug982141.html
   helper_bug1151663.html
   helper_iframe1.html
   helper_iframe2.html
   helper_subframe_style.css
+  helper_basic_pan.html
+  helper_div_pan.html
+  helper_iframe_pan.html
 tags = apz
 [test_bug982141.html]
 skip-if = toolkit != 'gonk'  # bug 991198
 [test_bug1151663.html]
 skip-if = toolkit != 'gonk'  # bug 991198
 [test_wheel_scroll.html]
 skip-if = (os == 'android') || (os == 'b2g') || (buildapp == 'mulet') # wheel events not supported on mobile; see bug 1164274 for mulet
 [test_bug1151667.html]
 skip-if = (os == 'android') || (os == 'b2g') # wheel events not supported on mobile
 [test_layerization.html]
 skip-if = (os == 'android') || (os == 'b2g') # uses wheel events which are not supported on mobile
+[test_basic_pan.html]
+skip-if = toolkit != 'gonk'
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/test_basic_pan.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Sanity panning test</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.testInChaosMode();
+
+// this page just serially loads each one of the following test helper pages in
+// a new window and waits for it to call testDone()
+var tests = [
+  'helper_basic_pan.html',
+  'helper_div_pan.html',
+  'helper_iframe_pan.html',
+];
+
+var testIndex = -1;
+var w = null;
+
+function testDone() {
+  if (w) {
+    w.close();
+  }
+  testIndex++;
+  if (testIndex < tests.length) {
+    w = window.open(tests[testIndex], "_blank");
+  } else {
+    SimpleTest.finish();
+  }
+}
+
+window.onload = function() {
+  SpecialPowers.pushPrefEnv(
+    { "set":
+      [
+        // Dropping the touch slop to 0 makes the tests easier to write because
+        // we can just do a one-pixel drag to get over the pan threshold rather
+        // than having to hard-code some larger value.
+        ["apz.touch_start_tolerance", "0.0"],
+
+        // The B2G emulator is hella slow, and needs more than 300ms to run the
+        // main-thread code that deals with layerizing subframes and running
+        // touch listeners. In my local runs this needs to be at least 1000.
+        ["apz.content_response_timeout", "5000"]
+      ]
+    }, testDone);
+};
+
+  </script>
+</head>
+<body>
+</body>
+</html>
--- a/gfx/layers/client/TextureClient.cpp
+++ b/gfx/layers/client/TextureClient.cpp
@@ -355,16 +355,17 @@ TextureClient::CreateForDrawing(ISurface
       aSize.width <= maxTextureSize &&
       aSize.height <= maxTextureSize) {
     if (gfxWindowsPlatform::GetPlatform()->GetD3D9Device()) {
       texture = new CairoTextureClientD3D9(aAllocator, aFormat, aTextureFlags);
     }
   }
 
   if (!texture && aFormat == SurfaceFormat::B8G8R8X8 &&
+      aAllocator->IsSameProcess() &&
       aMoz2DBackend == gfx::BackendType::CAIRO) {
     if (aAllocator->IsSameProcess()) {
       texture = new TextureClientMemoryDIB(aAllocator, aFormat, aTextureFlags);
     } else {
       texture = new TextureClientShmemDIB(aAllocator, aFormat, aTextureFlags);
     }
   }
 #endif
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -136,22 +136,24 @@ AsyncCompositionManager::ComputeRotation
 {
   if (!mTargetConfig.naturalBounds().IsEmpty()) {
     mWorldTransform =
       ComputeTransformForRotation(mTargetConfig.naturalBounds(),
                                   mTargetConfig.rotation());
   }
 }
 
-static bool
-GetBaseTransform2D(Layer* aLayer, Matrix* aTransform)
+static void
+GetBaseTransform(Layer* aLayer, Matrix4x4* aTransform)
 {
   // Start with the animated transform if there is one
-  return (aLayer->AsLayerComposite()->GetShadowTransformSetByAnimation() ?
-          aLayer->GetLocalTransform() : aLayer->GetTransform()).Is2D(aTransform);
+  *aTransform =
+    (aLayer->AsLayerComposite()->GetShadowTransformSetByAnimation()
+        ? aLayer->GetLocalTransform()
+        : aLayer->GetTransform());
 }
 
 static void
 TransformClipRect(Layer* aLayer,
                   const Matrix4x4& aTransform)
 {
   const Maybe<ParentLayerIntRect>& clipRect = aLayer->AsLayerComposite()->GetShadowClipRect();
   if (clipRect) {
@@ -178,64 +180,56 @@ SetShadowTransform(Layer* aLayer, Matrix
   }
   aTransform.PostScale(1.0f / aLayer->GetPostXScale(),
                        1.0f / aLayer->GetPostYScale(),
                        1);
   aLayer->AsLayerComposite()->SetShadowTransform(aTransform);
 }
 
 static void
-TranslateShadowLayer2D(Layer* aLayer,
-                       const gfxPoint& aTranslation,
-                       bool aAdjustClipRect)
+TranslateShadowLayer(Layer* aLayer,
+                     const gfxPoint& aTranslation,
+                     bool aAdjustClipRect)
 {
   // This layer might also be a scrollable layer and have an async transform.
   // To make sure we don't clobber that, we start with the shadow transform.
   // (i.e. GetLocalTransform() instead of GetTransform()).
   // Note that the shadow transform is reset on every frame of composition so
   // we don't have to worry about the adjustments compounding over successive
   // frames.
-  Matrix layerTransform;
-  if (!aLayer->GetLocalTransform().Is2D(&layerTransform)) {
-    return;
-  }
+  Matrix4x4 layerTransform = aLayer->GetLocalTransform();
 
-  // Apply the 2D translation to the layer transform.
-  layerTransform._31 += aTranslation.x;
-  layerTransform._32 += aTranslation.y;
+  // Apply the translation to the layer transform.
+  layerTransform.PostTranslate(aTranslation.x, aTranslation.y, 0);
 
-  SetShadowTransform(aLayer, Matrix4x4::From2D(layerTransform));
+  SetShadowTransform(aLayer, layerTransform);
   aLayer->AsLayerComposite()->SetShadowTransformSetByAnimation(false);
 
   if (aAdjustClipRect) {
     TransformClipRect(aLayer, Matrix4x4::Translation(aTranslation.x, aTranslation.y, 0));
   }
 
   // If a fixed- or sticky-position layer has a mask layer, that mask should
   // move along with the layer, so apply the translation to the mask layer too.
   if (Layer* maskLayer = aLayer->GetMaskLayer()) {
-    TranslateShadowLayer2D(maskLayer, aTranslation, false);
+    TranslateShadowLayer(maskLayer, aTranslation, false);
   }
 }
 
-static bool
-AccumulateLayerTransforms2D(Layer* aLayer,
-                            Layer* aAncestor,
-                            Matrix& aMatrix)
+static void
+AccumulateLayerTransforms(Layer* aLayer,
+                          Layer* aAncestor,
+                          Matrix4x4& aMatrix)
 {
   // Accumulate the transforms between this layer and the subtree root layer.
   for (Layer* l = aLayer; l && l != aAncestor; l = l->GetParent()) {
-    Matrix l2D;
-    if (!GetBaseTransform2D(l, &l2D)) {
-      return false;
-    }
-    aMatrix *= l2D;
+    Matrix4x4 transform;
+    GetBaseTransform(l, &transform);
+    aMatrix *= transform;
   }
-
-  return true;
 }
 
 static LayerPoint
 GetLayerFixedMarginsOffset(Layer* aLayer,
                            const LayerMargin& aFixedLayerMargins)
 {
   // Work out the necessary translation, in root scrollable layer space.
   // Because fixed layer margins are stored relative to the root scrollable
@@ -307,47 +301,35 @@ AsyncCompositionManager::AlignFixedAndSt
                                   aCurrentTransformForRoot, aFixedLayerMargins);
       }
     }
     return;
   }
 
   // Insert a translation so that the position of the anchor point is the same
   // before and after the change to the transform of aTransformedSubtreeRoot.
-  // This currently only works for fixed layers with 2D transforms.
 
   // Accumulate the transforms between this layer and the subtree root layer.
-  Matrix ancestorTransform;
-  if (!AccumulateLayerTransforms2D(aLayer->GetParent(), aTransformedSubtreeRoot,
-                                   ancestorTransform)) {
-    return;
-  }
-
-  Matrix oldRootTransform;
-  Matrix newRootTransform;
-  if (!aPreviousTransformForRoot.Is2D(&oldRootTransform) ||
-      !aCurrentTransformForRoot.Is2D(&newRootTransform)) {
-    return;
-  }
+  Matrix4x4 ancestorTransform;
+  AccumulateLayerTransforms(aLayer->GetParent(), aTransformedSubtreeRoot,
+                            ancestorTransform);
 
   // Calculate the cumulative transforms between the subtree root with the
   // old transform and the current transform.
-  Matrix oldCumulativeTransform = ancestorTransform * oldRootTransform;
-  Matrix newCumulativeTransform = ancestorTransform * newRootTransform;
+  Matrix4x4 oldCumulativeTransform = ancestorTransform * aPreviousTransformForRoot;
+  Matrix4x4 newCumulativeTransform = ancestorTransform * aCurrentTransformForRoot;
   if (newCumulativeTransform.IsSingular()) {
     return;
   }
-  Matrix newCumulativeTransformInverse = newCumulativeTransform.Inverse();
+  Matrix4x4 newCumulativeTransformInverse = newCumulativeTransform.Inverse();
 
   // Now work out the translation necessary to make sure the layer doesn't
   // move given the new sub-tree root transform.
-  Matrix layerTransform;
-  if (!GetBaseTransform2D(aLayer, &layerTransform)) {
-    return;
-  }
+  Matrix4x4 layerTransform;
+  GetBaseTransform(aLayer, &layerTransform);
 
   // Calculate any offset necessary, in previous transform sub-tree root
   // space. This is used to make sure fixed position content respects
   // content document fixed position margins.
   LayerPoint offsetInOldSubtreeLayerSpace = GetLayerFixedMarginsOffset(aLayer, aFixedLayerMargins);
 
   // Add the above offset to the anchor point so we can offset the layer by
   // and amount that's specified in old subtree layer space.
@@ -382,25 +364,25 @@ AsyncCompositionManager::AlignFixedAndSt
     const LayerRect& stickyInner = aLayer->GetStickyScrollRangeInner();
 
     translation.y = IntervalOverlap(translation.y, stickyOuter.y, stickyOuter.YMost()) -
                     IntervalOverlap(translation.y, stickyInner.y, stickyInner.YMost());
     translation.x = IntervalOverlap(translation.x, stickyOuter.x, stickyOuter.XMost()) -
                     IntervalOverlap(translation.x, stickyInner.x, stickyInner.XMost());
   }
 
-  // Finally, apply the 2D translation to the layer transform. Note that in
+  // Finally, apply the translation to the layer transform. Note that in
   // general we need to apply the same translation to the layer's clip rect, so
   // that the effective transform on the clip rect takes it back to where it was
   // originally, had there been no async scroll. In the case where the
   // fixed/sticky layer is the same as aTransformedSubtreeRoot, then the clip
   // rect is not affected by the scroll-induced async scroll transform anyway
   // (since the clip is applied post-transform) so we don't need to make the
   // adjustment.
-  TranslateShadowLayer2D(aLayer, ThebesPoint(translation), aLayer != aTransformedSubtreeRoot);
+  TranslateShadowLayer(aLayer, ThebesPoint(translation), aLayer != aTransformedSubtreeRoot);
 }
 
 static void
 SampleValue(float aPortion, Animation& aAnimation, StyleAnimationValue& aStart,
             StyleAnimationValue& aEnd, Animatable* aValue)
 {
   StyleAnimationValue interpolatedValue;
   NS_ASSERTION(aStart.GetUnit() == aEnd.GetUnit() ||
--- a/gfx/moz.build
+++ b/gfx/moz.build
@@ -29,8 +29,9 @@ DIRS += [
 if CONFIG['MOZ_ENABLE_SKIA']:
     DIRS += ['skia']
 
 if CONFIG['ENABLE_TESTS']:
     DIRS += ['tests/gtest']
 
 TEST_DIRS += ['tests']
 
+SPHINX_TREES['gfx'] = 'docs'
--- a/gfx/thebes/gfxFcPlatformFontList.cpp
+++ b/gfx/thebes/gfxFcPlatformFontList.cpp
@@ -53,17 +53,16 @@ ToFcChar8Ptr(const char* aStr)
 
 static const char*
 ToCharPtr(const FcChar8 *aStr)
 {
     return reinterpret_cast<const char*>(aStr);
 }
 
 FT_Library gfxFcPlatformFontList::sCairoFTLibrary = nullptr;
-FcChar8* gfxFcPlatformFontList::sSentinelFirstFamily = nullptr;
 
 static cairo_user_data_key_t sFcFontlistUserFontDataKey;
 
 // canonical name ==> first en name or first name if no en name
 // This is the required logic for fullname lookups as per CSS3 Fonts spec.
 static uint32_t
 FindCanonicalNameIndex(FcPattern* aFont, const char* aLangField)
 {
@@ -1035,17 +1034,16 @@ gfxFcPlatformFontList::InitFontList()
     mLastConfig = FcConfigGetCurrent();
 
     // reset font lists
     gfxPlatformFontList::InitFontList();
 
     mLocalNames.Clear();
     mGenericMappings.Clear();
     mFcSubstituteCache.Clear();
-    sSentinelFirstFamily = nullptr;
 
     // iterate over available fonts
     FcFontSet* systemFonts = FcConfigGetFonts(nullptr, FcSetSystem);
     AddFontSetFamilies(systemFonts);
 
 #ifdef MOZ_BUNDLED_FONTS
     ActivateBundledFonts();
     FcFontSet* appFonts = FcConfigGetFonts(nullptr, FcSetApplication);
@@ -1248,39 +1246,39 @@ gfxFcPlatformFontList::FindFamily(const 
     // actual font family found via this process. So check the cache first:
     NS_ConvertUTF16toUTF8 familyToFind(familyName);
     gfxFontFamily* cached = mFcSubstituteCache.GetWeak(familyToFind);
     if (cached) {
         return cached;
     }
 
     const FcChar8* kSentinelName = ToFcChar8Ptr("-moz-sentinel");
-    if (!sSentinelFirstFamily) {
-        nsAutoRef<FcPattern> sentinelSubst(FcPatternCreate());
-        FcPatternAddString(sentinelSubst, FC_FAMILY, kSentinelName);
-        FcConfigSubstitute(nullptr, sentinelSubst, FcMatchPattern);
-        FcPatternGetString(sentinelSubst, FC_FAMILY, 0, &sSentinelFirstFamily);
-    }
+    FcChar8* sentinelFirstFamily = nullptr;
+    nsAutoRef<FcPattern> sentinelSubst(FcPatternCreate());
+    FcPatternAddString(sentinelSubst, FC_FAMILY, kSentinelName);
+    FcConfigSubstitute(nullptr, sentinelSubst, FcMatchPattern);
+    FcPatternGetString(sentinelSubst, FC_FAMILY, 0, &sentinelFirstFamily);
 
     // substitutions for font, -moz-sentinel pattern
     nsAutoRef<FcPattern> fontWithSentinel(FcPatternCreate());
-    FcPatternAddString(fontWithSentinel, FC_FAMILY, ToFcChar8Ptr(familyToFind.get()));
+    FcPatternAddString(fontWithSentinel, FC_FAMILY,
+                       ToFcChar8Ptr(familyToFind.get()));
     FcPatternAddString(fontWithSentinel, FC_FAMILY, kSentinelName);
     FcConfigSubstitute(nullptr, fontWithSentinel, FcMatchPattern);
 
     // iterate through substitutions until hitting the sentinel
     FcChar8* substName = nullptr;
     for (int i = 0;
          FcPatternGetString(fontWithSentinel, FC_FAMILY,
                             i, &substName) == FcResultMatch;
          i++)
     {
         NS_ConvertUTF8toUTF16 subst(ToCharPtr(substName));
-        if (sSentinelFirstFamily &&
-            FcStrCmp(substName, sSentinelFirstFamily) == 0) {
+        if (sentinelFirstFamily &&
+            FcStrCmp(substName, sentinelFirstFamily) == 0) {
             break;
         }
         gfxFontFamily* foundFamily = gfxPlatformFontList::FindFamily(subst);
         if (foundFamily) {
             // We've figured out what family the given name maps to, after any
             // fontconfig subsitutions. Cache it to speed up future lookups.
             mFcSubstituteCache.Put(familyToFind, foundFamily);
             return foundFamily;
--- a/gfx/thebes/gfxFcPlatformFontList.h
+++ b/gfx/thebes/gfxFcPlatformFontList.h
@@ -258,12 +258,11 @@ protected:
 
     // caching family lookups as found by FindFamily after resolving substitutions
     nsRefPtrHashtable<nsCStringHashKey, gfxFontFamily> mFcSubstituteCache;
 
     nsCOMPtr<nsITimer> mCheckFontUpdatesTimer;
     nsCountedRef<FcConfig> mLastConfig;
 
     static FT_Library sCairoFTLibrary;
-    static FcChar8* sSentinelFirstFamily;
 };
 
 #endif /* GFXPLATFORMFONTLIST_H_ */
--- a/gfx/thebes/gfxTextRun.cpp
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -595,31 +595,16 @@ gfxTextRun::Draw(gfxContext *aContext, g
                                                       aContext, aProvider);
             *aAdvanceWidth = metrics.mAdvanceWidth * direction;
         }
 
         // return without drawing
         return;
     }
 
-    // Set up parameters that will be constant across all glyph runs we need
-    // to draw, regardless of the font used.
-    TextRunDrawParams params;
-    params.context = aContext;
-    params.devPerApp = 1.0 / double(GetAppUnitsPerDevUnit());
-    params.isVerticalRun = IsVertical();
-    params.isRTL = IsRightToLeft();
-    params.direction = direction;
-    params.drawMode = aDrawMode;
-    params.callbacks = aCallbacks;
-    params.runContextPaint = aContextPaint;
-    params.paintSVGGlyphs = !aCallbacks || aCallbacks->mShouldPaintSVGGlyphs;
-    params.dt = aContext->GetDrawTarget();
-    params.fontSmoothingBGColor = aContext->GetFontSmoothingBackgroundColor();
-
     // synthetic bolding draws glyphs twice ==> colors with opacity won't draw
     // correctly unless first drawn without alpha
     BufferAlphaColor syntheticBoldBuffer(aContext);
     gfxRGBA currentColor;
     bool needToRestore = false;
 
     if (aDrawMode == DrawMode::GLYPH_FILL &&
         HasNonOpaqueColor(aContext, currentColor) &&
@@ -629,16 +614,31 @@ gfxTextRun::Draw(gfxContext *aContext, g
         gfxTextRun::Metrics metrics = MeasureText(aStart, aLength,
                                                   gfxFont::LOOSE_INK_EXTENTS,
                                                   aContext, aProvider);
         metrics.mBoundingBox.MoveBy(aPt);
         syntheticBoldBuffer.PushSolidColor(metrics.mBoundingBox, currentColor,
                                            GetAppUnitsPerDevUnit());
     }
 
+    // Set up parameters that will be constant across all glyph runs we need
+    // to draw, regardless of the font used.
+    TextRunDrawParams params;
+    params.context = aContext;
+    params.devPerApp = 1.0 / double(GetAppUnitsPerDevUnit());
+    params.isVerticalRun = IsVertical();
+    params.isRTL = IsRightToLeft();
+    params.direction = direction;
+    params.drawMode = aDrawMode;
+    params.callbacks = aCallbacks;
+    params.runContextPaint = aContextPaint;
+    params.paintSVGGlyphs = !aCallbacks || aCallbacks->mShouldPaintSVGGlyphs;
+    params.dt = aContext->GetDrawTarget();
+    params.fontSmoothingBGColor = aContext->GetFontSmoothingBackgroundColor();
+
     GlyphRunIterator iter(this, aStart, aLength);
     gfxFloat advance = 0.0;
 
     while (iter.NextRun()) {
         gfxFont *font = iter.GetGlyphRun()->mFont;
         uint32_t start = iter.GetStringStart();
         uint32_t end = iter.GetStringEnd();
         uint32_t ligatureRunStart = start;
--- a/image/Decoder.cpp
+++ b/image/Decoder.cpp
@@ -78,19 +78,18 @@ Decoder::~Decoder()
  */
 
 void
 Decoder::Init()
 {
   // No re-initializing
   MOZ_ASSERT(!mInitialized, "Can't re-initialize a decoder!");
 
-  // Fire OnStartDecode at init time to support bug 512435.
   if (!IsSizeDecode()) {
-      mProgress |= FLAG_DECODE_STARTED | FLAG_ONLOAD_BLOCKED;
+      mProgress |= FLAG_DECODE_STARTED;
   }
 
   // Implementation-specific initialization
   InitInternal();
 
   mInitialized = true;
 }
 
@@ -268,17 +267,17 @@ Decoder::CompleteDecode()
 
       if (mInFrame) {
         PostFrameStop();
       }
       PostDecodeDone();
     } else {
       // We're not usable. Record some final progress indicating the error.
       if (!IsSizeDecode()) {
-        mProgress |= FLAG_DECODE_COMPLETE | FLAG_ONLOAD_UNBLOCKED;
+        mProgress |= FLAG_DECODE_COMPLETE;
       }
       mProgress |= FLAG_HAS_ERROR;
     }
   }
 }
 
 void
 Decoder::Finish()
@@ -619,17 +618,17 @@ Decoder::PostFrameStop(Opacity aFrameOpa
   MOZ_ASSERT(mInFrame, "Stopping frame when we didn't start one");
   MOZ_ASSERT(mCurrentFrame, "Stopping frame when we don't have one");
 
   // Update our state
   mInFrame = false;
 
   mCurrentFrame->Finish(aFrameOpacity, aDisposalMethod, aTimeout, aBlendMethod);
 
-  mProgress |= FLAG_FRAME_COMPLETE | FLAG_ONLOAD_UNBLOCKED;
+  mProgress |= FLAG_FRAME_COMPLETE;
 
   // If we're not sending partial invalidations, then we send an invalidation
   // here when the first frame is complete.
   if (!mSendPartialInvalidations && !mIsAnimated) {
     mInvalidRect.UnionRect(mInvalidRect,
                            gfx::IntRect(gfx::IntPoint(0, 0), GetSize()));
   }
 }
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -1206,21 +1206,19 @@ RasterImage::OnImageDataComplete(nsIRequ
   Progress loadProgress = LoadCompleteProgress(aLastPart, mError, finalStatus);
 
   if (mDecodeOnlyOnDraw) {
     // For decode-only-on-draw images, we want to send notifications as if we've
     // already finished decoding. Otherwise some observers will never even try
     // to draw. (We may have already sent some of these notifications from
     // NotifyForDecodeOnlyOnDraw(), but ProgressTracker will ensure no duplicate
     // notifications get sent.)
-    loadProgress |= FLAG_ONLOAD_BLOCKED |
-                    FLAG_DECODE_STARTED |
+    loadProgress |= FLAG_DECODE_STARTED |
                     FLAG_FRAME_COMPLETE |
-                    FLAG_DECODE_COMPLETE |
-                    FLAG_ONLOAD_UNBLOCKED;
+                    FLAG_DECODE_COMPLETE;
   }
 
   // Notify our listeners, which will fire this image's load event.
   NotifyProgress(loadProgress);
 
   return finalStatus;
 }
 
@@ -1229,17 +1227,17 @@ RasterImage::NotifyForDecodeOnlyOnDraw()
 {
   if (!NS_IsMainThread()) {
     nsCOMPtr<nsIRunnable> runnable =
       NS_NewRunnableMethod(this, &RasterImage::NotifyForDecodeOnlyOnDraw);
     NS_DispatchToMainThread(runnable);
     return;
   }
 
-  NotifyProgress(FLAG_DECODE_STARTED | FLAG_ONLOAD_BLOCKED);
+  NotifyProgress(FLAG_DECODE_STARTED);
 }
 
 nsresult
 RasterImage::OnImageDataAvailable(nsIRequest*,
                                   nsISupports*,
                                   nsIInputStream* aInStr,
                                   uint64_t aOffset,
                                   uint32_t aCount)
--- a/image/test/browser/browser_bug666317.js
+++ b/image/test/browser/browser_bug666317.js
@@ -36,16 +36,33 @@ function isImgDecoded() {
 function forceDecodeImg() {
   let doc = gBrowser.getBrowserForTab(newTab).contentWindow.document;
   let img = doc.getElementById('testImg');
   let canvas = doc.createElement('canvas');
   let ctx = canvas.getContext('2d');
   ctx.drawImage(img, 0, 0);
 }
 
+function runAfterAsyncEvents(aCallback) {
+  function handlePostMessage(aEvent) {
+    if (aEvent.data == 'next') {
+      window.removeEventListener('message', handlePostMessage, false);
+      aCallback();
+    }
+  }
+
+  window.addEventListener('message', handlePostMessage, false);
+
+  // We'll receive the 'message' event after everything else that's currently in
+  // the event queue (which is a stronger guarantee than setTimeout, because
+  // setTimeout events may be coalesced). This lets us ensure that we run
+  // aCallback *after* any asynchronous events are delivered.
+  window.postMessage('next', '*');
+}
+
 function test() {
   // Enable the discarding pref.
   oldDiscardingPref = prefBranch.getBoolPref('discardable');
   prefBranch.setBoolPref('discardable', true);
 
   // Create and focus a new tab.
   oldTab = gBrowser.selectedTab;
   newTab = gBrowser.addTab('data:text/html,' + pageSource);
@@ -67,37 +84,38 @@ function step2() {
                            .createScriptedObserver(observer);
 
   // Clone the current imgIRequest with our new observer.
   var request = currentRequest();
   var clonedRequest = request.clone(scriptedObserver);
 
   // Check that the image is decoded.
   forceDecodeImg();
+
+  // The FRAME_COMPLETE notification is delivered asynchronously, so continue
+  // after we're sure it has been delivered.
+  runAfterAsyncEvents(() => step3(result, scriptedObserver, clonedRequest));
+}
+
+function step3(result, scriptedObserver, clonedRequest) {
   ok(isImgDecoded(), 'Image should initially be decoded.');
 
   // Focus the old tab, then fire a memory-pressure notification.  This should
   // cause the decoded image in the new tab to be discarded.
   gBrowser.selectedTab = oldTab;
   var os = Cc["@mozilla.org/observer-service;1"]
              .getService(Ci.nsIObserverService);
   os.notifyObservers(null, 'memory-pressure', 'heap-minimize');
 
-  // The discard notification is delivered asynchronously, so pump the event
-  // loop before checking.
-  window.addEventListener('message', function (event) {
-    if (event.data == 'step3') {
-      step3(result, scriptedObserver, clonedRequest);
-    }
-  }, false);
-
-  window.postMessage('step3', '*');
+  // The DISCARD notification is delivered asynchronously, so continue after
+  // we're sure it has been delivered.
+  runAfterAsyncEvents(() => step4(result, scriptedObserver, clonedRequest));
 }
 
-function step3(result, scriptedObserver, clonedRequest) {
+function step4(result, scriptedObserver, clonedRequest) {
   ok(result.wasDiscarded, 'Image should be discarded.');
 
   // And we're done.
   gBrowser.removeTab(newTab);
   prefBranch.setBoolPref('discardable', oldDiscardingPref);
   clonedRequest.cancelAndForgetObserver(0);
   finish();
 }
--- a/image/test/mochitest/mochitest.ini
+++ b/image/test/mochitest/mochitest.ini
@@ -65,17 +65,16 @@ support-files =
 [test_bug468160.html]
 # [test_bug478398.html]
 # disabled - See bug 579139
 [test_bug490949.html]
 skip-if = (toolkit == 'android' && processor == 'x86') #x86 only
 [test_bug496292.html]
 [test_bug497665.html]
 skip-if = (toolkit == 'android' && processor == 'x86') #x86 only
-[test_bug512435.html]
 [test_bug552605-1.html]
 [test_bug552605-2.html]
 [test_bug553982.html]
 [test_bug601470.html]
 skip-if = (toolkit == 'android' && processor == 'x86') #x86 only
 [test_bug614392.html]
 [test_bug657191.html]
 [test_bug671906.html]
deleted file mode 100644
--- a/image/test/mochitest/test_bug512435.html
+++ /dev/null
@@ -1,49 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=512435
--->
-<head>
-  <title>Test for Bug 512435</title>
-  <script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/image/test/mochitest/imgutils.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=512435">Mozilla Bug 512435</a>
-<img id="img_a">
-<img id="img_b">
-</div>
-<pre id="test">
-<script type="application/javascript">
-
-// Boilerplate
-const Ci = SpecialPowers.Ci;
-const Cc = SpecialPowers.Cc;
-SimpleTest.waitForExplicitFinish();
-
-// We're relying on very particular behavior for certain images - clear the
-// image cache, _then_ set src
-clearImageCache();
-document.getElementById("img_a").src = "damon.jpg";
-document.getElementById("img_b").src = "shaver.png";
-
-// Our handler
-function loadHandler() {
-
-  // The two images should be decoded
-  ok(isFrameDecoded("img_a"), "img_a should be decoded before onload fires");
-  ok(isFrameDecoded("img_b"), "img_b should be decoded before onload fires");
-
-  // All done
-  SimpleTest.finish();
-}
-
-// Set our onload handler
-window.onload = loadHandler;
-
-</script>
-</pre>
-</body>
-</html>
--- a/js/public/Id.h
+++ b/js/public/Id.h
@@ -166,19 +166,17 @@ extern JS_PUBLIC_DATA(const jsid) JSID_E
 extern JS_PUBLIC_DATA(const JS::HandleId) JSID_VOIDHANDLE;
 extern JS_PUBLIC_DATA(const JS::HandleId) JSID_EMPTYHANDLE;
 
 namespace js {
 
 template <> struct GCMethods<jsid>
 {
     static jsid initial() { return JSID_VOID; }
-    static bool needsPostBarrier(jsid id) { return false; }
-    static void postBarrier(jsid* idp) {}
-    static void relocate(jsid* idp) {}
+    static void postBarrier(jsid* idp, jsid prev, jsid next) {}
 };
 
 // If the jsid is a GC pointer type, convert to that type and call |f| with
 // the pointer. If the jsid is not a GC type, calls F::defaultValue.
 template <typename F, typename... Args>
 auto
 DispatchIdTyped(F f, jsid& id, Args&&... args)
   -> decltype(f(static_cast<JSString*>(nullptr), mozilla::Forward<Args>(args)...))
--- a/js/public/RootingAPI.h
+++ b/js/public/RootingAPI.h
@@ -169,18 +169,17 @@ struct PersistentRootedMarker;
 namespace JS {
 
 template <typename T> class Rooted;
 template <typename T> class PersistentRooted;
 
 /* This is exposing internal state of the GC for inlining purposes. */
 JS_FRIEND_API(bool) isGCEnabled();
 
-JS_FRIEND_API(void) HeapObjectPostBarrier(JSObject** objp);
-JS_FRIEND_API(void) HeapObjectRelocate(JSObject** objp);
+JS_FRIEND_API(void) HeapObjectPostBarrier(JSObject** objp, JSObject* prev, JSObject* next);
 
 #ifdef JS_DEBUG
 /*
  * For generational GC, assert that an object is in the tenured generation as
  * opposed to being in the nursery.
  */
 extern JS_FRIEND_API(void)
 AssertGCThingMustBeTenured(JSObject* obj);
@@ -227,18 +226,17 @@ class Heap : public js::HeapBase<T>
      * For Heap, move semantics are equivalent to copy semantics. In C++, a
      * copy constructor taking const-ref is the way to get a single function
      * that will be used for both lvalue and rvalue copies, so we can simply
      * omit the rvalue variant.
      */
     explicit Heap(const Heap<T>& p) { init(p.ptr); }
 
     ~Heap() {
-        if (js::GCMethods<T>::needsPostBarrier(ptr))
-            relocate();
+        post(ptr, js::GCMethods<T>::initial());
     }
 
     DECLARE_POINTER_CONSTREF_OPS(T);
     DECLARE_POINTER_ASSIGN_OPS(Heap, T);
     DECLARE_NONPOINTER_ACCESSOR_METHODS(ptr);
 
     T* unsafeGet() { return &ptr; }
 
@@ -252,39 +250,27 @@ class Heap : public js::HeapBase<T>
 
     bool isSetToCrashOnTouch() {
         return ptr == crashOnTouchPointer;
     }
 
   private:
     void init(T newPtr) {
         ptr = newPtr;
-        if (js::GCMethods<T>::needsPostBarrier(ptr))
-            post();
+        post(js::GCMethods<T>::initial(), ptr);
     }
 
     void set(T newPtr) {
-        if (js::GCMethods<T>::needsPostBarrier(newPtr)) {
-            ptr = newPtr;
-            post();
-        } else if (js::GCMethods<T>::needsPostBarrier(ptr)) {
-            relocate();  /* Called before overwriting ptr. */
-            ptr = newPtr;
-        } else {
-            ptr = newPtr;
-        }
+        T tmp = ptr;
+        ptr = newPtr;
+        post(tmp, ptr);
     }
 
-    void post() {
-        MOZ_ASSERT(js::GCMethods<T>::needsPostBarrier(ptr));
-        js::GCMethods<T>::postBarrier(&ptr);
-    }
-
-    void relocate() {
-        js::GCMethods<T>::relocate(&ptr);
+    void post(const T& prev, const T& next) {
+        js::GCMethods<T>::postBarrier(&ptr, prev, next);
     }
 
     enum {
         crashOnTouchPointer = 1
     };
 
     T ptr;
 };
@@ -599,57 +585,46 @@ struct RootKind<T*>
 {
     static ThingRootKind rootKind() { return T::rootKind(); }
 };
 
 template <typename T>
 struct GCMethods<T*>
 {
     static T* initial() { return nullptr; }
-    static bool needsPostBarrier(T* v) { return false; }
-    static void postBarrier(T** vp) {
-        if (vp)
-            JS::AssertGCThingIsNotAnObjectSubclass(reinterpret_cast<js::gc::Cell*>(vp));
+    static void postBarrier(T** vp, T* prev, T* next) {
+        if (next)
+            JS::AssertGCThingIsNotAnObjectSubclass(reinterpret_cast<js::gc::Cell*>(next));
     }
     static void relocate(T** vp) {}
 };
 
 template <>
 struct GCMethods<JSObject*>
 {
     static JSObject* initial() { return nullptr; }
     static gc::Cell* asGCThingOrNull(JSObject* v) {
         if (!v)
             return nullptr;
         MOZ_ASSERT(uintptr_t(v) > 32);
         return reinterpret_cast<gc::Cell*>(v);
     }
-    static bool needsPostBarrier(JSObject* v) {
-        return v != nullptr && gc::IsInsideNursery(reinterpret_cast<gc::Cell*>(v));
-    }
-    static void postBarrier(JSObject** vp) {
-        JS::HeapObjectPostBarrier(vp);
-    }
-    static void relocate(JSObject** vp) {
-        JS::HeapObjectRelocate(vp);
+    static void postBarrier(JSObject** vp, JSObject* prev, JSObject* next) {
+        JS::HeapObjectPostBarrier(vp, prev, next);
     }
 };
 
 template <>
 struct GCMethods<JSFunction*>
 {
     static JSFunction* initial() { return nullptr; }
-    static bool needsPostBarrier(JSFunction* v) {
-        return v != nullptr && gc::IsInsideNursery(reinterpret_cast<gc::Cell*>(v));
-    }
-    static void postBarrier(JSFunction** vp) {
-        JS::HeapObjectPostBarrier(reinterpret_cast<JSObject**>(vp));
-    }
-    static void relocate(JSFunction** vp) {
-        JS::HeapObjectRelocate(reinterpret_cast<JSObject**>(vp));
+    static void postBarrier(JSFunction** vp, JSFunction* prev, JSFunction* next) {
+        JS::HeapObjectPostBarrier(reinterpret_cast<JSObject**>(vp),
+                                  reinterpret_cast<JSObject*>(prev),
+                                  reinterpret_cast<JSObject*>(next));
     }
 };
 
 } /* namespace js */
 
 namespace JS {
 
 /*
--- a/js/public/Value.h
+++ b/js/public/Value.h
@@ -1624,38 +1624,35 @@ SameType(const Value& lhs, const Value& 
     return JSVAL_SAME_TYPE_IMPL(lhs.data, rhs.data);
 }
 
 } // namespace JS
 
 /************************************************************************/
 
 namespace JS {
-JS_PUBLIC_API(void) HeapValuePostBarrier(Value* valuep);
-JS_PUBLIC_API(void) HeapValueRelocate(Value* valuep);
+JS_PUBLIC_API(void) HeapValuePostBarrier(Value* valuep, const Value& prev, const Value& next);
 }
 
 namespace js {
 
 template <> struct GCMethods<const JS::Value>
 {
     static JS::Value initial() { return JS::UndefinedValue(); }
 };
 
 template <> struct GCMethods<JS::Value>
 {
     static JS::Value initial() { return JS::UndefinedValue(); }
     static gc::Cell* asGCThingOrNull(const JS::Value& v) {
         return v.isMarkable() ? v.toGCThing() : nullptr;
     }
-    static bool needsPostBarrier(const JS::Value& v) {
-        return v.isObject() && gc::IsInsideNursery(reinterpret_cast<gc::Cell*>(&v.toObject()));
+    static void postBarrier(JS::Value* v, const JS::Value& prev, const JS::Value& next) {
+        JS::HeapValuePostBarrier(v, prev, next);
     }
-    static void postBarrier(JS::Value* v) { JS::HeapValuePostBarrier(v); }
-    static void relocate(JS::Value* v) { JS::HeapValueRelocate(v); }
 };
 
 template <class Outer> class MutableValueOperations;
 
 /*
  * A class designed for CRTP use in implementing the non-mutating parts of the
  * Value interface in Value-like classes.  Outer must be a class inheriting
  * ValueOperations<Outer> with a visible extract() method returning the
--- a/js/src/asmjs/AsmJSModule.h
+++ b/js/src/asmjs/AsmJSModule.h
@@ -846,17 +846,17 @@ class AsmJSModule
     ScriptSource *                        scriptSource_;
     PropertyName *                        globalArgumentName_;
     PropertyName *                        importArgumentName_;
     PropertyName *                        bufferArgumentName_;
     uint8_t *                             code_;
     uint8_t *                             interruptExit_;
     uint8_t *                             outOfBoundsExit_;
     StaticLinkData                        staticLinkData_;
-    RelocatablePtrArrayBufferObjectMaybeShared maybeHeap_;
+    HeapPtrArrayBufferObjectMaybeShared   maybeHeap_;
     AsmJSModule **                        prevLinked_;
     AsmJSModule *                         nextLinked_;
     bool                                  dynamicallyLinked_;
     bool                                  loadedFromCache_;
     bool                                  profilingEnabled_;
     bool                                  interrupted_;
 
     void restoreHeapToInitialState(ArrayBufferObjectMaybeShared* maybePrevBuffer);
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -207,28 +207,16 @@ TryEvalJSON(JSContext* cx, JSLinearStrin
     if (!linearChars.init(cx, str))
         return EvalJSON_Failure;
 
     return linearChars.isLatin1()
            ? ParseEvalStringAsJSON(cx, linearChars.latin1Range(), rval)
            : ParseEvalStringAsJSON(cx, linearChars.twoByteRange(), rval);
 }
 
-static bool
-HasPollutedScopeChain(JSObject* scopeChain)
-{
-    while (scopeChain) {
-        if (scopeChain->is<DynamicWithObject>())
-            return true;
-        scopeChain = scopeChain->enclosingScope();
-    }
-
-    return false;
-}
-
 // Define subset of ExecuteType so that casting performs the injection.
 enum EvalType { DIRECT_EVAL = EXECUTE_DIRECT_EVAL, INDIRECT_EVAL = EXECUTE_INDIRECT_EVAL };
 
 // Common code implementing direct and indirect eval.
 //
 // Evaluate call.argv[2], if it is a string, in the context of the given calling
 // frame, with the provided scope chain, with the semantics of either a direct
 // or indirect eval (see ES5 10.4.2).  If this is an indirect eval, scopeobj
@@ -327,23 +315,18 @@ EvalKernel(JSContext* cx, const CallArgs
 
         RootedObject enclosing(cx);
         if (evalType == DIRECT_EVAL)
             enclosing = callerScript->innermostStaticScope(pc);
         Rooted<StaticEvalObject*> staticScope(cx, StaticEvalObject::create(cx, enclosing));
         if (!staticScope)
             return false;
 
-        bool hasPollutedGlobalScope =
-            HasPollutedScopeChain(scopeobj) ||
-            (evalType == DIRECT_EVAL && callerScript->hasPollutedGlobalScope());
-
         CompileOptions options(cx);
         options.setFileAndLine(filename, 1)
-               .setHasPollutedScope(hasPollutedGlobalScope)
                .setIsRunOnce(true)
                .setForEval(true)
                .setNoScriptRval(false)
                .setMutedErrors(mutedErrors)
                .setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset)
                .maybeMakeStrictMode(evalType == DIRECT_EVAL && IsStrictEvalPC(pc));
 
         AutoStableStringChars linearChars(cx);
@@ -351,17 +334,17 @@ EvalKernel(JSContext* cx, const CallArgs
             return false;
 
         const char16_t* chars = linearChars.twoByteRange().start().get();
         SourceBufferHolder::Ownership ownership = linearChars.maybeGiveOwnershipToCaller()
                                                   ? SourceBufferHolder::GiveOwnership
                                                   : SourceBufferHolder::NoOwnership;
         SourceBufferHolder srcBuf(chars, linearStr->length(), ownership);
         JSScript* compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(),
-                                                     scopeobj, callerScript, staticScope,
+                                                     scopeobj, staticScope, callerScript,
                                                      options, srcBuf, linearStr, staticLevel);
         if (!compiled)
             return false;
 
         if (compiled->strict())
             staticScope->setStrict();
 
         esg.setNewScript(compiled);
@@ -405,31 +388,29 @@ js::DirectEvalStringFromIon(JSContext* c
 
     if (!esg.foundScript()) {
         RootedScript maybeScript(cx);
         const char* filename;
         unsigned lineno;
         bool mutedErrors;
         uint32_t pcOffset;
         DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno, &pcOffset,
-                                              &mutedErrors, CALLED_FROM_JSOP_EVAL);
+                                             &mutedErrors, CALLED_FROM_JSOP_EVAL);
 
         const char* introducerFilename = filename;
         if (maybeScript && maybeScript->scriptSource()->introducerFilename())
             introducerFilename = maybeScript->scriptSource()->introducerFilename();
 
         RootedObject enclosing(cx, callerScript->innermostStaticScope(pc));
         Rooted<StaticEvalObject*> staticScope(cx, StaticEvalObject::create(cx, enclosing));
         if (!staticScope)
             return false;
 
         CompileOptions options(cx);
         options.setFileAndLine(filename, 1)
-               .setHasPollutedScope(HasPollutedScopeChain(scopeobj) ||
-                                    callerScript->hasPollutedGlobalScope())
                .setIsRunOnce(true)
                .setForEval(true)
                .setNoScriptRval(false)
                .setMutedErrors(mutedErrors)
                .setIntroductionInfo(introducerFilename, "eval", lineno, maybeScript, pcOffset)
                .maybeMakeStrictMode(IsStrictEvalPC(pc));
 
         AutoStableStringChars linearChars(cx);
@@ -437,17 +418,17 @@ js::DirectEvalStringFromIon(JSContext* c
             return false;
 
         const char16_t* chars = linearChars.twoByteRange().start().get();
         SourceBufferHolder::Ownership ownership = linearChars.maybeGiveOwnershipToCaller()
                                                   ? SourceBufferHolder::GiveOwnership
                                                   : SourceBufferHolder::NoOwnership;
         SourceBufferHolder srcBuf(chars, linearStr->length(), ownership);
         JSScript* compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(),
-                                                     scopeobj, callerScript, staticScope,
+                                                     scopeobj, staticScope, callerScript,
                                                      options, srcBuf, linearStr, staticLevel);
         if (!compiled)
             return false;
 
         if (compiled->strict())
             staticScope->setStrict();
 
         esg.setNewScript(compiled);
@@ -508,45 +489,41 @@ js::IsAnyBuiltinEval(JSFunction* fun)
 
 JS_FRIEND_API(bool)
 js::ExecuteInGlobalAndReturnScope(JSContext* cx, HandleObject global, HandleScript scriptArg,
                                   MutableHandleObject scopeArg)
 {
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, global);
     MOZ_ASSERT(global->is<GlobalObject>());
-    MOZ_RELEASE_ASSERT(scriptArg->hasPollutedGlobalScope());
+    MOZ_RELEASE_ASSERT(scriptArg->hasNonSyntacticScope());
 
     RootedScript script(cx, scriptArg);
     if (script->compartment() != cx->compartment()) {
-        script = CloneScript(cx, nullptr, nullptr, script);
+        Rooted<ScopeObject*> staticScope(cx, StaticNonSyntacticScopeObjects::create(cx, nullptr));
+        if (!staticScope)
+            return false;
+        script = CloneGlobalScript(cx, staticScope, script);
         if (!script)
             return false;
 
         Debugger::onNewScript(cx, script);
     }
 
-    RootedObject scope(cx, JS_NewPlainObject(cx));
+    Rooted<GlobalObject*> globalRoot(cx, &global->as<GlobalObject>());
+    Rooted<ScopeObject*> scope(cx, NonSyntacticVariablesObject::create(cx, globalRoot));
     if (!scope)
         return false;
 
-    if (!scope->setQualifiedVarObj(cx))
-        return false;
-
-    if (!scope->setUnqualifiedVarObj(cx))
-        return false;
-
     JSObject* thisobj = GetThisObject(cx, global);
     if (!thisobj)
         return false;
 
     RootedValue thisv(cx, ObjectValue(*thisobj));
     RootedValue rval(cx);
-    // XXXbz when this is fixed to pass in an actual ScopeObject, fix
-    // up the assert in js::CloneFunctionObject accordingly.
     if (!ExecuteKernel(cx, script, *scope, thisv, UndefinedValue(), EXECUTE_GLOBAL,
                        NullFramePtr() /* evalInFrame */, rval.address()))
     {
         return false;
     }
 
     scopeArg.set(scope);
     return true;
--- a/js/src/builtin/MapObject.cpp
+++ b/js/src/builtin/MapObject.cpp
@@ -1194,17 +1194,17 @@ MapObject::set(JSContext* cx, HandleObje
     ValueMap* map = obj->as<MapObject>().getData();
     if (!map)
         return false;
 
     AutoHashableValueRooter key(cx);
     if (!key.setValue(cx, k))
         return false;
 
-    RelocatableValue rval(v);
+    HeapValue rval(v);
     if (!map->put(key, rval)) {
         ReportOutOfMemory(cx);
         return false;
     }
     WriteBarrierPost(cx->runtime(), map, key.get());
     return true;
 }
 
@@ -1289,17 +1289,17 @@ MapObject::construct(JSContext* cx, unsi
             RootedValue val(cx);
             if (!GetElement(cx, pairObj, pairObj, 1, &val))
                 return false;
 
             if (isOriginalAdder) {
                 if (!hkey.setValue(cx, key))
                     return false;
 
-                RelocatableValue rval(val);
+                HeapValue rval(val);
                 if (!map->put(hkey, rval)) {
                     ReportOutOfMemory(cx);
                     return false;
                 }
                 WriteBarrierPost(cx->runtime(), map, key);
             } else {
                 if (!args2.init(2))
                     return false;
@@ -1446,17 +1446,17 @@ MapObject::has(JSContext* cx, unsigned a
 
 bool
 MapObject::set_impl(JSContext* cx, CallArgs args)
 {
     MOZ_ASSERT(MapObject::is(args.thisv()));
 
     ValueMap& map = extract(args);
     ARG0_KEY(cx, args, key);
-    RelocatableValue rval(args.get(1));
+    HeapValue rval(args.get(1));
     if (!map.put(key, rval)) {
         ReportOutOfMemory(cx);
         return false;
     }
     WriteBarrierPost(cx->runtime(), &map, key.get());
     args.rval().set(args.thisv());
     return true;
 }
@@ -1467,24 +1467,24 @@ MapObject::set(JSContext* cx, unsigned a
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod<MapObject::is, MapObject::set_impl>(cx, args);
 }
 
 bool
 MapObject::delete_impl(JSContext* cx, CallArgs args)
 {
     // MapObject::mark does not mark deleted entries. Incremental GC therefore
-    // requires that no RelocatableValue objects pointing to heap values be
-    // left alive in the ValueMap.
+    // requires that no HeapValue objects pointing to heap values be left alive
+    // in the ValueMap.
     //
     // OrderedHashMap::remove() doesn't destroy the removed entry. It merely
     // calls OrderedHashMap::MapOps::makeEmpty. But that is sufficient, because
     // makeEmpty clears the value by doing e->value = Value(), and in the case
-    // of a ValueMap, Value() means RelocatableValue(), which is the same as
-    // RelocatableValue(UndefinedValue()).
+    // of a ValueMap, Value() means HeapValue(), which is the same as
+    // HeapValue(UndefinedValue()).
     MOZ_ASSERT(MapObject::is(args.thisv()));
 
     ValueMap& map = extract(args);
     ARG0_KEY(cx, args, key);
     bool found;
     if (!map.remove(key, &found)) {
         ReportOutOfMemory(cx);
         return false;
--- a/js/src/builtin/MapObject.h
+++ b/js/src/builtin/MapObject.h
@@ -72,17 +72,17 @@ class AutoHashableValueRooter : private 
 
 template <class Key, class Value, class OrderedHashPolicy, class AllocPolicy>
 class OrderedHashMap;
 
 template <class T, class OrderedHashPolicy, class AllocPolicy>
 class OrderedHashSet;
 
 typedef OrderedHashMap<HashableValue,
-                       RelocatableValue,
+                       HeapValue,
                        HashableValue::Hasher,
                        RuntimeAllocPolicy> ValueMap;
 
 typedef OrderedHashSet<HashableValue,
                        HashableValue::Hasher,
                        RuntimeAllocPolicy> ValueSet;
 
 class MapObject : public NativeObject {
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -2267,21 +2267,20 @@ EvalReturningScope(JSContext* cx, unsign
     JS::AutoFilename filename;
     unsigned lineno;
 
     DescribeScriptedCaller(cx, &filename, &lineno);
 
     JS::CompileOptions options(cx);
     options.setFileAndLine(filename.get(), lineno);
     options.setNoScriptRval(true);
-    options.setHasPollutedScope(true);
 
     JS::SourceBufferHolder srcBuf(src, srclen, JS::SourceBufferHolder::NoOwnership);
     RootedScript script(cx);
-    if (!JS::Compile(cx, options, srcBuf, &script))
+    if (!JS::CompileForNonSyntacticScope(cx, options, srcBuf, &script))
         return false;
 
     if (global) {
         global = CheckedUnwrap(global);
         if (!global) {
             JS_ReportError(cx, "Permission denied to access global");
             return false;
         }
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -139,20 +139,21 @@ MaybeCheckEvalFreeVariables(ExclusiveCon
             scope = scope->enclosingScope();
         }
     }
 
     return true;
 }
 
 static inline bool
-CanLazilyParse(ExclusiveContext* cx, const ReadOnlyCompileOptions& options)
+CanLazilyParse(ExclusiveContext* cx, HandleObject staticScope,
+               const ReadOnlyCompileOptions& options)
 {
     return options.canLazilyParse &&
-           !options.hasPollutedGlobalScope &&
+           !HasNonSyntacticStaticScopeChain(staticScope) &&
            !cx->compartment()->options().disableLazyParsing() &&
            !cx->compartment()->options().discardSource() &&
            !options.sourceIsLazy;
 }
 
 static void
 MarkFunctionsWithinEvalScript(JSScript* script)
 {
@@ -205,18 +206,18 @@ frontend::CreateScriptSourceObject(Exclu
             return nullptr;
     }
 
     return sso;
 }
 
 JSScript*
 frontend::CompileScript(ExclusiveContext* cx, LifoAlloc* alloc, HandleObject scopeChain,
+                        Handle<ScopeObject*> enclosingStaticScope,
                         HandleScript evalCaller,
-                        Handle<StaticEvalObject*> evalStaticScope,
                         const ReadOnlyCompileOptions& options,
                         SourceBufferHolder& srcBuf,
                         JSString* source_ /* = nullptr */,
                         unsigned staticLevel /* = 0 */,
                         SourceCompressionTask* extraSct /* = nullptr */)
 {
     MOZ_ASSERT(srcBuf.get());
 
@@ -255,17 +256,17 @@ frontend::CompileScript(ExclusiveContext
 
     if (!cx->compartment()->options().discardSource()) {
         if (options.sourceIsLazy)
             ss->setSourceRetrievable();
         else if (!ss->setSourceCopy(cx, srcBuf, false, sct))
             return nullptr;
     }
 
-    bool canLazilyParse = CanLazilyParse(cx, options);
+    bool canLazilyParse = CanLazilyParse(cx, enclosingStaticScope, options);
 
     Maybe<Parser<SyntaxParseHandler> > syntaxParser;
     if (canLazilyParse) {
         syntaxParser.emplace(cx, alloc, options, srcBuf.get(), srcBuf.length(),
                              /* foldConstants = */ false,
                              (Parser<SyntaxParseHandler>*) nullptr,
                              (LazyScript*) nullptr);
 
@@ -279,32 +280,32 @@ frontend::CompileScript(ExclusiveContext
     parser.sct = sct;
     parser.ss = ss;
 
     if (!parser.checkOptions())
         return nullptr;
 
     bool savedCallerFun = evalCaller && evalCaller->functionOrCallerFunction();
     Directives directives(options.strictOption);
-    GlobalSharedContext globalsc(cx, directives, evalStaticScope, options.extraWarningsOption);
+    GlobalSharedContext globalsc(cx, directives, enclosingStaticScope, options.extraWarningsOption);
 
-    Rooted<JSScript*> script(cx, JSScript::Create(cx, evalStaticScope, savedCallerFun,
+    Rooted<JSScript*> script(cx, JSScript::Create(cx, enclosingStaticScope, savedCallerFun,
                                                   options, staticLevel, sourceObject, 0,
                                                   srcBuf.length()));
     if (!script)
         return nullptr;
 
     bool insideNonGlobalEval =
-        evalStaticScope && evalStaticScope->enclosingScopeForStaticScopeIter();
+        enclosingStaticScope && enclosingStaticScope->is<StaticEvalObject>() &&
+        enclosingStaticScope->as<StaticEvalObject>().enclosingScopeForStaticScopeIter();
     BytecodeEmitter::EmitterMode emitterMode =
         options.selfHostingMode ? BytecodeEmitter::SelfHosting : BytecodeEmitter::Normal;
     BytecodeEmitter bce(/* parent = */ nullptr, &parser, &globalsc, script,
                         /* lazyScript = */ nullptr, options.forEval,
-                        evalCaller, insideNonGlobalEval,
-                        options.lineno, emitterMode);
+                        evalCaller, insideNonGlobalEval, options.lineno, emitterMode);
     if (!bce.init())
         return nullptr;
 
     // Syntax parsing may cause us to restart processing of top level
     // statements in the script. Use Maybe<> so that the parse context can be
     // reset when this occurs.
     Maybe<ParseContext<FullParseHandler> > pc;
 
@@ -556,17 +557,17 @@ CompileFunctionBody(JSContext* cx, Mutab
 
     SourceCompressionTask sct(cx);
     MOZ_ASSERT(!options.sourceIsLazy);
     if (!cx->compartment()->options().discardSource()) {
         if (!ss->setSourceCopy(cx, srcBuf, true, &sct))
             return false;
     }
 
-    bool canLazilyParse = CanLazilyParse(cx, options);
+    bool canLazilyParse = CanLazilyParse(cx, enclosingStaticScope, options);
 
     Maybe<Parser<SyntaxParseHandler> > syntaxParser;
     if (canLazilyParse) {
         syntaxParser.emplace(cx, &cx->tempLifoAlloc(),
                              options, srcBuf.get(), srcBuf.length(),
                              /* foldConstants = */ false,
                              (Parser<SyntaxParseHandler>*) nullptr,
                              (LazyScript*) nullptr);
--- a/js/src/frontend/BytecodeCompiler.h
+++ b/js/src/frontend/BytecodeCompiler.h
@@ -12,28 +12,27 @@
 class JSLinearString;
 
 namespace js {
 
 class AutoNameVector;
 class LazyScript;
 class LifoAlloc;
 class ScriptSourceObject;
-class StaticEvalObject;
+class ScopeObject;
 struct SourceCompressionTask;
 
 namespace frontend {
 
 JSScript*
 CompileScript(ExclusiveContext* cx, LifoAlloc* alloc,
-              HandleObject scopeChain, HandleScript evalCaller,
-              Handle<StaticEvalObject*> evalStaticScope,
-              const ReadOnlyCompileOptions& options, SourceBufferHolder& srcBuf,
-              JSString* source_ = nullptr, unsigned staticLevel = 0,
-              SourceCompressionTask* extraSct = nullptr);
+              HandleObject scopeChain, Handle<ScopeObject*> enclosingStaticScope,
+              HandleScript evalCaller, const ReadOnlyCompileOptions& options,
+              SourceBufferHolder& srcBuf, JSString* source_ = nullptr,
+              unsigned staticLevel = 0, SourceCompressionTask* extraSct = nullptr);
 
 bool
 CompileLazyFunction(JSContext* cx, Handle<LazyScript*> lazy, const char16_t* chars, size_t length);
 
 /*
  * enclosingStaticScope is a static enclosing scope (e.g. a StaticWithObject).
  * Must be null if the enclosing scope is a global.
  */
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -764,17 +764,17 @@ BytecodeEmitter::enclosingStaticScope()
     if (staticScope)
         return staticScope;
 
     if (!sc->isFunctionBox()) {
         MOZ_ASSERT(!parent);
 
         // Top-level eval scripts have a placeholder static scope so that
         // StaticScopeIter may iterate through evals.
-        return sc->asGlobalSharedContext()->evalStaticScope();
+        return sc->asGlobalSharedContext()->topStaticScope();
     }
 
     return sc->asFunctionBox()->function();
 }
 
 #ifdef DEBUG
 static bool
 AllLocalsAliased(StaticBlockObject& obj)
@@ -1543,24 +1543,24 @@ BytecodeEmitter::tryConvertFreeName(Pars
             return false;
         RootedObject outerScope(cx, script->enclosingStaticScope());
         for (StaticScopeIter<CanGC> ssi(cx, outerScope); !ssi.done(); ssi++) {
             if (ssi.type() != StaticScopeIter<CanGC>::Function) {
                 if (ssi.type() == StaticScopeIter<CanGC>::Block) {
                     // Use generic ops if a catch block is encountered.
                     return false;
                 }
-                if (ssi.hasDynamicScopeObject())
+                if (ssi.hasSyntacticDynamicScopeObject())
                     hops++;
                 continue;
             }
             RootedScript script(cx, ssi.funScript());
             if (script->functionNonDelazifying()->atom() == pn->pn_atom)
                 return false;
-            if (ssi.hasDynamicScopeObject()) {
+            if (ssi.hasSyntacticDynamicScopeObject()) {
                 uint32_t slot;
                 if (lookupAliasedName(script, pn->pn_atom->asPropertyName(), &slot, pn)) {
                     JSOp op;
                     switch (pn->getOp()) {
                       case JSOP_GETNAME: op = JSOP_GETALIASEDVAR; break;
                       case JSOP_SETNAME: op = JSOP_SETALIASEDVAR; break;
                       default: return false;
                     }
@@ -1582,19 +1582,19 @@ BytecodeEmitter::tryConvertFreeName(Pars
         }
     }
 
     // Unbound names aren't recognizable global-property references if the
     // script is inside a non-global eval call.
     if (insideNonGlobalEval)
         return false;
 
-    // Skip trying to use GNAME ops if we know our script has a polluted
-    // global scope, since they'll just get treated as NAME ops anyway.
-    if (script->hasPollutedGlobalScope())
+    // Skip trying to use GNAME ops if we know our script has a non-syntactic
+    // scope, since they'll just get treated as NAME ops anyway.
+    if (script->hasNonSyntacticScope())
         return false;
 
     // Deoptimized names also aren't necessarily globals.
     if (pn->isDeoptimized())
         return false;
 
     if (sc->isFunctionBox()) {
         // Unbound names in function code may not be globals if new locals can
@@ -2351,23 +2351,24 @@ bool
 BytecodeEmitter::checkRunOnceContext()
 {
     return checkSingletonContext() || (!isInLoop() && isRunOnceLambda());
 }
 
 bool
 BytecodeEmitter::needsImplicitThis()
 {
-    if (sc->isFunctionBox() && sc->asFunctionBox()->inWith)
+    if (sc->inWith())
         return true;
 
     for (StmtInfoBCE* stmt = topStmt; stmt; stmt = stmt->down) {
         if (stmt->type == STMT_WITH)
             return true;
     }
+
     return false;
 }
 
 void
 BytecodeEmitter::tellDebuggerAboutCompiledScript(ExclusiveContext* cx)
 {
     // Note: when parsing off thread the resulting scripts need to be handed to
     // the debugger after rejoining to the main thread.
@@ -3401,16 +3402,29 @@ BytecodeEmitter::emitFunctionScript(Pars
     /*
      * IonBuilder has assumptions about what may occur immediately after
      * script->main (e.g., in the case of destructuring params). Thus, put the
      * following ops into the range [script->code, script->main). Note:
      * execution starts from script->code, so this has no semantic effect.
      */
 
     FunctionBox* funbox = sc->asFunctionBox();
+
+    // Link the function and the script to each other, so that StaticScopeIter
+    // may walk the scope chain of currently compiling scripts.
+    RootedFunction fun(cx, funbox->function());
+    MOZ_ASSERT(fun->isInterpreted());
+
+    script->setFunction(fun);
+
+    if (fun->isInterpretedLazy())
+        fun->setUnlazifiedScript(script);
+    else
+        fun->setScript(script);
+
     if (funbox->argumentsHasLocalBinding()) {
         MOZ_ASSERT(offset() == 0);  /* See JSScript::argumentsBytecode. */
         switchToPrologue();
         if (!emit1(JSOP_ARGUMENTS))
             return false;
         InternalBindingsHandle bindings(script, &script->bindings);
         BindingIter bi = Bindings::argumentsBinding(cx, bindings);
         if (script->bindingIsAliased(bi)) {
@@ -3504,25 +3518,16 @@ BytecodeEmitter::emitFunctionScript(Pars
      * If this function is only expected to run once, mark the script so that
      * initializers created within it may be given more precise types.
      */
     if (runOnce) {
         script->setTreatAsRunOnce();
         MOZ_ASSERT(!script->hasRunOnce());
     }
 
-    /* Initialize fun->script() so that the debugger has a valid fun->script(). */
-    RootedFunction fun(cx, script->functionNonDelazifying());
-    MOZ_ASSERT(fun->isInterpreted());
-
-    if (fun->isInterpretedLazy())
-        fun->setUnlazifiedScript(script);
-    else
-        fun->setScript(script);
-
     tellDebuggerAboutCompiledScript(cx);
 
     return true;
 }
 
 bool
 BytecodeEmitter::maybeEmitVarDecl(JSOp prologueOp, ParseNode* pn, jsatomid* result)
 {
@@ -5760,18 +5765,16 @@ BytecodeEmitter::emitFunction(ParseNode*
             if (outersc->isFunctionBox() && outersc->asFunctionBox()->mightAliasLocals())
                 funbox->setMightAliasLocals();      // inherit mightAliasLocals from parent
             MOZ_ASSERT_IF(outersc->strict(), funbox->strictScript);
 
             // Inherit most things (principals, version, etc) from the parent.
             Rooted<JSScript*> parent(cx, script);
             CompileOptions options(cx, parser->options());
             options.setMutedErrors(parent->mutedErrors())
-                   .setHasPollutedScope(parent->hasPollutedGlobalScope())
-                   .setSelfHostingMode(parent->selfHosted())
                    .setNoScriptRval(false)
                    .setForEval(false)
                    .setVersion(parent->getVersion());
 
             Rooted<JSObject*> enclosingScope(cx, enclosingStaticScope());
             Rooted<JSObject*> sourceObject(cx, script->sourceObject());
             Rooted<JSScript*> script(cx, JSScript::Create(cx, enclosingScope, false, options,
                                                           parent->staticLevel() + 1,
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -17,17 +17,17 @@
 
 #include "frontend/ParseMaps.h"
 #include "frontend/Parser.h"
 #include "frontend/SharedContext.h"
 #include "frontend/SourceNotes.h"
 
 namespace js {
 
-class StaticEvalObject;
+class ScopeObject;
 
 namespace frontend {
 
 class FullParseHandler;
 class ObjectBox;
 class ParseNode;
 template <typename ParseHandler> class Parser;
 class SharedContext;
--- a/js/src/frontend/FoldConstants.cpp
+++ b/js/src/frontend/FoldConstants.cpp
@@ -1082,16 +1082,17 @@ Fold(ExclusiveContext* cx, ParseNode** p
         if (name && NameToId(name) == IdToTypeId(NameToId(name))) {
             // Optimization 3: We have pn1["foo"] where foo is not an index.
             // Convert to a property access (like pn1.foo) which we optimize
             // better downstream. Don't bother with this for names which TI
             // considers to be indexes, to simplify downstream analysis.
             ParseNode* expr = handler.newPropertyAccess(pn->pn_left, name, pn->pn_pos.end);
             if (!expr)
                 return false;
+            expr->setInParens(pn->isInParens());
             ReplaceNode(pnp, expr);
 
             // Supposing we're replacing |obj["prop"]| with |obj.prop|, we now
             // can free the |"prop"| node and |obj["prop"]| nodes -- but not
             // the |obj| node now a sub-node of |expr|.  Mutate |pn| into a
             // node with |"prop"| as its child so that our |pn| doesn't have a
             // necessarily-weird structure (say, by nulling out |pn->pn_left|
             // only) that would fail AST sanity assertions performed by
--- a/js/src/frontend/FullParseHandler.h
+++ b/js/src/frontend/FullParseHandler.h
@@ -78,18 +78,31 @@ class FullParseHandler
         return node->isKind(PNK_SUPERPROP) || node->isKind(PNK_SUPERELEM);
     }
 
     bool isFunctionCall(ParseNode* node) {
         // Note: super() is a special form, *not* a function call.
         return node->isKind(PNK_CALL);
     }
 
-    bool isDestructuringTarget(ParseNode* node) {
-        return node->isKind(PNK_OBJECT) || node->isKind(PNK_ARRAY);
+    static bool isUnparenthesizedDestructuringPattern(ParseNode* node) {
+        return !node->isInParens() && (node->isKind(PNK_OBJECT) || node->isKind(PNK_ARRAY));
+    }
+
+    static bool isParenthesizedDestructuringPattern(ParseNode* node) {
+        // Technically this isn't a destructuring pattern at all -- the grammar
+        // doesn't treat it as such.  But we need to know when this happens to
+        // consider it a SyntaxError rather than an invalid-left-hand-side
+        // ReferenceError.
+        return node->isInParens() && (node->isKind(PNK_OBJECT) || node->isKind(PNK_ARRAY));
+    }
+
+    static bool isDestructuringPatternAnyParentheses(ParseNode* node) {
+        return isUnparenthesizedDestructuringPattern(node) ||
+               isParenthesizedDestructuringPattern(node);
     }
 
     FullParseHandler(ExclusiveContext* cx, LifoAlloc& alloc,
                      TokenStream& tokenStream, Parser<SyntaxParseHandler>* syntaxParser,
                      LazyScript* lazyOuterFunction)
       : allocator(cx, alloc),
         tokenStream(tokenStream),
         lazyOuterFunction_(lazyOuterFunction),
@@ -775,19 +788,35 @@ class FullParseHandler
     }
     void setPrologue(ParseNode* pn) {
         pn->pn_prologue = true;
     }
 
     bool isConstant(ParseNode* pn) {
         return pn->isConstant();
     }
-    PropertyName* maybeName(ParseNode* pn) {
-        return pn->isKind(PNK_NAME) ? pn->pn_atom->asPropertyName() : nullptr;
+
+    PropertyName* maybeUnparenthesizedName(ParseNode* pn) {
+        if (!pn->isInParens() && pn->isKind(PNK_NAME))
+            return pn->pn_atom->asPropertyName();
+        return nullptr;
     }
+
+    PropertyName* maybeParenthesizedName(ParseNode* pn) {
+        if (pn->isInParens() && pn->isKind(PNK_NAME))
+            return pn->pn_atom->asPropertyName();
+        return nullptr;
+    }
+
+    PropertyName* maybeNameAnyParentheses(ParseNode* node) {
+        if (PropertyName* name = maybeUnparenthesizedName(node))
+            return name;
+        return maybeParenthesizedName(node);
+    }
+
     bool isCall(ParseNode* pn) {
         return pn->isKind(PNK_CALL);
     }
     PropertyName* maybeDottedProperty(ParseNode* pn) {
         return pn->is<PropertyAccess>() ? &pn->as<PropertyAccess>().name() : nullptr;
     }
     JSAtom* isStringExprStatement(ParseNode* pn, TokenPos* pos) {
         if (JSAtom* atom = pn->isStringExprStatement()) {
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -591,54 +591,63 @@ FunctionBox::FunctionBox(ExclusiveContex
                          bool extraWarnings, GeneratorKind generatorKind)
   : ObjectBox(fun, traceListHead),
     SharedContext(cx, directives, extraWarnings),
     bindings(),
     bufStart(0),
     bufEnd(0),
     length(0),
     generatorKindBits_(GeneratorKindAsBits(generatorKind)),
-    inWith(false),                  // initialized below
+    inWith_(false),                  // initialized below
     inGenexpLambda(false),
     hasDestructuringArgs(false),
     useAsm(false),
     insideUseAsm(outerpc && outerpc->useAsmOrInsideUseAsm()),
     usesArguments(false),
     usesApply(false),
     usesThis(false),
     funCxFlags()
 {
     // Functions created at parse time may be set singleton after parsing and
     // baked into JIT code, so they must be allocated tenured. They are held by
     // the JSScript so cannot be collected during a minor GC anyway.
     MOZ_ASSERT(fun->isTenured());
 
     if (!outerpc) {
-        inWith = false;
+        inWith_ = false;
 
     } else if (outerpc->parsingWith) {
         // This covers cases that don't involve eval().  For example:
         //
         //   with (o) { (function() { g(); })(); }
         //
         // In this case, |outerpc| corresponds to global code, and
         // outerpc->parsingWith is true.
-        inWith = true;
+        inWith_ = true;
 
     } else if (outerpc->sc->isFunctionBox()) {
         // This is like the above case, but for more deeply nested functions.
         // For example:
         //
         //   with (o) { eval("(function() { (function() { g(); })(); })();"); } }
         //
         // In this case, the inner anonymous function needs to inherit the
         // setting of |inWith| from the outer one.
         FunctionBox* parent = outerpc->sc->asFunctionBox();
-        if (parent && parent->inWith)
-            inWith = true;
+        if (parent && parent->inWith())
+            inWith_ = true;
+    } else {
+        // This is like the above case, but when inside eval.
+        //
+        // For example:
+        //
+        //   with(o) { eval("(function() { g(); })();"); }
+        //
+        // In this case, the static scope chain tells us the presence of with.
+        inWith_ = outerpc->sc->inWith();
     }
 }
 
 template <typename ParseHandler>
 FunctionBox*
 Parser<ParseHandler>::newFunctionBox(Node fn, JSFunction* fun, ParseContext<ParseHandler>* outerpc,
                                      Directives inheritedDirectives, GeneratorKind generatorKind)
 {
@@ -1150,17 +1159,17 @@ Parser<FullParseHandler>::makeDefIntoUse
     return true;
 }
 
 /*
  * Parameter block types for the several Binder functions.  We use a common
  * helper function signature in order to share code among destructuring and
  * simple variable declaration parsers.  In the destructuring case, the binder
  * function is called indirectly from the variable declaration parser by way
- * of checkDestructuring and its friends.
+ * of checkDestructuringPattern and its friends.
  */
 
 template <typename ParseHandler>
 struct BindData
 {
     explicit BindData(ExclusiveContext* cx) : let(cx) {}
 
     typedef bool
<