Merge inbound to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Fri, 17 Mar 2017 11:03:59 -0700
changeset 348260 23a4b7430dd7e83a2809bf3dc41471f154301eda
parent 348259 85ead1312fe6ca35789595f455788b599f599c9f (current diff)
parent 348201 e46c08babe02dd7ace9abb099ffbbf2330866832 (diff)
child 348261 5800d1391c10dcd268421a374bde6a514f8dcd10
child 348289 ef4b3a0066217062e2fa4d8e0be303fee9628ebc
child 348304 88576fd417e73ee08e8b7086f06c31accf6f4d57
child 349222 ceebf99c92ad17ebc9213942fff5fca5694c49e9
push id88170
push userkwierso@gmail.com
push dateFri, 17 Mar 2017 18:13:38 +0000
treeherdermozilla-inbound@5800d1391c10 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone55.0a1
first release with
nightly linux32
23a4b7430dd7 / 55.0a1 / 20170318110156 / files
nightly linux64
23a4b7430dd7 / 55.0a1 / 20170318110156 / files
nightly mac
23a4b7430dd7 / 55.0a1 / 20170317111607 / files
nightly win32
23a4b7430dd7 / 55.0a1 / 20170317111607 / files
nightly win64
23a4b7430dd7 / 55.0a1 / 20170317111607 / 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 inbound to central, a=merge MozReview-Commit-ID: Fq7B78wD7Wv
browser/base/content/test/general/browser_misused_characters_in_strings.js
browser/base/content/test/general/browser_parsable_css.js
browser/base/content/test/general/browser_parsable_script.js
browser/base/content/test/general/bug1262648_string_with_newlines.dtd
browser/base/content/test/general/parsingTestHelpers.jsm
testing/mozbase/docs/devicemanagement.rst
testing/mozbase/docs/mozdevice.rst
testing/mozbase/mozdevice/mozdevice/devicemanagerSUT.py
testing/mozbase/mozdevice/mozdevice/sutini.py
testing/mozbase/mozdevice/sut_tests/README.md
testing/mozbase/mozdevice/sut_tests/dmunit.py
testing/mozbase/mozdevice/sut_tests/runtests.py
testing/mozbase/mozdevice/sut_tests/test_datachannel.py
testing/mozbase/mozdevice/sut_tests/test_exec.py
testing/mozbase/mozdevice/sut_tests/test_exec_env.py
testing/mozbase/mozdevice/sut_tests/test_fileExists.py
testing/mozbase/mozdevice/sut_tests/test_getdir.py
testing/mozbase/mozdevice/sut_tests/test_info.py
testing/mozbase/mozdevice/sut_tests/test_prompt.py
testing/mozbase/mozdevice/sut_tests/test_ps.py
testing/mozbase/mozdevice/sut_tests/test_pull.py
testing/mozbase/mozdevice/sut_tests/test_push1.py
testing/mozbase/mozdevice/sut_tests/test_push2.py
testing/mozbase/mozdevice/sut_tests/test_pushbinary.py
testing/mozbase/mozdevice/sut_tests/test_pushsmalltext.py
testing/mozbase/mozdevice/tests/droidsut_launch.py
testing/mozbase/mozdevice/tests/manifest.ini
testing/mozbase/mozdevice/tests/sut.py
testing/mozbase/mozdevice/tests/sut_app.py
testing/mozbase/mozdevice/tests/sut_basic.py
testing/mozbase/mozdevice/tests/sut_chmod.py
testing/mozbase/mozdevice/tests/sut_copytree.py
testing/mozbase/mozdevice/tests/sut_fileExists.py
testing/mozbase/mozdevice/tests/sut_fileMethods.py
testing/mozbase/mozdevice/tests/sut_info.py
testing/mozbase/mozdevice/tests/sut_ip.py
testing/mozbase/mozdevice/tests/sut_kill.py
testing/mozbase/mozdevice/tests/sut_list.py
testing/mozbase/mozdevice/tests/sut_logcat.py
testing/mozbase/mozdevice/tests/sut_mkdir.py
testing/mozbase/mozdevice/tests/sut_movetree.py
testing/mozbase/mozdevice/tests/sut_ps.py
testing/mozbase/mozdevice/tests/sut_pull.py
testing/mozbase/mozdevice/tests/sut_push.py
testing/mozbase/mozdevice/tests/sut_remove.py
testing/mozbase/mozdevice/tests/sut_time.py
testing/mozbase/mozdevice/tests/sut_unpackfile.py
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -2,16 +2,17 @@
 
 module.exports = {
   // When adding items to this file please check for effects on sub-directories.
   "plugins": [
     "mozilla"
   ],
   "rules": {
     "mozilla/avoid-removeChild": "error",
+    "mozilla/avoid-nsISupportsString-preferences": "error",
     "mozilla/import-globals": "warn",
     "mozilla/no-import-into-var-and-global": "error",
     "mozilla/no-useless-parameters": "error",
     "mozilla/no-useless-removeEventListener": "error",
     "mozilla/use-default-preference-values": "error",
     "mozilla/use-ownerGlobal": "error",
 
     // No (!foo in bar) or (!object instanceof Class)
--- a/browser/base/content/aboutDialog.js
+++ b/browser/base/content/aboutDialog.js
@@ -7,39 +7,30 @@
 // Services = object with smart getters for common XPCOM services
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/AppConstants.jsm");
 
 function init(aEvent) {
   if (aEvent.target != document)
     return;
 
-  try {
-    var distroId = Services.prefs.getCharPref("distribution.id");
-    if (distroId) {
-      var distroVersion = Services.prefs.getCharPref("distribution.version");
-
-      var distroIdField = document.getElementById("distributionId");
-      distroIdField.value = distroId + " - " + distroVersion;
-      distroIdField.style.display = "block";
+  var distroId = Services.prefs.getCharPref("distribution.id", "");
+  if (distroId) {
+    var distroVersion = Services.prefs.getCharPref("distribution.version");
 
-      try {
-        // This is in its own try catch due to bug 895473 and bug 900925.
-        var distroAbout = Services.prefs.getComplexValue("distribution.about",
-          Components.interfaces.nsISupportsString);
-        var distroField = document.getElementById("distribution");
-        distroField.value = distroAbout;
-        distroField.style.display = "block";
-      } catch (ex) {
-        // Pref is unset
-        Components.utils.reportError(ex);
-      }
+    var distroIdField = document.getElementById("distributionId");
+    distroIdField.value = distroId + " - " + distroVersion;
+    distroIdField.style.display = "block";
+
+    var distroAbout = Services.prefs.getStringPref("distribution.about", "");
+    if (distroAbout) {
+      var distroField = document.getElementById("distribution");
+      distroField.value = distroAbout;
+      distroField.style.display = "block";
     }
-  } catch (e) {
-    // Pref is unset
   }
 
   // Include the build ID and display warning if this is an "a#" (nightly or aurora) build
   let versionField = document.getElementById("version");
   let version = Services.appinfo.version;
   if (/a\d+$/.test(version)) {
     let buildID = Services.appinfo.appBuildID;
     let year = buildID.slice(0, 4);
--- a/browser/base/content/aboutaccounts/aboutaccounts.js
+++ b/browser/base/content/aboutaccounts/aboutaccounts.js
@@ -25,27 +25,24 @@ const OBSERVER_TOPICS = [
 ];
 
 function log(msg) {
   // dump("FXA: " + msg + "\n");
 }
 
 function getPreviousAccountNameHash() {
   try {
-    return Services.prefs.getComplexValue(PREF_LAST_FXA_USER, Ci.nsISupportsString).data;
+    return Services.prefs.getStringPref(PREF_LAST_FXA_USER);
   } catch (_) {
     return "";
   }
 }
 
 function setPreviousAccountNameHash(acctName) {
-  let string = Cc["@mozilla.org/supports-string;1"]
-               .createInstance(Ci.nsISupportsString);
-  string.data = sha256(acctName);
-  Services.prefs.setComplexValue(PREF_LAST_FXA_USER, Ci.nsISupportsString, string);
+  Services.prefs.setStringPref(PREF_LAST_FXA_USER, sha256(acctName));
 }
 
 function needRelinkWarning(acctName) {
   let prevAcctHash = getPreviousAccountNameHash();
   return prevAcctHash && prevAcctHash != sha256(acctName);
 }
 
 // Given a string, returns the SHA265 hash in base64
--- a/browser/base/content/browser-feeds.js
+++ b/browser/base/content/browser-feeds.js
@@ -330,20 +330,17 @@ var FeedHandler = {
         break;
       case "FeedWriter:ShownFirstRun":
         Services.prefs.setBoolPref("browser.feeds.showFirstRunUI", false);
         break;
       case "FeedWriter:SetFeedCharPref":
         Services.prefs.setCharPref(msg.data.pref, msg.data.value);
         break;
       case "FeedWriter:SetFeedComplexString": {
-        let supportsString = Cc["@mozilla.org/supports-string;1"].
-                             createInstance(Ci.nsISupportsString);
-        supportsString.data = msg.data.value;
-        Services.prefs.setComplexValue(msg.data.pref, Ci.nsISupportsString, supportsString);
+        Services.prefs.setStringPref(msg.data.pref, msg.data.value);
         break;
       }
       case "FeedConverter:ExecuteClientApp":
         this.executeClientApp(msg.data.spec, msg.data.title,
                               msg.data.subtitle, msg.data.feedHandler);
         break;
     }
   },
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3606,21 +3606,17 @@ function openHomeDialog(aURL) {
   }
 
   var pressedVal  = Services.prompt.confirmEx(window, promptTitle, promptMsg,
                           Services.prompt.STD_YES_NO_BUTTONS,
                           null, null, null, null, {value:0});
 
   if (pressedVal == 0) {
     try {
-      var homepageStr = Components.classes["@mozilla.org/supports-string;1"]
-                        .createInstance(Components.interfaces.nsISupportsString);
-      homepageStr.data = aURL;
-      gPrefService.setComplexValue("browser.startup.homepage",
-                                   Components.interfaces.nsISupportsString, homepageStr);
+      gPrefService.setStringPref("browser.startup.homepage", aURL);
     } catch (ex) {
       dump("Failed to set the home page.\n" + ex + "\n");
     }
   }
 }
 
 var newTabButtonObserver = {
   onDragOver(aEvent) {
@@ -4473,30 +4469,33 @@ var XULBrowserWindow = {
   // Called before links are navigated to to allow us to retarget them if needed.
   onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab) {
     let target = BrowserUtils.onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab);
     SocialUI.closeSocialPanelForLinkTraversal(target, linkNode);
     return target;
   },
 
   // Check whether this URI should load in the current process
-  shouldLoadURI(aDocShell, aURI, aReferrer, aTriggeringPrincipal) {
+  shouldLoadURI(aDocShell, aURI, aReferrer, aHasPostData, aTriggeringPrincipal) {
     if (!gMultiProcessBrowser)
       return true;
 
     let browser = aDocShell.QueryInterface(Ci.nsIDocShellTreeItem)
                            .sameTypeRootTreeItem
                            .QueryInterface(Ci.nsIDocShell)
                            .chromeEventHandler;
 
     // Ignore loads that aren't in the main tabbrowser
     if (browser.localName != "browser" || !browser.getTabBrowser || browser.getTabBrowser() != gBrowser)
       return true;
 
-    if (!E10SUtils.shouldLoadURI(aDocShell, aURI, aReferrer)) {
+    if (!E10SUtils.shouldLoadURI(aDocShell, aURI, aReferrer, aHasPostData)) {
+      // XXX: Do we want to complain if we have post data but are still
+      // redirecting the load? Perhaps a telemetry probe? Theoretically we
+      // shouldn't do this, as it throws out data. See bug 1348018.
       E10SUtils.redirectLoad(aDocShell, aURI, aReferrer, aTriggeringPrincipal, false);
       return false;
     }
 
     return true;
   },
 
   onProgressChange(aWebProgress, aRequest,
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -12,17 +12,16 @@ support-files =
   browser_bug678392-2.html
   browser_bug970746.xhtml
   browser_fxa_web_channel.html
   browser_registerProtocolHandler_notification.html
   browser_star_hsts.sjs
   browser_tab_dragdrop2_frame1.xul
   browser_web_channel.html
   browser_web_channel_iframe.html
-  bug1262648_string_with_newlines.dtd
   bug592338.html
   bug792517-2.html
   bug792517.html
   bug792517.sjs
   bug839103.css
   clipboard_pastefile.html
   contextmenu_common.js
   ctxmenu-image.png
@@ -46,17 +45,16 @@ support-files =
   head.js
   healthreport_pingData.js
   healthreport_testRemoteCommands.html
   moz.png
   navigating_window_with_download.html
   offlineQuotaNotification.cacheManifest
   offlineQuotaNotification.html
   page_style_sample.html
-  parsingTestHelpers.jsm
   pinning_headers.sjs
   ssl_error_reports.sjs
   popup_blocker.html
   print_postdata.sjs
   searchSuggestionEngine.sjs
   searchSuggestionEngine.xml
   searchSuggestionEngine2.xml
   subtst_contextmenu.html
@@ -262,32 +260,27 @@ skip-if = e10s # Bug 863514 - no gesture
 [browser_keywordSearch_postData.js]
 [browser_lastAccessedTab.js]
 skip-if = toolkit == "windows" # Disabled on Windows due to frequent failures (bug 969405)
 [browser_menuButtonFitts.js]
 skip-if = os != "win" # The Fitts Law menu button is only supported on Windows (bug 969376)
 [browser_middleMouse_noJSPaste.js]
 subsuite = clipboard
 [browser_minimize.js]
-[browser_misused_characters_in_strings.js]
 [browser_modifiedclick_inherit_principal.js]
 [browser_new_http_window_opened_from_file_tab.js]
 [browser_offlineQuotaNotification.js]
 skip-if = os == "linux" && !debug # bug 1304273
 [browser_feed_discovery.js]
 support-files = feed_discovery.html
 [browser_gZipOfflineChild.js]
 support-files = test_offline_gzip.html gZipOfflineChild.cacheManifest gZipOfflineChild.cacheManifest^headers^ gZipOfflineChild.html gZipOfflineChild.html^headers^
 [browser_overflowScroll.js]
 [browser_page_style_menu.js]
 [browser_page_style_menu_update.js]
-[browser_parsable_css.js]
-skip-if = debug # no point in running on both opt and debug, and will likely intermittently timeout on debug
-[browser_parsable_script.js]
-skip-if = asan || (os == 'linux' && !debug && (bits == 32)) # disabled on asan because of timeouts, and bug 1172468 for the linux 32-bit pgo issue.
 [browser_permissions.js]
 support-files =
   permissions.html
 [browser_pinnedTabs.js]
 [browser_plainTextLinks.js]
 [browser_popupUI.js]
 [browser_popup_blocker.js]
 skip-if = (os == 'linux') || (e10s && debug) # Frequent bug 1081925 and bug 1125520 failures
--- a/browser/base/content/test/general/browser_homeDrop.js
+++ b/browser/base/content/test/general/browser_homeDrop.js
@@ -30,23 +30,21 @@ add_task(function*() {
     yield BrowserTestUtils.waitForEvent(setHomepageDialog, "load", false);
 
     let setHomepagePromise = new Promise(function(resolve) {
       let observer = {
         QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
         observe(subject, topic, data) {
           is(topic, "nsPref:changed", "observed correct topic");
           is(data, HOMEPAGE_PREF, "observed correct data");
-          let modified = Services.prefs.getComplexValue(HOMEPAGE_PREF,
-                                                        Ci.nsISupportsString);
-          is(modified.data, homepage, "homepage is set correctly");
+          let modified = Services.prefs.getStringPref(HOMEPAGE_PREF);
+          is(modified, homepage, "homepage is set correctly");
           Services.prefs.removeObserver(HOMEPAGE_PREF, observer);
 
-          Services.prefs.setComplexValue(HOMEPAGE_PREF,
-                                         Ci.nsISupportsString, homepageStr);
+          Services.prefs.setStringPref(HOMEPAGE_PREF, "about:mozilla;");
 
           resolve();
         }
       };
       Services.prefs.addObserver(HOMEPAGE_PREF, observer, false);
     });
 
     setHomepageDialog.document.documentElement.acceptDialog();
@@ -82,9 +80,8 @@ add_task(function*() {
   yield* drop([[{type: "text/plain",
                  data: "http://mochi.test:8888/"}]],
               "http://mochi.test:8888/");
   yield* drop([[{type: "text/plain",
                  data: "http://mochi.test:8888/\nhttp://mochi.test:8888/b\nhttp://mochi.test:8888/c"}]],
               "http://mochi.test:8888/|http://mochi.test:8888/b|http://mochi.test:8888/c");
   yield dropInvalidURI();
 });
-
--- a/browser/base/content/test/newtab/head.js
+++ b/browser/base/content/test/newtab/head.js
@@ -248,21 +248,17 @@ function setPinnedLinks(aLinks) {
       if (id)
         return {url: "http://example" + (id != "-1" ? id : "") + ".com/",
                 title: "site#" + id,
                 type: "history"};
       return undefined;
     });
   }
 
-  let string = Cc["@mozilla.org/supports-string;1"]
-                 .createInstance(Ci.nsISupportsString);
-  string.data = JSON.stringify(links);
-  Services.prefs.setComplexValue("browser.newtabpage.pinned",
-                                 Ci.nsISupportsString, string);
+  Services.prefs.setStringPref("browser.newtabpage.pinned", JSON.stringify(links));
 
   NewTabUtils.pinnedLinks.resetCache();
   NewTabUtils.allPages.update();
 }
 
 /**
  * Restore the grid state.
  */
--- a/browser/base/content/test/social/head.js
+++ b/browser/base/content/test/social/head.js
@@ -198,20 +198,17 @@ function checkSocialUI(win) {
   if (SocialService.hasEnabledProviders) {
     ok(Social.providers.length > 0, "providers are enabled");
   } else {
     is(Social.providers.length, 0, "providers are not enabled");
   }
 }
 
 function setManifestPref(name, manifest) {
-  let string = Cc["@mozilla.org/supports-string;1"].
-               createInstance(Ci.nsISupportsString);
-  string.data = JSON.stringify(manifest);
-  Services.prefs.setComplexValue(name, Ci.nsISupportsString, string);
+  Services.prefs.setStringPref(name, JSON.stringify(manifest));
 }
 
 function getManifestPrefname(aManifest) {
   // is same as the generated name in SocialServiceInternal.getManifestPrefname
   let originUri = Services.io.newURI(aManifest.origin);
   return "social.manifest." + originUri.hostPort.replace(".", "-");
 }
 
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/static/browser.ini
@@ -0,0 +1,13 @@
+[DEFAULT]
+support-files =
+  head.js
+
+[browser_misused_characters_in_strings.js]
+support-files =
+  bug1262648_string_with_newlines.dtd
+[browser_parsable_css.js]
+support-files =
+  dummy_page.html
+skip-if = debug # no point in running on both opt and debug, and will likely intermittently timeout on debug
+[browser_parsable_script.js]
+skip-if = asan || (os == 'linux' && !debug && (bits == 32)) # disabled on asan because of timeouts, and bug 1172468 for the linux 32-bit pgo issue.
rename from browser/base/content/test/general/browser_misused_characters_in_strings.js
rename to browser/base/content/test/static/browser_misused_characters_in_strings.js
--- a/browser/base/content/test/general/browser_misused_characters_in_strings.js
+++ b/browser/base/content/test/static/browser_misused_characters_in_strings.js
@@ -102,19 +102,16 @@ let gWhitelist = [{
     type: "double-quote"
   }, {
     file: "aboutNetworking.dtd",
     key: "aboutNetworking.logTutorial",
     type: "single-quote"
   }
 ];
 
-var moduleLocation = gTestPath.replace(/\/[^\/]*$/i, "/parsingTestHelpers.jsm");
-var {generateURIsFromDirTree} = Cu.import(moduleLocation, {});
-
 /**
  * Check if an error should be ignored due to matching one of the whitelist
  * objects defined in gWhitelist.
  *
  * @param filepath The URI spec of the locale file
  * @param key The key of the entity that is being checked
  * @param type The type of error that has been found
  * @return true if the error should be ignored, false otherwise.
@@ -130,16 +127,17 @@ function ignoredError(filepath, key, typ
     }
   }
   return false;
 }
 
 function fetchFile(uri) {
   return new Promise((resolve, reject) => {
     let xhr = new XMLHttpRequest();
+    xhr.responseType = "text";
     xhr.open("GET", uri, true);
     xhr.onreadystatechange = function() {
       if (this.readyState != this.DONE) {
         return;
       }
       try {
         resolve(this.responseText);
       } catch (ex) {
rename from browser/base/content/test/general/browser_parsable_css.js
rename to browser/base/content/test/static/browser_parsable_css.js
--- a/browser/base/content/test/general/browser_parsable_css.js
+++ b/browser/base/content/test/static/browser_parsable_css.js
@@ -62,19 +62,16 @@ let allowedImageReferences = [
   {file: "chrome://devtools/skin/images/dock-bottom-minimize@2x.png",
    from: "chrome://devtools/skin/toolbox.css",
    isFromDevTools: true},
   {file: "chrome://devtools/skin/images/dock-bottom-maximize@2x.png",
    from: "chrome://devtools/skin/toolbox.css",
    isFromDevTools: true},
 ];
 
-var moduleLocation = gTestPath.replace(/\/[^\/]*$/i, "/parsingTestHelpers.jsm");
-var {generateURIsFromDirTree} = Cu.import(moduleLocation, {});
-
 // Add suffix to stylesheets' URI so that we always load them here and
 // have them parsed. Add a random number so that even if we run this
 // test multiple times, it would be unlikely to affect each other.
 const kPathSuffix = "?always-parse-css-" + Math.random();
 
 /**
  * Check if an error should be ignored due to matching one of the whitelist
  * objects defined in whitelist
@@ -295,35 +292,35 @@ add_task(function* checkAllTheCSS() {
   // filter out either the devtools paths or the non-devtools paths:
   let isDevtools = SimpleTest.harnessParameters.subsuite == "devtools";
   let devtoolsPathBits = ["webide", "devtools"];
   uris = uris.filter(uri => isDevtools == devtoolsPathBits.some(path => uri.spec.includes(path)));
 
   for (let uri of uris) {
     let linkEl = doc.createElement("link");
     linkEl.setAttribute("rel", "stylesheet");
-    let promiseForThisSpec = Promise.defer();
-    let onLoad = (e) => {
-      processCSSRules(linkEl.sheet);
-      promiseForThisSpec.resolve();
-      linkEl.removeEventListener("load", onLoad);
-      linkEl.removeEventListener("error", onError);
-    };
-    let onError = (e) => {
-      ok(false, "Loading " + linkEl.getAttribute("href") + " threw an error!");
-      promiseForThisSpec.resolve();
-      linkEl.removeEventListener("load", onLoad);
-      linkEl.removeEventListener("error", onError);
-    };
-    linkEl.addEventListener("load", onLoad);
-    linkEl.addEventListener("error", onError);
-    linkEl.setAttribute("type", "text/css");
-    let chromeUri = convertToChromeUri(uri);
-    linkEl.setAttribute("href", chromeUri.spec + kPathSuffix);
-    allPromises.push(promiseForThisSpec.promise);
+    allPromises.push(new Promise(resolve => {
+      let onLoad = (e) => {
+        processCSSRules(linkEl.sheet);
+        resolve();
+        linkEl.removeEventListener("load", onLoad);
+        linkEl.removeEventListener("error", onError);
+      };
+      let onError = (e) => {
+        ok(false, "Loading " + linkEl.getAttribute("href") + " threw an error!");
+        resolve();
+        linkEl.removeEventListener("load", onLoad);
+        linkEl.removeEventListener("error", onError);
+      };
+      linkEl.addEventListener("load", onLoad);
+      linkEl.addEventListener("error", onError);
+      linkEl.setAttribute("type", "text/css");
+      let chromeUri = convertToChromeUri(uri);
+      linkEl.setAttribute("href", chromeUri.spec + kPathSuffix);
+    }));
     doc.head.appendChild(linkEl);
   }
 
   // Wait for all the files to have actually loaded:
   yield Promise.all(allPromises);
 
   // Check if all the files referenced from CSS actually exist.
   for (let [image, references] of imageURIsToReferencesMap) {
rename from browser/base/content/test/general/browser_parsable_script.js
rename to browser/base/content/test/static/browser_parsable_script.js
--- a/browser/base/content/test/general/browser_parsable_script.js
+++ b/browser/base/content/test/static/browser_parsable_script.js
@@ -5,20 +5,16 @@
  * detect newly occurring issues in shipping JS. It is a list of regexes
  * matching files which have errors:
  */
 const kWhitelist = new Set([
   /defaults\/profile\/prefs.js$/,
   /browser\/content\/browser\/places\/controller.js$/,
 ]);
 
-
-var moduleLocation = gTestPath.replace(/\/[^\/]*$/i, "/parsingTestHelpers.jsm");
-var {generateURIsFromDirTree} = Cu.import(moduleLocation, {});
-
 // Normally we would use reflect.jsm to get Reflect.parse. However, if
 // we do that, then all the AST data is allocated in reflect.jsm's
 // zone. That exposes a bug in our GC. The GC collects reflect.jsm's
 // zone but not the zone in which our test code lives (since no new
 // data is being allocated in it). The cross-compartment wrappers in
 // our zone that point to the AST data never get collected, and so the
 // AST data itself is never collected. We need to GC both zones at
 // once to fix the problem.
@@ -45,17 +41,17 @@ function parsePromise(uri) {
   let promise = new Promise((resolve, reject) => {
     let xhr = new XMLHttpRequest();
     xhr.open("GET", uri, true);
     xhr.onreadystatechange = function() {
       if (this.readyState == this.DONE) {
         let scriptText = this.responseText;
         try {
           info("Checking " + uri);
-          Reflect.parse(scriptText);
+          Reflect.parse(scriptText, {source: uri});
           resolve(true);
         } catch (ex) {
           let errorMsg = "Script error reading " + uri + ": " + ex;
           ok(false, errorMsg);
           resolve(false);
         }
       }
     };
@@ -90,17 +86,17 @@ add_task(function* checkAllTheJS() {
     requestLongerTimeout(30);
   }
 
   let uris;
   // If an absolute URI is specified on the command line, use it immediately.
   if (parseValue && parseValue.includes(":")) {
     uris = [NetUtil.newURI(parseValue)];
   } else {
-    let appDir = Services.dirsvc.get("XCurProcD", Ci.nsIFile);
+    let appDir = Services.dirsvc.get("GreD", Ci.nsIFile);
     // This asynchronously produces a list of URLs (sadly, mostly sync on our
     // test infrastructure because it runs against jarfiles there, and
     // our zipreader APIs are all sync)
     let startTimeMs = Date.now();
     info("Collecting URIs");
     uris = yield generateURIsFromDirTree(appDir, [".js", ".jsm"]);
     info("Collected URIs in " + (Date.now() - startTimeMs) + "ms");
 
rename from browser/base/content/test/general/bug1262648_string_with_newlines.dtd
rename to browser/base/content/test/static/bug1262648_string_with_newlines.dtd
copy from browser/base/content/test/general/dummy_page.html
copy to browser/base/content/test/static/dummy_page.html
rename from browser/base/content/test/general/parsingTestHelpers.jsm
rename to browser/base/content/test/static/head.js
--- a/browser/base/content/test/general/parsingTestHelpers.jsm
+++ b/browser/base/content/test/static/head.js
@@ -1,15 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-this.EXPORTED_SYMBOLS = ["generateURIsFromDirTree"];
-
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 /* Shorthand constructors to construct an nsI(Local)File and zip reader: */
 const LocalFile = new Components.Constructor("@mozilla.org/file/local;1", Ci.nsIFile, "initWithPath");
 const ZipReader = new Components.Constructor("@mozilla.org/libjar/zip-reader;1", "nsIZipReader", "open");
 
 
 Cu.import("resource://gre/modules/Services.jsm");
@@ -122,10 +120,8 @@ function* generateEntriesFromJarFile(jar
     if (entry.startsWith("jsloader") || entry.startsWith("jssubloader")) {
       continue;
     }
     let entryURISpec = "jar:" + kURIStart + "!/" + entry;
     yield Services.io.newURI(entryURISpec);
   }
   zr.close();
 }
-
-
--- a/browser/base/moz.build
+++ b/browser/base/moz.build
@@ -21,16 +21,17 @@ BROWSER_CHROME_MANIFESTS += [
     'content/test/general/browser.ini',
     'content/test/newtab/browser.ini',
     'content/test/pageinfo/browser.ini',
     'content/test/plugins/browser.ini',
     'content/test/popupNotifications/browser.ini',
     'content/test/referrer/browser.ini',
     'content/test/siteIdentity/browser.ini',
     'content/test/social/browser.ini',
+    'content/test/static/browser.ini',
     'content/test/tabcrashed/browser.ini',
     'content/test/tabPrompts/browser.ini',
     'content/test/tabs/browser.ini',
     'content/test/urlbar/browser.ini',
     'content/test/webextensions/browser.ini',
     'content/test/webrtc/browser.ini',
 ]
 
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -1071,20 +1071,17 @@ const CustomizableWidgets = [
 
       // The behavior as implemented here is directly based off of the
       // `MultiplexHandler()` method in browser.js.
       if (section != "detectors") {
         window.BrowserSetForcedCharacterSet(value);
       } else {
         // Set the detector pref.
         try {
-          let str = Cc["@mozilla.org/supports-string;1"]
-                      .createInstance(Ci.nsISupportsString);
-          str.data = value;
-          Services.prefs.setComplexValue("intl.charset.detector", Ci.nsISupportsString, str);
+          Services.prefs.setStringPref("intl.charset.detector", value);
         } catch (e) {
           Cu.reportError("Failed to set the intl.charset.detector preference.");
         }
         // Prepare a browser page reload with a changed charset.
         window.BrowserCharsetReload();
       }
     },
     onCreated(aNode) {
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -1363,32 +1363,28 @@ CustomizeMode.prototype = {
           else
             LightweightThemeManager.currentTheme = button.theme;
           onThemeSelected(panel);
         });
         panel.insertBefore(button, recommendedLabel);
       }
 
       let lwthemePrefs = Services.prefs.getBranch("lightweightThemes.");
-      let recommendedThemes = lwthemePrefs.getComplexValue("recommendedThemes",
-                                                           Ci.nsISupportsString).data;
+      let recommendedThemes = lwthemePrefs.getStringPref("recommendedThemes");
       recommendedThemes = JSON.parse(recommendedThemes);
       let sb = Services.strings.createBundle("chrome://browser/locale/lightweightThemes.properties");
       for (let theme of recommendedThemes) {
         theme.name = sb.GetStringFromName("lightweightThemes." + theme.id + ".name");
         theme.description = sb.GetStringFromName("lightweightThemes." + theme.id + ".description");
         let button = buildToolbarButton(theme);
         button.addEventListener("command", () => {
           LightweightThemeManager.setLocalTheme(button.theme);
           recommendedThemes = recommendedThemes.filter((aTheme) => { return aTheme.id != button.theme.id; });
-          let string = Cc["@mozilla.org/supports-string;1"]
-                         .createInstance(Ci.nsISupportsString);
-          string.data = JSON.stringify(recommendedThemes);
-          lwthemePrefs.setComplexValue("recommendedThemes",
-                                       Ci.nsISupportsString, string);
+          lwthemePrefs.setStringPref("recommendedThemes",
+                                     JSON.stringify(recommendedThemes));
           onThemeSelected(panel);
         });
         panel.insertBefore(button, footer);
       }
       let hideRecommendedLabel = (footer.previousSibling == recommendedLabel);
       recommendedLabel.hidden = hideRecommendedLabel;
     }.bind(this));
   },
--- a/browser/components/feeds/FeedWriter.js
+++ b/browser/components/feeds/FeedWriter.js
@@ -695,17 +695,17 @@ FeedWriter.prototype = {
     let prefs = Services.prefs;
     let handler = prefs.getCharPref(getPrefReaderForType(feedType), "bookmarks");
 
     switch (handler) {
       case "web": {
         if (this._handlersList) {
           let url;
           try {
-            url = prefs.getComplexValue(getPrefWebForType(feedType), Ci.nsISupportsString).data;
+            url = prefs.getStringPref(getPrefWebForType(feedType));
           } catch (ex) {
             LOG("FeedWriter._setSelectedHandler: invalid or no handler in prefs");
             return;
           }
           let handlers =
             this._getWebHandlerElementsForURL(url);
           if (handlers.length == 0) {
             LOG("FeedWriter._setSelectedHandler: selected web handler isn't in the menulist")
--- a/browser/components/feeds/WebContentConverter.js
+++ b/browser/components/feeds/WebContentConverter.js
@@ -691,22 +691,17 @@ WebContentConverterRegistrar.prototype =
     this._saveContentHandlerToPrefs(contentType, uri, title);
 
     if (contentType == TYPE_MAYBE_FEED) {
       // Make the new handler the last-selected reader in the preview page
       // and make sure the preview page is shown the next time a feed is visited
       let pb = Services.prefs.getBranch(null);
       pb.setCharPref(PREF_SELECTED_READER, "web");
 
-      let supportsString =
-        Cc["@mozilla.org/supports-string;1"].
-        createInstance(Ci.nsISupportsString);
-        supportsString.data = uri;
-      pb.setComplexValue(PREF_SELECTED_WEB, Ci.nsISupportsString,
-                         supportsString);
+      pb.setStringPref(PREF_SELECTED_WEB, uri);
       pb.setCharPref(PREF_SELECTED_ACTION, "ask");
       this._setAutoHandler(TYPE_MAYBE_FEED, null);
     }
   },
 
   /**
    * Update the content type -> handler map. This mapping is not persisted, use
    * registerContentHandler or _saveContentHandlerToPrefs for that purpose.
--- a/browser/components/migration/content/migration.js
+++ b/browser/components/migration/content/migration.js
@@ -421,22 +421,18 @@ var MigrationWizard = { /* exported Migr
               // set homepage properly
               var prefSvc = Components.classes["@mozilla.org/preferences-service;1"]
                                       .getService(Components.interfaces.nsIPrefService);
               var prefBranch = prefSvc.getBranch(null);
 
               if (this._newHomePage == "DEFAULT") {
                 prefBranch.clearUserPref("browser.startup.homepage");
               } else {
-                var str = Components.classes["@mozilla.org/supports-string;1"]
-                                  .createInstance(Components.interfaces.nsISupportsString);
-                str.data = this._newHomePage;
-                prefBranch.setComplexValue("browser.startup.homepage",
-                                           Components.interfaces.nsISupportsString,
-                                           str);
+                prefBranch.setStringPref("browser.startup.homepage",
+                                         this._newHomePage);
               }
 
               var dirSvc = Components.classes["@mozilla.org/file/directory_service;1"]
                                      .getService(Components.interfaces.nsIProperties);
               var prefFile = dirSvc.get("ProfDS", Components.interfaces.nsIFile);
               prefFile.append("prefs.js");
               prefSvc.savePrefFile(prefFile);
             } catch (ex) {
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -1833,29 +1833,26 @@ BrowserGlue.prototype = {
     if (currentUIVersion < 24) {
       // Reset homepage pref for users who have it set to start.mozilla.org
       // or google.com/firefox.
       const HOMEPAGE_PREF = "browser.startup.homepage";
       if (Services.prefs.prefHasUserValue(HOMEPAGE_PREF)) {
         const DEFAULT =
           Services.prefs.getDefaultBranch(HOMEPAGE_PREF)
                         .getComplexValue("", Ci.nsIPrefLocalizedString).data;
-        let value =
-          Services.prefs.getComplexValue(HOMEPAGE_PREF, Ci.nsISupportsString);
+        let value = Services.prefs.getStringPref(HOMEPAGE_PREF);
         let updated =
-          value.data.replace(/https?:\/\/start\.mozilla\.org[^|]*/i, DEFAULT)
-                    .replace(/https?:\/\/(www\.)?google\.[a-z.]+\/firefox[^|]*/i,
-                             DEFAULT);
-        if (updated != value.data) {
+          value.replace(/https?:\/\/start\.mozilla\.org[^|]*/i, DEFAULT)
+               .replace(/https?:\/\/(www\.)?google\.[a-z.]+\/firefox[^|]*/i,
+                        DEFAULT);
+        if (updated != value) {
           if (updated == DEFAULT) {
             Services.prefs.clearUserPref(HOMEPAGE_PREF);
           } else {
-            value.data = updated;
-            Services.prefs.setComplexValue(HOMEPAGE_PREF,
-                                           Ci.nsISupportsString, value);
+            Services.prefs.setStringPref(HOMEPAGE_PREF, updated);
           }
         }
       }
     }
 
     if (currentUIVersion < 25) {
       // Make sure the doNotTrack value conforms to the conversion from
       // three-state to two-state. (This reverts a setting of "please track me"
--- a/browser/components/tests/unit/test_distribution.js
+++ b/browser/components/tests/unit/test_distribution.js
@@ -92,17 +92,17 @@ add_task(function* () {
   // Force distribution.
   let glue = Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsIObserver)
   glue.observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_DISTRIBUTION_CUSTOMIZATION);
 
   var defaultBranch = Services.prefs.getDefaultBranch(null);
 
   Assert.equal(defaultBranch.getCharPref("distribution.id"), "disttest");
   Assert.equal(defaultBranch.getCharPref("distribution.version"), "1.0");
-  Assert.equal(defaultBranch.getComplexValue("distribution.about", Ci.nsISupportsString).data, "Tèƨƭ δïƨƭřïβúƭïôñ ƒïℓè");
+  Assert.equal(defaultBranch.getStringPref("distribution.about"), "Tèƨƭ δïƨƭřïβúƭïôñ ƒïℓè");
 
   Assert.equal(defaultBranch.getCharPref("distribution.test.string"), "Test String");
   Assert.equal(defaultBranch.getCharPref("distribution.test.string.noquotes"), "Test String");
   Assert.equal(defaultBranch.getIntPref("distribution.test.int"), 777);
   Assert.equal(defaultBranch.getBoolPref("distribution.test.bool.true"), true);
   Assert.equal(defaultBranch.getBoolPref("distribution.test.bool.false"), false);
 
   Assert.throws(() => defaultBranch.getCharPref("distribution.test.empty"));
--- a/browser/components/uitour/UITour.jsm
+++ b/browser/components/uitour/UITour.jsm
@@ -578,32 +578,28 @@ this.UITour = {
         let enginePromise = this.selectSearchEngine(data.identifier);
         enginePromise.catch(Cu.reportError);
         break;
       }
 
       case "setTreatmentTag": {
         let name = data.name;
         let value = data.value;
-        let string = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
-        string.data = value;
-        Services.prefs.setComplexValue("browser.uitour.treatment." + name,
-                                       Ci.nsISupportsString, string);
+        Services.prefs.setStringPref("browser.uitour.treatment." + name, value);
         // The notification is only meant to be used in tests.
         UITourHealthReport.recordTreatmentTag(name, value)
                           .then(() => this.notify("TreatmentTag:TelemetrySent"));
         break;
       }
 
       case "getTreatmentTag": {
         let name = data.name;
         let value;
         try {
-          value = Services.prefs.getComplexValue("browser.uitour.treatment." + name,
-                                                 Ci.nsISupportsString).data;
+          value = Services.prefs.getStringPref("browser.uitour.treatment." + name);
         } catch (ex) {}
         this.sendPageCallback(messageManager, data.callbackID, { value });
         break;
       }
 
       case "setSearchTerm": {
         let targetPromise = this.getTarget(window, "search");
         targetPromise.then(target => {
--- a/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
+++ b/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
@@ -88,17 +88,17 @@ function getIntPref(pref, def) {
     return Services.prefs.getIntPref(pref);
   } catch (ex) {
     return def;
   }
 }
 
 function getStringPref(pref, def) {
   try {
-    return Services.prefs.getComplexValue(pref, Ci.nsISupportsString).data;
+    return Services.prefs.getStringPref(pref);
   } catch (ex) {
     return def;
   }
 }
 
 function log(aMsg) {
   if (!getBoolPref(PREF_PREFIX + ".pdfBugEnabled", false)) {
     return;
--- a/browser/extensions/pdfjs/content/PdfjsChromeUtils.jsm
+++ b/browser/extensions/pdfjs/content/PdfjsChromeUtils.jsm
@@ -285,20 +285,17 @@ var PdfjsChromeUtils = {
 
   _setCharPref(aPrefName, aPrefValue) {
     this._ensurePreferenceAllowed(aPrefName);
     Services.prefs.setCharPref(aPrefName, aPrefValue);
   },
 
   _setStringPref(aPrefName, aPrefValue) {
     this._ensurePreferenceAllowed(aPrefName);
-    let str = Cc["@mozilla.org/supports-string;1"]
-                .createInstance(Ci.nsISupportsString);
-    str.data = aPrefValue;
-    Services.prefs.setComplexValue(aPrefName, Ci.nsISupportsString, str);
+    Services.prefs.setStringPref(aPrefName, aPrefValue);
   },
 
   /*
    * Svc.mime doesn't have profile information in the child, so
    * we bounce this pdfjs enabled configuration check over to the
    * parent.
    */
   isDefaultHandlerApp() {
--- a/browser/extensions/pocket/bootstrap.js
+++ b/browser/extensions/pocket/bootstrap.js
@@ -141,20 +141,17 @@ function CreatePocketWidget(reason) {
     SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
   }
 
   let origin = "https://getpocket.com";
   SocialService.getProvider(origin, provider => {
     if (provider) {
       let pref = "social.backup.getpocket-com";
       if (!Services.prefs.prefHasUserValue(pref)) {
-        let str = Cc["@mozilla.org/supports-string;1"].
-                  createInstance(Ci.nsISupportsString);
-        str.data = JSON.stringify(provider.manifest);
-        Services.prefs.setComplexValue(pref, Ci.nsISupportsString, str);
+        Services.prefs.setStringPref(pref, JSON.stringify(provider.manifest));
         SocialService.uninstallProvider(origin, () => {});
       }
     }
   });
 
 }
 
 // PocketContextMenu
--- a/browser/extensions/pocket/content/pktApi.jsm
+++ b/browser/extensions/pocket/content/pktApi.jsm
@@ -119,17 +119,17 @@ var pktApi = (function() {
      */
      function getSetting(key) {
         // TODO : Move this to sqlite or a local file so it's not editable (and is safer)
         // https://developer.mozilla.org/en-US/Add-ons/Overlay_Extensions/XUL_School/Local_Storage
 
         if (!prefBranch.prefHasUserValue(key))
             return undefined;
 
-        return prefBranch.getComplexValue(key, Components.interfaces.nsISupportsString).data;
+        return prefBranch.getStringPref(key);
      }
 
      /**
       * Wrapper for different plattforms to set a value for a given key in settings
       * @param {string} key     A string containing the name of the key you want
       *                         to create/update.
       * @param {string} value   String containing the value you want to give
       *                         the key you are creating/updating.
@@ -137,19 +137,17 @@ var pktApi = (function() {
     function setSetting(key, value) {
         // TODO : Move this to sqlite or a local file so it's not editable (and is safer)
         // https://developer.mozilla.org/en-US/Add-ons/Overlay_Extensions/XUL_School/Local_Storage
 
         if (!value)
             prefBranch.clearUserPref(key);
         else {
             // We use complexValue as tags can have utf-8 characters in them
-            var str = Components.classes["@mozilla.org/supports-string;1"].createInstance(Components.interfaces.nsISupportsString);
-            str.data = value;
-            prefBranch.setComplexValue(key, Components.interfaces.nsISupportsString, str);
+            prefBranch.setStringPref(key, value);
         }
     }
 
     /**
      * Auth
      */
 
     /*
--- a/browser/modules/E10SUtils.jsm
+++ b/browser/modules/E10SUtils.jsm
@@ -189,25 +189,27 @@ this.E10SUtils = {
     }
   },
 
   shouldLoadURIInThisProcess(aURI) {
     let remoteType = Services.appinfo.remoteType;
     return remoteType == this.getRemoteTypeForURIObject(aURI, true, remoteType);
   },
 
-  shouldLoadURI(aDocShell, aURI, aReferrer) {
+  shouldLoadURI(aDocShell, aURI, aReferrer, aHasPostData) {
     // Inner frames should always load in the current process
     if (aDocShell.QueryInterface(Ci.nsIDocShellTreeItem).sameTypeParent)
       return true;
 
     // If we are in a Large-Allocation process, and it wouldn't be content visible
     // to change processes, we want to load into a new process so that we can throw
-    // this one out.
-    if (Services.appinfo.remoteType == LARGE_ALLOCATION_REMOTE_TYPE &&
+    // this one out. We don't want to move into a new process if we have post data,
+    // because we would accidentally throw out that data.
+    if (!aHasPostData &&
+        Services.appinfo.remoteType == LARGE_ALLOCATION_REMOTE_TYPE &&
         !aDocShell.awaitingLargeAlloc &&
         aDocShell.isOnlyToplevelInTabGroup) {
       return false;
     }
 
     // If the URI can be loaded in the current process then continue
     return this.shouldLoadURIInThisProcess(aURI);
   },
--- a/browser/modules/SocialService.jsm
+++ b/browser/modules/SocialService.jsm
@@ -41,17 +41,17 @@ var SocialServiceInternal = {
     // Retrieve the manifests of installed providers from prefs
     let MANIFEST_PREFS = Services.prefs.getBranch("social.manifest.");
     let prefs = MANIFEST_PREFS.getChildList("", []);
     for (let pref of prefs) {
       // we only consider manifests in user level prefs to be *installed*
       if (!MANIFEST_PREFS.prefHasUserValue(pref))
         continue;
       try {
-        var manifest = JSON.parse(MANIFEST_PREFS.getComplexValue(pref, Ci.nsISupportsString).data);
+        var manifest = JSON.parse(MANIFEST_PREFS.getStringPref(pref));
         if (manifest && typeof(manifest) == "object" && manifest.origin)
           yield manifest;
       } catch (err) {
         Cu.reportError("SocialService: failed to load manifest: " + pref +
                        ", exception: " + err);
       }
     }
   },
@@ -60,17 +60,17 @@ var SocialServiceInternal = {
   },
   getManifestPrefname(origin) {
     // Retrieve the prefname for a given origin/manifest.
     // If no existing pref, return a generated prefname.
     let MANIFEST_PREFS = Services.prefs.getBranch("social.manifest.");
     let prefs = MANIFEST_PREFS.getChildList("", []);
     for (let pref of prefs) {
       try {
-        var manifest = JSON.parse(MANIFEST_PREFS.getComplexValue(pref, Ci.nsISupportsString).data);
+        var manifest = JSON.parse(MANIFEST_PREFS.getStringPref(pref));
         if (manifest.origin == origin) {
           return pref;
         }
       } catch (err) {
         Cu.reportError("SocialService: failed to load manifest: " + pref +
                        ", exception: " + err);
       }
     }
@@ -165,18 +165,17 @@ function getOriginActivationType(origin)
   return "foreign";
 }
 
 var ActiveProviders = {
   get _providers() {
     delete this._providers;
     this._providers = {};
     try {
-      let pref = Services.prefs.getComplexValue("social.activeProviders",
-                                                Ci.nsISupportsString);
+      let pref = Services.prefs.getStringPref("social.activeProviders");
       this._providers = JSON.parse(pref);
     } catch (ex) {}
     return this._providers;
   },
 
   has(origin) {
     return (origin in this._providers);
   },
@@ -197,21 +196,18 @@ var ActiveProviders = {
   },
 
   get _deferredTask() {
     delete this._deferredTask;
     return this._deferredTask = new DeferredTask(this._persist.bind(this), 0);
   },
 
   _persist() {
-    let string = Cc["@mozilla.org/supports-string;1"].
-                 createInstance(Ci.nsISupportsString);
-    string.data = JSON.stringify(this._providers);
-    Services.prefs.setComplexValue("social.activeProviders",
-                                   Ci.nsISupportsString, string);
+    Services.prefs.setStringPref("social.activeProviders",
+                                 JSON.stringify(this._providers));
   }
 };
 
 function migrateSettings() {
   let enabled;
   if (Services.prefs.prefHasUserValue("social.enabled")) {
     enabled = Services.prefs.getBoolPref("social.enabled");
   }
@@ -219,31 +215,30 @@ function migrateSettings() {
     // migration from fx21 to fx22 or later
     // ensure any *builtin* provider in activeproviders is in user level prefs
     for (let origin in ActiveProviders._providers) {
       let prefname;
       let manifest;
       let defaultManifest;
       try {
         prefname = getPrefnameFromOrigin(origin);
-        manifest = JSON.parse(Services.prefs.getComplexValue(prefname, Ci.nsISupportsString).data);
+        manifest = JSON.parse(Services.prefs.getStringPref(prefname));
       } catch (e) {
         // Our preference is missing or bad, remove from ActiveProviders and
         // continue. This is primarily an error-case and should only be
         // reached by either messing with preferences or hitting the one or
         // two days of nightly that ran into it, so we'll flush right away.
         ActiveProviders.delete(origin);
         ActiveProviders.flush();
         continue;
       }
       let needsUpdate = !manifest.updateDate;
       // fx23 may have built-ins with shareURL
       try {
-        defaultManifest = Services.prefs.getDefaultBranch(null)
-                        .getComplexValue(prefname, Ci.nsISupportsString).data;
+        defaultManifest = Services.prefs.getDefaultBranch(null).getStringPref(prefname);
         defaultManifest = JSON.parse(defaultManifest);
       } catch (e) {
         // not a built-in, continue
       }
       if (defaultManifest) {
         if (defaultManifest.shareURL && !manifest.shareURL) {
           manifest.shareURL = defaultManifest.shareURL;
           needsUpdate = true;
@@ -257,20 +252,17 @@ function migrateSettings() {
         // the provider was installed with an older build, so we will update the
         // timestamp and ensure the manifest is in user prefs
         delete manifest.builtin;
         // we're potentially updating for share, so always mark the updateDate
         manifest.updateDate = Date.now();
         if (!manifest.installDate)
           manifest.installDate = 0; // we don't know when it was installed
 
-        let string = Cc["@mozilla.org/supports-string;1"].
-                     createInstance(Ci.nsISupportsString);
-        string.data = JSON.stringify(manifest);
-        Services.prefs.setComplexValue(prefname, Ci.nsISupportsString, string);
+        Services.prefs.setStringPref(prefname, JSON.stringify(manifest));
       }
       // as of fx 29, we no longer rely on social.enabled. migration from prior
       // versions should disable all service addons if social.enabled=false
       if (enabled === false) {
         ActiveProviders.delete(origin);
       }
     }
     ActiveProviders.flush();
@@ -286,34 +278,33 @@ function migrateSettings() {
   // primary difference from SocialServiceInternal.manifests is that we
   // only read the default branch here.
   let manifestPrefs = Services.prefs.getDefaultBranch("social.manifest.");
   let prefs = manifestPrefs.getChildList("", []);
   for (let pref of prefs) {
     try {
       let manifest;
       try {
-        manifest = JSON.parse(manifestPrefs.getComplexValue(pref, Ci.nsISupportsString).data);
+        manifest = JSON.parse(manifestPrefs.getStringPref(pref));
       } catch (e) {
         // bad or missing preference, we wont update this one.
         continue;
       }
       if (manifest && typeof(manifest) == "object" && manifest.origin) {
         // our default manifests have been updated with the builtin flags as of
         // fx22, delete it so we can set the user-pref
         delete manifest.builtin;
         if (!manifest.updateDate) {
           manifest.updateDate = Date.now();
           manifest.installDate = 0; // we don't know when it was installed
         }
 
-        let string = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
-        string.data = JSON.stringify(manifest);
         // pref here is just the branch name, set the full pref name
-        Services.prefs.setComplexValue("social.manifest." + pref, Ci.nsISupportsString, string);
+        Services.prefs.setStringPref("social.manifest." + pref,
+                                     JSON.stringify(manifest));
         ActiveProviders.add(manifest.origin);
         ActiveProviders.flush();
         // social.active was used at a time that there was only one
         // builtin, we'll assume that is still the case
         return;
       }
     } catch (err) {
       Cu.reportError("SocialService: failed to load manifest: " + pref + ", exception: " + err);
@@ -637,20 +628,18 @@ this.SocialService = {
   updateProvider(aUpdateOrigin, aManifest) {
     let installType = this.getOriginActivationType(aUpdateOrigin);
     // if we get data, we MUST have a valid manifest generated from the data
     let manifest = this._manifestFromData(installType, aManifest, aUpdateOrigin);
     if (!manifest)
       throw new Error("SocialService.installProvider: service configuration is invalid from " + aUpdateOrigin);
 
     // overwrite the preference
-    let string = Cc["@mozilla.org/supports-string;1"].
-                 createInstance(Ci.nsISupportsString);
-    string.data = JSON.stringify(manifest);
-    Services.prefs.setComplexValue(getPrefnameFromOrigin(manifest.origin), Ci.nsISupportsString, string);
+    Services.prefs.setStringPref(getPrefnameFromOrigin(manifest.origin),
+                                 JSON.stringify(manifest));
 
     // overwrite the existing provider then notify the front end so it can
     // handle any reload that might be necessary.
     if (ActiveProviders.has(manifest.origin)) {
       let provider = SocialServiceInternal.providers[manifest.origin];
       provider.enabled = false;
       provider = new SocialProvider(manifest);
       SocialServiceInternal.providers[provider.origin] = provider;
@@ -824,20 +813,18 @@ function AddonInstaller(sourceURI, aMani
   this.sourceURI = sourceURI;
   this.install = function() {
     let addon = this.addon;
     if (isNewInstall) {
       AddonManagerPrivate.callInstallListeners("onExternalInstall", null, addon, null, false);
       AddonManagerPrivate.callAddonListeners("onInstalling", addon, false);
     }
 
-    let string = Cc["@mozilla.org/supports-string;1"].
-                 createInstance(Ci.nsISupportsString);
-    string.data = JSON.stringify(aManifest);
-    Services.prefs.setComplexValue(getPrefnameFromOrigin(aManifest.origin), Ci.nsISupportsString, string);
+    Services.prefs.setStringPref(getPrefnameFromOrigin(aManifest.origin),
+                                 JSON.stringify(aManifest));
 
     if (isNewInstall) {
       AddonManagerPrivate.callAddonListeners("onInstalled", addon);
     }
     installCallback(aManifest);
   };
   this.cancel = function() {
     Services.prefs.clearUserPref(getPrefnameFromOrigin(aManifest.origin));
--- a/browser/modules/test/unit/social/head.js
+++ b/browser/modules/test/unit/social/head.js
@@ -52,20 +52,18 @@ function initApp() {
   let internalManager = Cc["@mozilla.org/addons/integration;1"].
                      getService(Ci.nsIObserver).
                      QueryInterface(Ci.nsITimerCallback);
 
   internalManager.observe(null, "addons-startup", null);
 }
 
 function setManifestPref(manifest) {
-  let string = Cc["@mozilla.org/supports-string;1"].
-               createInstance(Ci.nsISupportsString);
-  string.data = JSON.stringify(manifest);
-  Services.prefs.setComplexValue("social.manifest." + manifest.origin, Ci.nsISupportsString, string);
+  Services.prefs.setStringPref("social.manifest." + manifest.origin,
+                               JSON.stringify(manifest));
 }
 
 function do_wait_observer(obsTopic, cb) {
   function observer(subject, topic, data) {
     Services.obs.removeObserver(observer, topic);
     cb();
   }
   Services.obs.addObserver(observer, obsTopic, false);
@@ -86,24 +84,21 @@ function do_initialize_social(enabledOnS
   initApp();
 
   if (enabledOnStartup) {
     // set prefs before initializing social
     manifests.forEach(function(manifest) {
       setManifestPref(manifest);
     });
     // Set both providers active and flag the first one as "current"
-    let activeVal = Cc["@mozilla.org/supports-string;1"].
-               createInstance(Ci.nsISupportsString);
     let active = {};
     for (let m of manifests)
       active[m.origin] = 1;
-    activeVal.data = JSON.stringify(active);
-    Services.prefs.setComplexValue("social.activeProviders",
-                                   Ci.nsISupportsString, activeVal);
+    Services.prefs.setStringPref("social.activeProviders",
+                                 JSON.stringify(active));
 
     do_register_cleanup(function() {
       manifests.forEach(function(manifest) {
         Services.prefs.clearUserPref("social.manifest." + manifest.origin);
       });
       Services.prefs.clearUserPref("social.activeProviders");
     });
 
--- a/browser/modules/test/unit/social/test_SocialServiceMigration21.js
+++ b/browser/modules/test/unit/social/test_SocialServiceMigration21.js
@@ -34,20 +34,18 @@ function* testMigration(manifest, next) 
   // look at social.activeProviders, we should have migrated into that, and
   // we should be set as a user level pref after migration
   do_check_false(MANIFEST_PREFS.prefHasUserValue(manifest.origin));
   // we need to access the providers for everything to initialize
   yield SocialService.getProviderList(next);
   do_check_true(SocialService.enabled);
   do_check_true(Services.prefs.prefHasUserValue("social.activeProviders"));
 
-  let activeProviders;
-  let pref = Services.prefs.getComplexValue("social.activeProviders",
-                                            Ci.nsISupportsString);
-  activeProviders = JSON.parse(pref);
+  let activeProviders =
+    JSON.parse(Services.prefs.getStringPref("social.activeProviders"));
   do_check_true(activeProviders[manifest.origin]);
   do_check_true(MANIFEST_PREFS.prefHasUserValue(manifest.origin));
   do_check_true(JSON.parse(DEFAULT_PREFS.getCharPref(manifest.origin)).builtin);
 
   let userPref = JSON.parse(MANIFEST_PREFS.getCharPref(manifest.origin));
   do_check_true(parseInt(userPref.updateDate) > 0);
   // migrated providers wont have an installDate
   do_check_true(userPref.installDate === 0);
--- a/browser/modules/test/unit/social/test_SocialServiceMigration22.js
+++ b/browser/modules/test/unit/social/test_SocialServiceMigration22.js
@@ -17,25 +17,22 @@ function run_test() {
     name: "provider 1",
     origin: "https://example1.com",
     builtin: true // as of fx22 this should be true for default prefs
   };
 
   DEFAULT_PREFS.setCharPref(manifest.origin, JSON.stringify(manifest));
 
   // Set both providers active and flag the first one as "current"
-  let activeVal = Cc["@mozilla.org/supports-string;1"].
-             createInstance(Ci.nsISupportsString);
   let active = {};
   active[manifest.origin] = 1;
   // bad.origin tests that a missing manifest does not break migration, bug 859715
   active["bad.origin"] = 1;
-  activeVal.data = JSON.stringify(active);
-  Services.prefs.setComplexValue("social.activeProviders",
-                                 Ci.nsISupportsString, activeVal);
+  Services.prefs.setStringPref("social.activeProviders",
+                               JSON.stringify(active));
 
   Cu.import("resource:///modules/SocialService.jsm");
 
   let runner = new AsyncRunner();
   let next = runner.next.bind(runner);
   runner.appendIterator(testMigration(manifest, next));
   runner.next();
 }
@@ -44,20 +41,18 @@ function* testMigration(manifest, next) 
   // look at social.activeProviders, we should have migrated into that, and
   // we should be set as a user level pref after migration
   do_check_false(MANIFEST_PREFS.prefHasUserValue(manifest.origin));
   // we need to access the providers for everything to initialize
   yield SocialService.getProviderList(next);
   do_check_true(SocialService.enabled);
   do_check_true(Services.prefs.prefHasUserValue("social.activeProviders"));
 
-  let activeProviders;
-  let pref = Services.prefs.getComplexValue("social.activeProviders",
-                                            Ci.nsISupportsString);
-  activeProviders = JSON.parse(pref);
+  let activeProviders =
+    JSON.parse(Services.prefs.getStringPref("social.activeProviders"));
   do_check_true(activeProviders[manifest.origin]);
   do_check_true(MANIFEST_PREFS.prefHasUserValue(manifest.origin));
   do_check_true(JSON.parse(DEFAULT_PREFS.getCharPref(manifest.origin)).builtin);
 
   let userPref = JSON.parse(MANIFEST_PREFS.getCharPref(manifest.origin));
   do_check_true(parseInt(userPref.updateDate) > 0);
   // migrated providers wont have an installDate
   do_check_true(userPref.installDate === 0);
--- a/browser/modules/test/unit/social/test_SocialServiceMigration29.js
+++ b/browser/modules/test/unit/social/test_SocialServiceMigration29.js
@@ -15,23 +15,20 @@ function run_test() {
   let manifest = { // normal provider
     name: "provider 1",
     origin: "https://example1.com",
   };
 
   MANIFEST_PREFS.setCharPref(manifest.origin, JSON.stringify(manifest));
 
   // Set both providers active and flag the first one as "current"
-  let activeVal = Cc["@mozilla.org/supports-string;1"].
-             createInstance(Ci.nsISupportsString);
   let active = {};
   active[manifest.origin] = 1;
-  activeVal.data = JSON.stringify(active);
-  Services.prefs.setComplexValue("social.activeProviders",
-                                 Ci.nsISupportsString, activeVal);
+  Services.prefs.setStringPref("social.activeProviders",
+                               JSON.stringify(active));
 
   // social.enabled pref is the key focus of this test. We set the user pref,
   // and then migration should a) remove the provider from activeProviders and
   // b) unset social.enabled
   Services.prefs.setBoolPref("social.enabled", false);
 
   Cu.import("resource:///modules/SocialService.jsm");
 
@@ -48,14 +45,13 @@ function* testMigration(manifest, next) 
   do_check_true(MANIFEST_PREFS.prefHasUserValue(manifest.origin));
   // we need to access the providers for everything to initialize
   yield SocialService.getProviderList(next);
   do_check_false(SocialService.enabled);
   do_check_false(Services.prefs.prefHasUserValue("social.enabled"));
   do_check_true(Services.prefs.prefHasUserValue("social.activeProviders"));
 
   let activeProviders;
-  let pref = Services.prefs.getComplexValue("social.activeProviders",
-                                            Ci.nsISupportsString).data;
+  let pref = Services.prefs.getStringPref("social.activeProviders");
   activeProviders = JSON.parse(pref);
   do_check_true(activeProviders[manifest.origin] == undefined);
   do_check_true(MANIFEST_PREFS.prefHasUserValue(manifest.origin));
 }
--- a/build/annotationProcessors/AnnotationInfo.java
+++ b/build/annotationProcessors/AnnotationInfo.java
@@ -25,16 +25,17 @@ public class AnnotationInfo {
 
         String nativeValue() {
             return "mozilla::jni::CallingThread::" + name();
         }
     }
 
     public enum DispatchTarget {
         GECKO,
+        GECKO_PRIORITY,
         PROXY,
         CURRENT;
 
         String nativeValue() {
             return "mozilla::jni::DispatchTarget::" + name();
         }
     }
 
--- a/devtools/client/commandline/test/browser_cmd_pref3.js
+++ b/devtools/client/commandline/test/browser_cmd_pref3.js
@@ -15,18 +15,17 @@ const TEST_URI = "data:text/html;charset
 function test() {
   return Task.spawn(spawnTest).then(finish, helpers.handleError);
 }
 
 function* spawnTest() {
   let options = yield helpers.openTab(TEST_URI);
   yield helpers.openToolbar(options);
 
-  let remoteHostOrig = prefBranch.getComplexValue("devtools.debugger.remote-host",
-                                                  Ci.nsISupportsString).data;
+  let remoteHostOrig = prefBranch.getStringPref("devtools.debugger.remote-host");
   info("originally: devtools.debugger.remote-host = " + remoteHostOrig);
 
   yield helpers.audit(options, [
     {
       setup: "pref show devtools.debugger.remote-host",
       check: {
         args: {
           setting: {
@@ -60,18 +59,17 @@ function* spawnTest() {
             value: options.requisition.system.settings.get("devtools.debugger.remote-host")
           }
         },
       },
       exec: {
         output: new RegExp("^devtools\.debugger\.remote-host: e.com$"),
       },
       post: function () {
-        var ecom = prefBranch.getComplexValue("devtools.debugger.remote-host",
-                                              Ci.nsISupportsString).data;
+        var ecom = prefBranch.getStringPref("devtools.debugger.remote-host");
         is(ecom, "e.com", "devtools.debugger.remote-host is e.com");
       }
     },
     {
       setup: "pref set devtools.debugger.remote-host moz.foo",
       check: {
         args: {
           setting: {
@@ -92,18 +90,17 @@ function* spawnTest() {
             value: options.requisition.system.settings.get("devtools.debugger.remote-host")
           }
         },
       },
       exec: {
         output: new RegExp("^devtools\.debugger\.remote-host: moz.foo$"),
       },
       post: function () {
-        var mozfoo = prefBranch.getComplexValue("devtools.debugger.remote-host",
-                                                Ci.nsISupportsString).data;
+        var mozfoo = prefBranch.getStringPref("devtools.debugger.remote-host");
         is(mozfoo, "moz.foo", "devtools.debugger.remote-host is moz.foo");
       }
     },
   ]);
 
   supportsString.data = remoteHostOrig;
   prefBranch.setComplexValue("devtools.debugger.remote-host",
                              Ci.nsISupportsString, supportsString);
--- a/devtools/client/commandline/test/browser_cmd_settings.js
+++ b/devtools/client/commandline/test/browser_cmd_settings.js
@@ -30,19 +30,18 @@ function* spawnTest() {
   let settings = system.settings;
 
   let hideIntroEnabled = settings.get("devtools.gcli.hideIntro");
   let tabSize = settings.get("devtools.editor.tabsize");
   let remoteHost = settings.get("devtools.debugger.remote-host");
 
   let hideIntroOrig = prefBranch.getBoolPref("devtools.gcli.hideIntro");
   let tabSizeOrig = prefBranch.getIntPref("devtools.editor.tabsize");
-  let remoteHostOrig = prefBranch.getComplexValue(
-          "devtools.debugger.remote-host",
-          Components.interfaces.nsISupportsString).data;
+  let remoteHostOrig = prefBranch.getStringPref(
+          "devtools.debugger.remote-host");
 
   info("originally: devtools.gcli.hideIntro = " + hideIntroOrig);
   info("originally: devtools.editor.tabsize = " + tabSizeOrig);
   info("originally: devtools.debugger.remote-host = " + remoteHostOrig);
 
   // Actual tests
   is(hideIntroEnabled.value, hideIntroOrig, "hideIntroEnabled default");
   is(tabSize.value, tabSizeOrig, "tabSize default");
--- a/devtools/client/framework/test/browser.ini
+++ b/devtools/client/framework/test/browser.ini
@@ -90,9 +90,9 @@ skip-if = e10s # Bug 1069044 - destroyIn
 [browser_toolbox_window_reload_target.js]
 [browser_toolbox_window_shortcuts.js]
 skip-if = os == "mac" && os_version == "10.8" || os == "win" && os_version == "5.1" # Bug 851129 - Re-enable browser_toolbox_window_shortcuts.js test after leaks are fixed
 [browser_toolbox_window_title_changes.js]
 [browser_toolbox_window_title_frame_select.js]
 [browser_toolbox_zoom.js]
 [browser_two_tabs.js]
 # We want this test to run for mochitest-dt as well, so we include it here:
-[../../../../browser/base/content/test/general/browser_parsable_css.js]
+[../../../../browser/base/content/test/static/browser_parsable_css.js]
--- a/devtools/client/inspector/rules/test/browser.ini
+++ b/devtools/client/inspector/rules/test/browser.ini
@@ -153,16 +153,17 @@ skip-if = (os == "win" && debug) # bug 9
 [browser_rules_grid-highlighter-on-mutation.js]
 [browser_rules_grid-highlighter-on-navigate.js]
 [browser_rules_grid-highlighter-on-reload.js]
 [browser_rules_grid-highlighter-restored-after-reload.js]
 [browser_rules_grid-toggle_01.js]
 [browser_rules_grid-toggle_01b.js]
 [browser_rules_grid-toggle_02.js]
 [browser_rules_grid-toggle_03.js]
+[browser_rules_grid-toggle_04.js]
 [browser_rules_guessIndentation.js]
 [browser_rules_inherited-properties_01.js]
 [browser_rules_inherited-properties_02.js]
 [browser_rules_inherited-properties_03.js]
 [browser_rules_inherited-properties_04.js]
 [browser_rules_inline-source-map.js]
 [browser_rules_invalid.js]
 [browser_rules_invalid-source-map.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_grid-toggle_04.js
@@ -0,0 +1,64 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test toggling the grid highlighter in the rule view from a 'display: grid !important'
+// declaration.
+
+const TEST_URI = `
+  <style type='text/css'>
+    #grid {
+      display: grid !important;
+    }
+  </style>
+  <div id="grid">
+    <div id="cell1">cell1</div>
+    <div id="cell2">cell2</div>
+  </div>
+`;
+
+const HIGHLIGHTER_TYPE = "CssGridHighlighter";
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let {inspector, view} = yield openRuleView();
+  let highlighters = view.highlighters;
+
+  yield selectNode("#grid", inspector);
+  let container = getRuleViewProperty(view, "#grid", "display").valueSpan;
+  let gridToggle = container.querySelector(".ruleview-grid");
+
+  info("Checking the initial state of the CSS grid toggle in the rule-view.");
+  ok(gridToggle, "Grid highlighter toggle is visible.");
+  ok(!gridToggle.classList.contains("active"),
+    "Grid highlighter toggle button is not active.");
+  ok(!highlighters.highlighters[HIGHLIGHTER_TYPE],
+    "No CSS grid highlighter exists in the rule-view.");
+  ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+
+  info("Toggling ON the CSS grid highlighter from the rule-view.");
+  let onHighlighterShown = highlighters.once("grid-highlighter-shown");
+  gridToggle.click();
+  yield onHighlighterShown;
+
+  info("Checking the CSS grid highlighter is created and toggle button is active in " +
+    "the rule-view.");
+  ok(gridToggle.classList.contains("active"),
+    "Grid highlighter toggle is active.");
+  ok(highlighters.highlighters[HIGHLIGHTER_TYPE],
+    "CSS grid highlighter created in the rule-view.");
+  ok(highlighters.gridHighlighterShown, "CSS grid highlighter is shown.");
+
+  info("Toggling OFF the CSS grid highlighter from the rule-view.");
+  let onHighlighterHidden = highlighters.once("grid-highlighter-hidden");
+  gridToggle.click();
+  yield onHighlighterHidden;
+
+  info("Checking the CSS grid highlighter is not shown and toggle button is not active " +
+    "in the rule-view.");
+  ok(!gridToggle.classList.contains("active"),
+    "Grid highlighter toggle button is not active.");
+  ok(!highlighters.gridHighlighterShown, "No CSS grid highlighter is shown.");
+});
--- a/devtools/client/scratchpad/scratchpad.js
+++ b/devtools/client/scratchpad/scratchpad.js
@@ -1283,18 +1283,17 @@ var Scratchpad = {
   {
     let branch = Services.prefs.getBranch("devtools.scratchpad.");
     let filePaths = [];
 
     // WARNING: Do not use getCharPref here, it doesn't play nicely with
     // Unicode strings.
 
     if (branch.prefHasUserValue("recentFilePaths")) {
-      let data = branch.getComplexValue("recentFilePaths",
-        Ci.nsISupportsString).data;
+      let data = branch.getStringPref("recentFilePaths");
       filePaths = JSON.parse(data);
     }
 
     return filePaths;
   },
 
   /**
    * Save a recent file in a JSON parsable string.
@@ -1331,26 +1330,18 @@ var Scratchpad = {
     // If we are not storing the file and the 'recent files'-list is full,
     // remove the oldest file from the list.
     else if (filesCount === maxRecent) {
       filePaths.shift();
     }
 
     filePaths.push(aFile.path);
 
-    // WARNING: Do not use setCharPref here, it doesn't play nicely with
-    // Unicode strings.
-
-    let str = Cc["@mozilla.org/supports-string;1"]
-      .createInstance(Ci.nsISupportsString);
-    str.data = JSON.stringify(filePaths);
-
-    let branch = Services.prefs.getBranch("devtools.scratchpad.");
-    branch.setComplexValue("recentFilePaths",
-      Ci.nsISupportsString, str);
+    Services.prefs.getBranch("devtools.scratchpad.")
+            .setStringPref("recentFilePaths", JSON.stringify(filePaths));
   },
 
   /**
    * Populates the 'Open Recent'-menu.
    */
   populateRecentFilesMenu: function SP_populateRecentFilesMenu()
   {
     let maxRecent = Services.prefs.getIntPref(PREF_RECENT_FILES_MAX);
@@ -1407,26 +1398,18 @@ var Scratchpad = {
    * @param integer aLength
    *        Number of files from the index 'aIndex' to remove.
    */
   clearFiles: function SP_clearFile(aIndex, aLength)
   {
     let filePaths = this.getRecentFiles();
     filePaths.splice(aIndex, aLength);
 
-    // WARNING: Do not use setCharPref here, it doesn't play nicely with
-    // Unicode strings.
-
-    let str = Cc["@mozilla.org/supports-string;1"]
-      .createInstance(Ci.nsISupportsString);
-    str.data = JSON.stringify(filePaths);
-
-    let branch = Services.prefs.getBranch("devtools.scratchpad.");
-    branch.setComplexValue("recentFilePaths",
-      Ci.nsISupportsString, str);
+    Services.prefs.getBranch("devtools.scratchpad.")
+            .setStringPref("recentFilePaths", JSON.stringify(filePaths));
   },
 
   /**
    * Clear all recent files.
    */
   clearRecentFiles: function SP_clearRecentFiles()
   {
     Services.prefs.clearUserPref("devtools.scratchpad.recentFilePaths");
--- a/devtools/client/shared/components/test/mochitest/head.js
+++ b/devtools/client/shared/components/test/mochitest/head.js
@@ -76,16 +76,33 @@ var TEST_TREE_INTERFACE = {
 
 function isRenderedTree(actual, expectedDescription, msg) {
   const expected = expectedDescription.map(x => x + "\n").join("");
   dumpn(`Expected tree:\n${expected}`);
   dumpn(`Actual tree:\n${actual}`);
   is(actual, expected, msg);
 }
 
+function isAccessibleTree(tree, options = {}) {
+  const treeNode = tree.refs.tree;
+  is(treeNode.getAttribute("tabindex"), "0", "Tab index is set");
+  is(treeNode.getAttribute("role"), "tree", "Tree semantics is present");
+  if (options.hasActiveDescendant) {
+    ok(treeNode.hasAttribute("aria-activedescendant"),
+       "Tree has an active descendant set");
+  }
+
+  const treeNodes = [...treeNode.querySelectorAll(".tree-node")];
+  for (let node of treeNodes) {
+    ok(node.id, "TreeNode has an id");
+    is(node.getAttribute("role"), "treeitem", "Tree item semantics is present");
+    ok(node.hasAttribute("aria-level"), "Aria level attribute is set");
+  }
+}
+
 // Encoding of the following tree/forest:
 //
 // A
 // |-- B
 // |   |-- E
 // |   |   |-- K
 // |   |   `-- L
 // |   |-- F
--- a/devtools/client/shared/components/test/mochitest/test_tree_01.html
+++ b/devtools/client/shared/components/test/mochitest/test_tree_01.html
@@ -26,16 +26,17 @@ window.onload = Task.async(function* () 
     ok(React, "Should get React");
     ok(Tree, "Should get Tree");
 
     const t = Tree(TEST_TREE_INTERFACE);
     ok(t, "Should be able to create Tree instances");
 
     const tree = ReactDOM.render(t, window.document.body);
     ok(tree, "Should be able to mount Tree instances");
+    isAccessibleTree(tree);
 
     TEST_TREE.expanded = new Set("ABCDEFGHIJKLMNO".split(""));
     yield forceRender(tree);
 
     isRenderedTree(document.body.textContent, [
       "A:false",
       "-B:false",
       "--E:false",
--- a/devtools/client/shared/components/test/mochitest/test_tree_02.html
+++ b/devtools/client/shared/components/test/mochitest/test_tree_02.html
@@ -19,16 +19,17 @@ Test that collapsed subtrees aren't rend
 window.onload = Task.async(function* () {
   try {
     let ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
     let React = browserRequire("devtools/client/shared/vendor/react");
     let Tree = React.createFactory(browserRequire("devtools/client/shared/components/tree"));
 
     const tree = ReactDOM.render(Tree(TEST_TREE_INTERFACE), window.document.body);
 
+    isAccessibleTree(tree);
     TEST_TREE.expanded = new Set("MNO".split(""));
     yield forceRender(tree);
 
     isRenderedTree(document.body.textContent, [
       "A:false",
       "M:false",
       "-N:false",
       "--O:false",
--- a/devtools/client/shared/components/test/mochitest/test_tree_03.html
+++ b/devtools/client/shared/components/test/mochitest/test_tree_03.html
@@ -21,16 +21,17 @@ window.onload = Task.async(function* () 
     let ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
     let React = browserRequire("devtools/client/shared/vendor/react");
     let Tree = React.createFactory(browserRequire("devtools/client/shared/components/tree"));
 
     const tree = ReactDOM.render(Tree(Object.assign({}, TEST_TREE_INTERFACE, {
       autoExpandDepth: 1
     })), window.document.body);
 
+    isAccessibleTree(tree);
     isRenderedTree(document.body.textContent, [
       "A:false",
       "-B:false",
       "-C:false",
       "-D:false",
       "M:false",
       "-N:false",
     ], "Tree should be auto expanded one level");
--- a/devtools/client/shared/components/test/mochitest/test_tree_04.html
+++ b/devtools/client/shared/components/test/mochitest/test_tree_04.html
@@ -37,16 +37,17 @@ window.onload = Task.async(function* () 
 
     TEST_TREE.expanded = new Set("ABCDEFGHIJKLMNO".split(""));
 
     yield setState(tree, {
       height: 3 * ITEM_HEIGHT,
       scroll: 1 * ITEM_HEIGHT
     });
 
+    isAccessibleTree(tree);
     isRenderedTree(document.body.textContent, [
       "A:false",
       "-B:false",
       "--E:false",
       "---K:false",
       "---L:false",
     ], "Tree should show the 2nd, 3rd, and 4th items + buffer of 1 item at each end");
 
@@ -54,16 +55,17 @@ window.onload = Task.async(function* () 
     is(spacers.top, 0, "Top spacer has the correct height");
     is(spacers.bottom, 10 * ITEM_HEIGHT, "Bottom spacer has the correct height");
 
     yield setState(tree, {
       height: 2 * ITEM_HEIGHT,
       scroll: 3 * ITEM_HEIGHT
     });
 
+    isAccessibleTree(tree);
     isRenderedTree(document.body.textContent, [
       "--E:false",
       "---K:false",
       "---L:false",
       "--F:false",
     ], "Tree should show the 4th and 5th item + buffer of 1 item at each end");
 
     spacers = getSpacerHeights();
--- a/devtools/client/shared/components/test/mochitest/test_tree_05.html
+++ b/devtools/client/shared/components/test/mochitest/test_tree_05.html
@@ -30,19 +30,21 @@ window.onload = Task.async(function* () 
         { onFocus: x => renderTree({ focused: x }) },
         props
       );
       return ReactDOM.render(Tree(treeProps), window.document.body);
     }
 
     const tree = renderTree();
 
+    isAccessibleTree(tree);
     TEST_TREE.expanded = new Set("ABCDEFGHIJKLMNO".split(""));
 
     renderTree({ focused: "G" });
+    isAccessibleTree(tree, { hasActiveDescendant: true });
 
     isRenderedTree(document.body.textContent, [
       "A:false",
       "-B:false",
       "--E:false",
       "---K:false",
       "---L:false",
       "--F:false",
--- a/devtools/client/shared/components/test/mochitest/test_tree_06.html
+++ b/devtools/client/shared/components/test/mochitest/test_tree_06.html
@@ -29,16 +29,17 @@ window.onload = Task.async(function* () 
         { onFocus: x => renderTree({ focused: x }) },
         props
       );
       return ReactDOM.render(Tree(treeProps), window.document.body);
     }
 
     const tree = renderTree();
 
+    isAccessibleTree(tree);
     TEST_TREE.expanded = new Set("ABCDEFGHIJKLMNO".split(""));
 
     // UP ----------------------------------------------------------------------
 
     info("Up to the previous sibling.");
     renderTree({ focused: "L" });
     Simulate.keyDown(document.querySelector(".tree"), { key: "ArrowUp" });
     yield forceRender(tree);
--- a/devtools/client/shared/components/tree.js
+++ b/devtools/client/shared/components/tree.js
@@ -193,16 +193,24 @@ module.exports = createClass({
     focused: PropTypes.any,
 
     // Handle when a new item is focused.
     onFocus: PropTypes.func,
 
     // The depth to which we should automatically expand new items.
     autoExpandDepth: PropTypes.number,
 
+    // Note: the two properties below are mutually exclusive. Only one of the
+    // label properties is necessary.
+    // ID of an element whose textual content serves as an accessible label for
+    // a tree.
+    labelledby: PropTypes.string,
+    // Accessibility label for a tree widget.
+    label: PropTypes.string,
+
     // Optional event handlers for when items are expanded or collapsed. Useful
     // for dispatching redux events and updating application state, maybe lazily
     // loading subtrees from a worker, etc.
     //
     // Type:
     //     onExpand(item: Item)
     //     onCollapse(item: Item)
     //
@@ -550,75 +558,103 @@ module.exports = createClass({
     const traversal = this._dfsFromRoots();
 
     // 'begin' and 'end' are the index of the first (at least partially) visible item
     // and the index after the last (at least partially) visible item, respectively.
     // `NUMBER_OF_OFFSCREEN_ITEMS` is removed from `begin` and added to `end` so that
     // the top and bottom of the page are filled with the `NUMBER_OF_OFFSCREEN_ITEMS`
     // previous and next items respectively, which helps the user to see fewer empty
     // gaps when scrolling quickly.
-    const { itemHeight } = this.props;
+    const { itemHeight, focused } = this.props;
     const { scroll, height } = this.state;
     const begin = Math.max(((scroll / itemHeight) | 0) - NUMBER_OF_OFFSCREEN_ITEMS, 0);
     const end = Math.ceil((scroll + height) / itemHeight) + NUMBER_OF_OFFSCREEN_ITEMS;
     const toRender = traversal.slice(begin, end);
     const topSpacerHeight = begin * itemHeight;
     const bottomSpacerHeight = Math.max(traversal.length - end, 0) * itemHeight;
 
     const nodes = [
       dom.div({
         key: "top-spacer",
+        role: "presentation",
         style: {
           padding: 0,
           margin: 0,
           height: topSpacerHeight + "px"
         }
       })
     ];
 
     for (let i = 0; i < toRender.length; i++) {
       const index = begin + i;
       const first = index == 0;
       const last = index == traversal.length - 1;
       const { item, depth } = toRender[i];
+      const key = this.props.getKey(item);
       nodes.push(TreeNode({
-        key: this.props.getKey(item),
+        key,
         index,
         first,
         last,
         item,
         depth,
+        id: key,
         renderItem: this.props.renderItem,
-        focused: this.props.focused === item,
+        focused: focused === item,
         expanded: this.props.isExpanded(item),
         hasChildren: !!this.props.getChildren(item).length,
         onExpand: this._onExpand,
         onCollapse: this._onCollapse,
-        onFocus: () => this._focus(begin + i, item),
-        onFocusedNodeUnmount: () => this.refs.tree && this.refs.tree.focus(),
+        onClick: () => this._focus(begin + i, item),
+        // Focus on the previous node if focused node is unmounted.
+        onFocusedNodeUnmount: () => this._focusPrevNode(),
       }));
     }
 
     nodes.push(dom.div({
       key: "bottom-spacer",
+      role: "presentation",
       style: {
         padding: 0,
         margin: 0,
         height: bottomSpacerHeight + "px"
       }
     }));
 
     return dom.div(
       {
         className: "tree",
         ref: "tree",
+        role: "tree",
+        tabIndex: "0",
         onKeyDown: this._onKeyDown,
         onKeyPress: this._preventArrowKeyScrolling,
         onKeyUp: this._preventArrowKeyScrolling,
         onScroll: this._onScroll,
+        onFocus: ({nativeEvent}) => {
+          if (focused || !nativeEvent || !this.refs.tree) {
+            return;
+          }
+
+          let { explicitOriginalTarget } = nativeEvent;
+          // Only set default focus to the first tree node if the focus came
+          // from outside the tree (e.g. by tabbing to the tree from other
+          // external elements).
+          if (explicitOriginalTarget !== this.refs.tree &&
+              !this.refs.tree.contains(explicitOriginalTarget)) {
+            this._focus(begin, toRender[0].item);
+          }
+        },
+        onClick: () => {
+          // Focus should always remain on the tree container itself.
+          this.refs.tree.focus();
+        },
+        "aria-label": this.props.label,
+        "aria-labelledby": this.props.labelledby,
+        "aria-activedescendant": focused && this.props.getKey(focused),
         style: {
           padding: 0,
           margin: 0
         }
       },
       nodes
     );
   }
@@ -664,74 +700,40 @@ const ArrowExpander = createFactory(crea
     }
 
     return dom.div(attrs);
   }
 }));
 
 const TreeNode = createFactory(createClass({
   propTypes: {
+    id: PropTypes.any.isRequired,
     focused: PropTypes.bool.isRequired,
     onFocusedNodeUnmount: PropTypes.func,
     item: PropTypes.any.isRequired,
     expanded: PropTypes.bool.isRequired,
     hasChildren: PropTypes.bool.isRequired,
     onExpand: PropTypes.func.isRequired,
     index: PropTypes.number.isRequired,
     first: PropTypes.bool,
     last: PropTypes.bool,
-    onFocus: PropTypes.func,
-    onBlur: PropTypes.func,
+    onClick: PropTypes.func,
     onCollapse: PropTypes.func.isRequired,
     depth: PropTypes.number.isRequired,
     renderItem: PropTypes.func.isRequired,
   },
 
-  componentDidMount() {
-    if (this.props.focused) {
-      this.refs.button.focus();
-    }
-  },
-
-  componentDidUpdate() {
+  componentWillUnmount() {
     if (this.props.focused) {
-      this.refs.button.focus();
-    }
-  },
-
-  componentWillUnmount() {
-    // If this node is being destroyed and has focus, transfer the focus manually
-    // to the parent tree component. Otherwise, the focus will get lost and keyboard
-    // navigation in the tree will stop working. This is a workaround for a XUL bug.
-    // See bugs 1259228 and 1152441 for details.
-    // DE-XUL: Remove this hack once all usages are only in HTML documents.
-    if (this.props.focused) {
-      this.refs.button.blur();
       if (this.props.onFocusedNodeUnmount) {
         this.props.onFocusedNodeUnmount();
       }
     }
   },
 
-  _buttonAttrs: {
-    ref: "button",
-    style: {
-      opacity: 0,
-      width: "0 !important",
-      height: "0 !important",
-      padding: "0 !important",
-      outline: "none",
-      MozAppearance: "none",
-      // XXX: Despite resetting all of the above properties (and margin), the
-      // button still ends up with ~79px width, so we set a large negative
-      // margin to completely hide it.
-      MozMarginStart: "-1000px !important",
-    }
-  },
-
   render() {
     const arrow = ArrowExpander({
       item: this.props.item,
       expanded: this.props.expanded,
       visible: this.props.hasChildren,
       onExpand: this.props.onExpand,
       onCollapse: this.props.onCollapse,
     });
@@ -742,39 +744,45 @@ const TreeNode = createFactory(createCla
     }
     if (this.props.first) {
       classList.push("tree-node-first");
     }
     if (this.props.last) {
       classList.push("tree-node-last");
     }
 
+    let ariaExpanded;
+    if (this.props.hasChildren) {
+      ariaExpanded = false;
+    }
+    if (this.props.expanded) {
+      ariaExpanded = true;
+    }
+
     return dom.div(
       {
+        id: this.props.id,
         className: classList.join(" "),
-        onFocus: this.props.onFocus,
-        onClick: this.props.onFocus,
-        onBlur: this.props.onBlur,
+        role: "treeitem",
+        "aria-level": this.props.depth,
+        onClick: this.props.onClick,
+        "aria-expanded": ariaExpanded,
         "data-expanded": this.props.expanded ? "" : undefined,
         "data-depth": this.props.depth,
         style: {
           padding: 0,
           margin: 0
         }
       },
 
       this.props.renderItem(this.props.item,
                             this.props.depth,
                             this.props.focused,
                             arrow,
                             this.props.expanded),
-
-      // XXX: OSX won't focus/blur regular elements even if you set tabindex
-      // unless there is an input/button child.
-      dom.button(this._buttonAttrs)
     );
   }
 }));
 
 /**
  * Create a function that calls the given function `fn` only once per animation
  * frame.
  *
--- a/devtools/client/shared/output-parser.js
+++ b/devtools/client/shared/output-parser.js
@@ -284,27 +284,24 @@ OutputParser.prototype = {
     return result;
   },
 
   /**
    * Return true if it's a display:[inline-]grid token.
    *
    * @param  {String} text
    *         the parsed text.
-   *
    * @param  {Object} token
    *         the parsed token.
-   *
    * @param  {Object} options
    *         the options given to _parse.
    */
   _isDisplayGrid: function (text, token, options) {
     return options.expectDisplay &&
-      (token.text === "grid" || token.text === "inline-grid") &&
-      text === token.text;
+      (token.text === "grid" || token.text === "inline-grid");
   },
 
   /**
    * Append a cubic-bezier timing function value to the output
    *
    * @param {String} bezier
    *        The cubic-bezier timing function
    * @param {Object} options
--- a/devtools/client/themes/boxmodel.css
+++ b/devtools/client/themes/boxmodel.css
@@ -229,25 +229,25 @@
 
 .boxmodel-position {
   color: var(--theme-highlight-purple);
 }
 
 .boxmodel-position.boxmodel-top,
 .boxmodel-position.boxmodel-bottom {
   border-left: 1px solid var(--theme-highlight-purple);
-  left: 49.5%;
+  left: calc(50% - 1px);
   padding-left: 1px;
 }
 
 .boxmodel-position.boxmodel-right,
 .boxmodel-position.boxmodel-left {
   border-top: 1px solid var(--theme-highlight-purple);
   line-height: 15px;
-  top: 49.5%;
+  top: calc(50% - 1px);
   width: 30px;
 }
 
 .boxmodel-position.boxmodel-top {
   top: -18px;
 }
 
 .boxmodel-position.boxmodel-right {
--- a/devtools/shared/gcli/commands/cmd.js
+++ b/devtools/shared/gcli/commands/cmd.js
@@ -12,32 +12,27 @@ const gcli = require("gcli/index");
 const l10n = require("gcli/l10n");
 
 loader.lazyGetter(this, "prefBranch", function () {
   let prefService = Cc["@mozilla.org/preferences-service;1"]
                       .getService(Ci.nsIPrefService);
   return prefService.getBranch(null).QueryInterface(Ci.nsIPrefBranch2);
 });
 
-loader.lazyGetter(this, "supportsString", function () {
-  return Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
-});
-
 loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
 
 const PREF_DIR = "devtools.commands.dir";
 
 /**
  * Load all the .mozcmd files in the directory pointed to by PREF_DIR
  * @return A promise of an array of items suitable for gcli.addItems or
  * using in gcli.addItemsByModule
  */
 function loadItemsFromMozDir() {
-  let dirName = prefBranch.getComplexValue(PREF_DIR,
-                                           Ci.nsISupportsString).data.trim();
+  let dirName = prefBranch.getStringPref(PREF_DIR).trim();
   if (dirName == "") {
     return Promise.resolve([]);
   }
 
   // replaces ~ with the home directory path in unix and windows
   if (dirName.indexOf("~") == 0) {
     let dirService = Cc["@mozilla.org/file/directory_service;1"]
                       .getService(Ci.nsIProperties);
@@ -138,18 +133,17 @@ exports.items = [
     name: "cmd refresh",
     description: l10n.lookup("cmdRefreshDesc"),
     get hidden() {
       return !prefBranch.prefHasUserValue(PREF_DIR);
     },
     exec: function (args, context) {
       gcli.load();
 
-      let dirName = prefBranch.getComplexValue(PREF_DIR,
-                                              Ci.nsISupportsString).data.trim();
+      let dirName = prefBranch.getStringPref(PREF_DIR).trim();
       return l10n.lookupFormat("cmdStatus3", [ dirName ]);
     }
   },
   {
     item: "command",
     runAt: "client",
     name: "cmd setdir",
     description: l10n.lookup("cmdSetdirDesc"),
@@ -167,17 +161,16 @@ exports.items = [
       }
     ],
     returnType: "string",
     get hidden() {
       // !prefBranch.prefHasUserValue(PREF_DIR);
       return true;
     },
     exec: function (args, context) {
-      supportsString.data = args.directory;
-      prefBranch.setComplexValue(PREF_DIR, Ci.nsISupportsString, supportsString);
+      prefBranch.setStringPref(PREF_DIR, args.directory);
 
       gcli.load();
 
       return l10n.lookupFormat("cmdStatus3", [ args.directory ]);
     }
   }
 ];
--- a/devtools/shared/gcli/source/lib/gcli/settings.js
+++ b/devtools/shared/gcli/source/lib/gcli/settings.js
@@ -233,18 +233,17 @@ Object.defineProperty(Setting.prototype,
     switch (imports.prefBranch.getPrefType(this.name)) {
       case imports.prefBranch.PREF_BOOL:
         return imports.prefBranch.getBoolPref(this.name);
 
       case imports.prefBranch.PREF_INT:
         return imports.prefBranch.getIntPref(this.name);
 
       case imports.prefBranch.PREF_STRING:
-        var value = imports.prefBranch.getComplexValue(this.name,
-                Ci.nsISupportsString).data;
+        var value = imports.prefBranch.getStringPref(this.name);
         // In case of a localized string
         if (/^chrome:\/\/.+\/locale\/.+\.properties/.test(value)) {
           value = imports.prefBranch.getComplexValue(this.name,
                   Ci.nsIPrefLocalizedString).data;
         }
         return value;
 
       default:
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -10606,17 +10606,18 @@ nsDocShell::InternalLoad(nsIURI* aURI,
     mTiming->NotifyUnloadAccepted(mCurrentURI);
   }
 
   // Check if the webbrowser chrome wants the load to proceed; this can be
   // used to cancel attempts to load URIs in the wrong process.
   nsCOMPtr<nsIWebBrowserChrome3> browserChrome3 = do_GetInterface(mTreeOwner);
   if (browserChrome3) {
     bool shouldLoad;
-    rv = browserChrome3->ShouldLoadURI(this, aURI, aReferrer, aTriggeringPrincipal, &shouldLoad);
+    rv = browserChrome3->ShouldLoadURI(this, aURI, aReferrer, !!aPostData,
+                                       aTriggeringPrincipal, &shouldLoad);
     if (NS_SUCCEEDED(rv) && !shouldLoad) {
       return NS_OK;
     }
   }
 
   if (browserChrome3 && aCheckForPrerender) {
     nsCOMPtr<nsIRunnable> ev =
       new InternalLoadEvent(this, aURI, aOriginalURI, aLoadReplace,
--- a/docshell/test/navigation/test_bug386782.html
+++ b/docshell/test/navigation/test_bug386782.html
@@ -35,17 +35,17 @@ https://bugzilla.mozilla.org/show_bug.cg
         expectedBodyAfterEdit:  'EDITED <br><p>contentEditable</p>',
         expectedBodyAfterSecondEdit: 'EDITED TWICE <br><p>contentEditable</p>',
       }
     ];
 
     var gTestNum = -1;
     var gTest = null;
 
-    window.onload = goNext();
+    window.onload = goNext;
 
     function goNext() {
       gTestNum++;
       if (gTestNum >= gTests.length) {
         SimpleTest.finish();
         return;
       }
       gTest = gTests[gTestNum];
--- a/dom/base/ChromeUtils.h
+++ b/dom/base/ChromeUtils.h
@@ -16,16 +16,18 @@ namespace mozilla {
 
 namespace devtools {
 class HeapSnapshot;
 } // namespace devtools
 
 namespace dom {
 
 class ArrayBufferViewOrArrayBuffer;
+class PrecompiledScript;
+class Promise;
 
 class ThreadSafeChromeUtils
 {
 public:
   // Implemented in devtools/shared/heapsnapshot/HeapSnapshot.cpp
   static void SaveHeapSnapshot(GlobalObject& global,
                                const HeapSnapshotBoundaries& boundaries,
                                nsAString& filePath,
@@ -96,14 +98,21 @@ public:
   IsOriginAttributesEqualIgnoringFPD(const dom::OriginAttributesDictionary& aA,
                                      const dom::OriginAttributesDictionary& aB)
   {
     return aA.mAppId == aB.mAppId &&
            aA.mInIsolatedMozBrowser == aB.mInIsolatedMozBrowser &&
            aA.mUserContextId == aB.mUserContextId &&
            aA.mPrivateBrowsingId == aB.mPrivateBrowsingId;
   }
+
+  // Implemented in js/xpconnect/loader/ChromeScriptLoader.cpp
+  static already_AddRefed<Promise>
+  CompileScript(GlobalObject& aGlobal,
+                const nsAString& aUrl,
+                const dom::CompileScriptOptionsDictionary& aOptions,
+                ErrorResult& aRv);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_ChromeUtils__
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -2315,43 +2315,51 @@ Element::SetAttr(int32_t aNamespaceID, n
                "Don't call SetAttr with unknown namespace");
 
   if (!mAttrsAndChildren.CanFitMoreAttrs()) {
     return NS_ERROR_FAILURE;
   }
 
   uint8_t modType;
   bool hasListeners;
+  // We don't want to spend time preparsing class attributes if the value is not
+  // changing, so just init our nsAttrValueOrString with aValue for the
+  // OnlyNotifySameValueSet call.
   nsAttrValueOrString value(aValue);
   nsAttrValue oldValue;
 
   if (OnlyNotifySameValueSet(aNamespaceID, aName, aPrefix, value, aNotify,
                              oldValue, &modType, &hasListeners)) {
     return NS_OK;
   }
 
-  nsresult rv = BeforeSetAttr(aNamespaceID, aName, &value, aNotify);
-  NS_ENSURE_SUCCESS(rv, rv);
-  nsAttrValue* preparsedAttrValue = value.GetStoredAttrValue();
+  nsAttrValue attrValue;
+  nsAttrValue* preparsedAttrValue;
+  if (aNamespaceID == kNameSpaceID_None && aName == nsGkAtoms::_class) {
+    attrValue.ParseAtomArray(aValue);
+    value.ResetToAttrValue(attrValue);
+    preparsedAttrValue = &attrValue;
+  } else {
+    preparsedAttrValue = nullptr;
+  }
 
   if (aNotify) {
     nsNodeUtils::AttributeWillChange(this, aNamespaceID, aName, modType,
                                      preparsedAttrValue);
   }
 
+  nsresult rv = BeforeSetAttr(aNamespaceID, aName, &value, aNotify);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   // Hold a script blocker while calling ParseAttribute since that can call
   // out to id-observers
   nsAutoScriptBlocker scriptBlocker;
 
-  nsAttrValue attrValue;
-  if (preparsedAttrValue) {
-    attrValue.SwapValueWith(*preparsedAttrValue);
-  }
-  // Even the value was pre-parsed in BeforeSetAttr, we still need to call
-  // ParseAttribute because it can have side effects.
+  // Even the value was pre-parsed, we still need to call ParseAttribute because
+  // it can have side effects.
   if (!ParseAttribute(aNamespaceID, aName, aValue, attrValue)) {
     attrValue.SetTo(aValue);
   }
 
   return SetAttrAndNotify(aNamespaceID, aName, aPrefix, oldValue,
                           attrValue, modType, hasListeners, aNotify,
                           kCallAfterSetAttr);
 }
@@ -2377,24 +2385,24 @@ Element::SetParsedAttr(int32_t aNamespac
   nsAttrValueOrString value(aParsedValue);
   nsAttrValue oldValue;
 
   if (OnlyNotifySameValueSet(aNamespaceID, aName, aPrefix, value, aNotify,
                              oldValue, &modType, &hasListeners)) {
     return NS_OK;
   }
 
-  nsresult rv = BeforeSetAttr(aNamespaceID, aName, &value, aNotify);
-  NS_ENSURE_SUCCESS(rv, rv);
-
   if (aNotify) {
     nsNodeUtils::AttributeWillChange(this, aNamespaceID, aName, modType,
                                      &aParsedValue);
   }
 
+  nsresult rv = BeforeSetAttr(aNamespaceID, aName, &value, aNotify);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   return SetAttrAndNotify(aNamespaceID, aName, aPrefix, oldValue,
                           aParsedValue, modType, hasListeners, aNotify,
                           kCallAfterSetAttr);
 }
 
 nsresult
 Element::SetAttrAndNotify(int32_t aNamespaceID,
                           nsIAtom* aName,
@@ -2453,18 +2461,16 @@ Element::SetAttrAndNotify(int32_t aNames
 
   if (document || HasFlag(NODE_FORCE_XBL_BINDINGS)) {
     RefPtr<nsXBLBinding> binding = GetXBLBinding();
     if (binding) {
       binding->AttributeChanged(aName, aNamespaceID, false, aNotify);
     }
   }
 
-  UpdateState(aNotify);
-
   nsIDocument* ownerDoc = OwnerDoc();
   if (ownerDoc && GetCustomElementData()) {
     nsCOMPtr<nsIAtom> oldValueAtom = oldValue->GetAsAtom();
     nsCOMPtr<nsIAtom> newValueAtom = valueForAfterSetAttr.GetAsAtom();
     LifecycleCallbackArgs args = {
       nsDependentAtomString(aName),
       aModType == nsIDOMMutationEvent::ADDITION ?
         NullString() : nsDependentAtomString(oldValueAtom),
@@ -2480,16 +2486,18 @@ Element::SetAttrAndNotify(int32_t aNames
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (aNamespaceID == kNameSpaceID_None && aName == nsGkAtoms::dir) {
       OnSetDirAttr(this, &valueForAfterSetAttr,
                    hadValidDir, hadDirAuto, aNotify);
     }
   }
 
+  UpdateState(aNotify);
+
   if (aNotify) {
     // Don't pass aOldValue to AttributeChanged since it may not be reliable.
     // Callers only compute aOldValue under certain conditions which may not
     // be triggered by all nsIMutationObservers.
     nsNodeUtils::AttributeChanged(this, aNamespaceID, aName, aModType,
         oldValue == &aParsedValue ? &aParsedValue : nullptr);
   }
 
@@ -2515,35 +2523,16 @@ Element::SetAttrAndNotify(int32_t aNames
 
     mozAutoSubtreeModified subtree(OwnerDoc(), this);
     (new AsyncEventDispatcher(this, mutation))->RunDOMEventWhenSafe();
   }
 
   return NS_OK;
 }
 
-nsresult
-Element::BeforeSetAttr(int32_t aNamespaceID, nsIAtom* aName,
-                       nsAttrValueOrString* aValue, bool aNotify)
-{
-  if (aNamespaceID == kNameSpaceID_None) {
-    if (aName == nsGkAtoms::_class) {
-      // aValue->GetAttrValue will only be non-null here when this is called
-      // via Element::SetParsedAttr. This shouldn't happen for "class", but
-      // this will handle it.
-      if (aValue && !aValue->GetAttrValue()) {
-        nsAttrValue attr;
-        attr.ParseAtomArray(aValue->String());
-        aValue->TakeParsedValue(attr);
-      }
-    }
-  }
-  return NS_OK;
-}
-
 bool
 Element::ParseAttribute(int32_t aNamespaceID,
                         nsIAtom* aAttribute,
                         const nsAString& aValue,
                         nsAttrValue& aResult)
 {
   if (aNamespaceID == kNameSpaceID_None) {
     if (aAttribute == nsGkAtoms::_class) {
@@ -2650,28 +2639,28 @@ Element::UnsetAttr(int32_t aNameSpaceID,
 {
   NS_ASSERTION(nullptr != aName, "must have attribute name");
 
   int32_t index = mAttrsAndChildren.IndexOfAttr(aName, aNameSpaceID);
   if (index < 0) {
     return NS_OK;
   }
 
-  nsresult rv = BeforeSetAttr(aNameSpaceID, aName, nullptr, aNotify);
-  NS_ENSURE_SUCCESS(rv, rv);
-
   nsIDocument *document = GetComposedDoc();
   mozAutoDocUpdate updateBatch(document, UPDATE_CONTENT_MODEL, aNotify);
 
   if (aNotify) {
     nsNodeUtils::AttributeWillChange(this, aNameSpaceID, aName,
                                      nsIDOMMutationEvent::REMOVAL,
                                      nullptr);
   }
 
+  nsresult rv = BeforeSetAttr(aNameSpaceID, aName, nullptr, aNotify);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   bool hasMutationListeners = aNotify &&
     nsContentUtils::HasMutationListeners(this,
                                          NS_EVENT_BITS_MUTATION_ATTRMODIFIED,
                                          this);
 
   // Grab the attr node if needed before we remove it from the attr map
   RefPtr<Attr> attrNode;
   if (hasMutationListeners) {
@@ -2710,41 +2699,41 @@ Element::UnsetAttr(int32_t aNameSpaceID,
 
   if (document || HasFlag(NODE_FORCE_XBL_BINDINGS)) {
     RefPtr<nsXBLBinding> binding = GetXBLBinding();
     if (binding) {
       binding->AttributeChanged(aName, aNameSpaceID, true, aNotify);
     }
   }
 
-  UpdateState(aNotify);
-
   nsIDocument* ownerDoc = OwnerDoc();
   if (ownerDoc && GetCustomElementData()) {
     nsCOMPtr<nsIAtom> oldValueAtom = oldValue.GetAsAtom();
     LifecycleCallbackArgs args = {
       nsDependentAtomString(aName),
       nsDependentAtomString(oldValueAtom),
       NullString()
     };
 
     nsContentUtils::EnqueueLifecycleCallback(
       ownerDoc, nsIDocument::eAttributeChanged, this, &args);
   }
 
+  rv = AfterSetAttr(aNameSpaceID, aName, nullptr, aNotify);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  UpdateState(aNotify);
+
   if (aNotify) {
     // We can always pass oldValue here since there is no new value which could
     // have corrupted it.
     nsNodeUtils::AttributeChanged(this, aNameSpaceID, aName,
                                   nsIDOMMutationEvent::REMOVAL, &oldValue);
   }
 
-  rv = AfterSetAttr(aNameSpaceID, aName, nullptr, aNotify);
-  NS_ENSURE_SUCCESS(rv, rv);
-
   if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::dir) {
     OnSetDirAttr(this, nullptr, hadValidDir, hadDirAuto, aNotify);
   }
 
   if (hasMutationListeners) {
     InternalMutationEvent mutation(true, eLegacyAttrModified);
 
     mutation.mRelatedNode = attrNode;
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -1375,26 +1375,27 @@ protected:
    * we're actually doing an attr set and will be called before
    * AttributeWillChange and before ParseAttribute and hence before we've set
    * the new value.
    *
    * @param aNamespaceID the namespace of the attr being set
    * @param aName the localname of the attribute being set
    * @param aValue the value it's being set to represented as either a string or
    *        a parsed nsAttrValue. Alternatively, if the attr is being removed it
-   *        will be null. BeforeSetAttr is allowed to modify aValue by parsing
-   *        the string to an nsAttrValue (to avoid having to reparse it in
-   *        ParseAttribute).
+   *        will be null.
    * @param aNotify Whether we plan to notify document observers.
    */
   // Note that this is inlined so that when subclasses call it it gets
   // inlined.  Those calls don't go through a vtable.
   virtual nsresult BeforeSetAttr(int32_t aNamespaceID, nsIAtom* aName,
-                                 nsAttrValueOrString* aValue,
-                                 bool aNotify);
+                                 const nsAttrValueOrString* aValue,
+                                 bool aNotify)
+  {
+    return NS_OK;
+  }
 
   /**
    * Hook that is called by Element::SetAttr to allow subclasses to
    * deal with attribute sets.  This will only be called after we have called
    * SetAndTakeAttr and AttributeChanged (that is, after we have actually set
    * the attr).  It will always be called under a scriptblocker.
    *
    * @param aNamespaceID the namespace of the attr being set
--- a/dom/base/nsAttrValueOrString.h
+++ b/dom/base/nsAttrValueOrString.h
@@ -44,30 +44,23 @@ public:
   { }
 
   explicit nsAttrValueOrString(const nsAttrValue* aValue)
     : mAttrValue(aValue)
     , mStringPtr(nullptr)
     , mCheapString(nullptr)
   { }
 
-  void TakeParsedValue(nsAttrValue& aValue)
+  void ResetToAttrValue(const nsAttrValue& aValue)
   {
-    mStoredAttrValue.SwapValueWith(aValue);
-    mAttrValue = &mStoredAttrValue;
+    mAttrValue = &aValue;
     mStringPtr = nullptr;
+    // No need to touch mCheapString here.  If we need to use it, we will reset
+    // it to the rigthe value anyway.
   }
-  /**
-   * If TakeParsedValue has been called, returns the value that it set.
-   */
-  nsAttrValue* GetStoredAttrValue()
-  {
-    return mAttrValue == &mStoredAttrValue ? &mStoredAttrValue : nullptr;
-  }
-  const nsAttrValue* GetAttrValue() { return mAttrValue; }
 
   /**
    * Returns a reference to the string value of the contents of this object.
    *
    * When this object points to a string or an nsAttrValue of string or atom
    * type this should be fairly cheap. Other nsAttrValue types will be
    * serialized the first time this is called and cached from thereon.
    */
@@ -84,12 +77,11 @@ public:
     }
     return aOther.EqualsAsStrings(*mAttrValue);
   }
 
 protected:
   const nsAttrValue*       mAttrValue;
   mutable const nsAString* mStringPtr;
   mutable nsCheapString    mCheapString;
-  nsAttrValue              mStoredAttrValue;
 };
 
 #endif // nsAttrValueOrString_h___
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -70,16 +70,18 @@
 #include "nsIFrame.h"
 #include "nsITabChild.h"
 
 #include "nsRange.h"
 #include "nsIDOMText.h"
 #include "nsIDOMComment.h"
 #include "mozilla/dom/DocumentType.h"
 #include "mozilla/dom/NodeIterator.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
 #include "mozilla/dom/TreeWalker.h"
 
 #include "nsIServiceManager.h"
 #include "mozilla/dom/workers/ServiceWorkerManager.h"
 #include "imgLoader.h"
 
 #include "nsCanvasFrame.h"
 #include "nsContentCID.h"
@@ -2739,16 +2741,23 @@ nsDocument::InitCSP(nsIChannel* aChannel
       // stop!  ERROR page!
       aChannel->Cancel(NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION);
     }
   }
   ApplySettingsFromCSP(false);
   return NS_OK;
 }
 
+already_AddRefed<nsIParser>
+nsDocument::CreatorParserOrNull()
+{
+  nsCOMPtr<nsIParser> parser = mParser;
+  return parser.forget();
+}
+
 void
 nsDocument::StopDocumentLoad()
 {
   if (mParser) {
     mParserAborted = true;
     mParser->Terminate();
   }
 }
@@ -10509,16 +10518,88 @@ nsIDocument::ObsoleteSheet(const nsAStri
     return;
   }
   res = CSSLoader()->ObsoleteSheet(uri);
   if (NS_FAILED(res)) {
     rv.Throw(res);
   }
 }
 
+class UnblockParsingPromiseHandler final : public PromiseNativeHandler
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  explicit UnblockParsingPromiseHandler(nsIDocument* aDocument, Promise* aPromise)
+    : mDocument(aDocument)
+    , mPromise(aPromise)
+  {
+    nsCOMPtr<nsIParser> parser = mDocument->CreatorParserOrNull();
+    if (parser) {
+      parser->BlockParser();
+    } else {
+      mDocument = nullptr;
+    }
+  }
+
+  void
+  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+  {
+    MaybeUnblockParser();
+
+    mPromise->MaybeResolve(aCx, aValue);
+  }
+
+  void
+  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+  {
+    MaybeUnblockParser();
+
+    mPromise->MaybeReject(aCx, aValue);
+  }
+
+protected:
+  virtual ~UnblockParsingPromiseHandler()
+  {
+    MaybeUnblockParser();
+  }
+
+private:
+  void MaybeUnblockParser() {
+    if (mDocument) {
+      nsCOMPtr<nsIParser> parser = mDocument->CreatorParserOrNull();
+      if (parser) {
+        parser->UnblockParser();
+        parser->ContinueInterruptedParsingAsync();
+      }
+      mDocument = nullptr;
+    }
+  }
+
+  RefPtr<nsIDocument> mDocument;
+  RefPtr<Promise> mPromise;
+};
+
+NS_IMPL_ISUPPORTS0(UnblockParsingPromiseHandler)
+
+already_AddRefed<Promise>
+nsIDocument::BlockParsing(OwningNonNull<Promise> aPromise,
+                          ErrorResult& aRv)
+{
+  RefPtr<Promise> resultPromise = Promise::Create(aPromise->GetParentObject(), aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  RefPtr<PromiseNativeHandler> promiseHandler = new UnblockParsingPromiseHandler(this, resultPromise);
+  aPromise->AppendNativeHandler(promiseHandler);
+
+  return resultPromise.forget();
+}
+
 already_AddRefed<nsIURI>
 nsIDocument::GetMozDocumentURIIfNotForErrorPages()
 {
   if (mFailedChannel) {
     nsCOMPtr<nsIURI> failedURI;
     if (NS_SUCCEEDED(mFailedChannel->GetURI(getter_AddRefs(failedURI)))) {
       return failedURI.forget();
     }
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -532,16 +532,18 @@ public:
   virtual void SetDocumentURI(nsIURI* aURI) override;
 
   virtual void SetChromeXHRDocURI(nsIURI* aURI) override;
 
   virtual void SetChromeXHRDocBaseURI(nsIURI* aURI) override;
 
   virtual void ApplySettingsFromCSP(bool aSpeculative) override;
 
+  virtual already_AddRefed<nsIParser> CreatorParserOrNull() override;
+
   /**
    * Set the principal responsible for this document.
    */
   virtual void SetPrincipal(nsIPrincipal *aPrincipal) override;
 
   /**
    * Get the Content-Type of this document.
    */
--- a/dom/base/nsGenConImageContent.cpp
+++ b/dom/base/nsGenConImageContent.cpp
@@ -51,17 +51,20 @@ public:
     MOZ_ASSERT(IsInNativeAnonymousSubtree());
     if (aVisitor.mEvent->mMessage == eLoad ||
         aVisitor.mEvent->mMessage == eLoadError) {
       // Don't propagate the events to the parent.
       return NS_OK;
     }
     return nsXMLElement::GetEventTargetParent(aVisitor);
   }
-  
+
+protected:
+  nsIContent* AsContent() override { return this; }
+
 private:
   virtual ~nsGenConImageContent();
 
 public:
   NS_DECL_ISUPPORTS_INHERITED
 };
 
 NS_IMPL_ISUPPORTS_INHERITED(nsGenConImageContent,
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -14738,16 +14738,20 @@ nsGlobalWindow::CreateImageBitmap(const 
 
 already_AddRefed<mozilla::dom::Promise>
 nsGlobalWindow::CreateImageBitmap(const ImageBitmapSource& aImage,
                                   int32_t aOffset, int32_t aLength,
                                   ImageBitmapFormat aFormat,
                                   const Sequence<ChannelPixelLayout>& aLayout,
                                   ErrorResult& aRv)
 {
+  if (!ImageBitmap::ExtensionsEnabled(nullptr, nullptr)) {
+    aRv.Throw(NS_ERROR_TYPE_ERR);
+    return nullptr;
+  }
   if (aImage.IsArrayBuffer() || aImage.IsArrayBufferView()) {
     return ImageBitmap::Create(this, aImage, aOffset, aLength, aFormat, aLayout,
                                aRv);
   }
   aRv.Throw(NS_ERROR_TYPE_ERR);
   return nullptr;
 }
 
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -10,16 +10,17 @@
 #include "nsAutoPtr.h"                   // for member
 #include "nsCOMArray.h"                  // for member
 #include "nsCompatibility.h"             // for member
 #include "nsCOMPtr.h"                    // for member
 #include "nsGkAtoms.h"                   // for static class members
 #include "nsIDocumentObserver.h"         // for typedef (nsUpdateType)
 #include "nsILoadGroup.h"                // for member (in nsCOMPtr)
 #include "nsINode.h"                     // for base class
+#include "nsIParser.h"
 #include "nsIScriptGlobalObject.h"       // for member (in nsCOMPtr)
 #include "nsIServiceManager.h"
 #include "nsIUUIDGenerator.h"
 #include "nsPIDOMWindow.h"               // for use in inline functions
 #include "nsPropertyTable.h"             // for member
 #include "nsDataHashtable.h"             // for member
 #include "nsURIHashKey.h"                // for member
 #include "mozilla/net/ReferrerPolicy.h"  // for member
@@ -354,16 +355,18 @@ public:
    */
   virtual void SetChromeXHRDocBaseURI(nsIURI* aURI) = 0;
 
   /**
    * Set referrer policy and upgrade-insecure-requests flags
    */
   virtual void ApplySettingsFromCSP(bool aSpeculative) = 0;
 
+  virtual already_AddRefed<nsIParser> CreatorParserOrNull() = 0;
+
   /**
    * Return the referrer policy of the document. Return "default" if there's no
    * valid meta referrer tag found in the document.
    */
   ReferrerPolicyEnum GetReferrerPolicy() const
   {
     return mReferrerPolicy;
   }
@@ -1972,17 +1975,17 @@ public:
     return mLoadedAsInteractiveData;
   }
 
   bool MayStartLayout()
   {
     return mMayStartLayout;
   }
 
-  void SetMayStartLayout(bool aMayStartLayout)
+  virtual void SetMayStartLayout(bool aMayStartLayout)
   {
     mMayStartLayout = aMayStartLayout;
   }
 
   already_AddRefed<nsIDocumentEncoder> GetCachedEncoder();
 
   void SetCachedEncoder(already_AddRefed<nsIDocumentEncoder> aEncoder);
 
@@ -2779,16 +2782,19 @@ public:
   {
     return mStyleSheetChangeEventsEnabled;
   }
 
   void ObsoleteSheet(nsIURI *aSheetURI, mozilla::ErrorResult& rv);
 
   void ObsoleteSheet(const nsAString& aSheetURI, mozilla::ErrorResult& rv);
 
+  already_AddRefed<mozilla::dom::Promise> BlockParsing(mozilla::OwningNonNull<mozilla::dom::Promise> aPromise,
+                                                       mozilla::ErrorResult& aRv);
+
   already_AddRefed<nsIURI> GetMozDocumentURIIfNotForErrorPages();
 
   // ParentNode
   nsIHTMLCollection* Children();
   uint32_t ChildElementCount();
 
   virtual nsHTMLDocument* AsHTMLDocument() { return nullptr; }
   virtual mozilla::dom::SVGDocument* AsSVGDocument() { return nullptr; }
--- a/dom/base/nsImageLoadingContent.cpp
+++ b/dom/base/nsImageLoadingContent.cpp
@@ -831,19 +831,18 @@ nsImageLoadingContent::LoadImage(nsIURI*
   // From this point on, our image state could change. Watch it.
   AutoStateChanger changer(this, aNotify);
 
   // Sanity check.
   //
   // We use the principal of aDocument to avoid having to QI |this| an extra
   // time. It should always be the same as the principal of this node.
 #ifdef DEBUG
-  nsCOMPtr<nsIContent> thisContent = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
-  MOZ_ASSERT(thisContent &&
-             thisContent->NodePrincipal() == aDocument->NodePrincipal(),
+  nsIContent* thisContent = AsContent();
+  MOZ_ASSERT(thisContent->NodePrincipal() == aDocument->NodePrincipal(),
              "Principal mismatch?");
 #endif
 
   // Are we blocked?
   int16_t cpDecision = nsIContentPolicy::REJECT_REQUEST;
   nsContentPolicyType policyType = PolicyTypeForLoad(aImageLoadType);
 
   nsContentUtils::CanLoadImage(aNewURI,
@@ -1018,20 +1017,17 @@ nsImageLoadingContent::UpdateImageState(
     // is destroyed. Need this to work around the fact that some ImageLib
     // stuff is actually sync and hence we can get OnStopDecode called while
     // we're still under LoadImage, and OnStopDecode doesn't know anything about
     // aNotify.
     // XXX - This machinery should be removed after bug 521604.
     return;
   }
 
-  nsCOMPtr<nsIContent> thisContent = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
-  if (!thisContent) {
-    return;
-  }
+  nsIContent* thisContent = AsContent();
 
   mLoading = mBroken = mUserDisabled = mSuppressed = false;
 
   // If we were blocked by server-based content policy, we claim to be
   // suppressed.  If we were blocked by type-based content policy, we claim to
   // be user-disabled.  Otherwise, claim to be broken.
   if (mImageBlockingStatus == nsIContentPolicy::REJECT_SERVER) {
     mSuppressed = true;
@@ -1085,39 +1081,29 @@ nsImageLoadingContent::UseAsPrimaryReque
   }
 
   return NS_OK;
 }
 
 nsIDocument*
 nsImageLoadingContent::GetOurOwnerDoc()
 {
-  nsCOMPtr<nsIContent> thisContent =
-    do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
-  NS_ENSURE_TRUE(thisContent, nullptr);
-
-  return thisContent->OwnerDoc();
+  return AsContent()->OwnerDoc();
 }
 
 nsIDocument*
 nsImageLoadingContent::GetOurCurrentDoc()
 {
-  nsCOMPtr<nsIContent> thisContent =
-    do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
-  NS_ENSURE_TRUE(thisContent, nullptr);
-
-  return thisContent->GetComposedDoc();
+  return AsContent()->GetComposedDoc();
 }
 
 nsIFrame*
 nsImageLoadingContent::GetOurPrimaryFrame()
 {
-  nsCOMPtr<nsIContent> thisContent =
-    do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
-  return thisContent->GetPrimaryFrame();
+  return AsContent()->GetPrimaryFrame();
 }
 
 nsPresContext* nsImageLoadingContent::GetFramePresContext()
 {
   nsIFrame* frame = GetOurPrimaryFrame();
   if (!frame) {
     return nullptr;
   }
@@ -1129,18 +1115,17 @@ nsresult
 nsImageLoadingContent::StringToURI(const nsAString& aSpec,
                                    nsIDocument* aDocument,
                                    nsIURI** aURI)
 {
   NS_PRECONDITION(aDocument, "Must have a document");
   NS_PRECONDITION(aURI, "Null out param");
 
   // (1) Get the base URI
-  nsCOMPtr<nsIContent> thisContent = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
-  NS_ASSERTION(thisContent, "An image loading content must be an nsIContent");
+  nsIContent* thisContent = AsContent();
   nsCOMPtr<nsIURI> baseURL = thisContent->GetBaseURI();
 
   // (2) Get the charset
   const nsAFlatCString &charset = aDocument->GetDocumentCharacterSet();
 
   // (3) Construct the silly thing
   return NS_NewURI(aURI,
                    aSpec,
--- a/dom/base/nsImageLoadingContent.h
+++ b/dom/base/nsImageLoadingContent.h
@@ -214,16 +214,20 @@ protected:
   void OnUnlockedDraw();
   nsresult OnImageIsAnimated(imgIRequest *aRequest);
 
   // The nsContentPolicyType we would use for this ImageLoadType
   static nsContentPolicyType PolicyTypeForLoad(ImageLoadType aImageLoadType);
 
   void AsyncEventRunning(mozilla::AsyncEventDispatcher* aEvent);
 
+  // Get ourselves as an nsIContent*.  Not const because some of the callers
+  // want a non-const nsIContent.
+  virtual nsIContent* AsContent() = 0;
+
 private:
   /**
    * Struct used to manage the image observers.
    */
   struct ImageObserver {
     explicit ImageObserver(imgINotificationObserver* aObserver);
     ~ImageObserver();
 
--- a/dom/base/nsScriptLoader.cpp
+++ b/dom/base/nsScriptLoader.cpp
@@ -8,16 +8,17 @@
  * A class that handles loading and evaluation of <script> elements.
  */
 
 #include "nsScriptLoader.h"
 
 #include "prsystem.h"
 #include "jsapi.h"
 #include "jsfriendapi.h"
+#include "js/Utility.h"
 #include "xpcpublic.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIContent.h"
 #include "nsJSUtils.h"
 #include "mozilla/dom/DocGroup.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/SRILogHelper.h"
--- a/dom/base/nsScriptLoader.h
+++ b/dom/base/nsScriptLoader.h
@@ -402,16 +402,31 @@ public:
    *                     of char16_t code units.
    */
   static nsresult ConvertToUTF16(nsIChannel* aChannel, const uint8_t* aData,
                                  uint32_t aLength,
                                  const nsAString& aHintCharset,
                                  nsIDocument* aDocument,
                                  char16_t*& aBufOut, size_t& aLengthOut);
 
+  static inline nsresult
+  ConvertToUTF16(nsIChannel* aChannel, const uint8_t* aData,
+                 uint32_t aLength, const nsAString& aHintCharset,
+                 nsIDocument* aDocument,
+                 JS::UniqueTwoByteChars& aBufOut, size_t& aLengthOut)
+  {
+    char16_t* bufOut;
+    nsresult rv = ConvertToUTF16(aChannel, aData, aLength, aHintCharset, aDocument,
+                                 bufOut, aLengthOut);
+    if (NS_SUCCEEDED(rv)) {
+      aBufOut.reset(bufOut);
+    }
+    return rv;
+  };
+
   /**
    * Handle the completion of a stream.  This is called by the
    * nsScriptLoadHandler object which observes the IncrementalStreamLoader
    * loading the script. The streamed content is expected to be stored on the
    * aRequest argument.
    */
   nsresult OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
                             nsScriptLoadRequest* aRequest,
--- a/dom/base/test/chrome.ini
+++ b/dom/base/test/chrome.ini
@@ -1,18 +1,24 @@
 [DEFAULT]
 skip-if = os == 'android'
 support-files =
   file_empty.html
   file_bug945152.jar
   file_bug945152_worker.js
   file_bug1008126_worker.js
+  file_inline_script.html
+  file_inline_script.xhtml
+  file_external_script.html
+  file_external_script.xhtml
+  file_script.js
   mozbrowser_api_utils.js
 
 [test_anonymousContent_xul_window.xul]
+[test_blockParsing.html]
 [test_bug715041.xul]
 [test_bug715041_removal.xul]
 [test_bug945152.html]
 [test_bug1008126.html]
 [test_bug1016960.html]
 [test_copypaste.xul]
 subsuite = clipboard
 [test_domrequesthelper.xul]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/file_external_script.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <script src="file_script.js"></script>
+  <meta charset="utf-8">
+  <title></title>
+</head>
+<body>
+  <p>Hello Mochitest</p>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/base/test/file_external_script.xhtml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html>
+<html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <script src="file_script.js"/>
+  <title/>
+</head>
+<body>
+  <p>Hello Mochitest</p>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/base/test/file_inline_script.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <script>window.scriptRan = true;</script>
+  <meta charset="utf-8">
+  <title></title>
+</head>
+<body>
+  <p>Hello Mochitest</p>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/base/test/file_inline_script.xhtml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html>
+<html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <script>window.scriptRan = true;</script>
+  <title/>
+</head>
+<body>
+  <p>Hello Mochitest</p>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/base/test/file_script.js
@@ -0,0 +1,1 @@
+window.scriptRan = true;
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_blockParsing.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for document.blockParsing</title>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+  <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<script>
+Components.utils.import("resource://testing-common/TestUtils.jsm");
+
+function* runTest(url, initialHTML, finalHTML) {
+  let iframe = document.createElement("iframe");
+  iframe.src = url;
+
+  let blockerPromise;
+  let promise = TestUtils.topicObserved("document-element-inserted", document => {
+    blockerPromise = new Promise(resolve => {
+      setTimeout(resolve, 0);
+    }).then(() => {
+      return new Promise(resolve => setTimeout(resolve, 0));
+    }).then(() => {
+      return new Promise(resolve => setTimeout(resolve, 0));
+    });
+
+    is(document.documentElement.outerHTML, initialHTML,
+       "Should have initial HTML during document-element-inserted");
+    is(document.defaultView.wrappedJSObject.scriptRan, undefined,
+       "Script node should not have run");
+
+    document.blockParsing(blockerPromise);
+
+    return true;
+  }).then(([document]) => {
+    return document;
+  });
+
+  document.body.appendChild(iframe);
+
+  // Wait for document-element-inserted to fire.
+  let doc = yield promise;
+  let win = doc.defaultView.wrappedJSObject;
+  let root = doc.documentElement;
+
+  // At this point, if the parser was successfully blocked, we should still
+  // have the initial skeleton HTML for the page.
+  is(root.outerHTML, initialHTML, "Should have initial HTML after document-element-inserted returns");
+  is(win.scriptRan, undefined, "Script node should still not have run");
+
+  yield blockerPromise;
+
+  // Just after the promise that's blocking the parser fires, we shouldn't have
+  // returned to the main event loop, so we should still have the initial HTML.
+  is(root.outerHTML, initialHTML, "Should still have initial HTML");
+  is(win.scriptRan, undefined, "Script node should still not have run");
+
+  yield new Promise(resolve => win.addEventListener("DOMContentLoaded", resolve, {once: true}));
+
+  // Parsing should have resumed, and we should have finished loading the document.
+  is(root.outerHTML, finalHTML, "Should have final HTML");
+  is(win.scriptRan, true, "Script node should have run");
+
+  iframe.remove();
+}
+
+add_task(function* () {
+  yield runTest("http://mochi.test:8888/chrome/dom/base/test/file_inline_script.html",
+                '<html lang="en"></html>',
+                '<html lang="en"><head>\n  <script>window.scriptRan = true;<\/script>\n  <meta charset="utf-8">\n  <title></title>\n</head>\n<body>\n  <p>Hello Mochitest</p>\n\n\n</body></html>');
+
+  yield runTest("http://mochi.test:8888/chrome/dom/base/test/file_inline_script.xhtml",
+                '<html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml"></html>',
+                '<html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">\n<head>\n  <script>window.scriptRan = true;<\/script>\n  <title></title>\n</head>\n<body>\n  <p>Hello Mochitest</p>\n</body>\n</html>');
+
+  yield runTest("http://mochi.test:8888/chrome/dom/base/test/file_external_script.html",
+                '<html lang="en"></html>',
+                '<html lang="en"><head>\n  <script src="file_script.js"><\/script>\n  <meta charset="utf-8">\n  <title></title>\n</head>\n<body>\n  <p>Hello Mochitest</p>\n\n\n</body></html>');
+
+  yield runTest("http://mochi.test:8888/chrome/dom/base/test/file_external_script.xhtml",
+                '<html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml"></html>',
+                '<html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">\n<head>\n  <script src="file_script.js"><\/script>\n  <title></title>\n</head>\n<body>\n  <p>Hello Mochitest</p>\n</body>\n</html>');
+});
+</script>
+</body>
+</html>
+
+
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -4847,19 +4847,20 @@ class IDLMethod(IDLInterfaceMember, IDLS
                      argument.type.inner.canBeEmpty())or
                     (argument.type.isUnion() and
                      argument.type.unroll().hasPossiblyEmptyDictionaryType())):
                     # Optional dictionaries and unions containing optional
                     # dictionaries at the end of the list or followed by
                     # optional arguments must be optional.
                     if (not argument.optional and
                         all(arg.optional for arg in arguments[idx+1:])):
-                        raise WebIDLError("Dictionary argument or union "
-                                          "argument containing a dictionary "
-                                          "not followed by a required argument "
+                        raise WebIDLError("Dictionary argument without any "
+                                          "required fields or union argument "
+                                          "containing such dictionary not "
+                                          "followed by a required argument "
                                           "must be optional",
                                           [argument.location])
 
                     # An argument cannot be a Nullable Dictionary
                     if argument.type.nullable():
                         raise WebIDLError("An argument cannot be a nullable "
                                           "dictionary or nullable union "
                                           "containing a dictionary",
--- a/dom/html/HTMLButtonElement.cpp
+++ b/dom/html/HTMLButtonElement.cpp
@@ -418,17 +418,17 @@ HTMLButtonElement::DoneCreatingElement()
     if (NS_SUCCEEDED(rv)) {
       RestoreFormControlState();
     }
   }
 }
 
 nsresult
 HTMLButtonElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                 nsAttrValueOrString* aValue,
+                                 const nsAttrValueOrString* aValue,
                                  bool aNotify)
 {
   if (aNotify && aName == nsGkAtoms::disabled &&
       aNameSpaceID == kNameSpaceID_None) {
     mDisabledChanged = true;
   }
 
   return nsGenericHTMLFormElementWithState::BeforeSetAttr(aNameSpaceID, aName,
@@ -443,17 +443,16 @@ HTMLButtonElement::AfterSetAttr(int32_t 
     if (aName == nsGkAtoms::type) {
       if (!aValue) {
         mType = kButtonDefaultType->value;
       }
     }
 
     if (aName == nsGkAtoms::type || aName == nsGkAtoms::disabled) {
       UpdateBarredFromConstraintValidation();
-      UpdateState(aNotify); 
     }
   }
 
   return nsGenericHTMLFormElementWithState::AfterSetAttr(aNameSpaceID, aName,
                                                          aValue, aNotify);
 }
 
 NS_IMETHODIMP
--- a/dom/html/HTMLButtonElement.h
+++ b/dom/html/HTMLButtonElement.h
@@ -76,17 +76,17 @@ public:
 
   void UpdateBarredFromConstraintValidation();
   // Element
   EventStates IntrinsicState() const override;
   /**
    * Called when an attribute is about to be changed
    */
   virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                 nsAttrValueOrString* aValue,
+                                 const nsAttrValueOrString* aValue,
                                  bool aNotify) override;
   /**
    * Called when an attribute has just been changed
    */
   nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
                         const nsAttrValue* aValue, bool aNotify) override;
   virtual bool ParseAttribute(int32_t aNamespaceID,
                               nsIAtom* aAttribute,
--- a/dom/html/HTMLDetailsElement.cpp
+++ b/dom/html/HTMLDetailsElement.cpp
@@ -41,17 +41,17 @@ HTMLDetailsElement::GetAttributeChangeHi
   if (aAttribute == nsGkAtoms::open) {
     hint |= nsChangeHint_ReconstructFrame;
   }
   return hint;
 }
 
 nsresult
 HTMLDetailsElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                  nsAttrValueOrString* aValue, bool aNotify)
+                                  const nsAttrValueOrString* aValue, bool aNotify)
 {
   if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::open) {
     bool setOpen = aValue != nullptr;
     if (Open() != setOpen) {
       if (mToggleEventDispatcher) {
         mToggleEventDispatcher->Cancel();
       }
       // According to the html spec, a 'toggle' event is a simple event which
--- a/dom/html/HTMLDetailsElement.h
+++ b/dom/html/HTMLDetailsElement.h
@@ -33,17 +33,18 @@ public:
   nsIContent* GetFirstSummary() const;
 
   nsresult Clone(NodeInfo* aNodeInfo, nsINode** aResult) const override;
 
   nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute,
                                       int32_t aModType) const override;
 
   nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                         nsAttrValueOrString* aValue, bool aNotify) override;
+                         const nsAttrValueOrString* aValue,
+                         bool aNotify) override;
 
   // HTMLDetailsElement WebIDL
   bool Open() const { return GetBoolAttr(nsGkAtoms::open); }
 
   void SetOpen(bool aOpen, ErrorResult& aError)
   {
     SetHTMLBoolAttr(nsGkAtoms::open, aOpen, aError);
   }
--- a/dom/html/HTMLImageElement.cpp
+++ b/dom/html/HTMLImageElement.cpp
@@ -363,17 +363,17 @@ nsMapRuleToAttributesFunc
 HTMLImageElement::GetAttributeMappingFunction() const
 {
   return &MapAttributesIntoRule;
 }
 
 
 nsresult
 HTMLImageElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                nsAttrValueOrString* aValue,
+                                const nsAttrValueOrString* aValue,
                                 bool aNotify)
 {
 
   if (aNameSpaceID == kNameSpaceID_None && mForm &&
       (aName == nsGkAtoms::name || aName == nsGkAtoms::id)) {
     // remove the image from the hashtable as needed
     nsAutoString tmp;
     GetAttr(kNameSpaceID_None, aName, tmp);
--- a/dom/html/HTMLImageElement.h
+++ b/dom/html/HTMLImageElement.h
@@ -339,22 +339,25 @@ protected:
   // only that it is valid.
   bool TryCreateResponsiveSelector(nsIContent *aSourceNode);
 
   CSSIntPoint GetXY();
   virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
   void UpdateFormOwner();
 
   virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                 nsAttrValueOrString* aValue,
+                                 const nsAttrValueOrString* aValue,
                                  bool aNotify) override;
 
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                 const nsAttrValue* aValue, bool aNotify) override;
 
+  // Override for nsImageLoadingContent.
+  nsIContent* AsContent() override { return this; }
+
   // This is a weak reference that this element and the HTMLFormElement
   // cooperate in maintaining.
   HTMLFormElement* mForm;
 
   // Created when we're tracking responsive image state
   RefPtr<ResponsiveImageSelector> mResponsiveSelector;
 
 private:
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -173,25 +173,28 @@ static const nsAttrValue::EnumTable kInp
   { "month", NS_FORM_INPUT_MONTH },
   { "number", NS_FORM_INPUT_NUMBER },
   { "password", NS_FORM_INPUT_PASSWORD },
   { "radio", NS_FORM_INPUT_RADIO },
   { "range", NS_FORM_INPUT_RANGE },
   { "search", NS_FORM_INPUT_SEARCH },
   { "submit", NS_FORM_INPUT_SUBMIT },
   { "tel", NS_FORM_INPUT_TEL },
-  { "text", NS_FORM_INPUT_TEXT },
   { "time", NS_FORM_INPUT_TIME },
   { "url", NS_FORM_INPUT_URL },
   { "week", NS_FORM_INPUT_WEEK },
+  // "text" must be last for ParseAttribute to work right.  If you add things
+  // before it, please update kInputDefaultType.
+  { "text", NS_FORM_INPUT_TEXT },
   { nullptr, 0 }
 };
 
 // Default type is 'text'.
-static const nsAttrValue::EnumTable* kInputDefaultType = &kInputTypeTable[18];
+static const nsAttrValue::EnumTable* kInputDefaultType =
+  &kInputTypeTable[ArrayLength(kInputTypeTable) - 2];
 
 static const uint8_t NS_INPUT_INPUTMODE_AUTO              = 0;
 static const uint8_t NS_INPUT_INPUTMODE_NUMERIC           = 1;
 static const uint8_t NS_INPUT_INPUTMODE_DIGIT             = 2;
 static const uint8_t NS_INPUT_INPUTMODE_UPPERCASE         = 3;
 static const uint8_t NS_INPUT_INPUTMODE_LOWERCASE         = 4;
 static const uint8_t NS_INPUT_INPUTMODE_TITLECASE         = 5;
 static const uint8_t NS_INPUT_INPUTMODE_AUTOCAPITALIZED   = 6;
@@ -1353,17 +1356,17 @@ HTMLInputElement::Clone(mozilla::dom::No
 
   it->mLastValueChangeWasInteractive = mLastValueChangeWasInteractive;
   it.forget(aResult);
   return NS_OK;
 }
 
 nsresult
 HTMLInputElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                nsAttrValueOrString* aValue,
+                                const nsAttrValueOrString* aValue,
                                 bool aNotify)
 {
   if (aNameSpaceID == kNameSpaceID_None) {
     //
     // When name or type changes, radio should be removed from radio group.
     // (type changes are handled in the form itself currently)
     // If we are not done creating the radio, we also should not do it.
     //
@@ -1444,46 +1447,25 @@ HTMLInputElement::AfterSetAttr(int32_t a
         mShouldInitChecked = true;
       } else {
         DoSetChecked(DefaultChecked(), true, true);
         SetCheckedChanged(false);
       }
     }
 
     if (aName == nsGkAtoms::type) {
+      uint8_t newType;
       if (!aValue) {
-        // We're now a text input.  Note that we have to handle this manually,
-        // since removing an attribute (which is what happened, since aValue is
-        // null) doesn't call ParseAttribute.
-        HandleTypeChange(kInputDefaultType->value);
-      }
-
-      UpdateBarredFromConstraintValidation();
-
-      if (mType != NS_FORM_INPUT_IMAGE) {
-        // We're no longer an image input.  Cancel our image requests, if we have
-        // any.  Note that doing this when we already weren't an image is ok --
-        // just does nothing.
-        CancelImageRequests(aNotify);
-      } else if (aNotify) {
-        // We just got switched to be an image input; we should see
-        // whether we have an image to load;
-        nsAutoString src;
-        if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
-          LoadImage(src, false, aNotify, eImageLoadType_Normal);
-        }
-      }
-
-      if (mType == NS_FORM_INPUT_PASSWORD && IsInComposedDoc()) {
-        AsyncEventDispatcher* dispatcher =
-          new AsyncEventDispatcher(this,
-                                   NS_LITERAL_STRING("DOMInputPasswordAdded"),
-                                   true,
-                                   true);
-        dispatcher->PostDOMEvent();
+        // We're now a text input.
+        newType = kInputDefaultType->value;
+      } else {
+        newType = aValue->GetEnumValue();
+      }
+      if (newType != mType) {
+        HandleTypeChange(newType, aNotify);
       }
     }
 
     if (aName == nsGkAtoms::required || aName == nsGkAtoms::disabled ||
         aName == nsGkAtoms::readonly) {
       UpdateValueMissingValidityState();
 
       // This *has* to be called *after* validity has changed.
@@ -1572,18 +1554,16 @@ HTMLInputElement::AfterSetAttr(int32_t a
         if (numberControlFrame) {
           numberControlFrame->SetValueOfAnonTextControl(value);
         }
       }
     } else if (aName == nsGkAtoms::autocomplete) {
       // Clear the cached @autocomplete attribute state.
       mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown;
     }
-
-    UpdateState(aNotify);
   }
 
   return nsGenericHTMLFormElementWithState::AfterSetAttr(aNameSpaceID, aName,
                                                          aValue, aNotify);
 }
 
 // nsIDOMHTMLInputElement
 
@@ -5116,24 +5096,33 @@ HTMLInputElement::UnbindFromTree(bool aD
   // We might be no longer disabled because of parent chain changed.
   UpdateBarredFromConstraintValidation();
 
   // And now make sure our state is up to date
   UpdateState(false);
 }
 
 void
-HTMLInputElement::HandleTypeChange(uint8_t aNewType)
-{
-  if (mType == NS_FORM_INPUT_RANGE && mIsDraggingRange) {
+HTMLInputElement::HandleTypeChange(uint8_t aNewType, bool aNotify)
+{
+  uint8_t oldType = mType;
+  MOZ_ASSERT(oldType != aNewType);
+
+  if (aNewType == NS_FORM_INPUT_FILE || oldType == NS_FORM_INPUT_FILE) {
+    // Strictly speaking, we only need to clear files on going _to_ or _from_
+    // the NS_FORM_INPUT_FILE type, not both, since we'll never confuse values
+    // and filenames. But this is safer.
+    ClearFiles(false);
+  }
+
+  if (oldType == NS_FORM_INPUT_RANGE && mIsDraggingRange) {
     CancelRangeThumbDrag(false);
   }
 
   ValueModeType aOldValueMode = GetValueMode();
-  uint8_t oldType = mType;
   nsAutoString aOldValue;
 
   if (aOldValueMode == VALUE_MODE_VALUE) {
     // Doesn't matter what caller type we pass here, since we know we're not a
     // file input anyway.
     GetValue(aOldValue, CallerType::NonSystem);
   }
 
@@ -5207,16 +5196,40 @@ HTMLInputElement::HandleTypeChange(uint8
   }
 
   UpdateHasRange();
 
   // Do not notify, it will be done after if needed.
   UpdateAllValidityStates(false);
 
   UpdateApzAwareFlag();
+
+  UpdateBarredFromConstraintValidation();
+
+  if (oldType == NS_FORM_INPUT_IMAGE) {
+    // We're no longer an image input.  Cancel our image requests, if we have
+    // any.
+    CancelImageRequests(aNotify);
+  } else if (aNotify && mType == NS_FORM_INPUT_IMAGE) {
+    // We just got switched to be an image input; we should see
+    // whether we have an image to load;
+    nsAutoString src;
+    if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
+      LoadImage(src, false, aNotify, eImageLoadType_Normal);
+    }
+  }
+
+  if (mType == NS_FORM_INPUT_PASSWORD && IsInComposedDoc()) {
+    AsyncEventDispatcher* dispatcher =
+      new AsyncEventDispatcher(this,
+                               NS_LITERAL_STRING("DOMInputPasswordAdded"),
+                               true,
+                               true);
+    dispatcher->PostDOMEvent();
+  }
 }
 
 void
 HTMLInputElement::SanitizeValue(nsAString& aValue)
 {
   NS_ASSERTION(mDoneCreating, "The element creation should be finished!");
 
   switch (mType) {
@@ -5930,55 +5943,44 @@ HTMLInputElement::IsInputColorEnabled()
 }
 
 bool
 HTMLInputElement::ParseAttribute(int32_t aNamespaceID,
                                  nsIAtom* aAttribute,
                                  const nsAString& aValue,
                                  nsAttrValue& aResult)
 {
+  // We can't make these static_asserts because kInputDefaultType and
+  // kInputTypeTable aren't constexpr.
+  MOZ_ASSERT(kInputDefaultType->value == NS_FORM_INPUT_TEXT,
+             "Someone forgot to update kInputDefaultType when adding a new "
+             "input type.");
+  MOZ_ASSERT(kInputTypeTable[ArrayLength(kInputTypeTable) - 1].tag == nullptr,
+             "Last entry in the table must be the nullptr guard");
+  MOZ_ASSERT(kInputTypeTable[ArrayLength(kInputTypeTable) - 2].value ==
+               NS_FORM_INPUT_TEXT,
+             "Next to last entry in the table must be the \"text\" entry");
+
   if (aNamespaceID == kNameSpaceID_None) {
     if (aAttribute == nsGkAtoms::type) {
-      // XXX ARG!! This is major evilness. ParseAttribute
-      // shouldn't set members. Override SetAttr instead
-      int32_t newType;
-      bool success = aResult.ParseEnumValue(aValue, kInputTypeTable, false);
-      if (success) {
-        newType = aResult.GetEnumValue();
-        if ((IsExperimentalMobileType(newType) &&
-             !IsExperimentalFormsEnabled()) ||
-            (newType == NS_FORM_INPUT_NUMBER && !IsInputNumberEnabled()) ||
-            (newType == NS_FORM_INPUT_COLOR && !IsInputColorEnabled()) ||
-            (IsDateTimeInputType(newType) &&
-             !IsDateTimeTypeSupported(newType))) {
-          newType = kInputDefaultType->value;
-          aResult.SetTo(newType, &aValue);
-        }
-      } else {
-        newType = kInputDefaultType->value;
-      }
-
-      if (newType != mType) {
-        // Make sure to do the check for newType being NS_FORM_INPUT_FILE and
-        // the corresponding SetValueInternal() call _before_ we set mType.
-        // That way the logic in SetValueInternal() will work right (that logic
-        // makes assumptions about our frame based on mType, but we won't have
-        // had time to recreate frames yet -- that happens later in the
-        // SetAttr() process).
-        if (newType == NS_FORM_INPUT_FILE || mType == NS_FORM_INPUT_FILE) {
-          // This call isn't strictly needed any more since we'll never
-          // confuse values and filenames. However it's there for backwards
-          // compat.
-          ClearFiles(false);
-        }
-
-        HandleTypeChange(newType);
-      }
-
-      return success;
+      aResult.ParseEnumValue(aValue, kInputTypeTable, false, kInputDefaultType);
+      int32_t newType = aResult.GetEnumValue();
+      if ((IsExperimentalMobileType(newType) &&
+           !IsExperimentalFormsEnabled()) ||
+          (newType == NS_FORM_INPUT_NUMBER && !IsInputNumberEnabled()) ||
+          (newType == NS_FORM_INPUT_COLOR && !IsInputColorEnabled()) ||
+          (IsDateTimeInputType(newType) &&
+           !IsDateTimeTypeSupported(newType))) {
+        // There's no public way to set an nsAttrValue to an enum value, but we
+        // can just re-parse with a table that doesn't have any types other than
+        // "text" in it.
+        aResult.ParseEnumValue(aValue, kInputDefaultType, false, kInputDefaultType);
+      }
+
+      return true;
     }
     if (aAttribute == nsGkAtoms::width) {
       return aResult.ParseSpecialIntValue(aValue);
     }
     if (aAttribute == nsGkAtoms::height) {
       return aResult.ParseSpecialIntValue(aValue);
     }
     if (aAttribute == nsGkAtoms::maxlength) {
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -960,17 +960,17 @@ protected:
 
   void SetIndeterminateInternal(bool aValue,
                                 bool aShouldInvalidate);
 
   /**
    * Called when an attribute is about to be changed
    */
   virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                 nsAttrValueOrString* aValue,
+                                 const nsAttrValueOrString* aValue,
                                  bool aNotify) override;
   /**
    * Called when an attribute has just been changed
    */
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                 const nsAttrValue* aValue, bool aNotify) override;
 
   /**
@@ -1102,17 +1102,17 @@ protected:
   bool MinOrMaxLengthApplies() const { return IsSingleLineTextControl(false, mType); }
 
   void FreeData();
   nsTextEditorState *GetEditorState() const;
 
   /**
    * Manages the internal data storage across type changes.
    */
-  void HandleTypeChange(uint8_t aNewType);
+  void HandleTypeChange(uint8_t aNewType, bool aNotify);
 
   /**
    * Sanitize the value of the element depending of its current type.
    * See: http://www.whatwg.org/specs/web-apps/current-work/#value-sanitization-algorithm
    */
   void SanitizeValue(nsAString& aValue);
 
   /**
@@ -1490,16 +1490,21 @@ protected:
   /**
    * A helper to get the current selection range.  Will throw on the ErrorResult
    * if we have no editor state.
    */
   void GetSelectionRange(uint32_t* aSelectionStart,
                          uint32_t* aSelectionEnd,
                          ErrorResult& aRv);
 
+  /**
+   * Override for nsImageLoadingContent.
+   */
+  nsIContent* AsContent() override { return this; }
+
   nsCOMPtr<nsIControllers> mControllers;
 
   /*
    * In mInputData, the mState field is used if IsSingleLineTextControl returns
    * true and mValue is used otherwise.  We have to be careful when handling it
    * on a type change.
    *
    * Accessing the mState member should be done using the GetEditorState function,
--- a/dom/html/HTMLLinkElement.cpp
+++ b/dom/html/HTMLLinkElement.cpp
@@ -328,17 +328,17 @@ HTMLLinkElement::UpdateImport()
     nsAutoScriptBlocker scriptBlocker;
     // CORS check will happen at the start of the load.
     mImportLoader = manager->Get(uri, this, doc);
   }
 }
 
 nsresult
 HTMLLinkElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                               nsAttrValueOrString* aValue, bool aNotify)
+                               const nsAttrValueOrString* aValue, bool aNotify)
 {
   if (aNameSpaceID == kNameSpaceID_None &&
       (aName == nsGkAtoms::href || aName == nsGkAtoms::rel)) {
     CancelDNSPrefetch(HTML_LINK_DNS_PREFETCH_DEFERRED,
                       HTML_LINK_DNS_PREFETCH_REQUESTED);
     CancelPrefetch();
   }
 
--- a/dom/html/HTMLLinkElement.h
+++ b/dom/html/HTMLLinkElement.h
@@ -57,17 +57,17 @@ public:
 
   // nsIContent
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent,
                               bool aCompileEventHandlers) override;
   virtual void UnbindFromTree(bool aDeep = true,
                               bool aNullParent = true) override;
   virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                 nsAttrValueOrString* aValue,
+                                 const nsAttrValueOrString* aValue,
                                  bool aNotify) override;
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                 const nsAttrValue* aValue,
                                 bool aNotify) override;
   virtual bool IsLink(nsIURI** aURI) const override;
   virtual already_AddRefed<nsIURI> GetHrefURI() const override;
 
   // Element
--- a/dom/html/HTMLObjectElement.h
+++ b/dom/html/HTMLObjectElement.h
@@ -247,16 +247,20 @@ public:
     return GetContentDocument(aSubjectPrincipal);
   }
 
   /**
    * Calls LoadObject with the correct arguments to start the plugin load.
    */
   void StartObjectLoad(bool aNotify, bool aForceLoad);
 
+protected:
+  // Override for nsImageLoadingContent.
+  nsIContent* AsContent() override { return this; }
+
 private:
   /**
    * Returns if the element is currently focusable regardless of it's tabindex
    * value. This is used to know the default tabindex value.
    */
   bool IsFocusableForTabIndex();
 
   nsContentPolicyType GetContentPolicyType() const override
--- a/dom/html/HTMLOptionElement.cpp
+++ b/dom/html/HTMLOptionElement.cpp
@@ -176,17 +176,17 @@ HTMLOptionElement::GetAttributeChangeHin
       aAttribute == nsGkAtoms::text) {
     retval |= NS_STYLE_HINT_REFLOW;
   }
   return retval;
 }
 
 nsresult
 HTMLOptionElement::BeforeSetAttr(int32_t aNamespaceID, nsIAtom* aName,
-                                 nsAttrValueOrString* aValue,
+                                 const nsAttrValueOrString* aValue,
                                  bool aNotify)
 {
   nsresult rv = nsGenericHTMLElement::BeforeSetAttr(aNamespaceID, aName,
                                                     aValue, aNotify);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (aNamespaceID != kNameSpaceID_None || aName != nsGkAtoms::selected ||
       mSelectedChanged) {
--- a/dom/html/HTMLOptionElement.h
+++ b/dom/html/HTMLOptionElement.h
@@ -42,17 +42,17 @@ public:
 
   bool Selected() const;
   bool DefaultSelected() const;
 
   virtual nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute,
                                               int32_t aModType) const override;
 
   virtual nsresult BeforeSetAttr(int32_t aNamespaceID, nsIAtom* aName,
-                                 nsAttrValueOrString* aValue,
+                                 const nsAttrValueOrString* aValue,
                                  bool aNotify) override;
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                 const nsAttrValue* aValue, bool aNotify) override;
 
   void SetSelectedInternal(bool aValue, bool aNotify);
 
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent,
--- a/dom/html/HTMLSelectElement.cpp
+++ b/dom/html/HTMLSelectElement.cpp
@@ -1291,17 +1291,17 @@ HTMLSelectElement::UnbindFromTree(bool a
   UpdateBarredFromConstraintValidation();
 
   // And now make sure our state is up to date
   UpdateState(false);
 }
 
 nsresult
 HTMLSelectElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                 nsAttrValueOrString* aValue,
+                                 const nsAttrValueOrString* aValue,
                                  bool aNotify)
 {
   if (aNotify && aName == nsGkAtoms::disabled &&
       aNameSpaceID == kNameSpaceID_None) {
     mDisabledChanged = true;
   }
 
   return nsGenericHTMLFormElementWithState::BeforeSetAttr(aNameSpaceID, aName,
@@ -1316,18 +1316,16 @@ HTMLSelectElement::AfterSetAttr(int32_t 
     if (aName == nsGkAtoms::disabled) {
       UpdateBarredFromConstraintValidation();
     } else if (aName == nsGkAtoms::required) {
       UpdateValueMissingValidityState();
     } else if (aName == nsGkAtoms::autocomplete) {
       // Clear the cached @autocomplete attribute state
       mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown;
     }
-
-    UpdateState(aNotify);
   }
 
   return nsGenericHTMLFormElementWithState::AfterSetAttr(aNameSpaceID, aName,
                                                          aValue, aNotify);
 }
 
 nsresult
 HTMLSelectElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
--- a/dom/html/HTMLSelectElement.h
+++ b/dom/html/HTMLSelectElement.h
@@ -377,17 +377,17 @@ public:
   /**
    * Called when an attribute is about to be changed
    */
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                                nsIContent* aBindingParent,
                                bool aCompileEventHandlers) override;
   virtual void UnbindFromTree(bool aDeep, bool aNullParent) override;
   virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                 nsAttrValueOrString* aValue,
+                                 const nsAttrValueOrString* aValue,
                                  bool aNotify) override;
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                 const nsAttrValue* aValue, bool aNotify) override;
   virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
                              bool aNotify) override;
 
   virtual void DoneAddingChildren(bool aHaveNotified) override;
   virtual bool IsDoneAddingChildren() override {
--- a/dom/html/HTMLSharedObjectElement.h
+++ b/dom/html/HTMLSharedObjectElement.h
@@ -195,16 +195,21 @@ public:
   {
     return GetContentDocument(aSubjectPrincipal);
   }
 
   /**
    * Calls LoadObject with the correct arguments to start the plugin load.
    */
   void StartObjectLoad(bool aNotify, bool aForceLoad);
+
+protected:
+  // Override for nsImageLoadingContent.
+  nsIContent* AsContent() override { return this; }
+
 private:
   virtual ~HTMLSharedObjectElement();
 
   nsIAtom *URIAttrName() const
   {
     return mNodeInfo->Equals(nsGkAtoms::applet) ?
            nsGkAtoms::code :
            nsGkAtoms::src;
--- a/dom/html/HTMLTableElement.cpp
+++ b/dom/html/HTMLTableElement.cpp
@@ -932,17 +932,17 @@ void
 HTMLTableElement::UnbindFromTree(bool aDeep, bool aNullParent)
 {
   ReleaseInheritedAttributes();
   nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
 }
 
 nsresult
 HTMLTableElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                nsAttrValueOrString* aValue,
+                                const nsAttrValueOrString* aValue,
                                 bool aNotify)
 {
   if (aName == nsGkAtoms::cellpadding && aNameSpaceID == kNameSpaceID_None) {
     ReleaseInheritedAttributes();
   }
   return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName, aValue,
                                              aNotify);
 }
--- a/dom/html/HTMLTableElement.h
+++ b/dom/html/HTMLTableElement.h
@@ -197,17 +197,17 @@ public:
                               nsIContent* aBindingParent,
                               bool aCompileEventHandlers) override;
   virtual void UnbindFromTree(bool aDeep = true,
                               bool aNullParent = true) override;
   /**
    * Called when an attribute is about to be changed
    */
   virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                 nsAttrValueOrString* aValue,
+                                 const nsAttrValueOrString* aValue,
                                  bool aNotify) override;
   /**
    * Called when an attribute has just been changed
    */
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                 const nsAttrValue* aValue, bool aNotify) override;
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLTableElement,
--- a/dom/html/HTMLTextAreaElement.cpp
+++ b/dom/html/HTMLTextAreaElement.cpp
@@ -980,17 +980,17 @@ HTMLTextAreaElement::UnbindFromTree(bool
   UpdateBarredFromConstraintValidation();
 
   // And now make sure our state is up to date
   UpdateState(false);
 }
 
 nsresult
 HTMLTextAreaElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                   nsAttrValueOrString* aValue,
+                                   const nsAttrValueOrString* aValue,
                                    bool aNotify)
 {
   if (aNotify && aName == nsGkAtoms::disabled &&
       aNameSpaceID == kNameSpaceID_None) {
     mDisabledChanged = true;
   }
 
   return nsGenericHTMLFormElementWithState::BeforeSetAttr(aNameSpaceID, aName,
@@ -1058,18 +1058,16 @@ HTMLTextAreaElement::AfterSetAttr(int32_
       if (aName == nsGkAtoms::readonly || aName == nsGkAtoms::disabled) {
         UpdateBarredFromConstraintValidation();
       }
     } else if (aName == nsGkAtoms::maxlength) {
       UpdateTooLongValidityState();
     } else if (aName == nsGkAtoms::minlength) {
       UpdateTooShortValidityState();
     }
-
-    UpdateState(aNotify);
   }
 
   return nsGenericHTMLFormElementWithState::AfterSetAttr(aNameSpaceID, aName, aValue,
                                                          aNotify);
   }
 
 nsresult
 HTMLTextAreaElement::CopyInnerTo(Element* aDest)
--- a/dom/html/HTMLTextAreaElement.h
+++ b/dom/html/HTMLTextAreaElement.h
@@ -142,17 +142,17 @@ public:
   virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
 
   nsresult CopyInnerTo(Element* aDest);
 
   /**
    * Called when an attribute is about to be changed
    */
   virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                 nsAttrValueOrString* aValue,
+                                 const nsAttrValueOrString* aValue,
                                  bool aNotify) override;
 
   // nsIMutationObserver
   NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
 
--- a/dom/html/nsGenericHTMLElement.cpp
+++ b/dom/html/nsGenericHTMLElement.cpp
@@ -1924,17 +1924,17 @@ nsGenericHTMLFormElement::UnbindFromTree
   nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
 
   // The element might not have a fieldset anymore.
   UpdateFieldSet(false);
 }
 
 nsresult
 nsGenericHTMLFormElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                        nsAttrValueOrString* aValue,
+                                        const nsAttrValueOrString* aValue,
                                         bool aNotify)
 {
   if (aNameSpaceID == kNameSpaceID_None) {
     nsAutoString tmp;
 
     // remove the control from the hashtable as needed
 
     if (mForm && (aName == nsGkAtoms::name || aName == nsGkAtoms::id)) {
@@ -1954,23 +1954,16 @@ nsGenericHTMLFormElement::BeforeSetAttr(
 
       GetAttr(kNameSpaceID_None, nsGkAtoms::id, tmp);
 
       if (!tmp.IsEmpty()) {
         mForm->RemoveElementFromTable(this, tmp);
       }
 
       mForm->RemoveElement(this, false);
-
-      // Removing the element from the form can make it not be the default
-      // control anymore.  Go ahead and notify on that change, though we might
-      // end up readding and becoming the default control again in
-      // AfterSetAttr.
-      // FIXME: Bug 656197
-      UpdateState(aNotify);
     }
 
     if (aName == nsGkAtoms::form) {
       // If @form isn't set or set to the empty string, there were no observer
       // so we don't have to remove it.
       if (nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None,
                                           nsGkAtoms::form)) {
         // The current form id observer is no longer needed.
@@ -2010,22 +2003,16 @@ nsGenericHTMLFormElement::AfterSetAttr(i
 
       GetAttr(kNameSpaceID_None, nsGkAtoms::id, tmp);
 
       if (!tmp.IsEmpty()) {
         mForm->AddElementToTable(this, tmp);
       }
 
       mForm->AddElement(this, false, aNotify);
-
-      // Adding the element to the form can make it be the default control .
-      // Go ahead and notify on that change.
-      // Note: no need to notify on CanBeDisabled(), since type attr
-      // changes can't affect that.
-      UpdateState(aNotify);
     }
 
     if (aName == nsGkAtoms::form) {
       // We need a new form id observer.
       //XXXsmaug How should this work in Shadow DOM?
       nsIDocument* doc = GetUncomposedDoc();
       if (doc) {
         Element* formIdElement = nullptr;
--- a/dom/html/nsGenericHTMLElement.h
+++ b/dom/html/nsGenericHTMLElement.h
@@ -1291,17 +1291,17 @@ public:
                                  int32_t* aTabIndex) override;
 
   virtual bool IsLabelable() const override;
 
 protected:
   virtual ~nsGenericHTMLFormElement();
 
   virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                 nsAttrValueOrString* aValue,
+                                 const nsAttrValueOrString* aValue,
                                  bool aNotify) override;
 
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                 const nsAttrValue* aValue, bool aNotify) override;
 
   /**
    * This method will update the form owner, using @form or looking to a parent.
    *
--- a/dom/html/nsHTMLDocument.cpp
+++ b/dom/html/nsHTMLDocument.cpp
@@ -2406,17 +2406,17 @@ nsHTMLDocument::GetDesignMode(nsAString&
     aDesignMode.AssignLiteral("off");
   }
   return NS_OK;
 }
 
 void
 nsHTMLDocument::MaybeEditingStateChanged()
 {
-  if (!mPendingMaybeEditingStateChanged &&
+  if (!mPendingMaybeEditingStateChanged && mMayStartLayout &&
       mUpdateNestLevel == 0 && (mContentEditableCount > 0) != IsEditingOn()) {
     if (nsContentUtils::IsSafeToRunScript()) {
       EditingStateChanged();
     } else if (!mInDestructor) {
       nsContentUtils::AddScriptRunner(
         NewRunnableMethod(this, &nsHTMLDocument::MaybeEditingStateChanged));
     }
   }
@@ -2429,16 +2429,25 @@ nsHTMLDocument::EndUpdate(nsUpdateType a
   mPendingMaybeEditingStateChanged = true;
   nsDocument::EndUpdate(aUpdateType);
   if (reset) {
     mPendingMaybeEditingStateChanged = false;
   }
   MaybeEditingStateChanged();
 }
 
+void
+nsHTMLDocument::SetMayStartLayout(bool aMayStartLayout)
+{
+  nsIDocument::SetMayStartLayout(aMayStartLayout);
+
+  MaybeEditingStateChanged();
+}
+
+
 
 // Helper class, used below in ChangeContentEditableCount().
 class DeferredContentEditableCountChangeEvent : public Runnable
 {
 public:
   DeferredContentEditableCountChangeEvent(nsHTMLDocument *aDoc,
                                           nsIContent *aElement)
     : mDoc(aDoc)
--- a/dom/html/nsHTMLDocument.h
+++ b/dom/html/nsHTMLDocument.h
@@ -139,16 +139,18 @@ public:
   private:
     nsHTMLDocument* mDoc;
     EditingState    mSavedState;
   };
   friend class nsAutoEditingState;
 
   void EndUpdate(nsUpdateType aUpdateType) override;
 
+  virtual void SetMayStartLayout(bool aMayStartLayout) override;
+
   virtual nsresult SetEditingState(EditingState aState) override;
 
   virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
 
   virtual void RemovedFromDocShell() override;
 
   virtual mozilla::dom::Element *GetElementById(const nsAString& aElementId) override
   {
--- a/dom/ipc/ContentPrefs.cpp
+++ b/dom/ipc/ContentPrefs.cpp
@@ -100,58 +100,16 @@ const char* mozilla::dom::ContentPrefs::
   "javascript.options.strict.debug",
   "javascript.options.throw_on_asmjs_validation_failure",
   "javascript.options.throw_on_debuggee_would_run",
   "javascript.options.wasm",
   "javascript.options.wasm_baselinejit",
   "javascript.options.werror",
   "javascript.use_us_english_locale",
   "jsloader.reuseGlobal",
-  "layout.css.all-shorthand.enabled",
-  "layout.css.background-blend-mode.enabled",
-  "layout.css.background-clip-text.enabled",
-  "layout.css.box-decoration-break.enabled",
-  "layout.css.color-adjust.enabled",
-  "layout.css.contain.enabled",
-  "layout.css.control-characters.visible",
-  "layout.css.display-flow-root.enabled",
-  "layout.css.expensive-style-struct-assertions.enabled",
-  "layout.css.float-logical-values.enabled",
-  "layout.css.font-variations.enabled",
-  "layout.css.grid.enabled",
-  "layout.css.image-orientation.enabled",
-  "layout.css.initial-letter.enabled",
-  "layout.css.isolation.enabled",
-  "layout.css.mix-blend-mode.enabled",
-  "layout.css.osx-font-smoothing.enabled",
-  "layout.css.overflow-clip-box.enabled",
-  "layout.css.prefixes.animations",
-  "layout.css.prefixes.border-image",
-  "layout.css.prefixes.box-sizing",
-  "layout.css.prefixes.device-pixel-ratio-webkit",
-  "layout.css.prefixes.font-features",
-  "layout.css.prefixes.gradients",
-  "layout.css.prefixes.transforms",
-  "layout.css.prefixes.transitions",
-  "layout.css.prefixes.webkit",
-  "layout.css.scope-pseudo.enabled",
-  "layout.css.scroll-behavior.property-enabled",
-  "layout.css.scroll-snap.enabled",
-  "layout.css.servo.enabled",
-  "layout.css.shape-outside.enabled",
-  "layout.css.text-align-unsafe-value.enabled",
-  "layout.css.text-combine-upright-digits.enabled",
-  "layout.css.text-combine-upright.enabled",
-  "layout.css.text-justify.enabled",
-  "layout.css.touch_action.enabled",
-  "layout.css.unprefixing-service.enabled",
-  "layout.css.unprefixing-service.globally-whitelisted",
-  "layout.css.unprefixing-service.include-test-domains",
-  "layout.css.variables.enabled",
-  "layout.css.visited_links_enabled",
   "layout.idle_period.required_quiescent_frames",
   "layout.idle_period.time_limit",
   "layout.interruptible-reflow.enabled",
   "mathml.disabled",
   "media.apple.forcevda",
   "media.clearkey.persistent-license.enabled",
   "media.cubeb.backend",
   "media.cubeb_latency_msg_frames",
--- a/dom/manifest/ManifestIcons.jsm
+++ b/dom/manifest/ManifestIcons.jsm
@@ -63,20 +63,20 @@ async function getIcon(aWindow, icons, e
   return fetchIcon(aWindow, icons[index].src).catch(err => {
     // Remove all icons with the failed source, the same source
     // may have been used for multiple sizes
     icons = icons.filter(x => x.src !== icons[index].src);
     return getIcon(aWindow, icons, expectedSize);
   });
 }
 
-function fetchIcon(aWindow, src) {
-  const manifestURL = new aWindow.URL(src);
-  const request = new aWindow.Request(manifestURL, {mode: "cors"});
-  request.overrideContentPolicyType(Ci.nsIContentPolicy.TYPE_WEB_MANIFEST);
+async function fetchIcon(aWindow, src) {
+  const iconURL = new aWindow.URL(src, aWindow.location);
+  const request = new aWindow.Request(iconURL, {mode: "cors"});
+  request.overrideContentPolicyType(Ci.nsIContentPolicy.TYPE_IMAGE);
   return aWindow.fetch(request)
     .then(response => response.blob())
     .then(blob => new Promise((resolve, reject) => {
       var reader = new FileReader();
       reader.onloadend = () => resolve(reader.result);
       reader.onerror = reject;
       reader.readAsDataURL(blob);
     }));
--- a/dom/svg/SVGFEImageElement.h
+++ b/dom/svg/SVGFEImageElement.h
@@ -80,16 +80,19 @@ private:
   nsresult LoadSVGImage(bool aForce, bool aNotify);
 
 protected:
   virtual bool ProducesSRGB() override { return true; }
 
   virtual SVGAnimatedPreserveAspectRatio *GetPreserveAspectRatio() override;
   virtual StringAttributesInfo GetStringInfo() override;
 
+  // Override for nsImageLoadingContent.
+  nsIContent* AsContent() override { return this; }
+
   enum { RESULT, HREF, XLINK_HREF };
   nsSVGString mStringAttributes[3];
   static StringInfo sStringInfo[3];
 
   SVGAnimatedPreserveAspectRatio mPreserveAspectRatio;
 };
 
 } // namespace dom
--- a/dom/svg/SVGImageElement.h
+++ b/dom/svg/SVGImageElement.h
@@ -81,16 +81,19 @@ public:
 
 protected:
   nsresult LoadSVGImage(bool aForce, bool aNotify);
 
   virtual LengthAttributesInfo GetLengthInfo() override;
   virtual SVGAnimatedPreserveAspectRatio *GetPreserveAspectRatio() override;
   virtual StringAttributesInfo GetStringInfo() override;
 
+  // Override for nsImageLoadingContent.
+  nsIContent* AsContent() override { return this; }
+
   enum { ATTR_X, ATTR_Y, ATTR_WIDTH, ATTR_HEIGHT };
   nsSVGLength2 mLengthAttributes[4];
   static LengthInfo sLengthInfo[4];
 
   SVGAnimatedPreserveAspectRatio mPreserveAspectRatio;
 
   enum { HREF, XLINK_HREF };
   nsSVGString mStringAttributes[2];
--- a/dom/svg/nsSVGElement.cpp
+++ b/dom/svg/nsSVGElement.cpp
@@ -1504,52 +1504,56 @@ nsSVGElement::GetAnimatedContentDeclarat
  * method, only DidChangeXXX which calls SetParsedAttr.
  */
 nsAttrValue
 nsSVGElement::WillChangeValue(nsIAtom* aName)
 {
   // We need an empty attr value:
   //   a) to pass to BeforeSetAttr when GetParsedAttr returns nullptr
   //   b) to store the old value in the case we have mutation listeners
-  // We can use the same value for both purposes since (a) happens before (b).
+  //
+  // We can use the same value for both purposes, because if GetParsedAttr
+  // returns non-null its return value is what will get passed to BeforeSetAttr,
+  // not matter what our mutation listener situation is.
+  //
   // Also, we should be careful to always return this value to benefit from
   // return value optimization.
   nsAttrValue emptyOrOldAttrValue;
   const nsAttrValue* attrValue = GetParsedAttr(aName);
 
+  // We only need to set the old value if we have listeners since otherwise it
+  // isn't used.
+  if (attrValue &&
+      nsContentUtils::HasMutationListeners(this,
+                                           NS_EVENT_BITS_MUTATION_ATTRMODIFIED,
+                                           this)) {
+    emptyOrOldAttrValue.SetToSerialized(*attrValue);
+  }
+
+  uint8_t modType = attrValue
+                  ? static_cast<uint8_t>(nsIDOMMutationEvent::MODIFICATION)
+                  : static_cast<uint8_t>(nsIDOMMutationEvent::ADDITION);
+  nsNodeUtils::AttributeWillChange(this, kNameSpaceID_None, aName, modType,
+                                   nullptr);
+
   // This is not strictly correct--the attribute value parameter for
   // BeforeSetAttr should reflect the value that *will* be set but that implies
   // allocating, e.g. an extra nsSVGLength2, and isn't necessary at the moment
   // since no SVG elements overload BeforeSetAttr. For now we just pass the
   // current value.
   nsAttrValueOrString attrStringOrValue(attrValue ? *attrValue
                                                   : emptyOrOldAttrValue);
   DebugOnly<nsresult> rv =
     BeforeSetAttr(kNameSpaceID_None, aName, &attrStringOrValue,
                   kNotifyDocumentObservers);
   // SVG elements aren't expected to overload BeforeSetAttr in such a way that
   // it may fail. So long as this is the case we don't need to check and pass on
   // the return value which simplifies the calling code significantly.
   MOZ_ASSERT(NS_SUCCEEDED(rv), "Unexpected failure from BeforeSetAttr");
 
-  // We only need to set the old value if we have listeners since otherwise it
-  // isn't used.
-  if (attrValue &&
-      nsContentUtils::HasMutationListeners(this,
-                                           NS_EVENT_BITS_MUTATION_ATTRMODIFIED,
-                                           this)) {
-    emptyOrOldAttrValue.SetToSerialized(*attrValue);
-  }
-
-  uint8_t modType = attrValue
-                  ? static_cast<uint8_t>(nsIDOMMutationEvent::MODIFICATION)
-                  : static_cast<uint8_t>(nsIDOMMutationEvent::ADDITION);
-  nsNodeUtils::AttributeWillChange(this, kNameSpaceID_None, aName, modType,
-                                   nullptr);
-
   return emptyOrOldAttrValue;
 }
 
 /**
  * Helper methods for the type-specific DidChangeXXX methods.
  *
  * aEmptyOrOldValue will normally be the object returned from the corresponding
  * WillChangeXXX call. This is because:
--- a/dom/svg/nsSVGElement.h
+++ b/dom/svg/nsSVGElement.h
@@ -338,17 +338,17 @@ protected:
 
 #ifdef DEBUG
   // We define BeforeSetAttr here and mark it final to ensure it is NOT used
   // by SVG elements.
   // This is because we're not currently passing the correct value for aValue to
   // BeforeSetAttr since it would involve allocating extra SVG value types.
   // See the comment in nsSVGElement::WillChangeValue.
   virtual nsresult BeforeSetAttr(int32_t aNamespaceID, nsIAtom* aName,
-                                 nsAttrValueOrString* aValue,
+                                 const nsAttrValueOrString* aValue,
                                  bool aNotify) override final
   {
     return nsSVGElementBase::BeforeSetAttr(aNamespaceID, aName, aValue, aNotify);
   }
 #endif // DEBUG
   virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
                                 const nsAttrValue* aValue, bool aNotify) override;
   virtual bool ParseAttribute(int32_t aNamespaceID, nsIAtom* aAttribute,
--- a/dom/tests/browser/browser.ini
+++ b/dom/tests/browser/browser.ini
@@ -8,16 +8,17 @@ support-files =
   test_bug1004814.html
   worker_bug1004814.js
   geo_leak_test.html
   dummy.html
   test_largeAllocation.html
   test_largeAllocation.html^headers^
   test_largeAllocation2.html
   test_largeAllocation2.html^headers^
+  test_largeAllocationFormSubmit.sjs
   helper_largeAllocation.js
   !/dom/tests/mochitest/geolocation/network_geolocation.sjs
 
 [browser_allocateGigabyte.js]
 disabled = Does not reliably pass on 32-bit systems - bug 1314098
 skip-if = !e10s
 [browser_autofocus_background.js]
 [browser_autofocus_preference.js]
--- a/dom/tests/browser/helper_largeAllocation.js
+++ b/dom/tests/browser/helper_largeAllocation.js
@@ -489,16 +489,56 @@ function* largeAllocSuccessTests() {
     let pid3 = yield getPID(newTab.linkedBrowser);
 
     is(pid2, pid3, "PIDs 2 and 3 should match");
     is(true, yield getInLAProc(newTab.linkedBrowser));
 
     yield BrowserTestUtils.closeWindow(newWindow);
   });
 
+  // XXX: Important - reset the process count, as it was set to 1 by the
+  // previous test.
+  yield SpecialPowers.pushPrefEnv({
+    set: [["dom.ipc.processCount.webLargeAllocation", 20]],
+  });
+
+  yield BrowserTestUtils.withNewTab("about:blank", function*(aBrowser) {
+    info("Starting test 11");
+
+    let pid1 = yield getPID(aBrowser);
+    is(false, yield getInLAProc(aBrowser));
+
+    let ready = Promise.all([expectProcessCreated(),
+                             BrowserTestUtils.browserLoaded(aBrowser)]);
+    yield ContentTask.spawn(aBrowser, TEST_URI, TEST_URI => {
+      content.document.location = TEST_URI;
+    });
+
+    yield ready;
+
+    let pid2 = yield getPID(aBrowser);
+
+    isnot(pid1, pid2, "PIDs 1 and 2 should not match");
+    is(true, yield getInLAProc(aBrowser));
+
+    yield Promise.all([
+      ContentTask.spawn(aBrowser, null, () => {
+        content.document.querySelector("#submit").click();
+      }),
+      BrowserTestUtils.browserLoaded(aBrowser)
+    ]);
+
+    let innerText = yield ContentTask.spawn(aBrowser, null, () => {
+      return content.document.body.innerText;
+    });
+    isnot(innerText, "FAIL", "We should not have sent a get request!");
+    is(innerText, "textarea=default+value&button=submit",
+       "The post data should be received by the callee");
+  });
+
   // XXX: Make sure to reset dom.ipc.processCount.webLargeAllocation if adding a
   // test after the above test.
 }
 
 function* largeAllocFailTests() {
   yield BrowserTestUtils.withNewTab("http://example.com", function*(aBrowser) {
     info("Starting test 1");
     let pid1 = yield getPID(aBrowser);
--- a/dom/tests/browser/test_largeAllocation.html
+++ b/dom/tests/browser/test_largeAllocation.html
@@ -1,4 +1,10 @@
 <!doctype html>
 <html>
-  <body>Loaded in a new process!</body>
+  <body>
+    Loaded in a new process!
+    <form action="test_largeAllocationFormSubmit.sjs" method="POST">
+      <input type="text" name="textarea" value="default value">
+      <input type="submit" name="button" value="submit" id="submit">
+    </form>
+  </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/browser/test_largeAllocationFormSubmit.sjs
@@ -0,0 +1,24 @@
+const BinaryInputStream = Components.Constructor("@mozilla.org/binaryinputstream;1",
+                                                 "nsIBinaryInputStream",
+                                                 "setInputStream");
+
+function handleRequest(request, response) {
+  response.setHeader("Large-Allocation", "0", false);
+  response.setHeader("Content-Type", "text/plain", false);
+  response.setStatusLine(request.httpVersion, "200", "Found");
+  if (request.method == "GET") {
+    response.write("FAIL");
+    return;
+  }
+
+  var body = new BinaryInputStream(request.bodyInputStream);
+
+  var avail;
+  var bytes = [];
+
+  while ((avail = body.available()) > 0)
+    Array.prototype.push.apply(bytes, body.readByteArray(avail));
+
+  var data = String.fromCharCode.apply(null, bytes);
+  response.bodyOutputStream.write(data, data.length);
+}
--- a/dom/webidl/ChromeUtils.webidl
+++ b/dom/webidl/ChromeUtils.webidl
@@ -55,16 +55,25 @@ interface ChromeUtils : ThreadSafeChrome
   fillNonDefaultOriginAttributes(optional OriginAttributesDictionary originAttrs);
 
   /**
    * Returns true if the 2 OriginAttributes are equal.
    */
   static boolean
   isOriginAttributesEqual(optional OriginAttributesDictionary aA,
                           optional OriginAttributesDictionary aB);
+
+  /**
+   * Loads and compiles the script at the given URL and returns an object
+   * which may be used to execute it repeatedly, in different globals, without
+   * re-parsing.
+   */
+  [NewObject, Throws]
+  static Promise<PrecompiledScript>
+  compileScript(DOMString url, optional CompileScriptOptionsDictionary options);
 };
 
 /**
  * Used by principals and the script security manager to represent origin
  * attributes. The first dictionary is designed to contain the full set of
  * OriginAttributes, the second is used for pattern-matching (i.e. does this
  * OriginAttributesDictionary match the non-empty attributes in this pattern).
  *
@@ -83,8 +92,29 @@ dictionary OriginAttributesDictionary {
 };
 dictionary OriginAttributesPatternDictionary {
   unsigned long appId;
   unsigned long userContextId;
   boolean inIsolatedMozBrowser;
   unsigned long privateBrowsingId;
   DOMString firstPartyDomain;
 };
+
+dictionary CompileScriptOptionsDictionary {
+  /**
+   * The character set from which to decode the script.
+   */
+  DOMString charset = "utf-8";
+
+  /**
+   * If true, certain parts of the script may be parsed lazily, the first time
+   * they are used, rather than eagerly parsed at load time.
+   */
+  boolean lazilyParse = false;
+
+  /**
+   * If true, the script will be compiled so that its last expression will be
+   * returned as the value of its execution. This makes certain types of
+   * optimization impossible, and disables the JIT in many circumstances, so
+   * should not be used when not absolutely necessary.
+   */
+  boolean hasReturnValue = false;
+};
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -374,16 +374,20 @@ partial interface Document {
   void obsoleteSheet(DOMString sheetURI);
 
   [ChromeOnly] readonly attribute nsIDocShell? docShell;
 
   [ChromeOnly] readonly attribute DOMString contentLanguage;
 
   [ChromeOnly] readonly attribute nsILoadGroup? documentLoadGroup;
 
+  // Blocks the initial document parser until the given promise is settled.
+  [ChromeOnly, Throws]
+  Promise<any> blockParsing(Promise<any> promise);
+
   // like documentURI, except that for error pages, it returns the URI we were
   // trying to load when we hit an error, rather than the error page's own URI.
   [ChromeOnly] readonly attribute URI? mozDocumentURIIfNotForErrorPages;
 };
 
 // Extension to give chrome JS the ability to determine when a document was
 // created to satisfy an iframe with srcdoc attribute.
 partial interface Document {
new file mode 100644
--- /dev/null
+++ b/dom/webidl/PrecompiledScript.webidl
@@ -0,0 +1,32 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ */
+
+/**
+ * Represents a pre-compiled JS script, which can be repeatedly exeuted in
+ * different globals without being re-parsed.
+ */
+[ChromeOnly, Exposed=(Window,System)]
+interface PrecompiledScript {
+  /**
+   * Executes the script in the global of the given object, and returns the
+   * value of its last expression, if compiled with a return value.
+   */
+  [Throws]
+  any executeInGlobal(object global);
+
+  /**
+   * The URL that the script was loaded from.
+   */
+  [Pure]
+  readonly attribute DOMString url;
+
+  /**
+   * True if the script was compiled with a return value, and will return the
+   * value of its last expression when executed.
+   */
+  [Pure]
+  readonly attribute boolean hasReturnValue;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -732,16 +732,17 @@ WEBIDL_FILES = [
     'Permissions.webidl',
     'PermissionStatus.webidl',
     'Plugin.webidl',
     'PluginArray.webidl',
     'PointerEvent.webidl',
     'PopupBoxObject.webidl',
     'Position.webidl',
     'PositionError.webidl',
+    'PrecompiledScript.webidl',
     'Presentation.webidl',
     'PresentationAvailability.webidl',
     'PresentationConnection.webidl',
     'PresentationConnectionList.webidl',
     'PresentationDeviceInfoManager.webidl',
     'PresentationReceiver.webidl',
     'PresentationRequest.webidl',
     'ProcessingInstruction.webidl',
--- a/dom/workers/ServiceWorkerEvents.cpp
+++ b/dom/workers/ServiceWorkerEvents.cpp
@@ -1186,17 +1186,18 @@ ExtendableMessageEvent::GetSource(Nullab
 {
   if (mClient) {
     aValue.SetValue().SetAsClient() = mClient;
   } else if (mServiceWorker) {
     aValue.SetValue().SetAsServiceWorker() = mServiceWorker;
   } else if (mMessagePort) {
     aValue.SetValue().SetAsMessagePort() = mMessagePort;
   } else {
-    MOZ_CRASH("Unexpected source value");
+    // nullptr source is possible for manually constructed event
+    aValue.SetNull();
   }
 }
 
 /* static */ already_AddRefed<ExtendableMessageEvent>
 ExtendableMessageEvent::Constructor(const GlobalObject& aGlobal,
                                     const nsAString& aType,
                                     const ExtendableMessageEventInit& aOptions,
                                     ErrorResult& aRv)
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -467,16 +467,24 @@ WorkerGlobalScope::CreateImageBitmap(con
 
 already_AddRefed<mozilla::dom::Promise>
 WorkerGlobalScope::CreateImageBitmap(const ImageBitmapSource& aImage,
                                      int32_t aOffset, int32_t aLength,
                                      ImageBitmapFormat aFormat,
                                      const Sequence<ChannelPixelLayout>& aLayout,
                                      ErrorResult& aRv)
 {
+  JSContext* cx = GetCurrentThreadJSContext();
+  MOZ_ASSERT(cx);
+
+  if (!ImageBitmap::ExtensionsEnabled(cx, nullptr)) {
+    aRv.Throw(NS_ERROR_TYPE_ERR);
+    return nullptr;
+  }
+
   if (aImage.IsArrayBuffer() || aImage.IsArrayBufferView()) {
     return ImageBitmap::Create(this, aImage, aOffset, aLength, aFormat, aLayout,
                                aRv);
   } else {
     aRv.Throw(NS_ERROR_TYPE_ERR);
     return nullptr;
   }
 }
--- a/dom/xml/nsXMLContentSink.cpp
+++ b/dom/xml/nsXMLContentSink.cpp
@@ -574,18 +574,16 @@ nsXMLContentSink::CloseElement(nsIConten
 
     // Now tell the script that it's ready to go. This may execute the script
     // or return true, or neither if the script doesn't need executing.
     bool block = sele->AttemptToExecute();
 
     // If the parser got blocked, make sure to return the appropriate rv.
     // I'm not sure if this is actually needed or not.
     if (mParser && !mParser->IsParserEnabled()) {
-      // XXX The HTML sink doesn't call BlockParser here, why do we?
-      GetParser()->BlockParser();
       block = true;
     }
 
     return block ? NS_ERROR_HTMLPARSER_BLOCK : NS_OK;
   }
 
   nsresult rv = NS_OK;
   if (nodeInfo->Equals(nsGkAtoms::meta, kNameSpaceID_XHTML) &&
@@ -1010,16 +1008,20 @@ nsXMLContentSink::HandleStartElement(con
   if (content != mDocElement && !mCurrentHead) {
     // This isn't the root and we're not inside an XHTML <head>.
     // Might need to start layout
     MaybeStartLayout(false);
   }
 
   if (content == mDocElement) {
     NotifyDocElementCreated(mDocument);
+
+    if (aInterruptable && NS_SUCCEEDED(result) && mParser && !mParser->IsParserEnabled()) {
+      return NS_ERROR_HTMLPARSER_BLOCK;
+    }
   }
 
   return aInterruptable && NS_SUCCEEDED(result) ? DidProcessATokenImpl() :
                                                   result;
 }
 
 NS_IMETHODIMP
 nsXMLContentSink::HandleEndElement(const char16_t *aName)
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -1013,17 +1013,17 @@ nsXULElement::UnregisterAccessKey(const 
                     UnregisterAccessKey(content, aOldValue.First());
             }
         }
     }
 }
 
 nsresult
 nsXULElement::BeforeSetAttr(int32_t aNamespaceID, nsIAtom* aName,
-                            nsAttrValueOrString* aValue, bool aNotify)
+                            const nsAttrValueOrString* aValue, bool aNotify)
 {
     if (aNamespaceID == kNameSpaceID_None && aName == nsGkAtoms::accesskey &&
         IsInUncomposedDoc()) {
         nsAutoString oldValue;
         if (GetAttr(aNamespaceID, aName, oldValue)) {
             UnregisterAccessKey(oldValue);
         }
     } else if (aNamespaceID == kNameSpaceID_None &&
--- a/dom/xul/nsXULElement.h
+++ b/dom/xul/nsXULElement.h
@@ -746,17 +746,17 @@ protected:
     nsIContent*                         mBindingParent;
 
     /**
      * Abandon our prototype linkage, and copy all attributes locally
      */
     nsresult MakeHeavyweight(nsXULPrototypeElement* aPrototype);
 
     virtual nsresult BeforeSetAttr(int32_t aNamespaceID, nsIAtom* aName,
-                                   nsAttrValueOrString* aValue,
+                                   const nsAttrValueOrString* aValue,
                                    bool aNotify) override;
     virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
                                   const nsAttrValue* aValue, bool aNotify) override;
 
     virtual void UpdateEditableState(bool aNotify) override;
 
     virtual bool ParseAttribute(int32_t aNamespaceID,
                                   nsIAtom* aAttribute,
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -18,17 +18,16 @@
 #include "HTMLEditUtils.h"
 #include "HTMLURIRefObject.h"
 #include "StyleSheetTransactions.h"
 #include "TextEditUtils.h"
 #include "TypeInState.h"
 
 #include "nsIDOMText.h"
 #include "nsIDOMMozNamedAttrMap.h"
-#include "nsIDOMNodeList.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMAttr.h"
 #include "nsIDocumentInlines.h"
 #include "nsIDOMEventTarget.h"
 #include "nsIDOMMouseEvent.h"
 #include "nsIDOMHTMLAnchorElement.h"
 #include "nsISelectionController.h"
 #include "nsIDOMHTMLDocument.h"
--- a/editor/libeditor/TextEditRules.cpp
+++ b/editor/libeditor/TextEditRules.cpp
@@ -25,17 +25,16 @@
 #include "nsError.h"
 #include "nsGkAtoms.h"
 #include "nsIContent.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMElement.h"
 #include "nsIDOMNode.h"
 #include "nsIDOMNodeFilter.h"
 #include "nsIDOMNodeIterator.h"
-#include "nsIDOMNodeList.h"
 #include "nsIDOMText.h"
 #include "nsNameSpaceManager.h"
 #include "nsINode.h"
 #include "nsIPlaintextEditor.h"
 #include "nsISupportsBase.h"
 #include "nsLiteralString.h"
 #include "nsUnicharUtils.h"
 #include "nsIHTMLCollection.h"
--- a/editor/libeditor/TextEditor.cpp
+++ b/editor/libeditor/TextEditor.cpp
@@ -32,17 +32,16 @@
 #include "nsGkAtoms.h"
 #include "nsIClipboard.h"
 #include "nsIContent.h"
 #include "nsIContentIterator.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMElement.h"
 #include "nsIDOMEventTarget.h"
 #include "nsIDOMNode.h"
-#include "nsIDOMNodeList.h"
 #include "nsIDocumentEncoder.h"
 #include "nsIEditRules.h"
 #include "nsINode.h"
 #include "nsIPresShell.h"
 #include "nsISelectionController.h"
 #include "nsISupportsPrimitives.h"
 #include "nsITransferable.h"
 #include "nsIWeakReferenceUtils.h"
@@ -227,76 +226,69 @@ TextEditor::EndEditorInit()
 
 NS_IMETHODIMP
 TextEditor::SetDocumentCharacterSet(const nsACString& characterSet)
 {
   nsresult rv = EditorBase::SetDocumentCharacterSet(characterSet);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Update META charset element.
-  nsCOMPtr<nsIDOMDocument> domdoc = GetDOMDocument();
-  NS_ENSURE_TRUE(domdoc, NS_ERROR_NOT_INITIALIZED);
+  nsCOMPtr<nsIDocument> doc = GetDocument();
+  if (NS_WARN_IF(!doc)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
 
-  if (UpdateMetaCharset(domdoc, characterSet)) {
+  if (UpdateMetaCharset(*doc, characterSet)) {
     return NS_OK;
   }
 
-  nsCOMPtr<nsIDOMNodeList> headList;
-  rv = domdoc->GetElementsByTagName(NS_LITERAL_STRING("head"), getter_AddRefs(headList));
-  NS_ENSURE_SUCCESS(rv, rv);
-  NS_ENSURE_TRUE(headList, NS_OK);
+  RefPtr<nsContentList> headList =
+    doc->GetElementsByTagName(NS_LITERAL_STRING("head"));
+  if (NS_WARN_IF(!headList)) {
+    return NS_OK;
+  }
 
-  nsCOMPtr<nsIDOMNode> headNode;
-  headList->Item(0, getter_AddRefs(headNode));
-  NS_ENSURE_TRUE(headNode, NS_OK);
+  nsCOMPtr<nsIContent> headNode = headList->Item(0);
+  if (NS_WARN_IF(!headNode)) {
+    return NS_OK;
+  }
 
   // Create a new meta charset tag
-  nsCOMPtr<nsIDOMNode> resultNode;
-  rv = CreateNode(NS_LITERAL_STRING("meta"), headNode, 0, getter_AddRefs(resultNode));
-  NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
-  NS_ENSURE_TRUE(resultNode, NS_OK);
+  RefPtr<Element> metaElement = CreateNode(nsGkAtoms::meta, headNode, 0);
+  if (NS_WARN_IF(!metaElement)) {
+    return NS_OK;
+  }
 
   // Set attributes to the created element
   if (characterSet.IsEmpty()) {
     return NS_OK;
   }
 
-  nsCOMPtr<dom::Element> metaElement = do_QueryInterface(resultNode);
-  if (!metaElement) {
-    return NS_OK;
-  }
-
   // not undoable, undo should undo CreateNode
   metaElement->SetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv,
                        NS_LITERAL_STRING("Content-Type"), true);
   metaElement->SetAttr(kNameSpaceID_None, nsGkAtoms::content,
                        NS_LITERAL_STRING("text/html;charset=") +
                          NS_ConvertASCIItoUTF16(characterSet),
                        true);
   return NS_OK;
 }
 
 bool
-TextEditor::UpdateMetaCharset(nsIDOMDocument* aDocument,
+TextEditor::UpdateMetaCharset(nsIDocument& aDocument,
                               const nsACString& aCharacterSet)
 {
-  MOZ_ASSERT(aDocument);
   // get a list of META tags
-  nsCOMPtr<nsIDOMNodeList> list;
-  nsresult rv = aDocument->GetElementsByTagName(NS_LITERAL_STRING("meta"),
-                                                getter_AddRefs(list));
-  NS_ENSURE_SUCCESS(rv, false);
-  NS_ENSURE_TRUE(list, false);
+  RefPtr<nsContentList> metaList =
+    aDocument.GetElementsByTagName(NS_LITERAL_STRING("meta"));
+  if (NS_WARN_IF(!metaList)) {
+    return false;
+  }
 
-  nsCOMPtr<nsINodeList> metaList = do_QueryInterface(list);
-
-  uint32_t listLength = 0;
-  metaList->GetLength(&listLength);
-
-  for (uint32_t i = 0; i < listLength; ++i) {
+  for (uint32_t i = 0; i < metaList->Length(true); ++i) {
     nsCOMPtr<nsIContent> metaNode = metaList->Item(i);
     MOZ_ASSERT(metaNode);
 
     if (!metaNode->IsElement()) {
       continue;
     }
 
     nsAutoString currentValue;
@@ -317,20 +309,21 @@ TextEditor::UpdateMetaCharset(nsIDOMDocu
     if (!FindInReadable(charsetEquals, start, end,
                         nsCaseInsensitiveStringComparator())) {
       continue;
     }
 
     // set attribute to <original prefix> charset=text/html
     RefPtr<Element> metaElement = metaNode->AsElement();
     MOZ_ASSERT(metaElement);
-    rv = EditorBase::SetAttribute(metaElement, nsGkAtoms::content,
-                                  Substring(originalStart, start) +
-                                    charsetEquals +
-                                    NS_ConvertASCIItoUTF16(aCharacterSet));
+    nsresult rv =
+      EditorBase::SetAttribute(metaElement, nsGkAtoms::content,
+                               Substring(originalStart, start) +
+                                 charsetEquals +
+                                 NS_ConvertASCIItoUTF16(aCharacterSet));
     return NS_SUCCEEDED(rv);
   }
   return false;
 }
 
 NS_IMETHODIMP
 TextEditor::InitRules()
 {
--- a/editor/libeditor/TextEditor.h
+++ b/editor/libeditor/TextEditor.h
@@ -222,17 +222,17 @@ protected:
     ePasswordFieldAllowed,
     ePasswordFieldNotAllowed
   };
   bool CanCutOrCopy(PasswordFieldAllowed aPasswordFieldAllowed);
   bool FireClipboardEvent(EventMessage aEventMessage,
                           int32_t aSelectionType,
                           bool* aActionTaken = nullptr);
 
-  bool UpdateMetaCharset(nsIDOMDocument* aDocument,
+  bool UpdateMetaCharset(nsIDocument& aDocument,
                          const nsACString& aCharacterSet);
 
 protected:
   nsCOMPtr<nsIEditRules> mRules;
   int32_t mWrapColumn;
   int32_t mMaxTextLength;
   int32_t mInitTriggerCounter;
   int32_t mNewlineHandling;
--- a/editor/libeditor/tests/mochitest.ini
+++ b/editor/libeditor/tests/mochitest.ini
@@ -230,16 +230,17 @@ skip-if = toolkit == 'android' # bug 131
 [test_bug1328023.html]
 [test_bug1330796.html]
 [test_bug1332876.html]
 
 [test_CF_HTML_clipboard.html]
 subsuite = clipboard
 [test_composition_event_created_in_chrome.html]
 [test_contenteditable_focus.html]
+[test_documentCharacterSet.html]
 [test_dom_input_event_on_htmleditor.html]
 skip-if = toolkit == 'android' # bug 1054087
 [test_dom_input_event_on_texteditor.html]
 [test_dragdrop.html]
 skip-if = os == 'android'
 [test_inline_style_cache.html]
 [test_inlineTableEditing.html]
 [test_keypress_untrusted_event.html]
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/tests/test_documentCharacterSet.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <script type="application/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>
+
+<iframe></iframe>
+
+<pre id="test">
+
+<script class="testbody" type="application/javascript">
+function getEditor() {
+  const Ci = SpecialPowers.Ci;
+  let editframe = window.frames[0];
+  return SpecialPowers.wrap(editframe)
+                      .QueryInterface(Ci.nsIInterfaceRequestor)
+                      .getInterface(Ci.nsIWebNavigation)
+                      .QueryInterface(Ci.nsIInterfaceRequestor)
+                      .getInterface(Ci.nsIEditingSession)
+                      .getEditorForWindow(editframe);
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+  let editdoc = window.frames[0].document;
+  editdoc.designMode = 'on';
+  let editor = getEditor();
+
+  editor.documentCharacterSet = "us-ascii";
+  let meta = editdoc.getElementsByTagName("meta")[0];
+  is(meta.getAttribute("http-equiv"), "Content-Type",
+     "meta element should have http-equiv");
+  is(meta.getAttribute("content"), "text/html;charset=us-ascii",
+     "charset should be set as us-ascii");
+
+  let dummyMeta = editdoc.createElement("meta");
+  dummyMeta.setAttribute("name", "keywords");
+  dummyMeta.setAttribute("content", "test");
+  meta.parentNode.insertBefore(dummyMeta, meta);
+
+  editor.documentCharacterSet = "utf-8";
+
+  meta = editdoc.getElementsByTagName("meta")[0];
+  isnot(meta.getAttribute("http-equiv"), "Content-Type",
+     "first meta element shouldn't have http-equiv");
+
+  meta = editdoc.getElementsByTagName("meta")[1];
+  is(meta.getAttribute("http-equiv"), "Content-Type",
+     "second meta element should have http-equiv");
+  is(meta.getAttribute("content"), "text/html;charset=utf-8",
+     "charset should be set as utf-8");
+
+  SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
--- a/extensions/pref/autoconfig/src/prefcalls.js
+++ b/extensions/pref/autoconfig/src/prefcalls.js
@@ -22,21 +22,17 @@ function getPrefBranch() {
 
 function pref(prefName, value) {
 
     try { 
         var prefBranch = getPrefBranch();
 
         if (typeof value == "string") {
             if (gIsUTF8) {
-                const nsISupportsString = Components.interfaces.nsISupportsString;
-                let string = Components.classes["@mozilla.org/supports-string;1"]
-                                       .createInstance(nsISupportsString);
-                string.data = value;
-                prefBranch.setComplexValue(prefName, nsISupportsString, string);
+                prefBranch.setStringPref(prefName, value);
                 return;
             }
             prefBranch.setCharPref(prefName, value);
         }
         else if (typeof value == "number") {
             prefBranch.setIntPref(prefName, value);
         }
         else if (typeof value == "boolean") {
@@ -51,21 +47,17 @@ function pref(prefName, value) {
 function defaultPref(prefName, value) {
     
     try {
         var prefService = Components.classes[PrefServiceContractID]
                                     .getService(nsIPrefService);        
         var prefBranch = prefService.getDefaultBranch(null);
         if (typeof value == "string") {
             if (gIsUTF8) {
-                const nsISupportsString = Components.interfaces.nsISupportsString;
-                let string = Components.classes["@mozilla.org/supports-string;1"]
-                                       .createInstance(nsISupportsString);
-                string.data = value;
-                prefBranch.setComplexValue(prefName, nsISupportsString, string);
+                prefBranch.setStringPref(prefName, value);
                 return;
             }
             prefBranch.setCharPref(prefName, value);
         }
         else if (typeof value == "number") {
             prefBranch.setIntPref(prefName, value);
         }
         else if (typeof value == "boolean") {
@@ -110,20 +102,17 @@ function getPref(prefName) {
     
     try {
         var prefBranch = getPrefBranch();
         
         switch (prefBranch.getPrefType(prefName)) {
             
         case prefBranch.PREF_STRING:
             if (gIsUTF8) {
-                const nsISupportsString = Components.interfaces.nsISupportsString;
-                let string = Components.classes["@mozilla.org/supports-string;1"]
-                                       .createInstance(nsISupportsString);
-                return prefBranch.getComplexValue(prefName, nsISupportsString).data;
+                return prefBranch.getStringPref(prefName);
             }
             return prefBranch.getCharPref(prefName);
             
         case prefBranch.PREF_INT:
             return prefBranch.getIntPref(prefName);
             
         case prefBranch.PREF_BOOL:
             return prefBranch.getBoolPref(prefName);
--- a/extensions/pref/autoconfig/test/unit/test_autoconfig_nonascii.js
+++ b/extensions/pref/autoconfig/test/unit/test_autoconfig_nonascii.js
@@ -53,17 +53,17 @@ function run_test() {
       let autoConfigCfg = testDir.clone();
       autoConfigCfg.append(test.filename);
       autoConfigCfg.copyTo(greD, "autoconfig.cfg");
   
       obsvc.notifyObservers(ps, "prefservice:before-read-userprefs", null);
   
       for (let prefName in test.prefs) {
         do_check_eq(test.prefs[prefName],
-                    prefs.getComplexValue(prefName, Ci.nsISupportsString).data);
+                    prefs.getStringPref(prefName));
       }
   
       ps.resetPrefs();
       // Make sure pref values are reset.
       for (let prefName in test.prefs) {
         do_check_eq(Ci.nsIPrefBranch.PREF_INVALID, prefs.getPrefType(prefName));
       }
     }
--- a/extensions/universalchardet/tests/CharsetDetectionTests.js
+++ b/extensions/universalchardet/tests/CharsetDetectionTests.js
@@ -59,21 +59,17 @@ function InitDetectorTests()
                 getChromeDir(getResolvedURI(window.location.href));
     gLocalDir = ioService.newFileURI(dir).spec;
 }
 
 function SetDetectorPref(aPrefValue)
 {
     var prefService = Cc["@mozilla.org/preferences-service;1"]
                       .getService(Ci.nsIPrefBranch);
-    var str = Cc["@mozilla.org/supports-string;1"]
-              .createInstance(Ci.nsISupportsString);
-    str.data = aPrefValue;
-    prefService.setComplexValue("intl.charset.detector",
-                                Ci.nsISupportsString, str);
+    prefService.setStringPref("intl.charset.detector", aPrefValue);
     gCurrentDetector = aPrefValue;
 }
 
 function DoDetectionTest() {
     var iframeDoc = $("testframe").contentDocument;
     var charset = iframeDoc.characterSet;
 
     is(charset, gExpectedCharset,
--- a/gfx/src/nsFontMetrics.cpp
+++ b/gfx/src/nsFontMetrics.cpp
@@ -76,39 +76,39 @@ private:
             }
         }
         return flags;
     }
 
     RefPtr<gfxTextRun> mTextRun;
 };
 
-class StubPropertyProvider : public gfxTextRun::PropertyProvider {
+class StubPropertyProvider final : public gfxTextRun::PropertyProvider {
 public:
-    virtual void GetHyphenationBreaks(gfxTextRun::Range aRange,
-                                      gfxTextRun::HyphenType* aBreakBefore) {
+    void GetHyphenationBreaks(gfxTextRun::Range aRange,
+                              gfxTextRun::HyphenType* aBreakBefore) const {
         NS_ERROR("This shouldn't be called because we never call BreakAndMeasureText");
     }
-    virtual mozilla::StyleHyphens GetHyphensOption() {
+    mozilla::StyleHyphens GetHyphensOption() const {
         NS_ERROR("This shouldn't be called because we never call BreakAndMeasureText");
         return mozilla::StyleHyphens::None;
     }
-    virtual gfxFloat GetHyphenWidth() {
+    gfxFloat GetHyphenWidth() const {
         NS_ERROR("This shouldn't be called because we never enable hyphens");
         return 0;
     }
-    virtual already_AddRefed<mozilla::gfx::DrawTarget> GetDrawTarget() {
+    already_AddRefed<mozilla::gfx::DrawTarget> GetDrawTarget() const {
         NS_ERROR("This shouldn't be called because we never enable hyphens");
         return nullptr;
     }
-    virtual uint32_t GetAppUnitsPerDevUnit() {
+    uint32_t GetAppUnitsPerDevUnit() const {
         NS_ERROR("This shouldn't be called because we never enable hyphens");
         return 60;
     }
-    virtual void GetSpacing(gfxTextRun::Range aRange, Spacing* aSpacing) {
+    void GetSpacing(gfxTextRun::Range aRange, Spacing* aSpacing) const {
         NS_ERROR("This shouldn't be called because we never enable spacing");
     }
 };
 
 } // namespace
 
 nsFontMetrics::nsFontMetrics(const nsFont& aFont, const Params& aParams,
                              nsDeviceContext *aContext)
--- a/gfx/thebes/gfxTextRun.cpp
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -2182,17 +2182,17 @@ gfxFontGroup::MakeHyphenTextRun(DrawTarg
     }
 
     static const uint8_t dash = '-';
     return MakeTextRun(&dash, 1, aDrawTarget, aAppUnitsPerDevUnit,
                        gfxFontGroup::TEXT_IS_PERSISTENT, nullptr);
 }
 
 gfxFloat
-gfxFontGroup::GetHyphenWidth(gfxTextRun::PropertyProvider *aProvider)
+gfxFontGroup::GetHyphenWidth(const gfxTextRun::PropertyProvider* aProvider)
 {
     if (mHyphenWidth < 0) {
         RefPtr<DrawTarget> dt(aProvider->GetDrawTarget());
         if (dt) {
             RefPtr<gfxTextRun>
                 hyphRun(MakeHyphenTextRun(dt,
                                           aProvider->GetAppUnitsPerDevUnit()));
             mHyphenWidth = hyphRun.get() ? hyphRun->GetAdvanceWidth() : 0;
--- a/gfx/thebes/gfxTextRun.h
+++ b/gfx/thebes/gfxTextRun.h
@@ -200,44 +200,44 @@ public:
      * is unable to apply it in some context. Exception: spacing around a
      * whitespace character MUST always be applied.
      */
     class PropertyProvider {
     public:
         // Detect hyphenation break opportunities in the given range; breaks
         // not at cluster boundaries will be ignored.
         virtual void GetHyphenationBreaks(Range aRange,
-                                          HyphenType *aBreakBefore) = 0;
+                                          HyphenType *aBreakBefore) const = 0;
 
         // Returns the provider's hyphenation setting, so callers can decide
         // whether it is necessary to call GetHyphenationBreaks.
         // Result is an StyleHyphens value.
-        virtual mozilla::StyleHyphens GetHyphensOption() = 0;
+        virtual mozilla::StyleHyphens GetHyphensOption() const = 0;
 
         // Returns the extra width that will be consumed by a hyphen. This should
         // be constant for a given textrun.
-        virtual gfxFloat GetHyphenWidth() = 0;
+        virtual gfxFloat GetHyphenWidth() const = 0;
 
         typedef gfxFont::Spacing Spacing;
 
         /**
          * Get the spacing around the indicated characters. Spacing must be zero
          * inside clusters. In other words, if character i is not
          * CLUSTER_START, then character i-1 must have zero after-spacing and
          * character i must have zero before-spacing.
          */
-        virtual void GetSpacing(Range aRange, Spacing *aSpacing) = 0;
+        virtual void GetSpacing(Range aRange, Spacing *aSpacing) const = 0;
 
         // Returns a gfxContext that can be used to measure the hyphen glyph.
         // Only called if the hyphen width is requested.
-        virtual already_AddRefed<DrawTarget> GetDrawTarget() = 0;
+        virtual already_AddRefed<DrawTarget> GetDrawTarget() const = 0;
 
         // Return the appUnitsPerDevUnit value to be used when measuring.
         // Only called if the hyphen width is requested.
-        virtual uint32_t GetAppUnitsPerDevUnit() = 0;
+        virtual uint32_t GetAppUnitsPerDevUnit() const = 0;
     };
 
     struct DrawParams
     {
         gfxContext* context;
         DrawMode drawMode = DrawMode::GLYPH_FILL;
         nscolor textStrokeColor = 0;
         gfxPattern* textStrokePattern = nullptr;
@@ -845,23 +845,18 @@ public:
                 gfxMissingFontRecorder *aMFR)
     {
         gfxTextRunFactory::Parameters params = {
             aRefDrawTarget, nullptr, nullptr, nullptr, 0, aAppUnitsPerDevUnit
         };
         return MakeTextRun(aString, aLength, &params, aFlags, aMFR);
     }
 
-    /**
-     * Get the (possibly-cached) width of the hyphen character.
-     * The aCtx and aAppUnitsPerDevUnit parameters will be used only if
-     * needed to initialize the cached hyphen width; otherwise they are
-     * ignored.
-     */
-    gfxFloat GetHyphenWidth(gfxTextRun::PropertyProvider* aProvider);
+    // Get the (possibly-cached) width of the hyphen character.
+    gfxFloat GetHyphenWidth(const gfxTextRun::PropertyProvider* aProvider);
 
     /**
      * Make a text run representing a single hyphen character.
      * This will use U+2010 HYPHEN if available in the first font,
      * otherwise fall back to U+002D HYPHEN-MINUS.
      * The caller is responsible for deleting the returned text run
      * when no longer required.
      */
--- a/image/FrameAnimator.cpp
+++ b/image/FrameAnimator.cpp
@@ -27,16 +27,27 @@ namespace image {
 
 void
 AnimationState::NotifyDecodeComplete()
 {
   // If we weren't discarded before the decode finished then mark ourselves as
   // currently decoded.
   if (!mDiscarded) {
     mIsCurrentlyDecoded = true;
+
+    // Animated images that have finished their animation (ie because it is a
+    // finite length animation) don't have RequestRefresh called on them, and so
+    // mCompositedFrameInvalid would never get cleared. We clear it here (and
+    // also in RasterImage::Decode when we create a decoder for an image that
+    // has finished animated so it can display sooner than waiting until the
+    // decode completes). This is safe to do for images that aren't finished
+    // animating because before we paint the refresh driver will call into us
+    // to advance to the correct frame, and that will succeed because we have
+    // all the frames.
+    mCompositedFrameInvalid = false;
   }
   mHasBeenDecoded = true;
 }
 
 void
 AnimationState::SetDiscarded(bool aDiscarded)
 {
   if (aDiscarded) {
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -1199,16 +1199,22 @@ RasterImage::Decode(const IntSize& aSize
     // alpha or not.
     surfaceFlags &= ~SurfaceFlags::NO_PREMULTIPLY_ALPHA;
   }
 
   // Create a decoder.
   RefPtr<IDecodingTask> task;
   if (mAnimationState && aPlaybackType == PlaybackType::eAnimated) {
     mAnimationState->SetDiscarded(false);
+    // If the animation is finished we can draw right away because we just draw
+    // the final frame all the time from now on. See comment in
+    // AnimationState::NotifyDecodeComplete.
+    if (mAnimationFinished) {
+      mAnimationState->SetCompositedFrameInvalid(false);
+    }
     task = DecoderFactory::CreateAnimationDecoder(mDecoderType, WrapNotNull(this),
                                                   mSourceBuffer, mSize,
                                                   decoderFlags, surfaceFlags);
   } else {
     task = DecoderFactory::CreateDecoder(mDecoderType, WrapNotNull(this),
                                          mSourceBuffer, mSize, aSize,
                                          decoderFlags, surfaceFlags);
   }
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -1212,36 +1212,20 @@ GetPropIRGenerator::tryAttachStringLengt
 bool
 GetPropIRGenerator::tryAttachStringChar(ValOperandId valId, ValOperandId indexId)
 {
     MOZ_ASSERT(idVal_.isInt32());
 
     if (!val_.isString())
         return false;
 
+    JSString* str = val_.toString();
     int32_t index = idVal_.toInt32();
-    if (index < 0)
-        return false;
-
-    JSString* str = val_.toString();
-    if (size_t(index) >= str->length())
-        return false;
-
-    // This follows JSString::getChar, otherwise we fail to attach getChar in a lot of cases.
-    if (str->isRope()) {
-        JSRope* rope = &str->asRope();
-
-        // Make sure the left side contains the index.
-        if (size_t(index) >= rope->leftChild()->length())
-            return false;
-
-        str = rope->leftChild();
-    }
-
-    if (!str->isLinear() ||
+    if (size_t(index) >= str->length() ||
+        !str->isLinear() ||
         str->asLinear().latin1OrTwoByteChar(index) >= StaticStrings::UNIT_STATIC_LIMIT)
     {
         return false;
     }
 
     StringOperandId strId = writer.guardIsString(valId);
     Int32OperandId int32IndexId = writer.guardIsInt32Index(indexId);
     writer.loadStringCharResult(strId, int32IndexId);
--- a/js/src/jit/CacheIRCompiler.cpp
+++ b/js/src/jit/CacheIRCompiler.cpp
@@ -1753,20 +1753,22 @@ CacheIRCompiler::emitLoadStringCharResul
     Register index = allocator.useRegister(masm, reader.int32OperandId());
     AutoScratchRegisterMaybeOutput scratch1(allocator, masm, output);
     AutoScratchRegister scratch2(allocator, masm);
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
+    masm.branchIfRope(str, failure->label());
+
     // Bounds check, load string char.
     masm.branch32(Assembler::BelowOrEqual, Address(str, JSString::offsetOfLength()),
                   index, failure->label());
-    masm.loadStringChar(str, index, scratch1, failure->label());
+    masm.loadStringChar(str, index, scratch1);
 
     // Load StaticString for this char.
     masm.branch32(Assembler::AboveOrEqual, scratch1, Imm32(StaticStrings::UNIT_STATIC_LIMIT),
                   failure->label());
     masm.movePtr(ImmPtr(&cx_->staticStrings().unitStaticTable), scratch2);
     masm.loadPtr(BaseIndex(scratch2, scratch1, ScalePointer), scratch2);
 
     EmitStoreResult(masm, scratch2, JSVAL_TYPE_STRING, output);
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -7961,17 +7961,20 @@ static const VMFunction CharCodeAtInfo =
 void
 CodeGenerator::visitCharCodeAt(LCharCodeAt* lir)
 {
     Register str = ToRegister(lir->str());
     Register index = ToRegister(lir->index());
     Register output = ToRegister(lir->output());
 
     OutOfLineCode* ool = oolCallVM(CharCodeAtInfo, lir, ArgList(str, index), StoreRegisterTo(output));
-    masm.loadStringChar(str, index, output, ool->entry());
+
+    masm.branchIfRope(str, ool->entry());
+    masm.loadStringChar(str, index, output);
+
     masm.bind(ool->rejoin());
 }
 
 typedef JSFlatString* (*StringFromCharCodeFn)(JSContext*, int32_t);
 static const VMFunction StringFromCharCodeInfo =
     FunctionInfo<StringFromCharCodeFn>(jit::StringFromCharCode, "StringFromCharCode");
 
 void
--- a/js/src/jit/MacroAssembler-inl.h
+++ b/js/src/jit/MacroAssembler-inl.h
@@ -391,24 +391,16 @@ void
 MacroAssembler::branchIfRope(Register str, Label* label)
 {
     Address flags(str, JSString::offsetOfFlags());
     static_assert(JSString::ROPE_FLAGS == 0, "Rope type flags must be 0");
     branchTest32(Assembler::Zero, flags, Imm32(JSString::TYPE_FLAGS_MASK), label);
 }
 
 void
-MacroAssembler::branchIfNotRope(Register str, Label* label)
-{
-    Address flags(str, JSString::offsetOfFlags());
-    static_assert(JSString::ROPE_FLAGS == 0, "Rope type flags must be 0");
-    branchTest32(Assembler::NonZero, flags, Imm32(JSString::TYPE_FLAGS_MASK), label);
-}
-
-void
 MacroAssembler::branchLatin1String(Register string, Label* label)
 {
     branchTest32(Assembler::NonZero, Address(string, JSString::offsetOfFlags()),
                  Imm32(JSString::LATIN1_CHARS_BIT), label);
 }
 
 void
 MacroAssembler::branchTwoByteString(Register string, Label* label)
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -1378,39 +1378,22 @@ MacroAssembler::loadStringChars(Register
 
     bind(&isInline);
     computeEffectiveAddress(Address(str, JSInlineString::offsetOfInlineStorage()), dest);
 
     bind(&done);
 }
 
 void
-MacroAssembler::loadStringChar(Register str, Register index, Register output, Label* fail)
+MacroAssembler::loadStringChar(Register str, Register index, Register output)
 {
     MOZ_ASSERT(str != output);
     MOZ_ASSERT(index != output);
 
-    movePtr(str, output);
-
-    // This follows JSString::getChar.
-    Label notRope;
-    branchIfNotRope(str, &notRope);
-
-    // Load leftChild.
-    loadPtr(Address(str, JSRope::offsetOfLeft()), output);
-
-    // Check if the index is contained in the leftChild.
-    // Todo: Handle index in the rightChild.
-    branch32(Assembler::BelowOrEqual, Address(output, JSString::offsetOfLength()), index, fail);
-
-    // If the left side is another rope, give up.
-    branchIfRope(output, fail);
-
-    bind(&notRope);
-    loadStringChars(output, output);
+    loadStringChars(str, output);
 
     Label isLatin1, done;
     branchLatin1String(str, &isLatin1);
     load16ZeroExtend(BaseIndex(output, index, TimesTwo), output);
     jump(&done);
 
     bind(&isLatin1);
     load8ZeroExtend(BaseIndex(output, index, TimesOne), output);
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -1108,17 +1108,16 @@ class MacroAssembler : public MacroAssem
     // Branches to |label| if |reg| is false. |reg| should be a C++ bool.
     template <class L>
     inline void branchIfFalseBool(Register reg, L label);
 
     // Branches to |label| if |reg| is true. |reg| should be a C++ bool.
     inline void branchIfTrueBool(Register reg, Label* label);
 
     inline void branchIfRope(Register str, Label* label);
-    inline void branchIfNotRope(Register str, Label* label);
 
     inline void branchLatin1String(Register string, Label* label);
     inline void branchTwoByteString(Register string, Label* label);
 
     inline void branchIfFunctionHasNoScript(Register fun, Label* label);
     inline void branchIfInterpreted(Register fun, Label* label);
 
     inline void branchFunctionKind(Condition cond, JSFunction::FunctionKind kind, Register fun,
@@ -1489,17 +1488,17 @@ class MacroAssembler : public MacroAssem
         loadPtr(Address(dest, ObjectGroup::offsetOfProto()), dest);
     }
 
     void loadStringLength(Register str, Register dest) {
         load32(Address(str, JSString::offsetOfLength()), dest);
     }
 
     void loadStringChars(Register str, Register dest);
-    void loadStringChar(Register str, Register index, Register output, Label* fail);
+    void loadStringChar(Register str, Register index, Register output);
 
     void loadJSContext(Register dest);
     void loadJitActivation(Register dest) {
         loadJSContext(dest);
         loadPtr(Address(dest, offsetof(JSContext, activation_)), dest);
     }
     void loadWasmActivationFromTls(Register dest) {
         loadPtr(Address(WasmTlsReg, offsetof(wasm::TlsData, cx)), dest);
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -7940,22 +7940,15 @@ js::gc::detail::CellIsNotGray(const Cell
 
     // The cell is gray, but may eventually be marked black if we are in an
     // incremental GC and the cell is reachable by something on the mark stack.
 
     auto rt = tc->runtimeFromAnyThread();
     if (!rt->gc.isIncrementalGCInProgress() || tc->zone()->wasGCStarted())
         return false;
 
-    // Bug 1346874 - The stack check is expensive. Android tests are already
-    // slow enough that this check can easily push them over the threshold to a
-    // timeout. So always assume the best on Android.
-# ifdef ANDROID
-    return true;
-# else
     Zone* sourceZone = rt->gc.marker.stackContainsCrossZonePointerTo(tc);
     if (sourceZone && sourceZone->wasGCStarted())
         return true;
 
     return false;
-# endif
 }
 #endif
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/loader/ChromeScriptLoader.cpp
@@ -0,0 +1,359 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PrecompiledScript.h"
+
+#include "nsIURI.h"
+#include "nsIChannel.h"
+#include "nsNetUtil.h"
+#include "nsScriptLoader.h"
+#include "nsThreadUtils.h"
+
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "js/Utility.h"
+
+#include "mozilla/dom/ChromeUtils.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/SystemGroup.h"
+#include "nsCycleCollectionParticipant.h"
+
+using namespace JS;
+using namespace mozilla;
+using namespace mozilla::dom;
+
+class AsyncScriptCompiler final : public nsIIncrementalStreamLoaderObserver
+                                , public Runnable
+{
+public:
+    NS_DECL_ISUPPORTS_INHERITED
+    NS_DECL_NSIINCREMENTALSTREAMLOADEROBSERVER
+    NS_DECL_NSIRUNNABLE
+
+    AsyncScriptCompiler(JSContext* aCx, nsIGlobalObject* aGlobal,
+                        const nsACString& aURL,
+                        const CompileScriptOptionsDictionary& aOptions,
+                        Promise* aPromise)
+        : mOptions(aCx)
+        , mURL(aURL)
+        , mGlobalObject(aGlobal)
+        , mPromise(aPromise)
+        , mCharset(aOptions.mCharset)
+    {
+        mOptions.setVersion(JSVERSION_DEFAULT)
+                .setNoScriptRval(!aOptions.mHasReturnValue)
+                .setCanLazilyParse(aOptions.mLazilyParse)
+                .setFile(aCx, mURL.get());
+    }
+
+    nsresult Start(nsIPrincipal* aPrincipal);
+
+    inline void
+    SetToken(void* aToken)
+    {
+        mToken = aToken;
+    }
+
+protected:
+    virtual ~AsyncScriptCompiler() {
+        if (mPromise->State() == Promise::PromiseState::Pending) {
+            mPromise->MaybeReject(NS_ERROR_FAILURE);
+        }
+    }
+
+private:
+    void Reject(JSContext* aCx);
+    void Reject(JSContext* aCx, const char* aMxg);
+
+    bool StartCompile(JSContext* aCx);
+    void FinishCompile(JSContext* aCx);
+    void Finish(JSContext* aCx, Handle<JSScript*> script);
+
+    OwningCompileOptions        mOptions;
+    nsCString                   mURL;
+    nsCOMPtr<nsIGlobalObject>   mGlobalObject;
+    RefPtr<Promise>             mPromise;
+    nsString                    mCharset;
+    void*                       mToken;
+    UniqueTwoByteChars          mScriptText;
+    size_t                      mScriptLength;
+};
+
+NS_IMPL_QUERY_INTERFACE_INHERITED(AsyncScriptCompiler, Runnable, nsIIncrementalStreamLoaderObserver)
+NS_IMPL_ADDREF_INHERITED(AsyncScriptCompiler, Runnable)
+NS_IMPL_RELEASE_INHERITED(AsyncScriptCompiler, Runnable)
+
+nsresult
+AsyncScriptCompiler::Start(nsIPrincipal* aPrincipal)
+{
+    nsCOMPtr<nsIURI> uri;
+    nsresult rv = NS_NewURI(getter_AddRefs(uri), mURL);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<nsIChannel> channel;
+    rv = NS_NewChannel(getter_AddRefs(channel),
+                       uri, aPrincipal,
+                       nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+                       nsIContentPolicy::TYPE_OTHER);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<nsIIncrementalStreamLoader> loader;
+    rv = NS_NewIncrementalStreamLoader(getter_AddRefs(loader), this);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return channel->AsyncOpen2(loader);
+}
+
+static void
+OffThreadScriptLoaderCallback(void* aToken, void* aCallbackData)
+{
+    RefPtr<AsyncScriptCompiler> scriptCompiler = dont_AddRef(
+        static_cast<AsyncScriptCompiler*>(aCallbackData));
+
+    scriptCompiler->SetToken(aToken);
+
+    SystemGroup::Dispatch("ScriptLoader::FinishCompile",
+                          TaskCategory::Other,
+                          scriptCompiler.forget());
+}
+
+bool
+AsyncScriptCompiler::StartCompile(JSContext* aCx)
+{
+    Rooted<JSObject*> global(aCx, mGlobalObject->GetGlobalJSObject());
+
+    if (JS::CanCompileOffThread(aCx, mOptions, mScriptLength)) {
+        if (!JS::CompileOffThread(aCx, mOptions, mScriptText.get(), mScriptLength,
+                                  OffThreadScriptLoaderCallback,
+                                  static_cast<void*>(this))) {
+            return false;
+        }
+
+        NS_ADDREF(this);
+        return true;
+    }
+
+    Rooted<JSScript*> script(aCx);
+    if (!JS::Compile(aCx, mOptions, mScriptText.get(), mScriptLength, &script)) {
+        return false;
+    }
+
+    Finish(aCx, script);
+    return true;
+}
+
+NS_IMETHODIMP
+AsyncScriptCompiler::Run()
+{
+    AutoJSAPI jsapi;
+    if (jsapi.Init(mGlobalObject)) {
+        FinishCompile(jsapi.cx());
+    } else {
+        jsapi.Init();
+        JS::CancelOffThreadScript(jsapi.cx(), mToken);
+
+        mPromise->MaybeReject(NS_ERROR_FAILURE);
+    }
+
+    return NS_OK;
+}
+
+void
+AsyncScriptCompiler::FinishCompile(JSContext* aCx)
+{
+    Rooted<JSScript*> script(aCx, JS::FinishOffThreadScript(aCx, mToken));
+
+    Finish(aCx, script);
+}
+
+
+void
+AsyncScriptCompiler::Finish(JSContext* aCx, Handle<JSScript*> aScript)
+{
+    RefPtr<PrecompiledScript> result = new PrecompiledScript(mGlobalObject, aScript, mOptions);
+
+    mPromise->MaybeResolve(result);
+}
+
+void
+AsyncScriptCompiler::Reject(JSContext* aCx)
+{
+    RootedValue value(aCx, JS::UndefinedValue());
+    if (JS_GetPendingException(aCx, &value)) {
+        JS_ClearPendingException(aCx);
+    }
+    mPromise->MaybeReject(aCx, value);
+}
+
+void
+AsyncScriptCompiler::Reject(JSContext* aCx, const char* aMsg)
+{
+    nsAutoCString msg(aMsg);
+    msg.Append(": ");
+    msg.Append(mURL);
+
+    RootedValue exn(aCx, StringValue(JS_NewStringCopyZ(aCx, msg.get())));
+    JS_SetPendingException(aCx, exn);
+
+    Reject(aCx);
+}
+
+NS_IMETHODIMP
+AsyncScriptCompiler::OnIncrementalData(nsIIncrementalStreamLoader* aLoader,
+                                       nsISupports* aContext,
+                                       uint32_t aDataLength,
+                                       const uint8_t* aData,
+                                       uint32_t *aConsumedData)
+{
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+AsyncScriptCompiler::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
+                                      nsISupports* aContext,
+                                      nsresult aStatus,
+                                      uint32_t aLength,
+                                      const uint8_t* aBuf)
+{
+    AutoJSAPI jsapi;
+    if (!jsapi.Init(mGlobalObject)) {
+        mPromise->MaybeReject(NS_ERROR_FAILURE);
+        return NS_OK;
+    }
+
+    JSContext* cx = jsapi.cx();
+
+    if (NS_FAILED(aStatus)) {
+        Reject(cx, "Unable to load script");
+        return NS_OK;
+    }
+
+    nsresult rv = nsScriptLoader::ConvertToUTF16(
+        nullptr, aBuf, aLength, mCharset, nullptr, mScriptText, mScriptLength);
+    if (NS_FAILED(rv)) {
+        Reject(cx, "Unable to decode script");
+        return NS_OK;
+    }
+
+    if (!StartCompile(cx)) {
+        Reject(cx);
+    }
+
+    return NS_OK;
+}
+
+
+namespace mozilla {
+namespace dom {
+
+/* static */ already_AddRefed<Promise>
+ChromeUtils::CompileScript(GlobalObject& aGlobal,
+                           const nsAString& aURL,
+                           const CompileScriptOptionsDictionary& aOptions,
+                           ErrorResult& aRv)
+{
+    nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+    MOZ_ASSERT(global);
+
+    RefPtr<Promise> promise = Promise::Create(global, aRv);
+    if (aRv.Failed()) {
+        return nullptr;
+    }
+
+    NS_ConvertUTF16toUTF8 url(aURL);
+    RefPtr<AsyncScriptCompiler> compiler = new AsyncScriptCompiler(aGlobal.Context(), global, url, aOptions, promise);
+
+    nsresult rv = compiler->Start(aGlobal.GetSubjectPrincipal());
+    if (NS_FAILED(rv)) {
+        promise->MaybeReject(rv);
+    }
+
+    return promise.forget();
+}
+
+PrecompiledScript::PrecompiledScript(nsISupports* aParent, Handle<JSScript*> aScript,
+                                     JS::ReadOnlyCompileOptions& aOptions)
+    : mParent(aParent)
+    , mScript(aScript)
+    , mURL(aOptions.filename())
+    , mHasReturnValue(!aOptions.noScriptRval)
+{
+    MOZ_ASSERT(aParent);
+    MOZ_ASSERT(aScript);
+
+    mozilla::HoldJSObjects(this);
+};
+
+PrecompiledScript::~PrecompiledScript()
+{
+    mozilla::DropJSObjects(this);
+}
+
+void
+PrecompiledScript::ExecuteInGlobal(JSContext* aCx, HandleObject aGlobal,
+                                   MutableHandleValue aRval,
+                                   ErrorResult& aRv)
+{
+    {
+        RootedObject targetObj(aCx, JS_FindCompilationScope(aCx, aGlobal));
+        JSAutoCompartment ac(aCx, targetObj);
+
+        Rooted<JSScript*> script(aCx, mScript);
+        if (!JS::CloneAndExecuteScript(aCx, script, aRval)) {
+            aRv.NoteJSContextException(aCx);
+        }
+    }
+
+    JS_WrapValue(aCx, aRval);
+}
+
+void
+PrecompiledScript::GetUrl(nsAString& aUrl)
+{
+    CopyUTF8toUTF16(mURL, aUrl);
+}
+
+bool
+PrecompiledScript::HasReturnValue()
+{
+    return mHasReturnValue;
+}
+
+JSObject*
+PrecompiledScript::WrapObject(JSContext* aCx, HandleObject aGivenProto)
+{
+    return PrecompiledScriptBinding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(PrecompiledScript)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PrecompiledScript)
+    NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+    NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PrecompiledScript)
+    NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
+    NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+
+    tmp->mScript = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PrecompiledScript)
+    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(PrecompiledScript)
+    NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mScript)
+    NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PrecompiledScript)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PrecompiledScript)
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/loader/PrecompiledScript.h
@@ -0,0 +1,56 @@
+/* -*-  Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PrecompiledScript_h
+#define mozilla_dom_PrecompiledScript_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/PrecompiledScriptBinding.h"
+
+#include "jsapi.h"
+
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+class PrecompiledScript : public nsISupports
+                        , public nsWrapperCache
+{
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PrecompiledScript)
+
+  explicit PrecompiledScript(nsISupports* aParent, JS::Handle<JSScript*> aScript, JS::ReadOnlyCompileOptions& aOptions);
+
+  void ExecuteInGlobal(JSContext* aCx, JS::HandleObject aGlobal,
+                       JS::MutableHandleValue aRval,
+                       ErrorResult& aRv);
+
+  void GetUrl(nsAString& aUrl);
+
+  bool HasReturnValue();
+
+  nsISupports* GetParentObject() const { return mParent; }
+
+  virtual JSObject* WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aGivenProto) override;
+
+protected:
+  virtual ~PrecompiledScript();
+
+private:
+  nsCOMPtr<nsISupports> mParent;
+
+  JS::Heap<JSScript*> mScript;
+  nsCString mURL;
+  const bool mHasReturnValue;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PrecompiledScript_h
--- a/js/xpconnect/loader/moz.build
+++ b/js/xpconnect/loader/moz.build
@@ -1,21 +1,26 @@
 # -*- Mode: python; 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/.
 
 # These files cannot be built in unified mode because they rely on plarena.h
 SOURCES += [
+    'ChromeScriptLoader.cpp',
     'mozJSComponentLoader.cpp',
     'mozJSLoaderUtils.cpp',
     'mozJSSubScriptLoader.cpp',
 ]
 
+EXPORTS.mozilla.dom += [
+    'PrecompiledScript.h',
+]
+
 EXTRA_JS_MODULES += [
     'ISO8601DateUtils.jsm',
     'XPCOMUtils.jsm',
 ]
 
 FINAL_LIBRARY = 'xul'
 
 LOCAL_INCLUDES += [
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/tests/unit/file_simple_script.js
@@ -0,0 +1,1 @@
+this.bar = ({foo: "®"});
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/tests/unit/test_compileScript.js
@@ -0,0 +1,55 @@
+"use strict";
+
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+add_task(function*() {
+  let scriptUrl = Services.io.newFileURI(do_get_file("file_simple_script.js")).spec;
+
+
+  let script1 = yield ChromeUtils.compileScript(scriptUrl, {hasReturnValue: true});
+  let script2 = yield ChromeUtils.compileScript(scriptUrl, {hasReturnValue: false});
+
+  equal(script1.url, scriptUrl, "Script URL is correct")
+  equal(script2.url, scriptUrl, "Script URL is correct")
+
+  equal(script1.hasReturnValue, true, "Script hasReturnValue property is correct")
+  equal(script2.hasReturnValue, false, "Script hasReturnValue property is correct")
+
+
+  // Test return-value version.
+
+  let sandbox1 = Cu.Sandbox("http://example.com");
+  let sandbox2 = Cu.Sandbox("http://example.org");
+
+  let obj = script1.executeInGlobal(sandbox1);
+  equal(Cu.getObjectPrincipal(obj).origin, "http://example.com", "Return value origin is correct");
+  equal(obj.foo, "\u00ae", "Return value has the correct charset");
+
+  obj = script1.executeInGlobal(sandbox2);
+  equal(Cu.getObjectPrincipal(obj).origin, "http://example.org", "Return value origin is correct");
+  equal(obj.foo, "\u00ae", "Return value has the correct charset");
+
+
+  // Test no-return-value version.
+
+  sandbox1.bar = null;
+  equal(sandbox1.bar, null);
+
+  obj = script2.executeInGlobal(sandbox1);
+  equal(obj, undefined, "No-return script has no return value");
+
+  equal(Cu.getObjectPrincipal(sandbox1.bar).origin, "http://example.com", "Object value origin is correct");
+  equal(sandbox1.bar.foo, "\u00ae", "Object value has the correct charset");
+
+
+  sandbox2.bar = null;
+  equal(sandbox2.bar, null);
+
+  obj = script2.executeInGlobal(sandbox2);
+  equal(obj, undefined, "No-return script has no return value");
+
+  equal(Cu.getObjectPrincipal(sandbox2.bar).origin, "http://example.org", "Object value origin is correct");
+  equal(sandbox2.bar.foo, "\u00ae", "Object value has the correct charset");
+});
--- a/js/xpconnect/tests/unit/xpcshell.ini
+++ b/js/xpconnect/tests/unit/xpcshell.ini
@@ -6,16 +6,17 @@ support-files =
   bogus_exports_type.jsm
   bug451678_subscript.js
   component-blob.js
   component-blob.manifest
   component-file.js
   component-file.manifest
   component_import.js
   component_import.manifest
+  file_simple_script.js
   importer.jsm
   recursive_importA.jsm
   recursive_importB.jsm
   subScriptWithEarlyError.js
   syntax_error.jsm
 
 [test_allowWaivers.js]
 [test_bogus_files.js]
@@ -56,16 +57,17 @@ support-files =
 [test_bug1150106.js]
 [test_bug1150771.js]
 [test_bug1151385.js]
 [test_bug1170311.js]
 [test_bug1244222.js]
 [test_bug_442086.js]
 [test_callFunctionWithAsyncStack.js]
 [test_classesByID_instanceof.js]
+[test_compileScript.js]
 [test_deepFreezeClone.js]
 [test_file.js]
 [test_blob.js]
 [test_blob2.js]
 [test_file2.js]
 [test_import.js]
 [test_import_fail.js]
 [test_interposition.js]
--- a/js/xpconnect/wrappers/FilteringWrapper.cpp
+++ b/js/xpconnect/wrappers/FilteringWrapper.cpp
@@ -304,19 +304,27 @@ CrossOriginXrayWrapper::delete_(JSContex
 bool
 CrossOriginXrayWrapper::setPrototype(JSContext* cx, JS::HandleObject wrapper,
                                      JS::HandleObject proto,
                                      JS::ObjectOpResult& result) const
 {
     // https://html.spec.whatwg.org/multipage/browsers.html#windowproxy-setprototypeof
     // and
     // https://html.spec.whatwg.org/multipage/browsers.html#location-setprototypeof
-    // both say to return false.  In terms of ObjectOpResult that means
-    // calling one of the fail*() things on it, and it's got one that does just
-    // what we want.
+    // both say to call SetImmutablePrototype, which does nothing and just
+    // returns whether the passed-in value equals the current prototype.  Our
+    // current prototype is always null, so this just comes down to returning
+    // whether null was passed in.
+    //
+    // In terms of ObjectOpResult that means calling one of the fail*() things
+    // on it if non-null was passed, and it's got one that does just what we
+    // want.
+    if (!proto) {
+        return result.succeed();
+    }
     return result.failCantSetProto();
 }
 
 #define XOW FilteringWrapper<CrossOriginXrayWrapper, CrossOriginAccessiblePropertiesOnly>
 #define NNXOW FilteringWrapper<CrossCompartmentSecurityWrapper, Opaque>
 #define NNXOWC FilteringWrapper<CrossCompartmentSecurityWrapper, OpaqueWithCall>
 
 template<> const XOW XOW::singleton(0);
--- a/layout/generic/nsAbsoluteContainingBlock.cpp
+++ b/layout/generic/nsAbsoluteContainingBlock.cpp
@@ -251,32 +251,16 @@ nsAbsoluteContainingBlock::FrameDependsO
 
     // See if f's position might have changed. If we're RTL then the
     // rules are slightly different. We'll assume percentage or auto
     // margins will always induce a dependency on the size
     if (!IsFixedMarginSize(margin->mMargin.GetIStart(wm)) ||
         !IsFixedMarginSize(margin->mMargin.GetIEnd(wm))) {
       return true;
     }
-    if (!wm.IsBidiLTR()) {
-      // Note that even if 'istart' is a length, our position can
-      // still depend on the containing block isze, because if
-      // 'iend' is also a length we will discard 'istart' and be
-      // positioned relative to the containing block iend edge.
-      // 'istart' length and 'iend' auto is the only combination
-      // we can be sure of.
-      if (!IsFixedOffset(pos->mOffset.GetIStart(wm)) ||
-          pos->mOffset.GetIEndUnit(wm) != eStyleUnit_Auto) {
-        return true;
-      }
-    } else {
-      if (!IsFixedOffset(pos->mOffset.GetIStart(wm))) {
-        return true;
-      }
-    }
   }
   if (wm.IsVertical() ? aCBWidthChanged : aCBHeightChanged) {
     // See if f's block-size might have changed.
     // If margin-block-start/end, padding-block-start/end,
     // min-block-size, and max-block-size are all lengths or 'none',
     // and bsize is a length or bsize and bend are auto and bstart is not auto,
     // then our frame bsize does not depend on the parent bsize.
     // Note that borders never depend on the parent bsize.
@@ -291,20 +275,52 @@ nsAbsoluteContainingBlock::FrameDependsO
       return true;
     }
       
     // See if f's position might have changed.
     if (!IsFixedMarginSize(margin->mMargin.GetBStart(wm)) ||
         !IsFixedMarginSize(margin->mMargin.GetBEnd(wm))) {
       return true;
     }
-    if (!IsFixedOffset(pos->mOffset.GetBStart(wm))) {
+  }
+
+  // Since we store coordinates relative to top and left, the position
+  // of a frame depends on that of its container if it is fixed relative
+  // to the right or bottom, or if it is positioned using percentages
+  // relative to the left or top.  Because of the dependency on the
+  // sides (left and top) that we use to store coordinates, these tests
+  // are easier to do using physical coordinates rather than logical.
+  if (aCBWidthChanged) {
+    if (!IsFixedOffset(pos->mOffset.GetLeft())) {
+      return true;
+    }
+    // Note that even if 'left' is a length, our position can still
+    // depend on the containing block width, because if our direction or
+    // writing-mode moves from right to left (in either block or inline
+    // progression) and 'right' is not 'auto', we will discard 'left'
+    // and be positioned relative to the containing block right edge.
+    // 'left' length and 'right' auto is the only combination we can be
+    // sure of.
+    if ((wm.GetInlineDir() == WritingMode::eInlineRTL ||
+         wm.GetBlockDir() == WritingMode::eBlockRL) &&
+        pos->mOffset.GetRightUnit() != eStyleUnit_Auto) {
       return true;
     }
   }
+  if (aCBHeightChanged) {
+    if (!IsFixedOffset(pos->mOffset.GetTop())) {
+      return true;
+    }
+    // See comment above for width changes.
+    if (wm.GetInlineDir() == WritingMode::eInlineBTT &&
+        pos->mOffset.GetBottomUnit() != eStyleUnit_Auto) {
+      return true;
+    }
+  }
+
   return false;
 }
 
 void
 nsAbsoluteContainingBlock::DestroyFrames(nsIFrame* aDelegatingFrame,
                                          nsIFrame* aDestructRoot)
 {
   mAbsoluteFrames.DestroyFramesFrom(aDestructRoot);
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -781,17 +781,17 @@ static bool IsSpaceCombiningSequenceTail
     return false;
   return nsTextFrameUtils::IsSpaceCombiningSequenceTail(
     aFrag->Get2b() + aPos, aFrag->GetLength() - aPos);
 }
 
 // Check whether aPos is a space for CSS 'word-spacing' purposes
 static bool
 IsCSSWordSpacingSpace(const nsTextFragment* aFrag, uint32_t aPos,
-                      nsTextFrame* aFrame, const nsStyleText* aStyleText)
+                      const nsTextFrame* aFrame, const nsStyleText* aStyleText)
 {
   NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
 
   char16_t ch = aFrag->CharAt(aPos);
   switch (ch) {
   case ' ':
   case CH_NBSP:
     return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
@@ -1961,17 +1961,17 @@ BuildTextRunsScanner::GetNextBreakBefore
   uint32_t index = *aIndex;
   if (index >= mLineBreakBeforeFrames.Length())
     return nullptr;
   *aIndex = index + 1;
   return static_cast<nsTextFrame*>(mLineBreakBeforeFrames.ElementAt(index));
 }
 
 static gfxFontGroup*
-GetFontGroupForFrame(nsIFrame* aFrame, float aFontSizeInflation,
+GetFontGroupForFrame(const nsIFrame* aFrame, float aFontSizeInflation,
                      nsFontMetrics** aOutFontMetrics = nullptr)
 {
   RefPtr<nsFontMetrics> metrics =
     nsLayoutUtils::GetFontMetricsForFrame(aFrame, aFontSizeInflation);
   gfxFontGroup* fontGroup = metrics->GetThebesFontGroup();
 
   // Populate outparam before we return:
   if (aOutFontMetrics) {
@@ -1980,17 +1980,17 @@ GetFontGroupForFrame(nsIFrame* aFrame, f
   // XXX this is a bit bogus, we're releasing 'metrics' so the
   // returned font-group might actually be torn down, although because
   // of the way the device context caches font metrics, this seems to
   // not actually happen. But we should fix this.
   return fontGroup;
 }
 
 static already_AddRefed<DrawTarget>
-CreateReferenceDrawTarget(nsTextFrame* aTextFrame)
+CreateReferenceDrawTarget(const nsTextFrame* aTextFrame)
 {
   RefPtr<gfxContext> ctx =
     aTextFrame->PresContext()->PresShell()->CreateReferenceRenderingContext();
   RefPtr<DrawTarget> dt = ctx->GetDrawTarget();
   return dt.forget();
 }
 
 static already_AddRefed<gfxTextRun>
@@ -2917,17 +2917,17 @@ GetEndOfTrimmedText(const nsTextFragment
     if (!IsTrimmableSpace(aFrag, aIterator->GetOriginalOffset(), aStyleText))
       return aIterator->GetSkippedOffset() + 1;
   }
   return aStart;
 }
 
 nsTextFrame::TrimmedOffsets
 nsTextFrame::GetTrimmedOffsets(const nsTextFragment* aFrag,
-                               bool aTrimAfter, bool aPostReflow)
+                               bool aTrimAfter, bool aPostReflow) const
 {
   NS_ASSERTION(mTextRun, "Need textrun here");
   if (aPostReflow) {
     // This should not be used during reflow. We need our TEXT_REFLOW_FLAGS
     // to be set correctly.  If our parent wasn't reflowed due to the frame
     // tree being too deep then the return value doesn't matter.
     NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW) ||
                  (GetParent()->GetStateBits() &
@@ -3059,17 +3059,17 @@ static int32_t FindChar(const nsTextFrag
       const void* p = memchr(str, ch, aLength);
       if (p)
         return (static_cast<const char*>(p) - str) + aOffset;
     }
   }
   return -1;
 }
 
-static bool IsChineseOrJapanese(nsTextFrame* aFrame)
+static bool IsChineseOrJapanese(const nsTextFrame* aFrame)
 {
   if (aFrame->ShouldSuppressLineBreak()) {
     // Always treat ruby as CJ language so that those characters can
     // be expanded properly even when surrounded by other language.
     return true;
   }
 
   nsIAtom* language = aFrame->StyleFont()->mLanguage;
@@ -3088,17 +3088,17 @@ static bool IsInBounds(const gfxSkipChar
   if (aContentLength == INT32_MAX)
     return true;
   gfxSkipCharsIterator iter(aStart);
   iter.AdvanceOriginal(aContentLength);
   return iter.GetSkippedOffset() >= aRange.end;
 }
 #endif
 
-class MOZ_STACK_CLASS PropertyProvider : public gfxTextRun::PropertyProvider {
+class MOZ_STACK_CLASS PropertyProvider final : public gfxTextRun::PropertyProvider {
   typedef gfxTextRun::Range Range;
   typedef gfxTextRun::HyphenType HyphenType;
 
 public:
   /**
    * Use this constructor for reflow, when we don't know what text is
    * really mapped by the frame and we have a lot of other data around.
    *
@@ -3153,108 +3153,110 @@ public:
     NS_ASSERTION(mTextRun, "Textrun not initialized!");
   }
 
   // Call this after construction if you're not going to reflow the text
   void InitializeForDisplay(bool aTrimAfter);
 
   void InitializeForMeasure();
 
-  virtual void GetSpacing(Range aRange, Spacing* aSpacing);
-  virtual gfxFloat GetHyphenWidth();
-  virtual void GetHyphenationBreaks(Range aRange, HyphenType* aBreakBefore);
-  virtual StyleHyphens GetHyphensOption() {
+  void GetSpacing(Range aRange, Spacing* aSpacing) const;
+  gfxFloat GetHyphenWidth() const;
+  void GetHyphenationBreaks(Range aRange, HyphenType* aBreakBefore) const;
+  StyleHyphens GetHyphensOption() const {
     return mTextStyle->mHyphens;
   }
 
-  virtual already_AddRefed<DrawTarget> GetDrawTarget() {
+  already_AddRefed<DrawTarget> GetDrawTarget() const {
     return CreateReferenceDrawTarget(GetFrame());
   }
 
-  virtual uint32_t GetAppUnitsPerDevUnit() {
+  uint32_t GetAppUnitsPerDevUnit() const {
     return mTextRun->GetAppUnitsPerDevUnit();
   }
 
-  void GetSpacingInternal(Range aRange, Spacing* aSpacing, bool aIgnoreTabs);
+  void GetSpacingInternal(Range aRange, Spacing* aSpacing, bool aIgnoreTabs) const;
 
   /**
    * Compute the justification information in given DOM range, return
    * justification info and assignments if requested.
    */
   JustificationInfo ComputeJustification(
     Range aRange, nsTArray<JustificationAssignment>* aAssignments = nullptr);
 
-  nsTextFrame* GetFrame() { return mFrame; }
+  const nsTextFrame* GetFrame() const { return mFrame; }
   // This may not be equal to the frame offset/length in because we may have
   // adjusted for whitespace trimming according to the state bits set in the frame
   // (for the static provider)
   const gfxSkipCharsIterator& GetStart() const { return mStart; }
   // May return INT32_MAX if that was given to the constructor
   uint32_t GetOriginalLength() const {
     NS_ASSERTION(mLength != INT32_MAX, "Length not known");
     return mLength;
   }
-  const nsTextFragment* GetFragment() { return mFrag; }
-
-  gfxFontGroup* GetFontGroup() {
-    if (!mFontGroup)
+  const nsTextFragment* GetFragment() const { return mFrag; }
+
+  gfxFontGroup* GetFontGroup() const {
+    if (!mFontGroup) {
       InitFontGroupAndFontMetrics();
+    }
     return mFontGroup;
   }
 
-  nsFontMetrics* GetFontMetrics() {
-    if (!mFontMetrics)
+  nsFontMetrics* GetFontMetrics() const {
+    if (!mFontMetrics) {
       InitFontGroupAndFontMetrics();
+    }
     return mFontMetrics;
   }
 
-  void CalcTabWidths(Range aTransformedRange, gfxFloat aTabWidth);
-
-  const gfxSkipCharsIterator& GetEndHint() { return mTempIterator; }
+  void CalcTabWidths(Range aTransformedRange, gfxFloat aTabWidth) const;
+
+  const gfxSkipCharsIterator& GetEndHint() const { return mTempIterator; }
 
 protected:
   void SetupJustificationSpacing(bool aPostReflow);
 
-  void InitFontGroupAndFontMetrics() {
+  void InitFontGroupAndFontMetrics() const {
     float inflation = (mWhichTextRun == nsTextFrame::eInflated)
       ? mFrame->GetFontSizeInflation() : 1.0f;
     mFontGroup = GetFontGroupForFrame(mFrame, inflation,
                                       getter_AddRefs(mFontMetrics));
   }
 
-  RefPtr<gfxTextRun>    mTextRun;
-  gfxFontGroup*         mFontGroup;
-  RefPtr<nsFontMetrics> mFontMetrics;
-  const nsStyleText*    mTextStyle;
-  const nsTextFragment* mFrag;
-  nsIFrame*             mLineContainer;
-  nsTextFrame*          mFrame;
-  gfxSkipCharsIterator  mStart;  // Offset in original and transformed string
-  gfxSkipCharsIterator  mTempIterator;
+  const RefPtr<gfxTextRun>        mTextRun;
+  mutable gfxFontGroup*           mFontGroup;
+  mutable RefPtr<nsFontMetrics>   mFontMetrics;
+  const nsStyleText*              mTextStyle;
+  const nsTextFragment*           mFrag;
+  const nsIFrame*                 mLineContainer;
+  const nsTextFrame*              mFrame;
+  gfxSkipCharsIterator            mStart;  // Offset in original and transformed string
+  const gfxSkipCharsIterator      mTempIterator;
 
   // Either null, or pointing to the frame's TabWidthProperty.
-  TabWidthStore*        mTabWidths;
+  mutable TabWidthStore*          mTabWidths;
   // How far we've done tab-width calculation; this is ONLY valid when
   // mTabWidths is nullptr (otherwise rely on mTabWidths->mLimit instead).
   // It's a DOM offset relative to the current frame's offset.
-  uint32_t              mTabWidthsAnalyzedLimit;
-
-  int32_t               mLength; // DOM string length, may be INT32_MAX
-  gfxFloat              mWordSpacing;     // space for each whitespace char
-  gfxFloat              mLetterSpacing;   // space for each letter
-  gfxFloat              mHyphenWidth;
-  gfxFloat              mOffsetFromBlockOriginForTabs;
+  mutable uint32_t                mTabWidthsAnalyzedLimit;
+
+  int32_t                         mLength;  // DOM string length, may be INT32_MAX
+  const gfxFloat                  mWordSpacing; // space for each whitespace char
+  const gfxFloat                  mLetterSpacing; // space for each letter
+  mutable gfxFloat                mHyphenWidth;
+  mutable gfxFloat                mOffsetFromBlockOriginForTabs;
 
   // The values in mJustificationSpacings corresponds to unskipped
   // characters start from mJustificationArrayStart.
-  uint32_t              mJustificationArrayStart;
-  nsTArray<Spacing>     mJustificationSpacings;
-
-  bool                  mReflowing;
-  nsTextFrame::TextRunType mWhichTextRun;
+  uint32_t                        mJustificationArrayStart;
+  nsTArray<Spacing>               mJustificationSpacings;
+
+  const bool                      mReflowing;
+  const nsTextFrame::TextRunType  mWhichTextRun;
 };
 
 /**
  * Finds the offset of the first character of the cluster containing aPos
  */
 static void FindClusterStart(const gfxTextRun* aTextRun,
                              int32_t aOriginalStart,
                              gfxSkipCharsIterator* aPos)
@@ -3372,33 +3374,33 @@ PropertyProvider::ComputeJustification(
   if (aAssignments) {
     *aAssignments = Move(assignments);
   }
   return info;
 }
 
 // aStart, aLength in transformed string offsets
 void
-PropertyProvider::GetSpacing(Range aRange, Spacing* aSpacing)
+PropertyProvider::GetSpacing(Range aRange, Spacing* aSpacing) const
 {
   GetSpacingInternal(aRange, aSpacing,
                      (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TAB) == 0);
 }
 
 static bool
 CanAddSpacingAfter(const gfxTextRun* aTextRun, uint32_t aOffset)
 {
   if (aOffset + 1 >= aTextRun->GetLength())
     return true;
   return aTextRun->IsClusterStart(aOffset + 1) &&
     aTextRun->IsLigatureGroupStart(aOffset + 1);
 }
 
 static gfxFloat
-ComputeTabWidthAppUnits(nsIFrame* aFrame, gfxTextRun* aTextRun)
+ComputeTabWidthAppUnits(const nsIFrame* aFrame, gfxTextRun* aTextRun)
 {
   const nsStyleText* textStyle = aFrame->StyleText();
   if (textStyle->mTabSize.GetUnit() != eStyleUnit_Factor) {
     nscoord w = textStyle->mTabSize.GetCoordValue();
     MOZ_ASSERT(w >= 0);
     return w;
   }
 
@@ -3411,17 +3413,17 @@ ComputeTabWidthAppUnits(nsIFrame* aFrame
     NS_round(GetFirstFontMetrics(aTextRun->GetFontGroup(),
                                  aTextRun->IsVertical()).spaceWidth *
              aTextRun->GetAppUnitsPerDevUnit());
   return spaces * spaceWidthAppUnits;
 }
 
 void
 PropertyProvider::GetSpacingInternal(Range aRange, Spacing* aSpacing,
-                                     bool aIgnoreTabs)
+                                     bool aIgnoreTabs) const
 {
   NS_PRECONDITION(IsInBounds(mStart, mLength, aRange), "Range out of bounds");
 
   uint32_t index;
   for (index = 0; index < aRange.Length(); ++index) {
     aSpacing[index].mBefore = 0.0;
     aSpacing[index].mAfter = 0.0;
   }
@@ -3490,28 +3492,27 @@ PropertyProvider::GetSpacingInternal(Ran
       aSpacing[offset].mBefore += spacing.mBefore;
       aSpacing[offset].mAfter += spacing.mAfter;
     }
   }
 }
 
 // aX and the result are in whole appunits.
 static gfxFloat
-AdvanceToNextTab(gfxFloat aX, nsIFrame* aFrame,
-                 gfxTextRun* aTextRun, gfxFloat aTabWidth)
+AdvanceToNextTab(gfxFloat aX, gfxFloat aTabWidth)
 {
 
   // Advance aX to the next multiple of *aCachedTabWidth. We must advance
   // by at least 1 appunit.
   // XXX should we make this 1 CSS pixel?
   return ceil((aX + 1) / aTabWidth) * aTabWidth;
 }
 
 void
-PropertyProvider::CalcTabWidths(Range aRange, gfxFloat aTabWidth)
+PropertyProvider::CalcTabWidths(Range aRange, gfxFloat aTabWidth) const
 {
   MOZ_ASSERT(aTabWidth > 0);
 
   if (!mTabWidths) {
     if (mReflowing && !mLineContainer) {
       // Intrinsic width computation does its own tab processing. We
       // just don't do anything here.
       return;
@@ -3560,17 +3561,17 @@ PropertyProvider::CalcTabWidths(Range aR
             mTextRun->GetAdvanceWidth(Range(i, clusterEnd), nullptr);
         }
       } else {
         if (!mTabWidths) {
           mTabWidths = new TabWidthStore(mFrame->GetContentOffset());
           mFrame->Properties().Set(TabWidthProperty(), mTabWidths);
         }
         double nextTab = AdvanceToNextTab(mOffsetFromBlockOriginForTabs,
-                mFrame, mTextRun, aTabWidth);
+                                          aTabWidth);
         mTabWidths->mWidths.AppendElement(TabWidth(i - startOffset,
                 NSToIntRound(nextTab - mOffsetFromBlockOriginForTabs)));
         mOffsetFromBlockOriginForTabs = nextTab;
       }
 
       mOffsetFromBlockOriginForTabs += spacing.mAfter;
     }
 
@@ -3583,17 +3584,17 @@ PropertyProvider::CalcTabWidths(Range aR
     // Delete any stale property that may be left on the frame
     mFrame->Properties().Delete(TabWidthProperty());
     mTabWidthsAnalyzedLimit = std::max(mTabWidthsAnalyzedLimit,
                                        aRange.end - startOffset);
   }
 }
 
 gfxFloat
-PropertyProvider::GetHyphenWidth()
+PropertyProvider::GetHyphenWidth() const
 {
   if (mHyphenWidth < 0) {
     mHyphenWidth = GetFontGroup()->GetHyphenWidth(this);
   }
   return mHyphenWidth + mLetterSpacing;
 }
 
 static inline bool
@@ -3602,17 +3603,17 @@ IS_HYPHEN(char16_t u)
   return (u == char16_t('-') ||
           u == 0x058A || // ARMENIAN HYPHEN
           u == 0x2010 || // HYPHEN
           u == 0x2012 || // FIGURE DASH
           u == 0x2013);  // EN DASH
 }
 
 void
-PropertyProvider::GetHyphenationBreaks(Range aRange, HyphenType* aBreakBefore)
+PropertyProvider::GetHyphenationBreaks(Range aRange, HyphenType* aBreakBefore) const
 {
   NS_PRECONDITION(IsInBounds(mStart, mLength, aRange), "Range out of bounds");
   NS_PRECONDITION(mLength != INT32_MAX, "Can't call this with undefined length");
 
   if (!mTextStyle->WhiteSpaceCanWrap(mFrame) ||
       mTextStyle->mHyphens == StyleHyphens::None)
   {
     memset(aBreakBefore, static_cast<uint8_t>(HyphenType::None),
@@ -3695,18 +3696,19 @@ PropertyProvider::InitializeForMeasure()
 }
 
 
 void
 PropertyProvider::SetupJustificationSpacing(bool aPostReflow)
 {
   NS_PRECONDITION(mLength != INT32_MAX, "Can't call this with undefined length");
 
-  if (!(mFrame->GetStateBits() & TEXT_JUSTIFICATION_ENABLED))
+  if (!(mFrame->GetStateBits() & TEXT_JUSTIFICATION_ENABLED)) {
     return;
+  }
 
   gfxSkipCharsIterator start(mStart), end(mStart);
   // We can't just use our mLength here; when InitializeForDisplay is
   // called with false for aTrimAfter, we still shouldn't be assigning
   // justification space to any trailing whitespace.
   nsTextFrame::TrimmedOffsets trimmed =
     mFrame->GetTrimmedOffsets(mFrag, true, aPostReflow);
   end.AdvanceOriginal(trimmed.mLength);
@@ -8468,17 +8470,17 @@ nsTextFrame::AddInlineMinISizeForFlow(ns
     if (preformattedTab) {
       PropertyProvider::Spacing spacing;
       provider.GetSpacing(Range(i, i + 1), &spacing);
       aData->mCurrentLine += nscoord(spacing.mBefore);
       if (tabWidth < 0) {
         tabWidth = ComputeTabWidthAppUnits(this, textRun);
       }
       gfxFloat afterTab =
-        AdvanceToNextTab(aData->mCurrentLine, this, textRun, tabWidth);
+        AdvanceToNextTab(aData->mCurrentLine, tabWidth);
       aData->mCurrentLine = nscoord(afterTab + spacing.mAfter);
       wordStart = i + 1;
     } else if (i < flowEndInTextRun ||
         (i == textRun->GetLength() &&
          (textRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK))) {
       if (preformattedNewline) {
         aData->ForceBreak();
       } else if (i < flowEndInTextRun && hyphenating &&
@@ -8631,17 +8633,17 @@ nsTextFrame::AddInlinePrefISizeForFlow(n
     if (preformattedTab) {
       PropertyProvider::Spacing spacing;
       provider.GetSpacing(Range(i, i + 1), &spacing);
       aData->mCurrentLine += nscoord(spacing.mBefore);
       if (tabWidth < 0) {
         tabWidth = ComputeTabWidthAppUnits(this, textRun);
       }
       gfxFloat afterTab =
-        AdvanceToNextTab(aData->mCurrentLine, this, textRun, tabWidth);
+        AdvanceToNextTab(aData->mCurrentLine, tabWidth);
       aData->mCurrentLine = nscoord(afterTab + spacing.mAfter);
       aData->mLineIsEmpty = false;
       lineStart = i + 1;
     } else if (preformattedNewline) {
       aData->ForceBreak();
       lineStart = i;
     }
   }
--- a/layout/generic/nsTextFrame.h
+++ b/layout/generic/nsTextFrame.h
@@ -636,17 +636,17 @@ public:
   struct TrimmedOffsets
   {
     int32_t mStart;
     int32_t mLength;
     int32_t GetEnd() const { return mStart + mLength; }
   };
   TrimmedOffsets GetTrimmedOffsets(const nsTextFragment* aFrag,
                                    bool aTrimAfter,
-                                   bool aPostReflow = true);
+                                   bool aPostReflow = true) const;
 
   // Similar to Reflow(), but for use from nsLineLayout
   void ReflowText(nsLineLayout& aLineLayout,
                   nscoord aAvailableWidth,
                   DrawTarget* aDrawTarget,
                   ReflowOutput& aMetrics,
                   nsReflowStatus& aStatus);
 
--- a/layout/reftests/svg/reftest.list
+++ b/layout/reftests/svg/reftest.list
@@ -47,17 +47,17 @@ skip-if(Android) pref(layout.css.mix-ble
 # pref(layout.css.mix-blend-mode.enabled,true) == blend-luminosity.svg blend-luminosity-ref.svg
 #skip-if(Android) pref(layout.css.mix-blend-mode.enabled,true) == blend-multiply-alpha.svg blend-multiply-alpha-ref.svg
 skip-if(Android) pref(layout.css.mix-blend-mode.enabled,true) == blend-multiply.svg blend-multiply-ref.svg
 pref(layout.css.mix-blend-mode.enabled,true) == blend-normal.svg blend-normal-ref.svg
 #skip-if(Android) pref(layout.css.mix-blend-mode.enabled,true) == blend-overlay.svg blend-overlay-ref.svg
 #skip-if(Android)  pref(layout.css.mix-blend-mode.enabled,true) == blend-saturation.svg blend-saturation-ref.svg
 #skip-if(Android) pref(layout.css.mix-blend-mode.enabled,true) == blend-screen.svg blend-screen-ref.svg
 #skip-if(Android) pref(layout.css.mix-blend-mode.enabled,true) == blend-soft-light.svg blend-soft-light-ref.svg
-skip-if(Android) pref(layout.css.mix-blend-mode.enabled,true) fails-if(webrender) == blend-difference-stacking.html blend-difference-stacking-ref.html
+skip-if(Android) pref(layout.css.mix-blend-mode.enabled,true) == blend-difference-stacking.html blend-difference-stacking-ref.html
 
 == border-radius-01.html pass.svg
 
 == clip-01.svg pass.svg
 == clip-02a.svg clip-02-ref.svg
 == clip-02b.svg clip-02-ref.svg
 == clip-surface-clone-01.svg clip-surface-clone-01-ref.svg
 == clip-use-element-01.svg pass.svg
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/writing-modes-3/dynamic-offset-rtl-001-ref.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<head>
+<meta charset="UTF-8">
+<title>CSS Test Reference: dynamic changes to offset properties</title>
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Mozilla" href="https://www.mozilla.org/">
+<style>
+
+body {
+  margin: 0;
+}
+
+#container {
+  position: absolute;
+  width: 100px;
+  height: 100px;
+  background: yellow;
+}
+
+#abspos {
+  margin-top: 10px;
+  margin-left: 70px;
+  width: 10px;
+  height: 10px;
+  background: fuchsia;
+}
+
+</style>
+
+<div id="container">
+  <div id="abspos"></div>
+</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/writing-modes-3/dynamic-offset-rtl-001.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<head>
+<meta charset="UTF-8">
+<title>CSS Test: dynamic changes to offset properties</title>
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Mozilla" href="https://www.mozilla.org/">
+<link rel="help" href="https://drafts.csswg.org/css-writing-modes-3/#vertical-layout">
+<link rel="match" href="dynamic-offset-rtl-001-ref.html">
+<meta name="flags" content="dom">
+<meta name="assert" content="Layout rules for 'right' and 'left' are handled correctly for absolutely positioned elements, in the presence of dynamic changes to 'width' of the containing block.">
+<style>
+
+html {
+  direction: rtl;
+  overflow: hidden;
+}
+
+#container {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 200px;
+  height: 100px;
+  background: yellow;
+}
+
+#abspos {
+  position: absolute;
+  top: 10px;
+  right: 20px;
+  width: 10px;
+  height: 10px;
+  background: fuchsia;
+}
+
+</style>
+
+<div id="container">
+  <div id="abspos"></div>
+</div>
+
+<script>
+
+window.addEventListener("load", function(event) {
+  var e = document.getElementById("abspos");
+  e.offsetTop; // flush layout
+  e.parentNode.style.width = "100px";
+});
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/writing-modes-3/dynamic-offset-rtl-002.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<head>
+<meta charset="UTF-8">
+<title>CSS Test: dynamic changes to offset properties</title>
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Mozilla" href="https://www.mozilla.org/">
+<link rel="help" href="https://drafts.csswg.org/css-writing-modes-3/#vertical-layout">
+<link rel="match" href="dynamic-offset-rtl-001-ref.html">
+<meta name="flags" content="dom">
+<meta name="assert" content="Layout rules for 'right' and 'left' are handled correctly for absolutely positioned elements, in the presence of dynamic changes to 'width' of the containing block.">
+<style>
+
+html {
+  direction: rtl;
+  overflow: hidden;
+}
+
+#container {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 200px;
+  height: 100px;
+  background: yellow;
+}
+
+#abspos {
+  position: absolute;
+  top: 10px;
+  right: 20px;
+  left: 10px; /* ignored */
+  width: 10px;
+  height: 10px;
+  background: fuchsia;
+}
+
+</style>
+
+<div id="container">
+  <div id="abspos"></div>
+</div>
+
+<script>
+
+window.addEventListener("load", function(event) {
+  var e = document.getElementById("abspos");
+  e.offsetTop; // flush layout
+  e.parentNode.style.width = "100px";
+});
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/writing-modes-3/dynamic-offset-vrl-001.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<head>
+<meta charset="UTF-8">
+<title>CSS Test: dynamic changes to offset properties</title>
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Mozilla" href="https://www.mozilla.org/">
+<link rel="help" href="https://drafts.csswg.org/css-writing-modes-3/#vertical-layout">
+<link rel="match" href="dynamic-offset-rtl-001-ref.html">
+<meta name="flags" content="dom">
+<meta name="assert" content="Layout rules for 'right' and 'left' are handled correctly for absolutely positioned elements, in the presence of dynamic changes to 'width' of the containing block.">
+<style>
+
+html {
+  writing-mode: vertical-rl;
+  overflow: hidden;
+}
+
+#container {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 200px;
+  height: 100px;
+  background: yellow;
+}
+
+#abspos {
+  position: absolute;
+  top: 10px;
+  right: 20px;
+  width: 10px;
+  height: 10px;
+  background: fuchsia;
+}
+
+</style>
+
+<div id="container">
+  <div id="abspos"></div>
+</div>
+
+<script>
+
+window.addEventListener("load", function(event) {
+  var e = document.getElementById("abspos");
+  e.offsetTop; // flush layout
+  e.parentNode.style.width = "100px";
+});
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/writing-modes-3/dynamic-offset-vrl-002.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<head>
+<meta charset="UTF-8">
+<title>CSS Test: dynamic changes to offset properties</title>
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Mozilla" href="https://www.mozilla.org/">
+<link rel="help" href="https://drafts.csswg.org/css-writing-modes-3/#vertical-layout">
+<link rel="match" href="dynamic-offset-rtl-001-ref.html">
+<meta name="flags" content="dom">
+<meta name="assert" content="Layout rules for 'right' and 'left' are handled correctly for absolutely positioned elements, in the presence of dynamic changes to 'width' of the containing block.">
+<style>
+
+html {
+  writing-mode: vertical-rl;
+  overflow: hidden;
+}
+
+#container {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 200px;
+  height: 100px;
+  background: yellow;
+}
+
+#abspos {
+  position: absolute;
+  top: 10px;
+  right: 20px;
+  left: 10px; /* ignored */
+  width: 10px;
+  height: 10px;
+  background: fuchsia;
+}
+
+</style>
+
+<div id="container">
+  <div id="abspos"></div>
+</div>
+
+<script>
+
+window.addEventListener("load", function(event) {
+  var e = document.getElementById("abspos");
+  e.offsetTop; // flush layout
+  e.parentNode.style.width = "100px";
+});
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/writing-modes-3/dynamic-offset-vrl-rtl-001.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<head>
+<meta charset="UTF-8">
+<title>CSS Test: dynamic changes to offset properties</title>
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Mozilla" href="https://www.mozilla.org/">
+<link rel="help" href="https://drafts.csswg.org/css-writing-modes-3/#vertical-layout">
+<link rel="match" href="dynamic-offset-rtl-001-ref.html">
+<meta name="flags" content="dom">
+<meta name="assert" content="Layout rules for 'right' and 'left' are handled correctly for absolutely positioned elements, in the presence of dynamic changes to 'width' of the containing block.">
+<style>
+
+html {
+  writing-mode: vertical-rl;
+  direction: rtl;
+  overflow: hidden;
+}
+
+#container {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100px;
+  height: 200px;
+  background: yellow;
+}
+
+#abspos {
+  position: absolute;
+  bottom: 80px;
+  left: 70px;
+  width: 10px;
+  height: 10px;
+  background: fuchsia;
+}
+
+</style>
+
+<div id="container">
+  <div id="abspos"></div>
+</div>
+
+<script>
+
+window.addEventListener("load", function(event) {
+  var e = document.getElementById("abspos");
+  e.offsetTop; // flush layout
+  e.parentNode.style.height = "100px";
+});
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/writing-modes-3/dynamic-offset-vrl-rtl-002.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<head>
+<meta charset="UTF-8">
+<title>CSS Test: dynamic changes to offset properties</title>
+<link rel="author" title="L. David Baron" href="https://dbaron.org/">
+<link rel="author" title="Mozilla" href="https://www.mozilla.org/">
+<link rel="help" href="https://drafts.csswg.org/css-writing-modes-3/#vertical-layout">
+<link rel="match" href="dynamic-offset-rtl-001-ref.html">
+<meta name="flags" content="dom">
+<meta name="assert" content="Layout rules for 'right' and 'left' are handled correctly for absolutely positioned elements, in the presence of dynamic changes to 'width' of the containing block.">
+<style>
+
+html {
+  writing-mode: vertical-rl;
+  direction: rtl;
+  overflow: hidden;
+}
+
+#container {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100px;
+  height: 200px;
+  background: yellow;
+}
+
+#abspos {
+  position: absolute;
+  bottom: 80px;
+  left: 70px;
+  top: 80px; /* ignored */
+  width: 10px;
+  height: 10px;
+  background: fuchsia;
+}
+
+</style>
+
+<div id="container">
+  <div id="abspos"></div>
+</div>
+
+<script>
+
+window.addEventListener("load", function(event) {
+  var e = document.getElementById("abspos");
+  e.offsetTop; // flush layout
+  e.parentNode.style.height = "100px";
+});
+
+</script>
--- a/layout/reftests/w3c-css/submitted/writing-modes-3/reftest.list
+++ b/layout/reftests/w3c-css/submitted/writing-modes-3/reftest.list
@@ -10,8 +10,15 @@ default-preferences pref(layout.css.text
 == text-combine-upright-compression-005a.html text-combine-upright-compression-005-ref.html
 == text-combine-upright-compression-006.html text-combine-upright-compression-006-ref.html
 == text-combine-upright-compression-006a.html text-combine-upright-compression-006-ref.html
 == text-combine-upright-compression-007.html text-combine-upright-compression-007-ref.html
 
 == text-orientation-upright-directionality-001.html text-orientation-upright-directionality-001-ref.html
 
 == logical-physical-mapping-001.html logical-physical-mapping-001-ref.html
+
+== dynamic-offset-rtl-001.html dynamic-offset-rtl-001-ref.html
+== dynamic-offset-rtl-002.html dynamic-offset-rtl-001-ref.html
+== dynamic-offset-vrl-001.html dynamic-offset-rtl-001-ref.html
+== dynamic-offset-vrl-002.html dynamic-offset-rtl-001-ref.html
+== dynamic-offset-vrl-rtl-001.html dynamic-offset-rtl-001-ref.html
+== dynamic-offset-vrl-rtl-002.html dynamic-offset-rtl-001-ref.html
--- a/layout/style/nsCSSProps.h
+++ b/layout/style/nsCSSProps.h
@@ -15,16 +15,18 @@
 #include <type_traits>
 #include "nsString.h"
 #include "nsCSSPropertyID.h"
 #include "nsStyleStructFwd.h"
 #include "nsCSSKeywords.h"
 #include "mozilla/CSSEnabledState.h"
 #include "mozilla/UseCounter.h"
 #include "mozilla/EnumTypeTraits.h"
+#include "mozilla/Preferences.h"
+#include "nsXULAppAPI.h"
 
 // Length of the "--" prefix on custom names (such as custom property names,
 // and, in the future, custom media query names).
 #define CSS_CUSTOM_NAME_PREFIX_LENGTH 2
 
 // Flags for ParseVariant method
 #define VARIANT_KEYWORD         0x000001  // K
 #define VARIANT_LENGTH          0x000002  // L
@@ -626,16 +628,22 @@ public:
     MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT,
                "out of range");
     return kIDLNameSortPositionTable[aProperty];
   }
 
   static bool IsEnabled(nsCSSPropertyID aProperty) {
     MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT_with_aliases,
                "out of range");
+    // We don't have useful pref init phases in the parent process.  But in the
+    // child process, assert that we're not trying to parse stylesheets before
+    // we've gotten all our prefs.
+    MOZ_ASSERT(XRE_IsParentProcess() ||
+               mozilla::Preferences::InitPhase() == END_ALL_PREFS,
+               "Checking style preferences before they have been set");
     return gPropertyEnabled[aProperty];
   }
 
   // A table for the use counter associated with each CSS property.  If a
   // property does not have a use counter defined in UseCounters.conf, then
   // its associated entry is |eUseCounter_UNKNOWN|.
   static const mozilla::UseCounter gPropertyUseCounter[eCSSProperty_COUNT_no_shorthands];
 
--- a/layout/tools/reftest/reftestcommandline.py
+++ b/layout/tools/reftest/reftestcommandline.py
@@ -448,19 +448,19 @@ class RemoteArgumentsParser(ReftestArgum
                           dest="pidFile",
                           default="",
                           help="name of the pidfile to generate")
 
         self.add_argument("--dm_trans",
                           action="store",
                           type=str,
                           dest="dm_trans",
-                          default="sut",
+                          default="adb",
                           help="the transport to use to communicate with device: "
-                               "[adb|sut]; default=sut")
+                               "[adb|sut]; default=adb")
 
         self.add_argument("--remoteTestRoot",
                           action="store",
                           type=str,
                           dest="remoteTestRoot",
                           help="remote directory to use as test root "
                                "(eg. /mnt/sdcard/tests or /data/local/tests)")
 
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -1251,16 +1251,19 @@ public abstract class GeckoApp
             "Accessibility:Ready",
             "Gecko:Exited",
             "Gecko:Ready",
             "PluginHelper:playFlash",
             null);
 
         EventDispatcher.getInstance().registerUiThreadListener(this,
             "Sanitize:Finished",
+            "Update:Check",
+            "Update:Download",
+            "Update:Install",
             null);
 
         GeckoThread.launch();
 
         Bundle stateBundle = IntentUtils.getBundleExtraSafe(getIntent(), EXTRA_STATE_BUNDLE);
         if (stateBundle != null) {
             // Use the state bundle if it was given as an intent extra. This is
             // only intended to be used internally via Robocop, so a boolean
@@ -1306,19 +1309,16 @@ public abstract class GeckoApp
             "Permissions:Data",
             "PrivateBrowsing:Data",
             "RuntimePermissions:Prompt",
             "Share:Text",
             "SystemUI:Visibility",
             "ToggleChrome:Focus",
             "ToggleChrome:Hide",
             "ToggleChrome:Show",
-            "Update:Check",
-            "Update:Download",
-            "Update:Install",
             null);
 
         Tabs.getInstance().attachToContext(this, mLayerView);
 
         // Use global layout state change to kick off additional initialization
         mMainLayout.getViewTreeObserver().addOnGlobalLayoutListener(this);
 
         if (Versions.preMarshmallow) {
@@ -2393,16 +2393,19 @@ public abstract class GeckoApp
             "Accessibility:Ready",
             "Gecko:Exited",
             "Gecko:Ready",
             "PluginHelper:playFlash",
             null);
 
         EventDispatcher.getInstance().unregisterUiThreadListener(this,
             "Sanitize:Finished",
+            "Update:Check",
+            "Update:Download",
+            "Update:Install",
             null);
 
         getAppEventDispatcher().unregisterGeckoThreadListener(this,
             "Accessibility:Event",
             "Locale:Set",
             null);
 
         getAppEventDispatcher().unregisterBackgroundThreadListener(this,
@@ -2418,19 +2421,16 @@ public abstract class GeckoApp
             "Permissions:Data",
             "PrivateBrowsing:Data",
             "RuntimePermissions:Prompt",
             "Share:Text",
             "SystemUI:Visibility",
             "ToggleChrome:Focus",
             "ToggleChrome:Hide",
             "ToggleChrome:Show",
-            "Update:Check",
-            "Update:Download",
-            "Update:Install",
             null);
 
         if (mPromptService != null) {
             mPromptService.destroy();
             mPromptService = null;
         }
 
         final HealthRecorder rec = mHealthRecorder;
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoUpdateReceiver.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoUpdateReceiver.java
@@ -12,17 +12,14 @@ import android.content.BroadcastReceiver
 import android.content.Context;
 import android.content.Intent;
 
 public class GeckoUpdateReceiver extends BroadcastReceiver
 {
     @Override
     public void onReceive(Context context, Intent intent) {
         if (UpdateServiceHelper.ACTION_CHECK_UPDATE_RESULT.equals(intent.getAction())) {
-            if (GeckoAppShell.getGeckoInterface() == null) {
-                return;
-            }
             final GeckoBundle data = new GeckoBundle(1);
             data.putString("result", intent.getStringExtra("result"));
-            GeckoApp.getEventDispatcher().dispatch("Update:CheckResult", data);
+            EventDispatcher.getInstance().dispatch("Update:CheckResult", data);
         }
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/ZoomedView.java
+++ b/mobile/android/base/java/org/mozilla/gecko/ZoomedView.java
@@ -775,17 +775,17 @@ public class ZoomedView extends FrameLay
     private boolean isRendering() {
         return (startTimeReRender != 0);
     }
 
     private boolean renderFrequencyTooHigh() {
         return ((System.nanoTime() - lastStartTimeReRender) < MINIMUM_DELAY_BETWEEN_TWO_RENDER_CALLS_NS);
     }
 
-    @WrapForJNI(dispatchTo = "gecko")
+    @WrapForJNI(dispatchTo = "gecko_priority")
     private static native void requestZoomedViewData(ByteBuffer buffer, int tabId,
                                                      int xPos, int yPos, int width,
                                                      int height, float scale);
 
     @Override
     public void requestZoomedViewRender() {
         if (stopUpdateView) {
             return;
--- a/mobile/android/chrome/content/about.js
+++ b/mobile/android/chrome/content/about.js
@@ -1,14 +1,14 @@
 /* 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/. */
 
 var Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils, Cr = Components.results;
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Messaging.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 function init() {
   // Include the build date and a warning about Telemetry
   // if this is an "a#" (nightly or aurora) build
 #expand const version = "__MOZ_APP_VERSION_DISPLAY__";
   if (/a\d+$/.test(version)) {
     let buildID = Services.appinfo.appBuildID;
@@ -54,42 +54,40 @@ function init() {
     links.forEach(function(link) {
       let url = formatter.formatURLPref(link.pref);
       let element = document.getElementById(link.id);
       element.setAttribute("href", url);
     });
   } catch (ex) {}
 
 #ifdef MOZ_UPDATER
+  function expectUpdateResult() {
+    EventDispatcher.instance.registerListener(function listener(event, data, callback) {
+      EventDispatcher.instance.unregisterListener(listener, event);
+      showUpdateMessage(data.result);
+    }, "Update:CheckResult");
+  }
+
   function checkForUpdates() {
     showCheckingMessage();
-
-    let window = Services.wm.getMostRecentWindow("navigator:browser");
+    expectUpdateResult();
 
-    window.WindowEventDispatcher.registerListener(
-        function listener(event, data, callback) {
-      if (event === "Update:CheckResult") {
-        EventDispatcher.instance.unregisterListener(listener, event);
-        showUpdateMessage(data.result);
-      }
-    }, "Update:CheckResult");
-
-    window.WindowEventDispatcher.sendRequest({ type: "Update:Check" });
+    EventDispatcher.instance.sendRequest({ type: "Update:Check" });
   }
 
   function downloadUpdate() {
-    let window = Services.wm.getMostRecentWindow("navigator:browser");
-    window.WindowEventDispatcher.sendRequest({ type: "Update:Download" });
+    expectUpdateResult();
+
+    EventDispatcher.instance.sendRequest({ type: "Update:Download" });
   }
 
   function installUpdate() {
     showCheckAction();
 
-    let window = Services.wm.getMostRecentWindow("navigator:browser");
-    window.WindowEventDispatcher.sendRequest({ type: "Update:Install" });
+    EventDispatcher.instance.sendRequest({ type: "Update:Install" });
   }
 
   let updateLink = document.getElementById("updateLink");
   let checkingSpan = document.getElementById("update-message-checking");
   let noneSpan = document.getElementById("update-message-none");
   let foundSpan = document.getElementById("update-message-found");
   let downloadingSpan = document.getElementById("update-message-downloading");
   let downloadedSpan = document.getElementById("update-message-downloaded");
@@ -130,16 +128,17 @@ function init() {
         noneSpan.style.display = "block";
         setTimeout(showCheckAction, 2000);
         break;
       case "AVAILABLE":
         foundSpan.style.display = "block";
         break;
       case "DOWNLOADING":
         downloadingSpan.style.display = "block";
+        expectUpdateResult();
         break;
       case "DOWNLOADED":
         downloadedSpan.style.display = "block";
         break;
     }
   }
 #endif
 }
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -6793,19 +6793,18 @@ var Distribution = {
     }
 
     // Force the distribution preferences on the default branch
     let defaults = Services.prefs.getDefaultBranch(null);
     defaults.setCharPref("distribution.id", global["id"]);
     defaults.setCharPref("distribution.version", global["version"]);
 
     let locale = BrowserApp.getUALocalePref();
-    let aboutString = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
-    aboutString.data = global["about." + locale] || global["about"];
-    defaults.setComplexValue("distribution.about", Ci.nsISupportsString, aboutString);
+    defaults.setStringPref("distribution.about",
+                           global["about." + locale] || global["about"]);
 
     let prefs = aData["Preferences"];
     for (let key in prefs) {
       try {
         let value = prefs[key];
         switch (typeof value) {
           case "boolean":
             defaults.setBoolPref(key, value);
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/AndroidGamepadManager.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/AndroidGamepadManager.java
@@ -124,21 +124,21 @@ public class AndroidGamepadManager {
                                             MotionEvent.AXIS_GAS};
                 } else {
                     triggerAxes = null;
                 }
             }
         }
     }
 
-    @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
+    @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko_priority")
     private static native void onGamepadChange(int id, boolean added);
-    @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
+    @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko_priority")
     private static native void onButtonChange(int id, int button, boolean pressed, float value);
-    @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
+    @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko_priority")
     private static native void onAxisChange(int id, boolean[] valid, float[] values);
 
     private static boolean sStarted;
     private static final SparseArray<Gamepad> sGamepads = new SparseArray<>();
     private static final SparseArray<List<KeyEvent>> sPendingGamepads = new SparseArray<>();
     private static InputManager.InputDeviceListener sListener;
     private static Timer sPollTimer;
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
@@ -245,21 +245,17 @@ public class GeckoAppShell
         sLayerView = lv;
     }
 
     @RobocopTarget
     public static LayerView getLayerView() {
         return sLayerView;
     }
 
-    // Synchronously notify a Gecko observer; must be called from Gecko thread.
-    @WrapForJNI(calledFrom = "gecko")
-    public static native void syncNotifyObservers(String topic, String data);
-
-    @WrapForJNI(stubName = "NotifyObservers", dispatchTo = "proxy")
+    @WrapForJNI(stubName = "NotifyObservers", dispatchTo = "gecko")
     private static native void nativeNotifyObservers(String topic, String data);
 
     @RobocopTarget
     public static void notifyObservers(final String topic, final String data) {
         notifyObservers(topic, data, GeckoThread.State.RUNNING);
     }
 
     public static void notifyObservers(final String topic, final String data, final GeckoThread.State state) {
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/WrapForJNI.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/annotation/WrapForJNI.java
@@ -25,27 +25,36 @@ public @interface WrapForJNI {
     /**
      * Optional parameter specifying the name of the generated method stub. If omitted,
      * the capitalized name of the Java method will be used.
      */
     String stubName() default "";
 
     /**
      * Action to take if member access returns an exception.
-     * One of "abort", "ignore", or "nsresult". "nsresult" is not supported for native
-     * methods.
+     * - "abort" will cause a crash if there is a pending exception.
+     * - "ignore" will not handle any pending exceptions; it is then the caller's
+     *   responsibility to handle exceptions.
+     * - "nsresult" will clear any pending exceptions and return an error code; not
+     *   supported for native methods.
      */
     String exceptionMode() default "abort";
 
     /**
      * The thread that the method will be called from.
      * One of "any", "gecko", or "ui". Not supported for fields.
      */
     String calledFrom() default "any";
 
     /**
      * The thread that the method call will be dispatched to.
-     * One of "current", "gecko", or "proxy". Not supported for non-native methods,
-     * fields, and constructors. Only void-return methods are supported for anything other
-     * than current thread.
+     * - "current" indicates no dispatching; only supported value for fields,
+     *   constructors, non-native methods, and non-void native methods.
+     * - "gecko" indicates dispatching to the Gecko XPCOM (nsThread) event queue.
+     * - "gecko_priority" indicates dispatching to the Gecko widget
+     *   (nsAppShell) event queue; in most cases, events in the widget event
+     *   queue (aka native event queue) are favored over events in the XPCOM
+     *   event queue.
+     * - "proxy" indicates dispatching to a proxy function as a function object; see
+     *   widget/jni/Natives.h.
      */
     String dispatchTo() default "current";
 }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerView.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/LayerView.java
@@ -75,17 +75,17 @@ public class LayerView extends FrameLayo
         @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
         @Override protected native void disposeNative();
 
         // Gecko thread sets its Java instances; does not block UI thread.
         @WrapForJNI(calledFrom = "any", dispatchTo = "gecko")
         /* package */ native void attachToJava(GeckoLayerClient layerClient,
                                                NativePanZoomController npzc);
 
-        @WrapForJNI(calledFrom = "any", dispatchTo = "gecko")
+        @WrapForJNI(calledFrom = "any", dispatchTo = "gecko_priority")
         /* package */ native void onSizeChanged(int windowWidth, int windowHeight,
                                                 int screenWidth, int screenHeight);
 
         // Gecko thread creates compositor; blocks UI thread.
         @WrapForJNI(calledFrom = "ui", dispatchTo = "proxy")
         /* package */ native void createCompositor(int width, int height, Object surface);
 
         // Gecko thread pauses compositor; blocks UI thread.
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/NativePanZoomController.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/NativePanZoomController.java
@@ -215,17 +215,17 @@ class NativePanZoomController extends JN
         disposeNative();
     }
 
     @Override
     public void attach() {
         mDestroyed = false;
     }
 
-    @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko") @Override // JNIObject
+    @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko_priority") @Override // JNIObject
     protected native void disposeNative();
 
     @Override
     public void setOverscrollHandler(final Overscroll handler) {
         mOverscroll = handler;
     }
 
     @WrapForJNI(stubName = "SetIsLongpressEnabled") // Called from test thread.
--- a/mobile/android/mach_commands.py
+++ b/mobile/android/mach_commands.py
@@ -98,18 +98,18 @@ class AndroidEmulatorCommands(MachComman
     """
        Run the Android emulator with one of the AVDs used in the Mozilla
        automated test environment. If necessary, the AVD is fetched from
        the tooltool server and installed.
     """
     @Command('android-emulator', category='devenv',
         conditions=[],
         description='Run the Android emulator with an AVD from test automation.')
-    @CommandArgument('--version', metavar='VERSION', choices=['4.3', '6.0', 'x86', 'x86-6.0'],
-        help='Specify Android version to run in emulator. One of "4.3", "6.0", "x86", or "x86-6.0".',
+    @CommandArgument('--version', metavar='VERSION', choices=['4.3', '6.0', '7.0', 'x86', 'x86-6.0'],
+        help='Specify Android version to run in emulator. One of "4.3", "6.0", "7.0", "x86", or "x86-6.0".',
         default='4.3')
     @CommandArgument('--wait', action='store_true',
         help='Wait for emulator to be closed.')
     @CommandArgument('--force-update', action='store_true',
         help='Update AVD definition even when AVD is already installed.')
     @CommandArgument('--verbose', action='store_true',
         help='Log informative status messages.')
     def emulator(self, version, wait=False, force_update=False, verbose=False):
--- a/mobile/android/modules/FxAccountsWebChannel.jsm
+++ b/mobile/android/modules/FxAccountsWebChannel.jsm
@@ -43,32 +43,29 @@ this.FxAccountsWebChannelHelpers = funct
 };
 
 this.FxAccountsWebChannelHelpers.prototype = {
   /**
    * Get the hash of account name of the previously signed in account.
    */
   getPreviousAccountNameHashPref() {
     try {
-      return Services.prefs.getComplexValue(PREF_LAST_FXA_USER, Ci.nsISupportsString).data;
+      return Services.prefs.getStringPref(PREF_LAST_FXA_USER);
     } catch (_) {
       return "";
     }
   },
 
   /**
    * Given an account name, set the hash of the previously signed in account.
    *
    * @param acctName the account name of the user's account.
    */
   setPreviousAccountNameHashPref(acctName) {
-    let string = Cc["@mozilla.org/supports-string;1"]
-                 .createInstance(Ci.nsISupportsString);
-    string.data = this.sha256(acctName);
-    Services.prefs.setComplexValue(PREF_LAST_FXA_USER, Ci.nsISupportsString, string);
+    Services.prefs.setStringPref(PREF_LAST_FXA_USER, this.sha256(acctName));
   },
 
   /**
    * Given a string, returns the SHA265 hash in base64.
    */
   sha256(str) {
     let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                       .createInstance(Ci.nsIScriptableUnicodeConverter);
--- a/modules/libpref/Preferences.cpp
+++ b/modules/libpref/Preferences.cpp
@@ -798,16 +798,22 @@ Preferences::GetPreferences(InfallibleTA
 }
 
 #ifdef DEBUG
 void
 Preferences::SetInitPhase(pref_initPhase phase)
 {
   pref_SetInitPhase(phase);
 }
+
+pref_initPhase
+Preferences::InitPhase()
+{
+  return pref_GetInitPhase();
+}
 #endif
 
 NS_IMETHODIMP
 Preferences::GetBranch(const char *aPrefRoot, nsIPrefBranch **_retval)
 {
   nsresult rv;
 
   if ((nullptr != aPrefRoot) && (*aPrefRoot != '\0')) {
--- a/modules/libpref/Preferences.h
+++ b/modules/libpref/Preferences.h
@@ -379,16 +379,17 @@ public:
   static void GetPreferences(InfallibleTArray<PrefSetting>* aPrefs);
   static void GetPreference(PrefSetting* aPref);
   static void SetPreference(const PrefSetting& aPref);
 
   static void SetInitPreferences(nsTArray<PrefSetting>* aPrefs);
 
 #ifdef DEBUG
   static void SetInitPhase(pref_initPhase phase);
+  static pref_initPhase InitPhase();
 #endif
 
   static int64_t SizeOfIncludingThisAndOtherStuff(mozilla::MallocSizeOf aMallocSizeOf);
 
   static void DirtyCallback();
 
 protected:
   virtual ~Preferences();
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -853,22 +853,18 @@ pref("ui.scrollToClick", 0);
 pref("canvas.focusring.enabled", true);
 pref("canvas.customfocusring.enabled", false);
 pref("canvas.hitregions.enabled", false);
 pref("canvas.filters.enabled", true);
 // Add support for canvas path objects
 pref("canvas.path.enabled", true);
 pref("canvas.capturestream.enabled", true);
 
-// Disable the ImageBitmap-extensions in the release build.
-#ifdef RELEASE_OR_BETA
+// Disable the ImageBitmap-extensions for now.
 pref("canvas.imagebitmap_extensions.enabled", false);
-#else
-pref("canvas.imagebitmap_extensions.enabled", true);
-#endif
 
 // We want the ability to forcibly disable platform a11y, because
 // some non-a11y-related components attempt to bring it up.  See bug
 // 538530 for details about Windows; we have a pref here that allows it
 // to be disabled for performance and testing resons.
 // See bug 761589 for the crossplatform aspect.
 //
 // This pref is checked only once, and the browser needs a restart to
--- a/modules/libpref/nsIPrefBranch.idl
+++ b/modules/libpref/nsIPrefBranch.idl
@@ -94,44 +94,71 @@ interface nsIPrefBranch : nsISupports
    * @see setCharPref
    */
   [optional_argc,binaryname(GetFloatPrefWithDefault)]
   float getFloatPref(in string aPrefName, [optional] in float aDefaultValue);
   [noscript,binaryname(GetFloatPref)]
   float getFloatPrefXPCOM(in string aPrefName);
 
   /**
-   * Called to get the state of an individual string preference.
+   * Called to get the state of an individual ascii string preference.
    *
    * @param aPrefName The string preference to retrieve.
    * @param aDefaultValue The string to return if the preference is not set.
    *
    * @return string   The value of the requested string preference.
    *
    * @see setCharPref
    */
   [optional_argc,binaryname(GetCharPrefWithDefault)]
   string getCharPref(in string aPrefName, [optional] in string aDefaultValue);
   [noscript,binaryname(GetCharPref)]
   string getCharPrefXPCOM(in string aPrefName);
 
   /**
-   * Called to set the state of an individual string preference.
+   * Called to set the state of an individual ascii string preference.
    *
    * @param aPrefName The string preference to set.
    * @param aValue    The string value to set the preference to.
    *
    * @throws Error if setting failed or the preference has a default
              value of a type other than string.
    *
    * @see getCharPref
    */
   void setCharPref(in string aPrefName, in string aValue);
 
   /**
+   * Called to get the state of an individual unicode string preference.
+   *
+   * @param aPrefName The string preference to retrieve.
+   * @param aDefaultValue The string to return if the preference is not set.
+   *
+   * @return string   The value of the requested string preference.
+   *
+   * @see setStringPref
+   */
+  [optional_argc]
+  AUTF8String getStringPref(in string aPrefName,
+                            [optional] in AUTF8String aDefaultValue);
+
+  /**
+   * Called to set the state of an individual unicode string preference.
+   *
+   * @param aPrefName The string preference to set.
+   * @param aValue    The string value to set the preference to.
+   *
+   * @throws Error if setting failed or the preference has a default
+             value of a type other than string.
+   *
+   * @see getStringPref
+   */
+  void setStringPref(in string aPrefName, in AUTF8String aValue);
+
+  /**
    * Called to get the state of an individual integer preference.
    *
    * @param aPrefName The integer preference to get the value of.
    * @param aDefaultValue The value to return if the preference is not set.
    *
    * @return long     The value of the requested integer preference.
    *
    * @see setIntPref
@@ -159,16 +186,17 @@ interface nsIPrefBranch : nsISupports
    * preference is a preference which represents an XPCOM object that can not
    * be easily represented using a standard boolean, integer or string value.
    *
    * @param aPrefName The complex preference to get the value of.
    * @param aType     The XPCOM interface that this complex preference
    *                  represents. Interfaces currently supported are:
    *                    - nsIFile
    *                    - nsISupportsString (UniChar)
+   *                      (deprecated; see getStringPref)
    *                    - nsIPrefLocalizedString (Localized UniChar)
    * @param aValue    The XPCOM object into which to the complex preference 
    *                  value should be retrieved.
    *
    * @throws Error The value does not exist or is the wrong type.
    *
    * @see setComplexValue
    */
@@ -180,16 +208,17 @@ interface nsIPrefBranch : nsISupports
    * preference is a preference which represents an XPCOM object that can not
    * be easily represented using a standard boolean, integer or string value.
    *
    * @param aPrefName The complex preference to set the value of.
    * @param aType     The XPCOM interface that this complex preference
    *                  represents. Interfaces currently supported are:
    *                    - nsIFile
    *                    - nsISupportsString (UniChar)
+   *                      (deprecated; see setStringPref)
    *                    - nsIPrefLocalizedString (Localized UniChar)
    * @param aValue    The XPCOM object from which to set the complex preference 
    *                  value.
    *
    * @throws Error if setting failed or the value is the wrong type.
    *
    * @see getComplexValue
    */
--- a/modules/libpref/nsPrefBranch.cpp
+++ b/modules/libpref/nsPrefBranch.cpp
@@ -237,16 +237,46 @@ nsresult nsPrefBranch::SetCharPrefIntern
 {
   ENSURE_MAIN_PROCESS("Cannot SetCharPref from content process:", aPrefName);
   NS_ENSURE_ARG(aPrefName);
   NS_ENSURE_ARG(aValue);
   const char *pref = getPrefName(aPrefName);
   return PREF_SetCharPref(pref, aValue, mIsDefault);
 }
 
+NS_IMETHODIMP nsPrefBranch::GetStringPref(const char *aPrefName,
+                                          const nsACString& aDefaultValue,
+                                          uint8_t _argc,
+                                          nsACString& _retval)
+{
+  nsXPIDLCString utf8String;
+  nsresult rv = GetCharPref(aPrefName, getter_Copies(utf8String));
+  if (NS_SUCCEEDED(rv)) {
+    _retval = utf8String;
+    return rv;
+  }
+
+  if (_argc == 1) {
+    _retval = aDefaultValue;
+    return NS_OK;
+  }
+
+  return rv;
+}
+
+NS_IMETHODIMP nsPrefBranch::SetStringPref(const char *aPrefName, const nsACString& aValue)
+{
+  nsresult rv = CheckSanityOfStringLength(aPrefName, aValue);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  return SetCharPrefInternal(aPrefName, PromiseFlatCString(aValue).get());
+}
+
 NS_IMETHODIMP nsPrefBranch::GetIntPrefWithDefault(const char *aPrefName,
                                                   int32_t aDefaultValue,
                                                   uint8_t _argc, int32_t *_retval)
 {
   nsresult rv = GetIntPref(aPrefName, _retval);
 
   if (NS_FAILED(rv) && _argc == 1) {
     *_retval = aDefaultValue;
@@ -420,16 +450,20 @@ nsresult nsPrefBranch::CheckSanityOfStri
   }
   return CheckSanityOfStringLength(aPrefName, strlen(aValue));
 }
 
 nsresult nsPrefBranch::CheckSanityOfStringLength(const char* aPrefName, const nsAString& aValue) {
   return CheckSanityOfStringLength(aPrefName, aValue.Length());
 }
 
+nsresult nsPrefBranch::CheckSanityOfStringLength(const char* aPrefName, const nsACString& aValue) {
+  return CheckSanityOfStringLength(aPrefName, aValue.Length());
+}
+
 nsresult nsPrefBranch::CheckSanityOfStringLength(const char* aPrefName, const uint32_t aLength) {
   if (aLength > MAX_PREF_LENGTH) {
     return NS_ERROR_ILLEGAL_VALUE;
   }
   if (aLength <= MAX_ADVISABLE_PREF_LENGTH) {
     return NS_OK;
   }
   nsresult rv;
--- a/modules/libpref/nsPrefBranch.h
+++ b/modules/libpref/nsPrefBranch.h
@@ -207,16 +207,17 @@ protected:
     , mFreeingObserverList(false)
   {}
 
   nsresult   GetDefaultFromPropertiesFile(const char *aPrefName, char16_t **return_buf);
   // As SetCharPref, but without any check on the length of |aValue|
   nsresult   SetCharPrefInternal(const char *aPrefName, const char *aValue);
   // Reject strings that are more than 1Mb, warn if strings are more than 16kb
   nsresult   CheckSanityOfStringLength(const char* aPrefName, const nsAString& aValue);
+  nsresult   CheckSanityOfStringLength(const char* aPrefName, const nsACString& aValue);
   nsresult   CheckSanityOfStringLength(const char* aPrefName, const char* aValue);
   nsresult   CheckSanityOfStringLength(const char* aPrefName, const uint32_t aLength);
   void RemoveExpiredCallback(PrefCallback *aCallback);
   const char *getPrefName(const char *aPrefName);
   void       freeObserverList(void);
 
 private:
   int32_t               mPrefRootLength;
--- a/modules/libpref/prefapi.cpp
+++ b/modules/libpref/prefapi.cpp
@@ -740,16 +740,22 @@ static pref_initPhase gPhase = START;
 static bool gWatchingPref = false;
 
 void
 pref_SetInitPhase(pref_initPhase phase)
 {
     gPhase = phase;
 }
 
+pref_initPhase
+pref_GetInitPhase()
+{
+    return gPhase;
+}
+
 void
 pref_SetWatchingPref(bool watching)
 {
     gWatchingPref = watching;
 }
 
 
 struct StringComparator
--- a/modules/libpref/prefapi_private_data.h
+++ b/modules/libpref/prefapi_private_data.h
@@ -25,16 +25,19 @@ pref_savePrefs(PLDHashTable* aTable, uin
 
 nsresult
 pref_SetPref(const mozilla::dom::PrefSetting& aPref);
 
 #ifdef DEBUG
 void
 pref_SetInitPhase(pref_initPhase phase);
 
+pref_initPhase
+pref_GetInitPhase();
+
 void
 pref_SetWatchingPref(bool watching);
 #endif
 
 int pref_CompareStrings(const void *v1, const void *v2, void* unused);
 PrefHashEntry* pref_HashTableLookup(const char *key);
 
 bool
--- a/modules/libpref/test/unit/test_defaultValues.js
+++ b/modules/libpref/test/unit/test_defaultValues.js
@@ -23,16 +23,32 @@ function run_test() {
   do_check_throws(function() { ps.getCharPref(prefName); },
                   Cr.NS_ERROR_UNEXPECTED);
   strictEqual(ps.getCharPref(prefName, ""), "");
   strictEqual(ps.getCharPref(prefName, "string"), "string");
   ps.setCharPref(prefName, "foo");
   strictEqual(ps.getCharPref(prefName), "foo");
   strictEqual(ps.getCharPref(prefName, "string"), "foo");
 
+  prefName = "test.default.values.string";
+  do_check_throws(function() { ps.getCharPref(prefName); },
+                  Cr.NS_ERROR_UNEXPECTED);
+  strictEqual(ps.getStringPref(prefName, ""), "");
+  strictEqual(ps.getStringPref(prefName, "éèçàê€"), "éèçàê€");
+  ps.setStringPref(prefName, "éèçàê€");
+  strictEqual(ps.getStringPref(prefName), "éèçàê€");
+  strictEqual(ps.getStringPref(prefName, "string"), "éèçàê€");
+  strictEqual(ps.getStringPref(prefName),
+              ps.getComplexValue(prefName, Ci.nsISupportsString).data);
+  let str = Cc["@mozilla.org/supports-string;1"].
+              createInstance(Ci.nsISupportsString);
+  str.data = "ù€ÚîœïŒëøÇ“";
+  ps.setComplexValue(prefName, Ci.nsISupportsString, str);
+  strictEqual(ps.getStringPref(prefName), "ù€ÚîœïŒëøÇ“");
+
   prefName = "test.default.values.float";
   do_check_throws(function() { ps.getFloatPref(prefName); },
                   Cr.NS_ERROR_UNEXPECTED);
   strictEqual(ps.getFloatPref(prefName, 3.5), 3.5);
   strictEqual(ps.getFloatPref(prefName, 0), 0);
   ps.setCharPref(prefName, 1.75);
   strictEqual(ps.getFloatPref(prefName), 1.75);
   strictEqual(ps.getFloatPref(prefName, 3.5), 1.75);
--- a/netwerk/protocol/http/ConnectionDiagnostics.cpp
+++ b/netwerk/protocol/http/ConnectionDiagnostics.cpp
@@ -199,10 +199,20 @@ nsHttpTransaction::PrintDiagnostics(nsCS
   nsAutoCString requestURI;
   mRequestHead->RequestURI(requestURI);
   log.AppendPrintf("     ::: uri = %s\n", requestURI.get());
   log.AppendPrintf("     caps = 0x%x\n", mCaps);
   log.AppendPrintf("     priority = %d\n", mPriority);
   log.AppendPrintf("     restart count = %u\n", mRestartCount);
 }
 
+void
+nsHttpConnectionMgr::PendingTransactionInfo::PrintDiagnostics(nsCString &log)
+{
+  log.AppendPrintf("     ::: Pending transaction\n");
+  mTransaction->PrintDiagnostics(log);
+  RefPtr<nsHalfOpenSocket> halfOpen = do_QueryReferent(mHalfOpen);
+  log.AppendPrintf("     Waiting for half open sock: %p or connection: %p\n",
+                   halfOpen.get(), mActiveConn.get());
+}
+
 } // namespace net
 } // namespace mozilla
--- a/netwerk/protocol/http/NullHttpTransaction.cpp
+++ b/netwerk/protocol/http/NullHttpTransaction.cpp
@@ -137,16 +137,22 @@ NullHttpTransaction::Claim()
   if (mClaimed) {
     return false;
   }
   mClaimed = true;
   return true;
 }
 
 void
+NullHttpTransaction::Unclaim()
+{
+  mClaimed = false;
+}
+
+void
 NullHttpTransaction::SetConnection(nsAHttpConnection *conn)
 {
   mConnection = conn;
 }
 
 nsAHttpConnection *
 NullHttpTransaction::Connection()
 {
--- a/netwerk/protocol/http/NullHttpTransaction.h
+++ b/netwerk/protocol/http/NullHttpTransaction.h
@@ -34,16 +34,17 @@ public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSAHTTPTRANSACTION
 
   NullHttpTransaction(nsHttpConnectionInfo *ci,
                       nsIInterfaceRequestor *callbacks,
                       uint32_t caps);
 
   MOZ_MUST_USE bool Claim();
+  void Unclaim();
 
   // Overload of nsAHttpTransaction methods
   bool IsNullTransaction() override final { return true; }
   NullHttpTransaction *QueryNullTransaction() override final { return this; }
   bool ResponseTimeoutEnabled() const override final {return true; }
   PRIntervalTime ResponseTimeout() override final
   {
     return PR_SecondsToInterval(15);
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -4893,16 +4893,19 @@ nsHttpChannel::InitCacheEntry()
         // then force recreation of the entry as memory/only.
         rv = mCacheEntry->GetPersistent(&recreate);
         if (NS_FAILED(rv))
             return rv;
     }
 
     if (recreate) {
         LOG(("  we have a ready entry, but reading it again from the server -> recreating cache entry\n"));
+        // clean the altData cache and reset this to avoid wrong content length
+        mAvailableCachedAltDataType.Truncate();
+
         nsCOMPtr<nsICacheEntry> currentEntry;
         currentEntry.swap(mCacheEntry);
         rv = currentEntry->Recreate(dontPersist, getter_AddRefs(mCacheEntry));
         if (NS_FAILED(rv)) {
           LOG(("  recreation failed, the response will not be cached"));
           return NS_OK;
         }
 
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -2069,21 +2069,32 @@ nsHttpConnection::DisableTCPKeepalives()
     }
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // nsHttpConnection::nsISupports
 //-----------------------------------------------------------------------------
 
-NS_IMPL_ISUPPORTS(nsHttpConnection,
-                  nsIInputStreamCallback,
-                  nsIOutputStreamCallback,
-                  nsITransportEventSink,
-                  nsIInterfaceRequestor)
+NS_IMPL_ADDREF(nsHttpConnection)
+NS_IMPL_RELEASE(nsHttpConnection)
+
+NS_INTERFACE_MAP_BEGIN(nsHttpConnection)
+    NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+    NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
+    NS_INTERFACE_MAP_ENTRY(nsIOutputStreamCallback)
+    NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
+    NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+    // we have no macro that covers this case.
+    if (aIID.Equals(NS_GET_IID(nsHttpConnection)) ) {
+        AddRef();
+        *aInstancePtr = this;
+        return NS_OK;
+    } else
+NS_INTERFACE_MAP_END
 
 //-----------------------------------------------------------------------------
 // nsHttpConnection::nsIInputStreamCallback
 //-----------------------------------------------------------------------------
 
 // called on the socket transport thread
 NS_IMETHODIMP
 nsHttpConnection::OnInputStreamReady(nsIAsyncInputStream *in)
--- a/netwerk/protocol/http/nsHttpConnection.h
+++ b/netwerk/protocol/http/nsHttpConnection.h
@@ -26,35 +26,41 @@ class nsISocketTransport;
 class nsISSLSocketControl;
 
 namespace mozilla {
 namespace net {
 
 class nsHttpHandler;
 class ASpdySession;
 
+// 1dcc863e-db90-4652-a1fe-13fea0b54e46
+#define NS_HTTPCONNECTION_IID \
+{ 0x1dcc863e, 0xdb90, 0x4652, {0xa1, 0xfe, 0x13, 0xfe, 0xa0, 0xb5, 0x4e, 0x46 }}
+
 //-----------------------------------------------------------------------------
 // nsHttpConnection - represents a connection to a HTTP server (or proxy)
 //
 // NOTE: this objects lives on the socket thread only.  it should not be
 // accessed from any other thread.
 //-----------------------------------------------------------------------------
 
 class nsHttpConnection final : public nsAHttpSegmentReader
                              , public nsAHttpSegmentWriter
                              , public nsIInputStreamCallback
                              , public nsIOutputStreamCallback
                              , public nsITransportEventSink
                              , public nsIInterfaceRequestor
                              , public NudgeTunnelCallback
                              , public ARefBase
+                             , public nsSupportsWeakReference
 {
     virtual ~nsHttpConnection();
 
 public:
+    NS_DECLARE_STATIC_IID_ACCESSOR(NS_HTTPCONNECTION_IID)
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSAHTTPSEGMENTREADER
     NS_DECL_NSAHTTPSEGMENTWRITER
     NS_DECL_NSIINPUTSTREAMCALLBACK
     NS_DECL_NSIOUTPUTSTREAMCALLBACK
     NS_DECL_NSITRANSPORTEVENTSINK
     NS_DECL_NSIINTERFACEREQUESTOR
     NS_DECL_NUDGETUNNELCALLBACK
@@ -371,12 +377,14 @@ private:
                                                              // for the end of
                                                              // the handsake.
     int64_t                        mContentBytesWritten0RTT;
     bool                           mEarlyDataNegotiated; //Only used for telemetry
     nsCString                      mEarlyNegotiatedALPN;
     bool                           mDid0RTTSpdy;
 };
 
+NS_DEFINE_STATIC_IID_ACCESSOR(nsHttpConnection, NS_HTTPCONNECTION_IID)
+
 } // namespace net
 } // namespace mozilla
 
 #endif // nsHttpConnection_h__
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -39,41 +39,44 @@
 
 namespace mozilla {
 namespace net {
 
 //-----------------------------------------------------------------------------
 
 NS_IMPL_ISUPPORTS(nsHttpConnectionMgr, nsIObserver)
 
-static void
-InsertTransactionSorted(nsTArray<RefPtr<nsHttpTransaction>> &pendingQ, nsHttpTransaction *trans)
+void
+nsHttpConnectionMgr::InsertTransactionSorted(nsTArray<RefPtr<nsHttpConnectionMgr::PendingTransactionInfo> > &pendingQ,
+                                             nsHttpConnectionMgr::PendingTransactionInfo *pendingTransInfo)
 {
     // insert into queue with smallest valued number first.  search in reverse
     // order under the assumption that many of the existing transactions will
     // have the same priority (usually 0).
 
+    nsHttpTransaction *trans = pendingTransInfo->mTransaction;
+
     for (int32_t i = pendingQ.Length() - 1; i >= 0; --i) {
-        nsHttpTransaction *t = pendingQ[i];
+        nsHttpTransaction *t = pendingQ[i]->mTransaction;
         if (trans->Priority() >= t->Priority()) {
             if (ChaosMode::isActive(ChaosFeature::NetworkScheduling)) {
                 int32_t samePriorityCount;
                 for (samePriorityCount = 0; i - samePriorityCount >= 0; ++samePriorityCount) {
-                    if (pendingQ[i - samePriorityCount]->Priority() != trans->Priority()) {
+                    if (pendingQ[i - samePriorityCount]->mTransaction->Priority() != trans->Priority()) {
                         break;
                     }
                 }
                 // skip over 0...all of the elements with the same priority.
                 i -= ChaosMode::randomUint32LessThan(samePriorityCount + 1);
             }
-            pendingQ.InsertElementAt(i+1, trans);
+            pendingQ.InsertElementAt(i+1, pendingTransInfo);
             return;
         }
     }
-    pendingQ.InsertElementAt(0, trans);
+    pendingQ.InsertElementAt(0, pendingTransInfo);
 }
 
 //-----------------------------------------------------------------------------
 
 nsHttpConnectionMgr::nsHttpConnectionMgr()
     : mReentrantMonitor("nsHttpConnectionMgr.mReentrantMonitor")
     , mMaxUrgentStartQ(0)
     , mMaxConns(0)
@@ -628,18 +631,18 @@ nsHttpConnectionMgr::LookupConnectionEnt
         // likely the only active connection in preferred - so start with that.
         if (preferred->mActiveConns.Contains(conn))
             return preferred;
         if (preferred->mIdleConns.Contains(conn))
             return preferred;
     }
 
     if (trans &&
-        (preferred->mPendingQ.Contains(trans) ||
-         preferred->mUrgentStartQ.Contains(trans))) {
+        (preferred->mPendingQ.Contains(trans, PendingComparator()) ||
+         preferred->mUrgentStartQ.Contains(trans, PendingComparator()))) {
         return preferred;
     }
 
     // Neither conn nor trans found in preferred, use the default entry
     return ent;
 }
 
 nsresult
@@ -872,56 +875,94 @@ nsHttpConnectionMgr::GetSpdyPreferredEnt
          preferred->mConnInfo->Origin(), aOriginalEntry->mConnInfo->Origin(),
          aOriginalEntry->mConnInfo->Origin(), preferred->mConnInfo->Origin()));
     Telemetry::Accumulate(Telemetry::SPDY_NPN_JOIN, true);
     return preferred;
 }
 
 //-----------------------------------------------------------------------------
 void
-nsHttpConnectionMgr::DispatchPendingQ(nsTArray<RefPtr<nsHttpTransaction>> &pendingQ,
+nsHttpConnectionMgr::DispatchPendingQ(nsTArray<RefPtr<nsHttpConnectionMgr::PendingTransactionInfo> > &pendingQ,
                                       nsConnectionEntry *ent,
                                       bool &dispatchedSuccessfully,
                                       bool considerAll)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
-    nsHttpTransaction *trans = nullptr;
+    PendingTransactionInfo *pendingTransInfo = nullptr;
     nsresult rv;
     // if !considerAll iterate the pending list until one is dispatched successfully.
     // Keep iterating afterwards only until a transaction fails to dispatch.
     // if considerAll == true then try and dispatch all items.
     for (uint32_t i = 0; i < pendingQ.Length(); ) {
-        trans = pendingQ[i];
+        pendingTransInfo = pendingQ[i];
+        LOG(("nsHttpConnectionMgr::ProcessPendingQForEntry "
+             "[trans=%p, halfOpen=%p, activeConn=%p]\n",
+             pendingTransInfo->mTransaction.get(),
+             pendingTransInfo->mHalfOpen.get(),
+             pendingTransInfo->mActiveConn.get()));
 
         // When this transaction has already established a half-open
         // connection, we want to prevent any duplicate half-open
         // connections from being established and bound to this
         // transaction. Allow only use of an idle persistent connection
         // (if found) for transactions referred by a half-open connection.
-        bool alreadyHalfOpen = false;
-        for (int32_t j = 0; j < ((int32_t) ent->mHalfOpens.Length()); ++j) {
-            if (ent->mHalfOpens[j]->Transaction() == trans) {
-                alreadyHalfOpen = true;
-                break;
+        bool alreadyHalfOpenOrWaitingForTLS = false;
+        if (pendingTransInfo->mHalfOpen) {
+            MOZ_ASSERT(!pendingTransInfo->mActiveConn);
+            RefPtr<nsHalfOpenSocket> halfOpen =
+                do_QueryReferent(pendingTransInfo->mHalfOpen);
+            if (halfOpen) {
+                // The half open socket was made for this transaction, in
+                // that case ent->mHalfOpens[j]->Transaction() == trans or
+                // the half open socket was opened speculatively and this
+                // transaction took it (in this case it must be:
+                // ent->mHalfOpens[j]->Transaction().IsNullTransaction())
+                MOZ_ASSERT(halfOpen->Transaction()->IsNullTransaction() ||
+                           halfOpen->Transaction() == pendingTransInfo->mTransaction);
+                alreadyHalfOpenOrWaitingForTLS = true;
+            } else {
+                // If we have not found the halfOpen socket, remove the pointer.
+                pendingTransInfo->mHalfOpen = nullptr;
+            }
+        }  else if (pendingTransInfo->mActiveConn) {
+            MOZ_ASSERT(!pendingTransInfo->mHalfOpen);
+            RefPtr<nsHttpConnection> activeConn =
+                do_QueryReferent(pendingTransInfo->mActiveConn);
+            // Check if this transaction claimed a connection that is still
+            // performing tls handshake with a NullHttpTransaction or it is between
+            // finishing tls and reclaiming (When nullTrans finishes tls handshake,
+            // httpConnection does not have a transaction any more and a
+            // ReclaimConnection is dispatched). But if an error occurred the
+            // connection will be closed, it will exist but CanReused will be
+            // false. 
+            if (activeConn &&
+                ((activeConn->Transaction() &&
+                  activeConn->Transaction()->IsNullTransaction()) ||
+                 (!activeConn->Transaction() && activeConn->CanReuse()))) {
+                alreadyHalfOpenOrWaitingForTLS = true;
+            } else {
+                // If we have not found the connection, remove the pointer.
+                pendingTransInfo->mActiveConn = nullptr;
             }
         }
 
         rv = TryDispatchTransaction(ent,
-                                    alreadyHalfOpen || !!trans->TunnelProvider(),
-                                    trans);
+                                    alreadyHalfOpenOrWaitingForTLS || !!pendingTransInfo->mTransaction->TunnelProvider(),
+                                    pendingTransInfo);
         if (NS_SUCCEEDED(rv) || (rv != NS_ERROR_NOT_AVAILABLE)) {
             if (NS_SUCCEEDED(rv)) {
                 LOG(("  dispatching pending transaction...\n"));
             } else {
                 LOG(("  removing pending transaction based on "
                      "TryDispatchTransaction returning hard error %" PRIx32 "\n",
                      static_cast<uint32_t>(rv)));
             }
-            if (pendingQ.RemoveElement(trans)) {
-                // trans is now potentially destroyed
+            ReleaseClaimedSockets(ent, pendingTransInfo);
+            if (pendingQ.RemoveElement(pendingTransInfo)) {
+                // pendingTransInfo is now potentially destroyed
                 dispatchedSuccessfully = true;
                 continue; // dont ++i as we just made the array shorter
             }
 
             LOG(("  transaction not found in pending queue\n"));
         }
 
         if (dispatchedSuccessfully && !considerAll)
@@ -944,16 +985,19 @@ nsHttpConnectionMgr::ProcessPendingQForE
          ent->mIdleConns.Length(), ent->mUrgentStartQ.Length(),
          ent->mPendingQ.Length()));
 
     ProcessSpdyPendingQ(ent);
 
     bool dispatchedSuccessfully = false;
 
     DispatchPendingQ(ent->mUrgentStartQ, ent, dispatchedSuccessfully, considerAll);
+    if (dispatchedSuccessfully && !considerAll) {
+        return dispatchedSuccessfully;
+    }
     DispatchPendingQ(ent->mPendingQ, ent, dispatchedSuccessfully, considerAll);
 
     return dispatchedSuccessfully;
 }
 
 bool
 nsHttpConnectionMgr::ProcessPendingQForEntry(nsHttpConnectionInfo *ci)
 {
@@ -1088,18 +1132,20 @@ nsHttpConnectionMgr::RestrictConnections
 }
 
 // returns NS_OK if a connection was started
 // return NS_ERROR_NOT_AVAILABLE if a new connection cannot be made due to
 //        ephemeral limits
 // returns other NS_ERROR on hard failure conditions
 nsresult
 nsHttpConnectionMgr::MakeNewConnection(nsConnectionEntry *ent,
-                                       nsHttpTransaction *trans)
+                                       PendingTransactionInfo *pendingTransInfo)
 {
+    nsHttpTransaction *trans = pendingTransInfo->mTransaction;
+
     LOG(("nsHttpConnectionMgr::MakeNewConnection %p ent=%p trans=%p",
          this, ent, trans));
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
     uint32_t halfOpenLength = ent->mHalfOpens.Length();
     for (uint32_t i = 0; i < halfOpenLength; i++) {
         if (ent->mHalfOpens[i]->IsSpeculative()) {
             // We've found a speculative connection in the half
@@ -1108,16 +1154,18 @@ nsHttpConnectionMgr::MakeNewConnection(n
             // (or another one in the pending queue) - we don't
             // need to open a new connection here.
             LOG(("nsHttpConnectionMgr::MakeNewConnection [ci = %s]\n"
                  "Found a speculative half open connection\n",
                  ent->mConnInfo->HashKey().get()));
 
             uint32_t flags;
             ent->mHalfOpens[i]->SetSpeculative(false);
+            pendingTransInfo->mHalfOpen =
+                do_GetWeakReference(static_cast<nsISupportsWeakReference*>(ent->mHalfOpens[i]));
             nsISocketTransport *transport = ent->mHalfOpens[i]->SocketTransport();
             if (transport && NS_SUCCEEDED(transport->GetConnectionFlags(&flags))) {
                 flags &= ~nsISocketTransport::DISABLE_RFC1918;
                 transport->SetConnectionFlags(flags);
             }
 
             Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_USED_SPECULATIVE_CONN> usedSpeculativeConn;
             ++usedSpeculativeConn;
@@ -1139,16 +1187,18 @@ nsHttpConnectionMgr::MakeNewConnection(n
         uint32_t activeLength = ent->mActiveConns.Length();
         for (uint32_t i = 0; i < activeLength; i++) {
             nsAHttpTransaction *activeTrans = ent->mActiveConns[i]->Transaction();
             NullHttpTransaction *nullTrans = activeTrans ? activeTrans->QueryNullTransaction() : nullptr;
             if (nullTrans && nullTrans->Claim()) {
                 LOG(("nsHttpConnectionMgr::MakeNewConnection [ci = %s] "
                      "Claiming a null transaction for later use\n",
                      ent->mConnInfo->HashKey().get()));
+                pendingTransInfo->mActiveConn =
+                    do_GetWeakReference(static_cast<nsISupportsWeakReference*>(ent->mActiveConns[i]));
                 return NS_OK;
             }
         }
     }
 
     // If this host is trying to negotiate a SPDY session right now,
     // don't create any new connections until the result of the
     // negotiation is known.
@@ -1215,17 +1265,18 @@ nsHttpConnectionMgr::MakeNewConnection(n
         }
       outerLoopEnd:
         ;
     }
 
     if (AtActiveConnectionLimit(ent, trans->Caps()))
         return NS_ERROR_NOT_AVAILABLE;
 
-    nsresult rv = CreateTransport(ent, trans, trans->Caps(), false, false, true);
+    nsresult rv = CreateTransport(ent, trans, trans->Caps(), false, false,
+                                  true, pendingTransInfo);
     if (NS_FAILED(rv)) {
         /* hard failure */
         LOG(("nsHttpConnectionMgr::MakeNewConnection [ci = %s trans = %p] "
              "CreateTransport() hard failure.\n",
              ent->mConnInfo->HashKey().get(), trans));
         trans->Close(rv);
         if (rv == NS_ERROR_NOT_AVAILABLE)
             rv = NS_ERROR_FAILURE;
@@ -1239,23 +1290,28 @@ nsHttpConnectionMgr::MakeNewConnection(n
 //   and the transaction is started.
 // returns ERROR_NOT_AVAILABLE if no connection can be found and it
 //   should be queued until circumstances change
 // returns other ERROR when transaction has a hard failure and should
 //   not remain in the pending queue
 nsresult
 nsHttpConnectionMgr::TryDispatchTransaction(nsConnectionEntry *ent,
                                             bool onlyReusedConnection,
-                                            nsHttpTransaction *trans)
+                                            PendingTransactionInfo *pendingTransInfo)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+    nsHttpTransaction *trans = pendingTransInfo->mTransaction;
+
     LOG(("nsHttpConnectionMgr::TryDispatchTransaction without conn "
-         "[trans=%p ci=%p ci=%s caps=%x tunnelprovider=%p onlyreused=%d "
-         "active=%" PRIuSIZE " idle=%" PRIuSIZE "]\n", trans,
-         ent->mConnInfo.get(), ent->mConnInfo->HashKey().get(),
+         "[trans=%p halfOpen=%p conn=%p ci=%p ci=%s caps=%x tunnelprovider=%p "
+         "onlyreused=%d active=%" PRIuSIZE " idle=%" PRIuSIZE "]\n", trans,
+         pendingTransInfo->mHalfOpen.get(),
+         pendingTransInfo->mActiveConn.get(), ent->mConnInfo.get(),
+         ent->mConnInfo->HashKey().get(),
          uint32_t(trans->Caps()), trans->TunnelProvider(),
          onlyReusedConnection, ent->mActiveConns.Length(),
          ent->mIdleConns.Length()));
 
     uint32_t caps = trans->Caps();
 
     // 0 - If this should use spdy then dispatch it post haste.
     // 1 - If there is connection pressure then see if we can pipeline this on
@@ -1381,17 +1437,17 @@ nsHttpConnectionMgr::TryDispatchTransact
     }
 
     // step 3
     // consider pipelining scripts and revalidations
     // h1 pipelining has been removed
 
     // step 4
     if (!onlyReusedConnection) {
-        nsresult rv = MakeNewConnection(ent, trans);
+        nsresult rv = MakeNewConnection(ent, pendingTransInfo);
         if (NS_SUCCEEDED(rv)) {
             // this function returns NOT_AVAILABLE for asynchronous connects
             LOG(("   dispatched step 4 (async new conn) trans=%p\n", trans));
             return NS_ERROR_NOT_AVAILABLE;
         }
 
         if (rv != NS_ERROR_NOT_AVAILABLE) {
             // not available return codes should try next step as they are
@@ -1610,16 +1666,17 @@ nsHttpConnectionMgr::ProcessNewTransacti
     ReportProxyTelemetry(ent);
 
     // Check if the transaction already has a sticky reference to a connection.
     // If so, then we can just use it directly by transferring its reference
     // to the new connection variable instead of searching for a new one
 
     nsAHttpConnection *wrappedConnection = trans->Connection();
     RefPtr<nsHttpConnection> conn;
+    RefPtr<PendingTransactionInfo> pendingTransInfo;
     if (wrappedConnection)
         conn = wrappedConnection->TakeHttpConnection();
 
     if (conn) {
         MOZ_ASSERT(trans->Caps() & NS_HTTP_STICKY_CONNECTION);
         LOG(("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
              "sticky connection=%p\n", trans, conn.get()));
 
@@ -1633,37 +1690,41 @@ nsHttpConnectionMgr::ProcessNewTransacti
             MOZ_ASSERT(!conn->IsExperienced());
 
             AddActiveConn(conn, ent); // make it active
         }
 
         trans->SetConnection(nullptr);
         rv = DispatchTransaction(ent, trans, conn);
     } else {
-        rv = TryDispatchTransaction(ent, !!trans->TunnelProvider(), trans);
+        pendingTransInfo = new PendingTransactionInfo(trans);
+        rv = TryDispatchTransaction(ent, !!trans->TunnelProvider(), pendingTransInfo);
     }
 
     if (NS_SUCCEEDED(rv)) {
         LOG(("  ProcessNewTransaction Dispatch Immediately trans=%p\n", trans));
         return rv;
     }
 
     if (rv == NS_ERROR_NOT_AVAILABLE) {
+        if (!pendingTransInfo) {
+            pendingTransInfo = new PendingTransactionInfo(trans);
+        }
         if (trans->Caps() & NS_HTTP_URGENT_START) {
             LOG(("  adding transaction to pending queue "
                  "[trans=%p urgent-start-count=%" PRIuSIZE "]\n",
                  trans, ent->mUrgentStartQ.Length()+1));
             // put this transaction on the urgent-start queue...
-            InsertTransactionSorted(ent->mUrgentStartQ, trans);
+            InsertTransactionSorted(ent->mUrgentStartQ, pendingTransInfo);
         } else {
             LOG(("  adding transaction to pending queue "
                  "[trans=%p pending-count=%" PRIuSIZE "]\n",
                  trans, ent->mPendingQ.Length()+1));
             // put this transaction on the pending queue...
-            InsertTransactionSorted(ent->mPendingQ, trans);
+            InsertTransactionSorted(ent->mPendingQ, pendingTransInfo);
         }
         return NS_OK;
     }
 
     LOG(("  ProcessNewTransaction Hard Error trans=%p rv=%" PRIx32 "\n",
          trans, static_cast<uint32_t>(rv)));
     return rv;
 }
@@ -1695,23 +1756,53 @@ nsHttpConnectionMgr::StartedConnect()
 
 void
 nsHttpConnectionMgr::RecvdConnect()
 {
     mNumActiveConns--;
     ConditionallyStopTimeoutTick();
 }
 
+void
+nsHttpConnectionMgr::ReleaseClaimedSockets(nsConnectionEntry *ent,
+                                           PendingTransactionInfo * pendingTransInfo)
+{
+    if (pendingTransInfo->mHalfOpen) {
+        RefPtr<nsHalfOpenSocket> halfOpen =
+            do_QueryReferent(pendingTransInfo->mHalfOpen);
+        if (halfOpen) {
+            if (halfOpen->Transaction() &&
+                halfOpen->Transaction()->IsNullTransaction()) {
+                LOG(("nsHttpConnectionMgr::ReleaseClaimedSockets - mark halfOpne %p "
+                    "speculative again.", halfOpen.get()));
+                halfOpen->SetSpeculative(true);
+            }
+        }
+        pendingTransInfo->mHalfOpen = nullptr;
+    } else if (pendingTransInfo->mActiveConn) {
+        RefPtr<nsHttpConnection> activeConn =
+            do_QueryReferent(pendingTransInfo->mActiveConn);
+        if (activeConn && activeConn->Transaction() &&
+            activeConn->Transaction()->IsNullTransaction()) {
+            NullHttpTransaction *nullTrans = activeConn->Transaction()->QueryNullTransaction();
+            nullTrans->Unclaim();
+            LOG(("nsHttpConnectionMgr::ReleaseClaimedSockets - mark %p unclaimed.",
+                 activeConn.get()));
+        }
+    }
+}
+
 nsresult
 nsHttpConnectionMgr::CreateTransport(nsConnectionEntry *ent,
                                      nsAHttpTransaction *trans,
                                      uint32_t caps,
                                      bool speculative,
                                      bool isFromPredictor,
-                                     bool allow1918)
+                                     bool allow1918,
+                                     PendingTransactionInfo *pendingTransInfo)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
     RefPtr<nsHalfOpenSocket> sock = new nsHalfOpenSocket(ent, trans, caps);
     if (speculative) {
         sock->SetSpeculative(true);
         sock->SetAllow1918(allow1918);
         Telemetry::AutoCounter<Telemetry::HTTPCONNMGR_TOTAL_SPECULATIVE_CONN> totalSpeculativeConn;
@@ -1725,58 +1816,67 @@ nsHttpConnectionMgr::CreateTransport(nsC
     }
 
     // The socket stream holds the reference to the half open
     // socket - so if the stream fails to init the half open
     // will go away.
     nsresult rv = sock->SetupPrimaryStreams();
     NS_ENSURE_SUCCESS(rv, rv);
 
+    if (pendingTransInfo) {
+        pendingTransInfo->mHalfOpen =
+            do_GetWeakReference(static_cast<nsISupportsWeakReference*>(sock));
+    }
+
     ent->mHalfOpens.AppendElement(sock);
     mNumHalfOpenConns++;
     return NS_OK;
 }
 
 void
-nsHttpConnectionMgr::DispatchSpdyPendingQ(nsTArray<RefPtr<nsHttpTransaction>> &pendingQ,
+nsHttpConnectionMgr::DispatchSpdyPendingQ(nsTArray<RefPtr<PendingTransactionInfo>> &pendingQ,
                                           nsConnectionEntry *ent,
                                           nsHttpConnection *conn)
 {
     if (pendingQ.Length() == 0) {
         return;
     }
 
-    nsTArray<RefPtr<nsHttpTransaction>> leftovers;
+    nsTArray<RefPtr<PendingTransactionInfo>> leftovers;
     uint32_t index;
     // Dispatch all the transactions we can
-    for (index = 0; index < pendingQ.Length(); ++index) {
-        nsHttpTransaction *trans = pendingQ[index];
-
-        if (!(trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) ||
-            trans->Caps() & NS_HTTP_DISALLOW_SPDY) {
-            leftovers.AppendElement(trans);
+    for (index = 0;
+         index < pendingQ.Length() && conn->CanDirectlyActivate();
+         ++index) {
+        PendingTransactionInfo *pendingTransInfo = pendingQ[index];
+
+        if (!(pendingTransInfo->mTransaction->Caps() & NS_HTTP_ALLOW_KEEPALIVE) ||
+            pendingTransInfo->mTransaction->Caps() & NS_HTTP_DISALLOW_SPDY) {
+            leftovers.AppendElement(pendingTransInfo);
             continue;
         }
 
-        nsresult rv = DispatchTransaction(ent, trans, conn);
+        nsresult rv = DispatchTransaction(ent, pendingTransInfo->mTransaction,
+                                          conn);
         if (NS_FAILED(rv)) {
             // this cannot happen, but if due to some bug it does then
             // close the transaction
             MOZ_ASSERT(false, "Dispatch SPDY Transaction");
             LOG(("ProcessSpdyPendingQ Dispatch Transaction failed trans=%p\n",
-                    trans));
-            trans->Close(rv);
+                 pendingTransInfo->mTransaction.get()));
+            pendingTransInfo->mTransaction->Close(rv);
         }
+        ReleaseClaimedSockets(ent, pendingTransInfo);
     }
 
     // Slurp up the rest of the pending queue into our leftovers bucket (we
     // might have some left if conn->CanDirectlyActivate returned false)
     for (; index < pendingQ.Length(); ++index) {
-        nsHttpTransaction *trans = pendingQ[index];
-        leftovers.AppendElement(trans);
+        PendingTransactionInfo *pendingTransInfo = pendingQ[index];
+        leftovers.AppendElement(pendingTransInfo);
     }
 
     // Put the leftovers back in the pending queue and get rid of the
     // transactions we dispatched
     leftovers.SwapElements(pendingQ);
     leftovers.Clear();
 }
 
@@ -1789,16 +1889,19 @@ nsHttpConnectionMgr::DispatchSpdyPending
 void
 nsHttpConnectionMgr::ProcessSpdyPendingQ(nsConnectionEntry *ent)
 {
     nsHttpConnection *conn = GetSpdyPreferredConn(ent);
     if (!conn || !conn->CanDirectlyActivate()) {
         return;
     }
     DispatchSpdyPendingQ(ent->mUrgentStartQ, ent, conn);
+    if (!conn->CanDirectlyActivate()) {
+        return;
+    }
     DispatchSpdyPendingQ(ent->mPendingQ, ent, conn);
 }
 
 void
 nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ(int32_t, ARefBase *)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
     LOG(("nsHttpConnectionMgr::OnMsgProcessAllSpdyPendingQ\n"));
@@ -1898,25 +2001,25 @@ nsHttpConnectionMgr::OnMsgShutdown(int32
         }
 
         // If all idle connections are removed we can stop pruning dead
         // connections.
         ConditionallyStopPruneDeadConnectionsTimer();
 
         // Close all urgentStart transactions.
         while (ent->mUrgentStartQ.Length()) {
-            nsHttpTransaction *trans = ent->mUrgentStartQ[0];
-            trans->Close(NS_ERROR_ABORT);
+            PendingTransactionInfo *pendingTransInfo = ent->mUrgentStartQ[0];
+            pendingTransInfo->mTransaction->Close(NS_ERROR_ABORT);
             ent->mUrgentStartQ.RemoveElementAt(0);
         }
 
         // Close all pending transactions.
         while (ent->mPendingQ.Length()) {
-            nsHttpTransaction *trans = ent->mPendingQ[0];
-            trans->Close(NS_ERROR_ABORT);
+            PendingTransactionInfo *pendingTransInfo = ent->mPendingQ[0];
+            pendingTransInfo->mTransaction->Close(NS_ERROR_ABORT);
             ent->mPendingQ.RemoveElementAt(0);
         }
 
         // Close all half open tcp connections.
         for (int32_t i = int32_t(ent->mHalfOpens.Length()) - 1; i >= 0; i--) {
             ent->mHalfOpens[i]->Abandon();
         }
 
@@ -1975,27 +2078,28 @@ nsHttpConnectionMgr::OnMsgReschedTransac
     RefPtr<nsHttpTransaction> trans = static_cast<nsHttpTransaction *>(param);
     trans->SetPriority(priority);
 
     nsConnectionEntry *ent = LookupConnectionEntry(trans->ConnectionInfo(),
                                                    nullptr, trans);
 
     if (ent) {
         int32_t caps = trans->Caps();
-        nsTArray<RefPtr<nsHttpTransaction>> *pendingQ;
+        nsTArray<RefPtr<PendingTransactionInfo>> *pendingQ;
         if (caps & NS_HTTP_URGENT_START) {
             pendingQ = &(ent->mUrgentStartQ);
         } else {
             pendingQ = &(ent->mPendingQ);
         }
 
-        int32_t index = pendingQ->IndexOf(trans);
+        int32_t index = pendingQ->IndexOf(trans, 0, PendingComparator());
         if (index >= 0) {
+            RefPtr<PendingTransactionInfo> pendingTransInfo = (*pendingQ)[index];
             pendingQ->RemoveElementAt(index);
-            InsertTransactionSorted(*pendingQ, trans);
+            InsertTransactionSorted(*pendingQ, pendingTransInfo);
         }
     }
 }
 
 void
 nsHttpConnectionMgr::OnMsgCancelTransaction(int32_t reason, ARefBase *param)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
@@ -2016,42 +2120,53 @@ nsHttpConnectionMgr::OnMsgCancelTransact
         conn->CloseTransaction(trans, closeCode);
     } else {
         nsConnectionEntry *ent =
             LookupConnectionEntry(trans->ConnectionInfo(), nullptr, trans);
 
         if (ent) {
             uint32_t caps = trans->Caps();
             int32_t transIndex;
+            // We will abandon all half-open sockets belonging to the given
+            // transaction.
+            RefPtr<PendingTransactionInfo> pendingTransInfo;
             if (caps & NS_HTTP_URGENT_START) {
-                transIndex = ent->mUrgentStartQ.IndexOf(trans);
+                transIndex = ent->mUrgentStartQ.IndexOf(trans, 0,
+                                                        PendingComparator());
                 if (transIndex >=0) {
                     LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]"
                          " found in urgentStart queue\n", trans));
+                    pendingTransInfo = ent->mUrgentStartQ[transIndex];
+                    // We do not need to ReleaseClaimedSockets while we are
+                    // going to close them all any way!
                     ent->mUrgentStartQ.RemoveElementAt(transIndex);
                 }
             } else {
-                transIndex = ent->mPendingQ.IndexOf(trans);
+                transIndex = ent->mPendingQ.IndexOf(trans, 0,
+                                                    PendingComparator());
                 if (transIndex >=0) {
                     LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]"
                          " found in pending queue\n", trans));
+                    pendingTransInfo = ent->mPendingQ[transIndex];
+                    // We do not need to ReleaseClaimedSockets while we are
+                    // going to close them all any way!
                     ent->mPendingQ.RemoveElementAt(transIndex);
                 }
             }
 
             // Abandon all half-open sockets belonging to the given transaction.
-            for (uint32_t index = 0;
-                 index < ent->mHalfOpens.Length();
-                 ++index) {
-                nsHalfOpenSocket *half = ent->mHalfOpens[index];
-                if (trans == half->Transaction()) {
+            if (pendingTransInfo) {
+                RefPtr<nsHalfOpenSocket> half =
+                    do_QueryReferent(pendingTransInfo->mHalfOpen);
+                if (half) {
+                    MOZ_ASSERT(trans == half->Transaction() ||
+                               half->Transaction()->IsNullTransaction());
                     half->Abandon();
-                    // there is only one, and now mHalfOpens[] has been changed.
-                    break;
                 }
+                pendingTransInfo->mHalfOpen = nullptr;
             }
         }
 
         trans->Close(closeCode);
 
         // Cancel is a pretty strong signal that things might be hanging
         // so we want to cancel any null transactions related to this connection
         // entry. They are just optimizations, but they aren't hooked up to
@@ -2120,31 +2235,31 @@ nsHttpConnectionMgr::OnMsgCancelTransact
     nsHttpConnectionInfo *ci = static_cast<nsHttpConnectionInfo *>(param);
     nsConnectionEntry *ent = mCT.Get(ci->HashKey());
     LOG(("nsHttpConnectionMgr::OnMsgCancelTransactions %s %p\n",
          ci->HashKey().get(), ent));
     if (!ent) {
         return;
     }
 
-    for (int32_t i = ent->mUrgentStartQ.Length() - 1; i > 0;) {
+    for (uint32_t i = ent->mUrgentStartQ.Length(); i > 0;) {
         --i;
-        nsHttpTransaction *trans = ent->mUrgentStartQ[i];
+        PendingTransactionInfo *pendingTransInfo = ent->mUrgentStartQ[i];
         LOG(("nsHttpConnectionMgr::OnMsgCancelTransactions %s %p %p\n",
-             ci->HashKey().get(), ent, trans));
-        trans->Close(reason);
+             ci->HashKey().get(), ent, pendingTransInfo->mTransaction.get()));
+        pendingTransInfo->mTransaction->Close(reason);
         ent->mUrgentStartQ.RemoveElementAt(i);
     }
 
-    for (int32_t i = ent->mPendingQ.Length() - 1; i > 0;) {
+    for (uint32_t i = ent->mPendingQ.Length(); i > 0;) {
         --i;
-        nsHttpTransaction *trans = ent->mPendingQ[i];
+        PendingTransactionInfo *pendingTransInfo = ent->mPendingQ[i];
         LOG(("nsHttpConnectionMgr::OnMsgCancelTransactions %s %p %p\n",
-             ci->HashKey().get(), ent, trans));
-        trans->Close(reason);
+             ci->HashKey().get(), ent, pendingTransInfo->mTransaction.get()));
+        pendingTransInfo->mTransaction->Close(reason);
         ent->mPendingQ.RemoveElementAt(i);
     }
 }
 
 void
 nsHttpConnectionMgr::OnMsgPruneDeadConnections(int32_t, ARefBase *)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
@@ -2729,17 +2844,18 @@ nsHttpConnectionMgr::OnMsgSpeculativeCon
     bool keepAlive = args->mTrans->Caps() & NS_HTTP_ALLOW_KEEPALIVE;
     if (mNumHalfOpenConns < parallelSpeculativeConnectLimit &&
         ((ignoreIdle && (ent->mIdleConns.Length() < parallelSpeculativeConnectLimit)) ||
          !ent->mIdleConns.Length()) &&
         !(keepAlive && RestrictConnections(ent)) &&
         !AtActiveConnectionLimit(ent, args->mTrans->Caps())) {
         DebugOnly<nsresult> rv = CreateTransport(ent, args->mTrans,
                                                  args->mTrans->Caps(), true,
-                                                 isFromPredictor, allow1918);
+                                                 isFromPredictor, allow1918,
+                                                 nullptr);
         MOZ_ASSERT(NS_SUCCEEDED(rv));
     } else {
         LOG(("OnMsgSpeculativeConnect Transport "
              "not created due to existing connection count\n"));
     }
 }
 
 bool
@@ -2763,22 +2879,32 @@ ConnectionHandle::DontReuse()
 nsresult
 ConnectionHandle::PushBack(const char *buf, uint32_t bufLen)
 {
     return mConn->PushBack(buf, bufLen);
 }
 
 
 //////////////////////// nsHalfOpenSocket
-
-NS_IMPL_ISUPPORTS(nsHttpConnectionMgr::nsHalfOpenSocket,
-                  nsIOutputStreamCallback,
-                  nsITransportEventSink,
-                  nsIInterfaceRequestor,
-                  nsITimerCallback)
+NS_IMPL_ADDREF(nsHttpConnectionMgr::nsHalfOpenSocket)
+NS_IMPL_RELEASE(nsHttpConnectionMgr::nsHalfOpenSocket)
+
+NS_INTERFACE_MAP_BEGIN(nsHttpConnectionMgr::nsHalfOpenSocket)
+    NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+    NS_INTERFACE_MAP_ENTRY(nsIOutputStreamCallback)
+    NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
+    NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+    NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
+    // we have no macro that covers this case.
+    if (aIID.Equals(NS_GET_IID(nsHttpConnectionMgr::nsHalfOpenSocket)) ) {
+        AddRef();
+        *aInstancePtr = this;
+        return NS_OK;
+    } else
+NS_INTERFACE_MAP_END
 
 nsHttpConnectionMgr::
 nsHalfOpenSocket::nsHalfOpenSocket(nsConnectionEntry *ent,
                                    nsAHttpTransaction *trans,
                                    uint32_t caps)
     : mEnt(ent)
     , mTransaction(trans)
     , mDispatchedMTransaction(false)
@@ -3177,33 +3303,36 @@ nsHalfOpenSocket::OnOutputStreamReady(ns
     // This half-open socket has created a connection.  This flag excludes it
     // from counter of actual connections used for checking limits.
     mHasConnected = true;
 
     // if this is still in the pending list, remove it and dispatch it
     uint32_t caps = mTransaction->Caps();
     int32_t index;
     if (caps & NS_HTTP_URGENT_START) {
-        index = mEnt->mUrgentStartQ.IndexOf(mTransaction);
+        index = mEnt->mUrgentStartQ.IndexOf(mTransaction, 0,
+                                            PendingComparator());
     } else {
-        index = mEnt->mPendingQ.IndexOf(mTransaction);
+        index = mEnt->mPendingQ.IndexOf(mTransaction, 0, PendingComparator());
     }
     if (index > -1) {
         MOZ_ASSERT(!mSpeculative,
                    "Speculative Half Open found mTransaction");
-        RefPtr<nsHttpTransaction> temp;
+        RefPtr<PendingTransactionInfo> temp;
         if (caps & NS_HTTP_URGENT_START) {
             temp = mEnt->mUrgentStartQ[index];
             mEnt->mUrgentStartQ.RemoveElementAt(index);
          } else {
             temp = mEnt->mPendingQ[index];
             mEnt->mPendingQ.RemoveElementAt(index);
         }
         gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt);
-        rv = gHttpHandler->ConnMgr()->DispatchTransaction(mEnt, temp, conn);
+        rv = gHttpHandler->ConnMgr()->DispatchTransaction(mEnt,
+                                                          temp->mTransaction,
+                                                          conn);
     } else {
         // this transaction was dispatched off the pending q before all the
         // sockets established themselves.
 
         // After about 1 second allow for the possibility of restarting a
         // transaction due to server close. Keep at sub 1 second as that is the
         // minimum granularity we can expect a server to be timing out with.
         conn->SetIsReusedAfter(950);
@@ -3248,18 +3377,31 @@ nsHalfOpenSocket::OnOutputStreamReady(ns
 NS_IMETHODIMP
 nsHttpConnectionMgr::nsHalfOpenSocket::OnTransportStatus(nsITransport *trans,
                                                          nsresult status,
                                                          int64_t progress,
                                                          int64_t progressMax)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
-    if (mTransaction)
-        mTransaction->OnTransportStatus(trans, status, progress);
+    if (mTransaction) {
+        if ((trans == mSocketTransport) ||
+            ((trans == mBackupTransport) && (status == NS_NET_STATUS_CONNECTED_TO) &&
+            mEnt->mPendingQ.Contains(mTransaction, PendingComparator()))) {
+            // Send this status event only if the transaction is still panding,
+            // i.e. it has not found a free already connected socket.
+            // Sockets in halfOpen state can only get following events:
+            // NS_NET_STATUS_RESOLVING_HOST, NS_NET_STATUS_RESOLVED_HOST,
+            // NS_NET_STATUS_CONNECTING_TO and NS_NET_STATUS_CONNECTED_TO.
+            // mBackupTransport is only started after
+            // NS_NET_STATUS_CONNECTING_TO of mSocketTransport, so ignore all
+            // mBackupTransport events until NS_NET_STATUS_CONNECTED_TO.
+            mTransaction->OnTransportStatus(trans, status, progress);
+        }
+    }
 
     MOZ_ASSERT(trans == mSocketTransport || trans == mBackupTransport);
     if (status == NS_NET_STATUS_CONNECTED_TO) {
         if (trans == mSocketTransport) {
             mPrimaryConnectedOK = true;
         } else {
             mBackupConnectedOK = true;
         }
--- a/netwerk/protocol/http/nsHttpConnectionMgr.h
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -13,28 +13,33 @@
 #include "nsClassHashtable.h"
 #include "nsDataHashtable.h"
 #include "nsAutoPtr.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Attributes.h"
 #include "AlternateServices.h"
 #include "ARefBase.h"
+#include "nsWeakReference.h"
 
 #include "nsIObserver.h"
 #include "nsITimer.h"
 
 class nsIHttpUpgradeListener;
 
 namespace mozilla {
 namespace net {
 class EventTokenBucket;
 class NullHttpTransaction;
 struct HttpRetParams;
 
+// 8d411b53-54bc-4a99-8b78-ff125eab1564
+#define NS_HALFOPENSOCKET_IID \
+{ 0x8d411b53, 0x54bc, 0x4a99, {0x8b, 0x78, 0xff, 0x12, 0x5e, 0xab, 0x15, 0x64 }}
+
 //-----------------------------------------------------------------------------
 
 // message handlers have this signature
 class nsHttpConnectionMgr;
 typedef void (nsHttpConnectionMgr:: *nsConnEventHandler)(int32_t, ARefBase *);
 
 class nsHttpConnectionMgr final : public nsIObserver
                                 , public AltSvcCache
@@ -195,32 +200,33 @@ public:
     void ActivateTimeoutTick();
 
     nsresult UpdateCurrentTopLevelOuterContentWindowId(uint64_t aWindowId);
 
 private:
     virtual ~nsHttpConnectionMgr();
 
     class nsHalfOpenSocket;
+    class PendingTransactionInfo;
 
     // nsConnectionEntry
     //
     // mCT maps connection info hash key to nsConnectionEntry object, which
     // contains list of active and idle connections as well as the list of
     // pending transactions.
     //
     class nsConnectionEntry
     {
     public:
         explicit nsConnectionEntry(nsHttpConnectionInfo *ci);
         ~nsConnectionEntry();
 
         RefPtr<nsHttpConnectionInfo> mConnInfo;
-        nsTArray<RefPtr<nsHttpTransaction> > mUrgentStartQ;// the urgent start transaction queue
-        nsTArray<RefPtr<nsHttpTransaction> > mPendingQ;    // pending transaction queue
+        nsTArray<RefPtr<PendingTransactionInfo> > mUrgentStartQ;// the urgent start transaction queue
+        nsTArray<RefPtr<PendingTransactionInfo> > mPendingQ;    // pending transaction queue
         nsTArray<RefPtr<nsHttpConnection> >  mActiveConns; // active connections
         nsTArray<RefPtr<nsHttpConnection> >  mIdleConns;   // idle persistent connections
         nsTArray<nsHalfOpenSocket*>  mHalfOpens;   // half open connections
 
         bool AvailableForDispatchNow();
 
         // calculate the number of half open sockets that have not had at least 1
         // connection complete
@@ -271,21 +277,23 @@ public:
 private:
 
     // nsHalfOpenSocket is used to hold the state of an opening TCP socket
     // while we wait for it to establish and bind it to a connection
 
     class nsHalfOpenSocket final : public nsIOutputStreamCallback,
                                    public nsITransportEventSink,
                                    public nsIInterfaceRequestor,
-                                   public nsITimerCallback
+                                   public nsITimerCallback,
+                                   public nsSupportsWeakReference
     {
         ~nsHalfOpenSocket();
 
     public:
+        NS_DECLARE_STATIC_IID_ACCESSOR(NS_HALFOPENSOCKET_IID)
         NS_DECL_THREADSAFE_ISUPPORTS
         NS_DECL_NSIOUTPUTSTREAMCALLBACK
         NS_DECL_NSITRANSPORTEVENTSINK
         NS_DECL_NSIINTERFACEREQUESTOR
         NS_DECL_NSITIMERCALLBACK
 
         nsHalfOpenSocket(nsConnectionEntry *ent,
                          nsAHttpTransaction *trans,
@@ -356,16 +364,45 @@ private:
         // connection process. It may have completed unsuccessfully.
         bool                           mHasConnected;
 
         bool                           mPrimaryConnectedOK;
         bool                           mBackupConnectedOK;
     };
     friend class nsHalfOpenSocket;
 
+    class PendingTransactionInfo : public ARefBase
+    {
+    public:
+        explicit PendingTransactionInfo(nsHttpTransaction * trans)
+            : mTransaction(trans)
+        {}
+
+        NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PendingTransactionInfo)
+
+        void PrintDiagnostics(nsCString &log);
+    public: // meant to be public.
+        RefPtr<nsHttpTransaction> mTransaction;
+        nsWeakPtr mHalfOpen;
+        nsWeakPtr mActiveConn;
+
+    private:
+        virtual ~PendingTransactionInfo() {}
+    };
+    friend class PendingTransactionInfo;
+
+    class PendingComparator
+    {
+    public:
+        bool Equals(const PendingTransactionInfo *aPendingTrans,
+                    const nsAHttpTransaction *aTrans) const {
+            return aPendingTrans->mTransaction.get() == aTrans;
+        }
+    };
+
     //-------------------------------------------------------------------------
     // NOTE: these members may be accessed from any thread (use mReentrantMonitor)
     //-------------------------------------------------------------------------
 
     ReentrantMonitor    mReentrantMonitor;
     nsCOMPtr<nsIEventTarget>     mSocketThreadTarget;
 
     // connection limits
@@ -377,64 +414,74 @@ private:
     Atomic<bool, mozilla::Relaxed> mIsShuttingDown;
 
     //-------------------------------------------------------------------------
     // NOTE: these members are only accessed on the socket transport thread
     //-------------------------------------------------------------------------
 
     MOZ_MUST_USE bool ProcessPendingQForEntry(nsConnectionEntry *,
                                               bool considerAll);
-    void DispatchPendingQ(nsTArray<RefPtr<nsHttpTransaction>> &pendingQ,
+    void DispatchPendingQ(nsTArray<RefPtr<PendingTransactionInfo>> &pendingQ,
                                    nsConnectionEntry *ent,
                                    bool &dispatchedSuccessfully,
                                    bool considerAll);
     bool     AtActiveConnectionLimit(nsConnectionEntry *, uint32_t caps);
     MOZ_MUST_USE nsresult TryDispatchTransaction(nsConnectionEntry *ent,
                                                  bool onlyReusedConnection,
-                                                 nsHttpTransaction *trans);
+                                                 PendingTransactionInfo *pendingTransInfo);
     MOZ_MUST_USE nsresult DispatchTransaction(nsConnectionEntry *,
                                               nsHttpTransaction *,
                                               nsHttpConnection *);
     MOZ_MUST_USE nsresult DispatchAbstractTransaction(nsConnectionEntry *,
                                                       nsAHttpTransaction *,
                                                       uint32_t,
                                                       nsHttpConnection *,
                                                       int32_t);
     bool     RestrictConnections(nsConnectionEntry *);
     MOZ_MUST_USE nsresult ProcessNewTransaction(nsHttpTransaction *);
     MOZ_MUST_USE nsresult EnsureSocketThreadTarget();
     void     ClosePersistentConnections(nsConnectionEntry *ent);
     void     ReportProxyTelemetry(nsConnectionEntry *ent);
     MOZ_MUST_USE nsresult CreateTransport(nsConnectionEntry *,
                                           nsAHttpTransaction *, uint32_t, bool,
-                                          bool, bool);
+                                          bool, bool,
+                                          PendingTransactionInfo *pendingTransInfo);
     void     AddActiveConn(nsHttpConnection *, nsConnectionEntry *);
     void     DecrementActiveConnCount(nsHttpConnection *);
     void     StartedConnect();
     void     RecvdConnect();
 
+    // This function will unclaim the claimed connection or set a halfOpen
+    // socket to the speculative state if the transaction claiming them ends up
+    // using another connection.
+    void ReleaseClaimedSockets(nsConnectionEntry *ent,
+                               PendingTransactionInfo * pendingTransInfo);
+
+    void InsertTransactionSorted(nsTArray<RefPtr<PendingTransactionInfo> > &pendingQ,
+                                 PendingTransactionInfo *pendingTransInfo);
+
     nsConnectionEntry *GetOrCreateConnectionEntry(nsHttpConnectionInfo *,
                                                   bool allowWildCard);
 
     MOZ_MUST_USE nsresult MakeNewConnection(nsConnectionEntry *ent,
-                                            nsHttpTransaction *trans);
+                                            PendingTransactionInfo *pendingTransInfo);
 
     // Manage the preferred spdy connection entry for this address
     nsConnectionEntry *GetSpdyPreferredEnt(nsConnectionEntry *aOriginalEntry);
     nsConnectionEntry *LookupPreferredHash(nsConnectionEntry *ent);
     void               StorePreferredHash(nsConnectionEntry *ent);
     void               RemovePreferredHash(nsConnectionEntry *ent);
     nsHttpConnection  *GetSpdyPreferredConn(nsConnectionEntry *ent);
     nsDataHashtable<nsCStringHashKey, nsConnectionEntry *>   mSpdyPreferredHash;
     nsConnectionEntry *LookupConnectionEntry(nsHttpConnectionInfo *ci,
                                              nsHttpConnection *conn,
                                              nsHttpTransaction *trans);
 
     void               ProcessSpdyPendingQ(nsConnectionEntry *ent);
-    void               DispatchSpdyPendingQ(nsTArray<RefPtr<nsHttpTransaction>> &pendingQ,
+    void               DispatchSpdyPendingQ(nsTArray<RefPtr<PendingTransactionInfo>> &pendingQ,
                                             nsConnectionEntry *ent,
                                             nsHttpConnection *conn);
     // used to marshall events to the socket transport thread.
     MOZ_MUST_USE nsresult PostEvent(nsConnEventHandler  handler,
                                     int32_t             iparam = 0,
                                     ARefBase            *vparam = nullptr);
 
     // message handlers
@@ -499,12 +546,14 @@ private:
 
     // For diagnostics
     void OnMsgPrintDiagnostics(int32_t, ARefBase *);
 
     nsCString mLogData;
     uint64_t mCurrentTopLevelOuterContentWindowId;
 };
 
+NS_DEFINE_STATIC_IID_ACCESSOR(nsHttpConnectionMgr::nsHalfOpenSocket, NS_HALFOPENSOCKET_IID)
+
 } // namespace net
 } // namespace mozilla
 
 #endif // !nsHttpConnectionMgr_h__
--- a/netwerk/test/unit/test_alt-data_simple.js
+++ b/netwerk/test/unit/test_alt-data_simple.js
@@ -19,38 +19,41 @@ XPCOMUtils.defineLazyGetter(this, "URL",
 
 var httpServer = null;
 
 function make_channel(url, callback, ctx) {
   return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true});
 }
 
 const responseContent = "response body";
+const responseContent2 = "response body 2";
 const altContent = "!@#$%^&*()";
 const altContentType = "text/binary";
 
 var servedNotModified = false;
+var shouldPassRevalidation = true;
 
 function contentHandler(metadata, response)
 {
   response.setHeader("Content-Type", "text/plain");
   response.setHeader("Cache-Control", "no-cache");
   response.setHeader("ETag", "test-etag1");
 
   try {
     var etag = metadata.getHeader("If-None-Match");
   } catch(ex) {
     var etag = "";
   }
 
-  if (etag == "test-etag1") {
+  if (etag == "test-etag1" && shouldPassRevalidation) {
     response.setStatusLine(metadata.httpVersion, 304, "Not Modified");
     servedNotModified = true;
   } else {
-    response.bodyOutputStream.write(responseContent, responseContent.length);
+    var content = shouldPassRevalidation ? responseContent : responseContent2;
+    response.bodyOutputStream.write(content, content.length);
   }
 }
 
 function run_test()
 {
   do_get_profile();
   httpServer = new HttpServer();
   httpServer.registerPathHandler("/content", contentHandler);
@@ -102,10 +105,30 @@ function flushAndOpenAltChannel()
 function readAltContent(request, buffer)
 {
   var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
 
   do_check_eq(servedNotModified, true);
   do_check_eq(cc.alternativeDataType, altContentType);
   do_check_eq(buffer, altContent);
 
+  requestAgain();
+}
+
+function requestAgain()
+{
+  shouldPassRevalidation = false;
+  var chan = make_channel(URL);
+  var cc = chan.QueryInterface(Ci.nsICacheInfoChannel);
+  cc.preferAlternativeDataType(altContentType);
+  chan.asyncOpen2(new ChannelListener(readEmptyAltContent, null));
+}
+
+function readEmptyAltContent(request, buffer)
+{
+  var cc = request.QueryInterface(Ci.nsICacheInfoChannel);
+
+  // the cache is overwrite and the alt-data is reset
+  do_check_eq(cc.alternativeDataType, "");
+  do_check_eq(buffer, responseContent2);
+
   httpServer.stop(do_test_finished);
 }
--- a/parser/html/nsHtml5Parser.cpp
+++ b/parser/html/nsHtml5Parser.cpp
@@ -32,17 +32,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsHtml5Parser)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mExecutor)
   tmp->DropStreamParser();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 nsHtml5Parser::nsHtml5Parser()
   : mLastWasCR(false)
   , mDocWriteSpeculativeLastWasCR(false)
-  , mBlocked(false)
+  , mBlocked(0)
   , mDocWriteSpeculatorActive(false)
   , mInsertionPointPushLevel(0)
   , mDocumentClosed(false)
   , mInDocumentWrite(false)
   , mFirstBuffer(new nsHtml5OwningUTF16Buffer((void*)nullptr))
   , mLastBuffer(mFirstBuffer)
   , mExecutor(new nsHtml5TreeOpExecutor())
   , mTreeBuilder(new nsHtml5TreeBuilder(mExecutor, nullptr))
@@ -141,24 +141,29 @@ nsHtml5Parser::ContinueInterruptedParsin
 {
   NS_NOTREACHED("Don't call. For interface compat only.");
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP_(void)
 nsHtml5Parser::BlockParser()
 {
-  mBlocked = true;
+  mBlocked++;
 }
 
 NS_IMETHODIMP_(void)
 nsHtml5Parser::UnblockParser()
 {
-  mBlocked = false;
-  mExecutor->ContinueInterruptedParsingAsync();
+  MOZ_DIAGNOSTIC_ASSERT(mBlocked > 0);
+  if (MOZ_LIKELY(mBlocked > 0)) {
+    mBlocked--;
+  }
+  if (MOZ_LIKELY(mBlocked == 0)) {
+    mExecutor->ContinueInterruptedParsingAsync();
+  }
 }
 
 NS_IMETHODIMP_(void)
 nsHtml5Parser::ContinueInterruptedParsingAsync()
 {
   mExecutor->ContinueInterruptedParsingAsync();
 }
 
--- a/parser/html/nsHtml5Parser.h
+++ b/parser/html/nsHtml5Parser.h
@@ -273,19 +273,20 @@ class nsHtml5Parser final : public nsIPa
 
     /**
      * Whether the last character tokenized was a carriage return (for CRLF)
      * when preparsing document.write.
      */
     bool                          mDocWriteSpeculativeLastWasCR;
 
     /**
-     * The parser is blocking on a script
+     * The parser is blocking on the load of an external script from a web
+     * page, or any number of extension content scripts.
      */
-    bool                          mBlocked;
+    uint32_t                      mBlocked;
 
     /**
      * Whether the document.write() speculator is already active.
      */
     bool                          mDocWriteSpeculatorActive;
     
     /**
      * The number of PushDefinedInsertionPoint calls we've seen without a
--- a/parser/html/nsHtml5TreeOpExecutor.cpp
+++ b/parser/html/nsHtml5TreeOpExecutor.cpp
@@ -435,41 +435,43 @@ nsHtml5TreeOpExecutor::RunFlushLoop()
       // Avoid bothering the rest of the engine with a doc update if there's 
       // nothing to do.
       return;
     }
 
     mFlushState = eInFlush;
 
     nsIContent* scriptElement = nullptr;
+    bool interrupted = false;
     
     BeginDocUpdate();
 
     uint32_t numberOfOpsToFlush = mOpQueue.Length();
 
     const nsHtml5TreeOperation* first = mOpQueue.Elements();
     const nsHtml5TreeOperation* last = first + numberOfOpsToFlush - 1;
     for (nsHtml5TreeOperation* iter = const_cast<nsHtml5TreeOperation*>(first);;) {
       if (MOZ_UNLIKELY(!mParser)) {
         // The previous tree op caused a call to nsIParser::Terminate().
         break;
       }
       NS_ASSERTION(mFlushState == eInDocUpdate, 
         "Tried to perform tree op outside update batch.");
-      nsresult rv = iter->Perform(this, &scriptElement);
+      nsresult rv = iter->Perform(this, &scriptElement, &interrupted);
       if (NS_FAILED(rv)) {
         MarkAsBroken(rv);
         break;
       }
 
       // Be sure not to check the deadline if the last op was just performed.
       if (MOZ_UNLIKELY(iter == last)) {
         break;
-      } else if (MOZ_UNLIKELY(nsContentSink::DidProcessATokenImpl() == 
-                 NS_ERROR_HTMLPARSER_INTERRUPTED)) {
+      } else if (MOZ_UNLIKELY(interrupted) ||
+                 MOZ_UNLIKELY(nsContentSink::DidProcessATokenImpl() ==
+                              NS_ERROR_HTMLPARSER_INTERRUPTED)) {
         mOpQueue.RemoveElementsAt(0, (iter - first) + 1);
         
         EndDocUpdate();
 
         mFlushState = eNotFlushing;
 
         #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
           printf("REFLUSH SCHEDULED (executing ops): %d\n", 
@@ -541,33 +543,34 @@ nsHtml5TreeOpExecutor::FlushDocumentWrit
   NS_ASSERTION(!mReadingFromStage,
     "Got doc write flush when reading from stage");
 
 #ifdef DEBUG
   mStage.AssertEmpty();
 #endif
   
   nsIContent* scriptElement = nullptr;
+  bool interrupted = false;
   
   BeginDocUpdate();
 
   uint32_t numberOfOpsToFlush = mOpQueue.Length();
 
   const nsHtml5TreeOperation* start = mOpQueue.Elements();
   const nsHtml5TreeOperation* end = start + numberOfOpsToFlush;
   for (nsHtml5TreeOperation* iter = const_cast<nsHtml5TreeOperation*>(start);
        iter < end;
        ++iter) {
     if (MOZ_UNLIKELY(!mParser)) {
       // The previous tree op caused a call to nsIParser::Terminate().
       break;
     }
     NS_ASSERTION(mFlushState == eInDocUpdate, 
       "Tried to perform tree op outside update batch.");
-    rv = iter->Perform(this, &scriptElement);
+    rv = iter->Perform(this, &scriptElement, &interrupted);
     if (NS_FAILED(rv)) {
       MarkAsBroken(rv);
       break;
     }
   }
 
   mOpQueue.Clear();
   
@@ -602,31 +605,48 @@ nsHtml5TreeOpExecutor::IsScriptEnabled()
   if (!globalObject) {
     globalObject = mDocShell->GetScriptGlobalObject();
   }
   NS_ENSURE_TRUE(globalObject && globalObject->GetGlobalJSObject(), true);
   return xpc::Scriptability::Get(globalObject->GetGlobalJSObject()).Allowed();
 }
 
 void
-nsHtml5TreeOpExecutor::StartLayout() {
+nsHtml5TreeOpExecutor::StartLayout(bool* aInterrupted) {
   if (mLayoutStarted || !mDocument) {
     return;
   }
 
   EndDocUpdate();
 
   if (MOZ_UNLIKELY(!mParser)) {
     // got terminate
     return;
   }
 
   nsContentSink::StartLayout(false);
 
-  BeginDocUpdate();
+  if (mParser) {
+    *aInterrupted = !GetParser()->IsParserEnabled();
+
+    BeginDocUpdate();
+  }
+}
+
+void
+nsHtml5TreeOpExecutor::PauseDocUpdate(bool* aInterrupted) {
+  // Pausing the document update allows JS to run, and potentially block
+  // further parsing.
+  EndDocUpdate();
+
+  if (MOZ_LIKELY(mParser)) {
+    *aInterrupted = !GetParser()->IsParserEnabled();
+
+    BeginDocUpdate();
+  }
 }
 
 /**
  * The reason why this code is here and not in the tree builder even in the 
  * main-thread case is to allow the control to return from the tokenizer 
  * before scripts run. This way, the tokenizer is not invoked re-entrantly 
  * although the parser is.
  *
--- a/parser/html/nsHtml5TreeOpExecutor.h
+++ b/parser/html/nsHtml5TreeOpExecutor.h
@@ -165,17 +165,19 @@ class nsHtml5TreeOpExecutor final : publ
     }
     
     void InitializeDocWriteParserState(nsAHtml5TreeBuilderState* aState, int32_t aLine);
 
     bool IsScriptEnabled();
 
     virtual nsresult MarkAsBroken(nsresult aReason) override;
 
-    void StartLayout();
+    void StartLayout(bool* aInterrupted);
+
+    void PauseDocUpdate(bool* aInterrupted);
     
     void FlushSpeculativeLoads();
                   
     void RunFlushLoop();
 
     nsresult FlushDocumentWrite();
 
     void MaybeSuspend();
--- a/parser/html/nsHtml5TreeOperation.cpp
+++ b/parser/html/nsHtml5TreeOperation.cpp
@@ -633,17 +633,18 @@ nsHtml5TreeOperation::MarkMalformedIfScr
   if (sele) {
     // Make sure to serialize this script correctly, for nice round tripping.
     sele->SetIsMalformed();
   }
 }
 
 nsresult
 nsHtml5TreeOperation::Perform(nsHtml5TreeOpExecutor* aBuilder,
-                              nsIContent** aScriptElement)
+                              nsIContent** aScriptElement,
+                              bool* aInterrupted)
 {
   switch(mOpCode) {
     case eTreeOpUninitialized: {
       MOZ_CRASH("eTreeOpUninitialized");
     }
     case eTreeOpAppend: {
       nsIContent* node = *(mOne.node);
       nsIContent* parent = *(mTwo.node);
@@ -662,17 +663,20 @@ nsHtml5TreeOperation::Perform(nsHtml5Tre
     case eTreeOpFosterParent: {
       nsIContent* node = *(mOne.node);
       nsIContent* parent = *(mTwo.node);
       nsIContent* table = *(mThree.node);
       return FosterParent(node, parent, table, aBuilder);
     }
     case eTreeOpAppendToDocument: {
       nsIContent* node = *(mOne.node);
-      return AppendToDocument(node, aBuilder);
+      nsresult rv = AppendToDocument(node, aBuilder);
+
+      aBuilder->PauseDocUpdate(aInterrupted);
+      return rv;
     }
     case eTreeOpAddAttributes: {
       nsIContent* node = *(mOne.node);
       nsHtml5HtmlAttributes* attributes = mTwo.attributes;
       return AddAttributes(node, attributes, aBuilder);
     }
     case eTreeOpDocumentMode: {
       aBuilder->SetDocumentMode(mOne.mode);
@@ -990,17 +994,17 @@ nsHtml5TreeOperation::Perform(nsHtml5Tre
       nsIContent* node = *(mOne.node);
       int32_t lineNumber = mFour.integer;
       nsAutoString val(NS_LITERAL_STRING("line"));
       val.AppendInt(lineNumber);
       node->SetAttr(kNameSpaceID_None, nsGkAtoms::id, val, true);
       return NS_OK;
     }
     case eTreeOpStartLayout: {
-      aBuilder->StartLayout(); // this causes a notification flush anyway
+      aBuilder->StartLayout(aInterrupted); // this causes a notification flush anyway
       return NS_OK;
     }
     default: {
       MOZ_CRASH("Bogus tree op");
     }
   }
   return NS_OK; // keep compiler happy
 }
--- a/parser/html/nsHtml5TreeOperation.h
+++ b/parser/html/nsHtml5TreeOperation.h
@@ -483,17 +483,18 @@ class nsHtml5TreeOperation {
       NS_ASSERTION(IsRunScript(), 
         "Setting a snapshot for a tree operation other than eTreeOpRunScript!");
       NS_PRECONDITION(aSnapshot, "Initialized tree op with null snapshot.");
       mTwo.state = aSnapshot;
       mFour.integer = aLine;
     }
 
     nsresult Perform(nsHtml5TreeOpExecutor* aBuilder,
-                     nsIContent** aScriptElement);
+                     nsIContent** aScriptElement,
+                     bool* aInterrupted);
 
   private:
     // possible optimization:
     // Make the queue take items the size of pointer and make the op code
     // decide how many operands it dequeues after it.
     eHtml5TreeOperation mOpCode;
     union {
       nsIContent**                    node;
--- a/parser/htmlparser/nsParser.cpp
+++ b/parser/htmlparser/nsParser.cpp
@@ -40,17 +40,16 @@
 #include "nsIHTMLContentSink.h"
 
 #include "mozilla/dom/EncodingUtils.h"
 #include "mozilla/BinarySearch.h"
 
 using namespace mozilla;
 using mozilla::dom::EncodingUtils;
 
-#define NS_PARSER_FLAG_PARSER_ENABLED         0x00000002
 #define NS_PARSER_FLAG_OBSERVERS_ENABLED      0x00000004
 #define NS_PARSER_FLAG_PENDING_CONTINUE_EVENT 0x00000008
 #define NS_PARSER_FLAG_FLUSH_TOKENS           0x00000020
 #define NS_PARSER_FLAG_CAN_TOKENIZE           0x00000040
 
 //-------------- Begin ParseContinue Event Definition ------------------------
 /*
 The parser can be explicitly interrupted by passing a return value of
@@ -152,18 +151,18 @@ nsParser::Initialize(bool aConstructor)
   }
 
   mContinueEvent = nullptr;
   mCharsetSource = kCharsetUninitialized;
   mCharset.AssignLiteral("ISO-8859-1");
   mInternalState = NS_OK;
   mStreamStatus = NS_OK;
   mCommand = eViewNormal;
+  mBlocked = 0;
   mFlags = NS_PARSER_FLAG_OBSERVERS_ENABLED |
-           NS_PARSER_FLAG_PARSER_ENABLED |
            NS_PARSER_FLAG_CAN_TOKENIZE;
 
   mProcessingNetworkData = false;
   mIsAboutBlank = false;
 }
 
 void
 nsParser::Cleanup()
@@ -626,17 +625,17 @@ nsParser::ContinueInterruptedParsing()
   // that we might start closing things down when the parser
   // is reenabled. To make sure that we're not deleted across
   // the reenabling process, hold a reference to ourselves.
   nsresult result=NS_OK;
   nsCOMPtr<nsIParser> kungFuDeathGrip(this);
   nsCOMPtr<nsIContentSink> sinkDeathGrip(mSink);
 
 #ifdef DEBUG
-  if (!(mFlags & NS_PARSER_FLAG_PARSER_ENABLED)) {
+  if (mBlocked) {
     NS_WARNING("Don't call ContinueInterruptedParsing on a blocked parser.");
   }
 #endif
 
   bool isFinalChunk = mParserContext &&
                         mParserContext->mStreamListenerState == eOnStop;
 
   mProcessingNetworkData = true;
@@ -649,54 +648,55 @@ nsParser::ContinueInterruptedParsing()
   if (result != NS_OK) {
     result=mInternalState;
   }
 
   return result;
 }
 
 /**
- *  Stops parsing temporarily. That's it will prevent the
- *  parser from building up content model.
+ *  Stops parsing temporarily. That is, it will prevent the
+ *  parser from building up content model while scripts
+ *  are being loaded (either an external script from a web
+ *  page, or any number of extension content scripts).
  */
 NS_IMETHODIMP_(void)
 nsParser::BlockParser()
 {
-  mFlags &= ~NS_PARSER_FLAG_PARSER_ENABLED;
+  mBlocked++;
 }
 
 /**
  *  Open up the parser for tokenization, building up content
  *  model..etc. However, this method does not resume parsing
  *  automatically. It's the callers' responsibility to restart
  *  the parsing engine.
  */
 NS_IMETHODIMP_(void)
 nsParser::UnblockParser()
 {
-  if (!(mFlags & NS_PARSER_FLAG_PARSER_ENABLED)) {
-    mFlags |= NS_PARSER_FLAG_PARSER_ENABLED;
-  } else {
-    NS_WARNING("Trying to unblock an unblocked parser.");
+  MOZ_DIAGNOSTIC_ASSERT(mBlocked > 0);
+  if (MOZ_LIKELY(mBlocked > 0)) {
+    mBlocked--;
   }
 }
 
 NS_IMETHODIMP_(void)
 nsParser::ContinueInterruptedParsingAsync()
 {
   mSink->ContinueInterruptedParsingAsync();
 }
 
 /**
  * Call this to query whether the parser is enabled or not.
  */
 NS_IMETHODIMP_(bool)
 nsParser::IsParserEnabled()
 {
-  return (mFlags & NS_PARSER_FLAG_PARSER_ENABLED) != 0;
+  return !mBlocked;
 }
 
 /**
  * Call this to query whether the parser thinks it's done with parsing.
  */
 NS_IMETHODIMP_(bool)
 nsParser::IsComplete()
 {
@@ -1019,18 +1019,17 @@ nsParser::ParseFragment(const nsAString&
  *  @return  error code -- 0 if ok, non-zero if error.
  */
 nsresult
 nsParser::ResumeParse(bool allowIteration, bool aIsFinalChunk,
                       bool aCanInterrupt)
 {
   nsresult result = NS_OK;
 
-  if ((mFlags & NS_PARSER_FLAG_PARSER_ENABLED) &&
-      mInternalState != NS_ERROR_HTMLPARSER_STOPPARSING) {
+  if (!mBlocked && mInternalState != NS_ERROR_HTMLPARSER_STOPPARSING) {
 
     result = WillBuildModel(mParserContext->mScanner->GetFilename());
     if (NS_FAILED(result)) {
       mFlags &= ~NS_PARSER_FLAG_CAN_TOKENIZE;
       return result;
     }
 
     if (mDTD) {
@@ -1065,17 +1064,17 @@ nsParser::ResumeParse(bool allowIteratio
         // down the parser, it's important to check whether the input buffer
         // has been scanned to completion (theTokenizerResult should be kEOF).
         // kEOF -> End of buffer.
 
         // If we're told to block the parser, we disable all further parsing
         // (and cache any data coming in) until the parser is re-enabled.
         if (NS_ERROR_HTMLPARSER_BLOCK == result) {
           mSink->WillInterrupt();
-          if (mFlags & NS_PARSER_FLAG_PARSER_ENABLED) {
+          if (!mBlocked) {
             // If we were blocked by a recursive invocation, don't re-block.
             BlockParser();
           }
           return NS_OK;
         }
         if (NS_ERROR_HTMLPARSER_STOPPARSING == result) {
           // Note: Parser Terminate() calls DidBuildModel.
           if (mInternalState != NS_ERROR_HTMLPARSER_STOPPARSING) {
--- a/parser/htmlparser/nsParser.h
+++ b/parser/htmlparser/nsParser.h
@@ -380,16 +380,17 @@ protected:
     nsIRunnable*                 mContinueEvent;  // weak ref
 
     eParserCommands     mCommand;
     nsresult            mInternalState;
     nsresult            mStreamStatus;
     int32_t             mCharsetSource;
     
     uint16_t            mFlags;
+    uint32_t            mBlocked;
 
     nsString            mUnusedInput;
     nsCString           mCharset;
     nsCString           mCommandStr;
 
     bool                mProcessingNetworkData;
     bool                mIsAboutBlank;
 };
--- a/security/nss/TAG-INFO
+++ b/security/nss/TAG-INFO
@@ -1,1 +1,1 @@
-cf81ccc154dd
+37ccb22f8e51
--- a/security/nss/automation/taskcluster/docker-aarch64/Dockerfile
+++ b/security/nss/automation/taskcluster/docker-aarch64/Dockerfile
@@ -8,17 +8,17 @@ WORKDIR /home/worker
 ADD bin /home/worker/bin
 RUN chmod +x /home/worker/bin/*
 
 # Install dependencies.
 ADD setup.sh /tmp/setup.sh
 RUN bash /tmp/setup.sh
 
 # Change user.
-USER worker
+# USER worker # See bug 1347473.
 
 # Env variables.
 ENV HOME /home/worker
 ENV SHELL /bin/bash
 ENV USER worker
 ENV LOGNAME worker
 ENV HOSTNAME taskcluster-worker
 ENV LANG en_US.UTF-8
--- a/security/nss/coreconf/config.gypi
+++ b/security/nss/coreconf/config.gypi
@@ -103,16 +103,17 @@
     'fuzz%': 0,
     'fuzz_tls%': 0,
     'fuzz_oss%': 0,
     'sign_libs%': 1,
     'use_pprof%': 0,
     'ct_verif%': 0,
     'nss_public_dist_dir%': '<(nss_dist_dir)/public',
     'nss_private_dist_dir%': '<(nss_dist_dir)/private',
+    'only_dev_random%': 1,
   },
   'target_defaults': {
     # Settings specific to targets should go here.
     # This is mostly for linking to libraries.
     'variables': {
       'mapfile%': '',
       'test_build%': 0,
       'debug_optimization_level%': '0',
@@ -213,26 +214,28 @@
       # Shared library specific settings.
       [ '_type=="shared_library"', {
         'conditions': [
           [ 'cc_use_gnu_ld==1', {
             'ldflags': [
               '-Wl,--gc-sections',
             ],
             'conditions': [
-              ['OS=="dragonfly" or OS=="freebsd" or OS=="netbsd" or OS=="openbsd"', {
-                # Bug 1321317 - unix_rand.c:880: undefined reference to `environ'
<