Merge m-c to oak
authorRobert Strong <robert.bugzilla@gmail.com>
Tue, 11 Jul 2017 13:00:32 -0700
changeset 677580 4edfc8c516dc257b198068103300bc0f26c38199
parent 677579 e169738240992616e6133154e8e4c80531e0730a (current diff)
parent 606958 6fec4855b5345eb63fef57089e61829b88f5f4eb (diff)
child 677581 64bb2dbb667ecd2d80902c4109ca102fb2c5f5d1
push id83767
push userbmo:sledru@mozilla.com
push dateTue, 10 Oct 2017 16:25:09 +0000
milestone56.0a1
Merge m-c to oak
.cron.yml
accessible/tests/mochitest/bounds/test_zoom.html
accessible/tests/mochitest/bounds/test_zoom_text.html
accessible/tests/mochitest/events/test_docload.xul
accessible/tests/mochitest/events/test_focus_browserui.xul
accessible/tests/mochitest/events/test_focus_dialog.html
browser/app/profile/firefox.js
browser/base/content/browser.js
browser/installer/package-manifest.in
dom/base/test/test_bug1268962.html
dom/events/test/test_submitevent_on_form.html
gfx/layers/mlgpu/ShaderDefinitionsMLGPU.cpp
taskcluster/ci/build/macosx.yml
taskcluster/ci/test/tests.yml
taskcluster/taskgraph/transforms/tests.py
testing/mozharness/mozharness/mozilla/building/buildbase.py
testing/web-platform/meta/content-security-policy/font-src/font-match-allowed.sub.html.ini
testing/web-platform/meta/content-security-policy/font-src/font-mismatch-blocked.sub.html.ini
testing/web-platform/meta/content-security-policy/font-src/font-none-blocked.sub.html.ini
testing/web-platform/meta/content-security-policy/font-src/font-self-allowed.html.ini
testing/web-platform/meta/preload/avoid-delaying-onload-link-preload.html.ini
testing/web-platform/meta/preload/delaying-onload-link-preload-after-discovery.html.ini
testing/web-platform/meta/preload/download-resources.html.ini
testing/web-platform/meta/preload/dynamic-adding-preload.html.ini
testing/web-platform/meta/preload/fetch-destination.https.html.ini
testing/web-platform/meta/preload/link-header-preload-delay-onload.html.ini
testing/web-platform/meta/preload/link-header-preload.html.ini
testing/web-platform/meta/preload/onerror-event.html.ini
testing/web-platform/meta/preload/onload-event.html.ini
testing/web-platform/meta/preload/preload-csp.sub.html.ini
testing/web-platform/meta/preload/preload-default-csp.sub.html.ini
testing/web-platform/meta/preload/preload-with-type.html.ini
testing/web-platform/meta/preload/single-download-late-used-preload.html.ini
testing/web-platform/meta/preload/single-download-preload.html.ini
testing/web-platform/meta/service-workers/service-worker/opaque-response-preloaded.https.html.ini
toolkit/locales/en-US/chrome/global/headsUpDisplay.properties
--- a/.cron.yml
+++ b/.cron.yml
@@ -5,26 +5,24 @@
 jobs:
     - name: nightly-desktop
       job:
           type: decision-task
           treeherder-symbol: Nd
           target-tasks-method: nightly_linux
       run-on-projects:
           - mozilla-central
-          - mozilla-aurora
           - date
           - oak
       when:
           by-project:
             # Match buildbot starts for now
             date: [{hour: 15, minute: 0}]
             oak: [{hour: 11, minute: 0}]
             mozilla-central: [{hour: 10, minute: 0}]
-            mozilla-aurora: [] # bug 1358976
             # No default
 
     - name: nightly-desktop-osx
       job:
           type: decision-task
           treeherder-symbol: Nd-OSX
           target-tasks-method: nightly_macosx
       run-on-projects:
@@ -50,28 +48,38 @@ jobs:
 
     - name: nightly-android
       job:
           type: decision-task
           treeherder-symbol: Na
           target-tasks-method: nightly_fennec
       run-on-projects:
           - mozilla-central
-          - mozilla-aurora
           - date
       when:
         by-project:
             # Match buildbot starts for now
             date: [{hour: 15, minute: 0}]
             mozilla-central: [{hour: 10, minute: 0}]
-            mozilla-aurora: [] # bug 1358976
             # No default
 
     - name: nightly-mochitest-valgrind
       job:
           type: decision-task
           treeherder-symbol: Vg
           target-tasks-method: mochitest_valgrind
       run-on-projects:
           - mozilla-central
       when:
           - {hour: 16, minute: 0}
           - {hour: 4, minute: 0}
+
+    - name: nightly-dmd
+      job:
+          type: decision-task
+          treeherder-symbol: Ndmd
+          target-tasks-method: nightly_dmd
+      run-on-projects:
+          - mozilla-central
+      when:
+          by-project:
+            mozilla-central: [{hour: 10, minute: 0}]
+            # No default
--- a/accessible/moz.build
+++ b/accessible/moz.build
@@ -26,16 +26,18 @@ DIRS += [ 'aom',
 ]
 
 if CONFIG['MOZ_XUL']:
     DIRS += ['xul']
 
 TEST_DIRS += ['tests/mochitest']
 
 BROWSER_CHROME_MANIFESTS += [
+  'tests/browser/bounds/browser.ini',
   'tests/browser/browser.ini',
   'tests/browser/e10s/browser.ini',
+  'tests/browser/events/browser.ini',
   'tests/browser/scroll/browser.ini',
   'tests/browser/states/browser.ini'
 ]
 
 with Files("**"):
-    BUG_COMPONENT = ("Core", "Disability Access APIs")
\ No newline at end of file
+    BUG_COMPONENT = ("Core", "Disability Access APIs")
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/bounds/browser.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+skip-if = e10s && os == 'win' && release_or_beta
+support-files =
+  head.js
+  !/accessible/tests/browser/events.js
+  !/accessible/tests/browser/shared-head.js
+  !/accessible/tests/mochitest/*.js
+  !/accessible/tests/mochitest/letters.gif
+
+[browser_test_zoom.js]
+[browser_test_zoom_text.js]
+skip-if = os == 'win' # Bug 1372296
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/bounds/browser_test_zoom.js
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* import-globals-from ../../mochitest/layout.js */
+
+async function getContentBoundsForDOMElm(browser, id) {
+  return ContentTask.spawn(browser, id, contentId => {
+    this.ok = ok;
+    return getBoundsForDOMElm(contentId);
+  });
+}
+
+async function testContentBounds(browser, acc) {
+  let [expectedX, expectedY, expectedWidth, expectedHeight] =
+    await getContentBoundsForDOMElm(browser, getAccessibleDOMNodeID(acc));
+
+  let [x, y, width, height] = getBounds(acc);
+  let prettyAccName = prettyName(acc);
+  is(x, expectedX, "Wrong x coordinate of " + prettyAccName);
+  is(y, expectedY, "Wrong y coordinate of " + prettyAccName);
+  is(width, expectedWidth, "Wrong width of " + prettyAccName);
+  is(height, expectedHeight, "Wrong height of " + prettyAccName);
+}
+
+async function runTests(browser, accDoc) {
+  loadFrameScripts(browser, { name: 'layout.js', dir: MOCHITESTS_DIR });
+
+  let p1 = findAccessibleChildByID(accDoc, "p1");
+  let p2 = findAccessibleChildByID(accDoc, "p2");
+  let imgmap = findAccessibleChildByID(accDoc, "imgmap");
+  if (!imgmap.childCount) {
+    // An image map may not be available even after the doc and image load
+    // is complete. We don't recieve any DOM events for this change either,
+    // so we need to wait for a REORDER.
+    await waitForEvent(EVENT_REORDER, "imgmap");
+  }
+  let area = imgmap.firstChild;
+
+  await testContentBounds(browser, p1);
+  await testContentBounds(browser, p2);
+  await testContentBounds(browser, area);
+
+  await ContentTask.spawn(browser, {}, () => {
+    zoomDocument(document, 2.0);
+  });
+
+  await testContentBounds(browser, p1);
+  await testContentBounds(browser, p2);
+  await testContentBounds(browser, area);
+}
+
+/**
+ * Test accessible boundaries when page is zoomed
+ */
+addAccessibleTask(`
+<p id="p1">para 1</p><p id="p2">para 2</p>
+<map name="atoz_map" id="map">
+  <area id="area1" href="http://mozilla.org"
+        coords=17,0,30,14" alt="mozilla.org" shape="rect">
+</map>
+<img id="imgmap" width="447" height="15"
+     usemap="#atoz_map"
+     src="http://example.com/a11y/accessible/tests/mochitest/letters.gif">`,
+  runTests
+);
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/bounds/browser_test_zoom_text.js
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* import-globals-from ../../mochitest/layout.js */
+
+async function runTests(browser, accDoc) {
+  function testTextNode(id) {
+    let hyperTextNode = findAccessibleChildByID(accDoc, id);
+    let textNode = hyperTextNode.firstChild;
+
+    let [x, y, width, height] = getBounds(textNode);
+    testTextBounds(hyperTextNode, 0, -1, [x, y, width, height],
+                   COORDTYPE_SCREEN_RELATIVE);
+  }
+
+  loadFrameScripts(browser, { name: 'layout.js', dir: MOCHITESTS_DIR });
+
+  testTextNode("p1");
+  testTextNode("p2");
+
+  await ContentTask.spawn(browser, {}, () => {
+    zoomDocument(document, 2.0);
+  });
+
+  testTextNode("p1");
+
+  await ContentTask.spawn(browser, {}, () => {
+    zoomDocument(document, 1.0);
+  });
+}
+
+/**
+ * Test the text range boundary when page is zoomed
+ */
+addAccessibleTask(`
+  <p id='p1' style='font-family: monospace;'>Tilimilitryamdiya</p>
+  <p id='p2'>ل</p>`, runTests
+);
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/bounds/head.js
@@ -0,0 +1,16 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+// Load the shared-head file first.
+/* import-globals-from ../shared-head.js */
+Services.scriptloader.loadSubScript(
+  'chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js',
+  this);
+
+// Loading and common.js from accessible/tests/mochitest/ for all tests, as
+// well as events.js.
+loadScripts({ name: 'common.js', dir: MOCHITESTS_DIR },
+            { name: 'layout.js', dir: MOCHITESTS_DIR }, 'events.js');
--- a/accessible/tests/browser/e10s/browser_caching_description.js
+++ b/accessible/tests/browser/e10s/browser_caching_description.js
@@ -21,117 +21,117 @@ const tests = [{
   desc: 'No description when there are no @alt, @title and @aria-describedby',
   expected: ''
 }, {
   desc: 'Description from @aria-describedby attribute',
   attrs: [{
     attr: 'aria-describedby',
     value: 'description'
   }],
-  waitFor: [{ eventType: EVENT_DESCRIPTION_CHANGE, id: 'image' }],
+  waitFor: [[EVENT_DESCRIPTION_CHANGE, 'image']],
   expected: 'aria description'
 }, {
   desc: 'No description from @aria-describedby since it is the same as the ' +
         '@alt attribute which is used as the name',
   attrs: [{
     attr: 'alt',
     value: 'aria description'
   }],
-  waitFor: [{ eventType: EVENT_REORDER, id: 'body' }],
+  waitFor: [[EVENT_REORDER, 'body']],
   expected: ''
 }, {
   desc: 'Description from @aria-describedby attribute when @alt and ' +
         '@aria-describedby are not the same',
   attrs: [{
     attr: 'aria-describedby',
     value: 'description2'
   }],
-  waitFor: [{ eventType: EVENT_DESCRIPTION_CHANGE, id: 'image' }],
+  waitFor: [[EVENT_DESCRIPTION_CHANGE, 'image']],
   expected: 'another description'
 }, {
   desc: 'Description from @aria-describedby attribute when @title (used for ' +
         'name) and @aria-describedby are not the same',
   attrs: [{
     attr: 'alt'
   }, {
     attr: 'title',
     value: 'title'
   }],
-  waitFor: [{ eventType: EVENT_REORDER, id: 'body' }],
+  waitFor: [[EVENT_REORDER, 'body']],
   expected: 'another description'
 }, {
   desc: 'No description from @aria-describedby since it is the same as the ' +
         '@title attribute which is used as the name',
   attrs: [{
     attr: 'title',
     value: 'another description'
   }],
-  waitFor: [{ eventType: EVENT_NAME_CHANGE, id: 'image' }],
+  waitFor: [[EVENT_NAME_CHANGE, 'image']],
   expected: ''
 }, {
   desc: 'No description with only @title attribute which is used as the name',
   attrs: [{
     attr: 'aria-describedby'
   }],
-  waitFor: [{ eventType: EVENT_DESCRIPTION_CHANGE, id: 'image' }],
+  waitFor: [[EVENT_DESCRIPTION_CHANGE, 'image']],
   expected: ''
 }, {
   desc: 'Description from @title attribute when @alt and @atitle are not the ' +
         'same',
   attrs: [{
     attr: 'alt',
     value: 'aria description'
   }],
-  waitFor: [{ eventType: EVENT_REORDER, id: 'body' }],
+  waitFor: [[EVENT_REORDER, 'body']],
   expected: 'another description'
 }, {
   desc: 'No description from @title since it is the same as the @alt ' +
         'attribute which is used as the name',
   attrs: [{
     attr: 'alt',
     value: 'another description'
   }],
-  waitFor: [{ eventType: EVENT_NAME_CHANGE, id: 'image' }],
+  waitFor: [[EVENT_NAME_CHANGE, 'image']],
   expected: ''
 }, {
   desc: 'No description from @aria-describedby since it is the same as the ' +
         '@alt (used for name) and @title attributes',
   attrs: [{
     attr: 'aria-describedby',
     value: 'description2'
   }],
-  waitFor: [{ eventType: EVENT_DESCRIPTION_CHANGE, id: 'image' }],
+  waitFor: [[EVENT_DESCRIPTION_CHANGE, 'image']],
   expected: ''
 }, {
   desc: 'Description from @aria-describedby attribute when it is different ' +
         'from @alt (used for name) and @title attributes',
   attrs: [{
     attr: 'aria-describedby',
     value: 'description'
   }],
-  waitFor: [{ eventType: EVENT_DESCRIPTION_CHANGE, id: 'image' }],
+  waitFor: [[EVENT_DESCRIPTION_CHANGE, 'image']],
   expected: 'aria description'
 }, {
   desc: 'No description from @aria-describedby since it is the same as the ' +
         '@alt attribute (used for name) but different from title',
   attrs: [{
     attr: 'alt',
     value: 'aria description'
   }],
-  waitFor: [{ eventType: EVENT_NAME_CHANGE, id: 'image' }],
+  waitFor: [[EVENT_NAME_CHANGE, 'image']],
   expected: ''
 }, {
   desc: 'Description from @aria-describedby attribute when @alt (used for ' +
         'name) and @aria-describedby are not the same but @title and ' +
         'aria-describedby are',
   attrs: [{
     attr: 'aria-describedby',
     value: 'description2'
   }],
-  waitFor: [{ eventType: EVENT_DESCRIPTION_CHANGE, id: 'image' }],
+  waitFor: [[EVENT_DESCRIPTION_CHANGE, 'image']],
   expected: 'another description'
 }];
 
 /**
  * Test caching of accessible object description
  */
 addAccessibleTask(`
   <p id="description">aria description</p>
@@ -139,17 +139,17 @@ addAccessibleTask(`
   <img id="image" />`,
   async function(browser, accDoc) {
     let imgAcc = findAccessibleChildByID(accDoc, 'image');
 
     for (let { desc, waitFor, attrs, expected } of tests) {
       info(desc);
       let onUpdate;
       if (waitFor) {
-        onUpdate = waitForMultipleEvents(waitFor);
+        onUpdate = waitForOrderedEvents(waitFor);
       }
       if (attrs) {
         for (let { attr, value } of attrs) {
           await invokeSetAttribute(browser, 'image', attr, value);
         }
       }
       await onUpdate;
       // When attribute change (alt) triggers reorder event, accessible will
--- a/accessible/tests/browser/e10s/browser_events_textchange.js
+++ b/accessible/tests/browser/e10s/browser_events_textchange.js
@@ -11,19 +11,19 @@ function checkTextChangeEvent(event, id,
   is(tcEvent.isInserted, isInserted,
     `Correct isInserted flag for ${prettyName(id)}`);
   is(tcEvent.modifiedText, text, `Correct text for ${prettyName(id)}`);
   is(tcEvent.isFromUserInput, isFromUserInput,
     `Correct value of isFromUserInput for ${prettyName(id)}`);
 }
 
 async function changeText(browser, id, value, events) {
-  let onEvents = waitForMultipleEvents(events.map(({ isInserted }) => {
+  let onEvents = waitForOrderedEvents(events.map(({ isInserted }) => {
     let eventType = isInserted ? EVENT_TEXT_INSERTED : EVENT_TEXT_REMOVED;
-    return { id, eventType };
+    return [ eventType, id ];
   }));
   // Change text in the subtree.
   await ContentTask.spawn(browser, [id, value], ([contentId, contentValue]) => {
     content.document.getElementById(contentId).firstChild.textContent =
       contentValue;
   });
   let resolvedEvents = await onEvents;
 
--- a/accessible/tests/browser/events.js
+++ b/accessible/tests/browser/events.js
@@ -8,31 +8,33 @@
 // globals from there.
 /* import-globals-from shared-head.js */
 /* import-globals-from ../mochitest/common.js */
 
 /* exported EVENT_REORDER, EVENT_SHOW, EVENT_TEXT_INSERTED, EVENT_TEXT_REMOVED,
             EVENT_DOCUMENT_LOAD_COMPLETE, EVENT_HIDE, EVENT_TEXT_CARET_MOVED,
             EVENT_DESCRIPTION_CHANGE, EVENT_NAME_CHANGE, EVENT_STATE_CHANGE,
             EVENT_VALUE_CHANGE, EVENT_TEXT_VALUE_CHANGE, EVENT_FOCUS,
-            waitForEvent, waitForMultipleEvents */
+            EVENT_DOCUMENT_RELOAD,
+            waitForEvent, waitForEvents, waitForOrderedEvents */
 
 const EVENT_DOCUMENT_LOAD_COMPLETE = nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_COMPLETE;
 const EVENT_HIDE = nsIAccessibleEvent.EVENT_HIDE;
 const EVENT_REORDER = nsIAccessibleEvent.EVENT_REORDER;
 const EVENT_SHOW = nsIAccessibleEvent.EVENT_SHOW;
 const EVENT_STATE_CHANGE = nsIAccessibleEvent.EVENT_STATE_CHANGE;
 const EVENT_TEXT_CARET_MOVED = nsIAccessibleEvent.EVENT_TEXT_CARET_MOVED;
 const EVENT_TEXT_INSERTED = nsIAccessibleEvent.EVENT_TEXT_INSERTED;
 const EVENT_TEXT_REMOVED = nsIAccessibleEvent.EVENT_TEXT_REMOVED;
 const EVENT_DESCRIPTION_CHANGE = nsIAccessibleEvent.EVENT_DESCRIPTION_CHANGE;
 const EVENT_NAME_CHANGE = nsIAccessibleEvent.EVENT_NAME_CHANGE;
 const EVENT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_VALUE_CHANGE;
 const EVENT_TEXT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_TEXT_VALUE_CHANGE;
 const EVENT_FOCUS = nsIAccessibleEvent.EVENT_FOCUS;
+const EVENT_DOCUMENT_RELOAD = nsIAccessibleEvent.EVENT_DOCUMENT_RELOAD;
 
 /**
  * Describe an event in string format.
  * @param  {nsIAccessibleEvent}  event  event to strigify
  */
 function eventToString(event) {
   let type = eventTypeToString(event.eventType);
   let info = `Event type: ${type}`;
@@ -45,28 +47,55 @@ function eventToString(event) {
     let tcType = event.isInserted ? 'inserted' : 'removed';
     info += `, start: ${event.start}, length: ${event.length}, ${tcType} text: ${event.modifiedText}`;
   }
 
   info += `. Target: ${prettyName(event.accessible)}`;
   return info;
 }
 
+function matchEvent(event, matchCriteria) {
+  let acc = event.accessible;
+  switch (typeof matchCriteria) {
+    case "string":
+      let id = getAccessibleDOMNodeID(acc);
+      if (id === matchCriteria) {
+        Logger.log(`Event matches DOMNode id: ${id}`);
+        return true;
+      }
+      break;
+    case "function":
+      if (matchCriteria(event)) {
+        Logger.log(`Lambda function matches event: ${eventToString(event)}`);
+        return true;
+      }
+      break;
+    default:
+      if (acc === matchCriteria) {
+        Logger.log(`Event matches accessible: ${prettyName(acc)}`);
+        return true;
+      }
+  }
+
+  return false;
+}
+
 /**
  * A helper function that returns a promise that resolves when an accessible
  * event of the given type with the given target (defined by its id or
  * accessible) is observed.
- * @param  {String|nsIAccessible}  expectedIdOrAcc  expected content element id
- *                                                  for the event
+ * @param  {String|nsIAccessible|Function}  matchCriteria  expected content
+ *                                                         element id
+ *                                                         for the event
  * @param  {Number}                eventType        expected accessible event
  *                                                  type
  * @return {Promise}                                promise that resolves to an
  *                                                  event
  */
-function waitForEvent(eventType, expectedIdOrAcc) {
+function waitForEvent(eventType, matchCriteria) {
   return new Promise(resolve => {
     let eventObserver = {
       observe(subject, topic, data) {
         if (topic !== 'accessible-event') {
           return;
         }
 
         let event = subject.QueryInterface(nsIAccessibleEvent);
@@ -76,54 +105,82 @@ function waitForEvent(eventType, expecte
           Logger.log(eventToString(event));
         }
 
         // If event type does not match expected type, skip the event.
         if (event.eventType !== eventType) {
           return;
         }
 
-        let acc = event.accessible;
-        let id = getAccessibleDOMNodeID(acc);
-        let isID = typeof expectedIdOrAcc === 'string';
-        let actualIdOrAcc = isID ? id : acc;
-        // If event's accessible does not match expected DOMNode id or
-        // accessible, skip the event.
-        if (actualIdOrAcc === expectedIdOrAcc) {
-          if (isID) {
-            Logger.log(`Correct event DOMNode id: ${id}`);
-          } else {
-            Logger.log(`Correct event accessible: ${prettyName(acc)}`);
-          }
+        if (matchEvent(event, matchCriteria)) {
           Logger.log(`Correct event type: ${eventTypeToString(eventType)}`);
           ok(event.accessibleDocument instanceof nsIAccessibleDocument,
             'Accessible document present.');
 
           Services.obs.removeObserver(this, 'accessible-event');
           resolve(event);
         }
       }
     };
     Services.obs.addObserver(eventObserver, 'accessible-event');
   });
 }
 
+class UnexpectedEvents {
+  constructor(unexpected) {
+    if (unexpected.length) {
+      this.unexpected = unexpected;
+      Services.obs.addObserver(this, 'accessible-event');
+    }
+  }
+
+  observe(subject, topic, data) {
+    if (topic !== 'accessible-event') {
+      return;
+    }
+
+    let event = subject.QueryInterface(nsIAccessibleEvent);
+
+    let unexpectedEvent = this.unexpected.find(([etype, criteria]) =>
+      etype === event.eventType && matchEvent(event, criteria));
+
+    if (unexpectedEvent) {
+      ok(false, `Got unexpected event: ${eventToString(event)}`);
+    }
+  }
+
+  stop() {
+    if (this.unexpected) {
+      Services.obs.removeObserver(this, 'accessible-event');
+    }
+  }
+}
+
 /**
  * A helper function that waits for a sequence of accessible events in
  * specified order.
  * @param {Array} events        a list of events to wait (same format as
  *                              waitForEvent arguments)
  */
-function waitForMultipleEvents(events) {
+function waitForEvents(events, unexpected = [], ordered = false) {
   // Next expected event index.
   let currentIdx = 0;
 
-  return Promise.all(events.map(({ eventType, id }, idx) =>
-    // In addition to waiting for an event, attach an order checker for the
-    // event.
-    waitForEvent(eventType, id).then(resolvedEvent => {
-      // Verify that event happens in order and increment expected index.
-      is(idx, currentIdx++,
-        `Unexpected event order: ${eventToString(resolvedEvent)}`);
-      return resolvedEvent;
-    })
-  ));
+  let unexpectedListener = new UnexpectedEvents(unexpected);
+
+  return Promise.all(events.map((evt, idx) => {
+    let promise = evt instanceof Array ? waitForEvent(...evt) : evt;
+    return promise.then(result => {
+      if (ordered) {
+        is(idx, currentIdx++,
+          `Unexpected event order: ${result}`);
+      }
+      return result;
+    });
+  })).then(results => {
+    unexpectedListener.stop();
+    return results;
+  });
 }
+
+function waitForOrderedEvents(events, unexpected = []) {
+  return waitForEvents(events, unexpected, true);
+}
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/events/browser.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+skip-if = e10s && os == 'win' && release_or_beta
+support-files =
+  head.js
+  !/accessible/tests/browser/events.js
+  !/accessible/tests/browser/shared-head.js
+  !/accessible/tests/mochitest/*.js
+
+[browser_test_docload.js]
+skip-if = e10s
+[browser_test_focus_browserui.js]
+[browser_test_focus_dialog.js]
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/events/browser_test_docload.js
@@ -0,0 +1,114 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+function busyChecker(isBusy) {
+  return function (event) {
+    let scEvent;
+    try {
+      scEvent = event.QueryInterface(nsIAccessibleStateChangeEvent);
+    } catch (e) {
+      return false;
+    }
+
+    return scEvent.state == STATE_BUSY && scEvent.isEnabled == isBusy;
+  };
+}
+
+function inIframeChecker(iframeId) {
+  return function (event) {
+    return getAccessibleDOMNodeID(event.accessibleDocument.parent) == iframeId;
+  };
+}
+
+function urlChecker(url) {
+  return function (event) {
+    info(`${event.accessibleDocument.URL} == ${url}`);
+    return event.accessibleDocument.URL == url;
+  };
+}
+
+async function runTests(browser, accDoc) {
+  let onLoadEvents = waitForEvents([
+    [EVENT_REORDER, getAccessible(browser)],
+    [EVENT_DOCUMENT_LOAD_COMPLETE, 'body2'],
+    [EVENT_STATE_CHANGE, busyChecker(false)]
+  ], [ // unexpected
+    [EVENT_DOCUMENT_LOAD_COMPLETE, inIframeChecker("iframe1")],
+    [EVENT_STATE_CHANGE, inIframeChecker("iframe1")]
+  ]);
+
+  browser.loadURI(`data:text/html;charset=utf-8,
+    <html><body id="body2">
+      <iframe id="iframe1" src="http://example.com"></iframe>
+    </body></html>`);
+
+  await onLoadEvents;
+
+  onLoadEvents = waitForEvents([
+      [EVENT_DOCUMENT_LOAD_COMPLETE, urlChecker("about:")],
+      [EVENT_STATE_CHANGE, busyChecker(false)],
+      [EVENT_REORDER, getAccessible(browser)]
+  ]);
+
+  browser.loadURI("about:");
+
+  await onLoadEvents;
+
+  onLoadEvents = waitForEvents([
+    [EVENT_DOCUMENT_RELOAD, evt => evt.isFromUserInput],
+    [EVENT_REORDER, getAccessible(browser)],
+    [EVENT_STATE_CHANGE, busyChecker(false)]
+  ]);
+
+  EventUtils.synthesizeKey("VK_F5", {}, browser.ownerGlobal);
+
+  await onLoadEvents;
+
+  onLoadEvents = waitForEvents([
+    [EVENT_DOCUMENT_LOAD_COMPLETE, urlChecker("about:mozilla")],
+    [EVENT_STATE_CHANGE, busyChecker(false)],
+    [EVENT_REORDER, getAccessible(browser)]
+  ]);
+
+  browser.loadURI("about:mozilla");
+
+  await onLoadEvents;
+
+  onLoadEvents = waitForEvents([
+    [EVENT_DOCUMENT_RELOAD, evt => !evt.isFromUserInput],
+    [EVENT_REORDER, getAccessible(browser)],
+    [EVENT_STATE_CHANGE, busyChecker(false)]
+  ]);
+
+  browser.reload();
+
+  await onLoadEvents;
+
+  onLoadEvents = waitForEvents([
+    [EVENT_DOCUMENT_LOAD_COMPLETE, urlChecker("http://www.wronguri.wronguri/")],
+    [EVENT_STATE_CHANGE, busyChecker(false)],
+    [EVENT_REORDER, getAccessible(browser)]
+  ]);
+
+  browser.loadURI("http://www.wronguri.wronguri/");
+
+  await onLoadEvents;
+
+  onLoadEvents = waitForEvents([
+    [EVENT_DOCUMENT_LOAD_COMPLETE, urlChecker("https://nocert.example.com/")],
+    [EVENT_STATE_CHANGE, busyChecker(false)],
+    [EVENT_REORDER, getAccessible(browser)]
+  ]);
+
+  browser.loadURI("https://nocert.example.com:443/");
+
+  await onLoadEvents;
+}
+
+/**
+ * Test caching of accessible object states
+ */
+addAccessibleTask("", runTests);
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/events/browser_test_focus_browserui.js
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* import-globals-from ../../mochitest/states.js */
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: 'states.js', dir: MOCHITESTS_DIR },
+            { name: 'role.js', dir: MOCHITESTS_DIR });
+
+async function runTests(browser, accDoc) {
+  let onFocus = waitForEvent(EVENT_FOCUS, "input");
+  EventUtils.synthesizeKey("VK_TAB", {}, browser.ownerGlobal);
+  let evt = await onFocus;
+  testStates(evt.accessible, STATE_FOCUSED);
+
+  onFocus = waitForEvent(EVENT_FOCUS, "buttonInputDoc");
+  browser.loadURI(snippetToURL(
+    `<input id="input" type="button" value="button">`, { id: "buttonInputDoc" }));
+  evt = await onFocus;
+  testStates(evt.accessible, STATE_FOCUSED);
+
+  onFocus = waitForEvent(EVENT_FOCUS, "input");
+  browser.goBack();
+  evt = await onFocus;
+  testStates(evt.accessible, STATE_FOCUSED);
+
+  let inputField = browser.ownerDocument.getElementById("urlbar").inputField;
+  onFocus = waitForEvent(EVENT_FOCUS, getAccessible(inputField));
+  EventUtils.synthesizeKey("t", { accelKey: true }, browser.ownerGlobal);
+  evt = await onFocus;
+  testStates(evt.accessible, STATE_FOCUSED);
+
+  onFocus = waitForEvent(EVENT_FOCUS, "input");
+  EventUtils.synthesizeKey("w", { accelKey: true }, browser.ownerGlobal);
+  evt = await onFocus;
+  testStates(evt.accessible, STATE_FOCUSED);
+}
+
+/**
+ * Accessibility loading document events test.
+ */
+addAccessibleTask(`<input id="input">`, runTests);
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/events/browser_test_focus_dialog.js
@@ -0,0 +1,67 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* import-globals-from ../../mochitest/states.js */
+/* import-globals-from ../../mochitest/role.js */
+loadScripts({ name: 'states.js', dir: MOCHITESTS_DIR },
+            { name: 'role.js', dir: MOCHITESTS_DIR });
+
+async function runTests(browser, accDoc) {
+  let onFocus = waitForEvent(EVENT_FOCUS, "button");
+  await ContentTask.spawn(browser, {}, () => {
+    content.document.getElementById("button").focus();
+  });
+  let button = (await onFocus).accessible;
+  testStates(button, STATE_FOCUSED);
+
+  // Bug 1377942 - The target of the focus event changes under different
+  // circumstances.
+  // In e10s the focus event is the new window, in non-e10s it's the doc.
+  onFocus = waitForEvent(EVENT_FOCUS, () => true);
+  let newWin = await BrowserTestUtils.openNewBrowserWindow();
+  // button should be blurred
+  await onFocus;
+  testStates(button, 0, 0, STATE_FOCUSED);
+
+  onFocus = waitForEvent(EVENT_FOCUS, "button");
+  await BrowserTestUtils.closeWindow(newWin);
+  testStates((await onFocus).accessible, STATE_FOCUSED);
+
+  onFocus = waitForEvent(EVENT_FOCUS, "body2");
+  await ContentTask.spawn(browser, {}, () => {
+    content.document.getElementById("editabledoc").contentWindow.document.body.focus();
+  });
+  testStates((await onFocus).accessible, STATE_FOCUSED);
+
+  onFocus = waitForEvent(EVENT_FOCUS, "body2");
+  newWin = await BrowserTestUtils.openNewBrowserWindow();
+  await BrowserTestUtils.closeWindow(newWin);
+  testStates((await onFocus).accessible, STATE_FOCUSED);
+
+  let onShow = waitForEvent(EVENT_SHOW, "alertdialog");
+  onFocus = waitForEvent(EVENT_FOCUS, "alertdialog");
+  await ContentTask.spawn(browser, {}, () => {
+    let alertDialog = content.document.getElementById("alertdialog");
+    alertDialog.style.display = "block";
+    alertDialog.focus();
+  });
+  await onShow;
+  testStates((await onFocus).accessible, STATE_FOCUSED);
+}
+
+/**
+ * Accessible dialog focus testing
+ */
+addAccessibleTask(`
+  <button id="button">button</button>
+  <iframe id="editabledoc"
+          src="${snippetToURL("", { id: "body2", contentEditable: "true"})}">
+  </iframe>
+  <div id="alertdialog" style="display: none" tabindex="-1" role="alertdialog" aria-labelledby="title2" aria-describedby="desc2">
+    <div id="title2">Blah blah</div>
+    <div id="desc2">Woof woof woof.</div>
+    <button>Close</button>
+  </div>`, runTests);
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/events/head.js
@@ -0,0 +1,15 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+// Load the shared-head file first.
+/* import-globals-from ../shared-head.js */
+Services.scriptloader.loadSubScript(
+  'chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js',
+  this);
+
+// Loading and common.js from accessible/tests/mochitest/ for all tests, as
+// well as events.js.
+loadScripts({ name: 'common.js', dir: MOCHITESTS_DIR }, 'events.js', 'layout.js');
--- a/accessible/tests/browser/scroll/browser_test_zoom_text.js
+++ b/accessible/tests/browser/scroll/browser_test_zoom_text.js
@@ -3,27 +3,31 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 'use strict';
 
 /* import-globals-from ../../mochitest/layout.js */
 loadScripts({ name: 'layout.js', dir: MOCHITESTS_DIR });
 
 async function runTests(browser, accDoc) {
+  loadFrameScripts(browser, { name: 'layout.js', dir: MOCHITESTS_DIR });
+
   let paragraph = findAccessibleChildByID(accDoc, "paragraph", [nsIAccessibleText]);
   let offset = 64; // beginning of 4th stanza
 
   let [x /*,y*/] = getPos(paragraph);
   let [docX, docY] = getPos(accDoc);
 
   paragraph.scrollSubstringToPoint(offset, offset,
                                    COORDTYPE_SCREEN_RELATIVE, docX, docY);
   testTextPos(paragraph, offset, [x, docY], COORDTYPE_SCREEN_RELATIVE);
 
-  await zoomContent(browser, 2.0);
+  await ContentTask.spawn(browser, {}, () => {
+    zoomDocument(document, 2.0);
+  });
 
   paragraph = findAccessibleChildByID(accDoc, "paragraph2", [nsIAccessibleText]);
   offset = 52; // // beginning of 4th stanza
   [x /*,y*/] = getPos(paragraph);
   paragraph.scrollSubstringToPoint(offset, offset,
                                    COORDTYPE_SCREEN_RELATIVE, docX, docY);
   testTextPos(paragraph, offset, [x, docY], COORDTYPE_SCREEN_RELATIVE);
 }
--- a/accessible/tests/browser/scroll/head.js
+++ b/accessible/tests/browser/scroll/head.js
@@ -1,30 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 'use strict';
 
-/* exported zoomContent */
-
 // Load the shared-head file first.
 /* import-globals-from ../shared-head.js */
 Services.scriptloader.loadSubScript(
   'chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js',
   this);
 
-async function zoomContent(browser, zoom)
-{
-  return ContentTask.spawn(browser, zoom, _zoom => {
-    let docShell = content
-      .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-      .getInterface(Components.interfaces.nsIWebNavigation)
-      .QueryInterface(Components.interfaces.nsIDocShell);
-    let docViewer = docShell.contentViewer;
-
-    docViewer.fullZoom = _zoom;
-  });
-}
-
 // Loading and common.js from accessible/tests/mochitest/ for all tests, as
 // well as events.js.
 loadScripts({ name: 'common.js', dir: MOCHITESTS_DIR }, 'events.js');
--- a/accessible/tests/browser/shared-head.js
+++ b/accessible/tests/browser/shared-head.js
@@ -3,19 +3,20 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 'use strict';
 
 /* import-globals-from ../mochitest/common.js */
 /* import-globals-from events.js */
 
 /* exported Logger, MOCHITESTS_DIR, invokeSetAttribute, invokeFocus,
-            invokeSetStyle, getAccessibleDOMNodeID,
+            invokeSetStyle, getAccessibleDOMNodeID, getAccessibleTagName,
             addAccessibleTask, findAccessibleChildByID, isDefunct,
-            CURRENT_CONTENT_DIR, loadScripts, loadFrameScripts, Cc, Cu */
+            CURRENT_CONTENT_DIR, loadScripts, loadFrameScripts, snippetToURL,
+            Cc, Cu */
 
 const { interfaces: Ci, utils: Cu, classes: Cc } = Components;
 
 /**
  * Current browser test directory path used to load subscripts.
  */
 const CURRENT_DIR =
   'chrome://mochitests/content/browser/accessible/tests/browser/';
@@ -26,16 +27,18 @@ const CURRENT_DIR =
 const MOCHITESTS_DIR =
   'chrome://mochitests/content/a11y/accessible/tests/mochitest/';
 /**
  * A base URL for test files used in content.
  */
 const CURRENT_CONTENT_DIR =
   'http://example.com/browser/accessible/tests/browser/';
 
+const LOADED_FRAMESCRIPTS = new Map();
+
 /**
  * Used to dump debug information.
  */
 let Logger = {
   /**
    * Set up this variable to dump log messages into console.
    */
   dumpToConsole: false,
@@ -183,45 +186,71 @@ function loadFrameScripts(browser, ...sc
       } else {
         // Otherwise it is a serealized script.
         frameScript = `data:,${script}`;
       }
     } else {
       // Script is a object that has { dir, name } format.
       frameScript = `${script.dir}${script.name}`;
     }
+
+    let loadedScriptSet = LOADED_FRAMESCRIPTS.get(frameScript);
+    if (!loadedScriptSet) {
+      loadedScriptSet = new WeakSet();
+      LOADED_FRAMESCRIPTS.set(frameScript, loadedScriptSet);
+    } else if (loadedScriptSet.has(browser)) {
+      continue;
+    }
+
     mm.loadFrameScript(frameScript, false, true);
+    loadedScriptSet.add(browser);
   }
 }
 
 /**
+ * Takes an HTML snippet and returns an encoded URI for a full document
+ * with the snippet.
+ * @param {String} snippet   a markup snippet.
+ * @param {Object} bodyAttrs extra attributes to use in the body tag. Default is
+ *                           { id: "body "}.
+ * @return {String} a base64 encoded data url of the document container the
+ *                  snippet.
+ **/
+function snippetToURL(snippet, bodyAttrs={}) {
+  let attrs = Object.assign({}, { id: "body" }, bodyAttrs);
+  let attrsString = Object.entries(attrs).map(
+    ([attr, value]) => `${attr}=${JSON.stringify(value)}`).join(" ");
+  let encodedDoc = btoa(
+    `<html>
+      <head>
+        <meta charset="utf-8"/>
+        <title>Accessibility Test</title>
+      </head>
+      <body ${attrsString}>${snippet}</body>
+    </html>`);
+
+  return `data:text/html;charset=utf-8;base64,${encodedDoc}`;
+}
+
+/**
  * A wrapper around browser test add_task that triggers an accessible test task
  * as a new browser test task with given document, data URL or markup snippet.
  * @param  {String}                 doc  URL (relative to current directory) or
  *                                       data URL or markup snippet that is used
  *                                       to test content with
  * @param  {Function|AsyncFunction} task a generator or a function with tests to
  *                                       run
  */
 function addAccessibleTask(doc, task) {
   add_task(async function() {
     let url;
     if (doc.includes('doc_')) {
       url = `${CURRENT_CONTENT_DIR}e10s/${doc}`;
     } else {
-      // Assume it's a markup snippet.
-      url = "data:text/html;charset=utf-8;base64,";
-      url += btoa(
-        `<html>
-          <head>
-            <meta charset="utf-8"/>
-            <title>Accessibility Test</title>
-          </head>
-          <body id="body">${doc}</body>
-        </html>`);
+      url = snippetToURL(doc);
     }
 
     registerCleanupFunction(() => {
       let observers = Services.obs.enumerateObservers('accessible-event');
       while (observers.hasMoreElements()) {
         Services.obs.removeObserver(
           observers.getNext().QueryInterface(Ci.nsIObserver),
           'accessible-event');
@@ -276,16 +305,29 @@ function isDefunct(accessible) {
     if (defunct) {
       Logger.log(`Defunct accessible: ${prettyName(accessible)}`);
     }
   }
   return defunct;
 }
 
 /**
+ * Get the DOM tag name for a given accessible.
+ * @param  {nsIAccessible}  accessible accessible
+ * @return {String?}                   tag name of associated DOM node, or null.
+ */
+function getAccessibleTagName(acc) {
+  try {
+    return acc.attributes.getStringProperty("tag");
+  } catch (e) {
+    return null;
+  }
+}
+
+/**
  * Traverses the accessible tree starting from a given accessible as a root and
  * looks for an accessible that matches based on its DOMNode id.
  * @param  {nsIAccessible}  accessible root accessible
  * @param  {String}         id         id to look up accessible for
  * @param  {Array?}         interfaces the interface or an array interfaces
  *                                     to query it/them from obtained accessible
  * @return {nsIAccessible?}            found accessible if any
  */
--- a/accessible/tests/mochitest/bounds/a11y.ini
+++ b/accessible/tests/mochitest/bounds/a11y.ini
@@ -1,8 +1,6 @@
 [DEFAULT]
 support-files =
   !/accessible/tests/mochitest/*.js
 
 [test_list.html]
 [test_select.html]
-[test_zoom.html]
-[test_zoom_text.html]
deleted file mode 100644
--- a/accessible/tests/mochitest/bounds/test_zoom.html
+++ /dev/null
@@ -1,96 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <title>Accessible boundaries when page is zoomed</title>
-  <link rel="stylesheet" type="text/css"
-        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
-
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
-
-  <script type="application/javascript"
-          src="../common.js"></script>
-  <script type="application/javascript"
-          src="../role.js"></script>
-  <script type="application/javascript"
-          src="../events.js"></script>
-  <script type="application/javascript"
-          src="../layout.js"></script>
-  <script type="application/javascript"
-          src="../browser.js"></script>
-
-  <script type="application/javascript">
-    //gA11yEventDumpToConsole = true;
-    //enableLogging("tree,verbose");
-    function doPreTest()
-    {
-      var tabDocument = currentTabDocument();
-      var imgMap = tabDocument.getElementById("imgmap");
-      waitForImageMap(imgMap, doTest);
-    }
-
-    function doTest()
-    {
-      // Bug 746176: Failure of this whole test file on OS X.
-      if (MAC) {
-        todo(false, "Fix bug 746176 on Mac");
-        closeBrowserWindow();
-        SimpleTest.finish();
-        return;
-      }
-
-      var tabDocument = currentTabDocument();
-      var p1 = tabDocument.getElementById("p1");
-      var p2 = tabDocument.getElementById("p2");
-
-      var imgMap = tabDocument.getElementById("imgmap");
-      var imgMapAcc = getAccessible(imgMap);
-      var area = imgMapAcc.firstChild;
-
-      testBounds(p1);
-      testBounds(p2);
-      testBounds(area);
-
-      zoomDocument(tabDocument, 2.0);
-
-      testBounds(p1);
-      testBounds(p2);
-      testBounds(area);
-
-      closeBrowserWindow();
-      SimpleTest.finish();
-    }
-
-    var url = "data:text/html,<html><body>";
-    url += "<p id='p1'>para 1</p><p id='p2'>para 2</p>";
-    url += "<map name='atoz_map' id='map'>";
-    url += "  <area id='area1' href='http%3A%2F%2Fmozilla.org'";
-    url += "        coords=17,0,30,14' alt='mozilla.org' shape='rect'>";
-    url += "</map>";
-    url += "<img id='imgmap' width='447' height='15'";
-    url += "     usemap='%23atoz_map'";
-    url += "     src='chrome%3A%2F%2Fmochitests%2Fcontent%2Fa11y%2Faccessible%2Fletters.gif'>";
-    url += "</body></html>";
-
-    SimpleTest.waitForExplicitFinish();
-    openBrowserWindow(doPreTest,
-                      url,
-                      { left: 0, top: 0, width: 600, height: 600 });
-  </script>
-
-</head>
-<body>
-
-  <a target="_blank"
-     href="https://bugzilla.mozilla.org/show_bug.cgi?id=650241"
-     title="Location returned by accessibles incorrect when page zoomed">
-    Mozilla Bug 650241
-  </a>
-  <p id="display"></p>
-  <div id="content" style="display: none"></div>
-  <pre id="test">
-  </pre>
-</body>
-</html>
deleted file mode 100644
--- a/accessible/tests/mochitest/bounds/test_zoom_text.html
+++ /dev/null
@@ -1,77 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <title>The text range boundary when page is zoomed</title>
-  <meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
-  <link rel="stylesheet" type="text/css"
-        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
-
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
-
-  <script type="application/javascript"
-          src="../common.js"></script>
-  <script type="application/javascript"
-          src="../role.js"></script>
-  <script type="application/javascript"
-          src="../layout.js"></script>
-  <script type="application/javascript"
-          src="../browser.js"></script>
-
-  <script type="application/javascript">
-    function testTextNode(aDoc, aContainerID)
-    {
-      var hyperTextNode = aDoc.getElementById(aContainerID);
-      var textNode = hyperTextNode.firstChild;
-
-      var [x, y, width, height] = getBounds(textNode);
-      testTextBounds(hyperTextNode, 0, -1, [x, y, width, height],
-                     COORDTYPE_SCREEN_RELATIVE);
-    }
-
-    function doTest()
-    {
-      var tabDocument = currentTabDocument();
-      testTextNode(tabDocument, "p1");
-      testTextNode(tabDocument, "p2");
-
-      zoomDocument(tabDocument, 2.0);
-
-      testTextNode(tabDocument, "p1");
-
-      zoomDocument(tabDocument, 1.0);
-
-      closeBrowserWindow();
-      SimpleTest.finish();
-    }
-
-    var url = "data:text/html,<html>" +
-      "<meta http-equiv='Content-Type' content='text/html;charset=utf-8'>" +
-      "</meta><body>" +
-      "<p id='p1' style='font-family: monospace;'>Tilimilitryamdiya</p>" +
-      "<p id='p2'>ل</p>"
-      "</body></html>";
-
-    SimpleTest.waitForExplicitFinish();
-    openBrowserWindow(doTest,
-                      url,
-                      { left: 0, top: 0, width: 600, height: 600 });
-
-  </script>
-
-</head>
-<body>
-
-  <a target="_blank"
-     href="https://bugzilla.mozilla.org/show_bug.cgi?id=727942"
-     title="Text range boundaries are incorrect when page is zoomed">
-    Mozilla Bug 727942
-  </a>
-  <p id="display"></p>
-  <div id="content" style="display: none"></div>
-  <pre id="test">
-  </pre>
-</body>
-</html>
--- a/accessible/tests/mochitest/events/a11y.ini
+++ b/accessible/tests/mochitest/events/a11y.ini
@@ -15,30 +15,26 @@ support-files =
 [test_bug1322593.html]
 [test_bug1322593-2.html]
 [test_caretmove.html]
 [test_caretmove.xul]
 [test_coalescence.html]
 [test_contextmenu.html]
 [test_descrchange.html]
 [test_docload.html]
-[test_docload.xul]
-skip-if = buildapp == 'mulet'
 [test_docload_aria.html]
 [test_dragndrop.html]
 [test_flush.html]
 [test_focus_aria_activedescendant.html]
 [test_focus_autocomplete.xul]
 # Disabled on Linux and Windows due to frequent failures - bug 695019, bug 890795
 skip-if = os == 'win' || os == 'linux'
-[test_focus_browserui.xul]
 [test_focus_canvas.html]
 [test_focus_contextmenu.xul]
 [test_focus_controls.html]
-[test_focus_dialog.html]
 [test_focus_doc.html]
 [test_focus_general.html]
 [test_focus_general.xul]
 [test_focus_listcontrols.xul]
 [test_focus_menu.xul]
 [test_focus_name.html]
 [test_focus_selects.html]
 [test_focus_tabbox.xul]
deleted file mode 100644
--- a/accessible/tests/mochitest/events/test_docload.xul
+++ /dev/null
@@ -1,243 +0,0 @@
-<?xml version="1.0"?>
-<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
-<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
-                 type="text/css"?>
-
-<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        title="Accessibility Loading Document Events Test.">
-
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
-
-  <script type="application/javascript"
-          src="../common.js"></script>
-  <script type="application/javascript"
-          src="../role.js"></script>
-  <script type="application/javascript"
-          src="../states.js"></script>
-  <script type="application/javascript"
-          src="../events.js"></script>
-  <script type="application/javascript"
-          src="../browser.js"></script>
-
-  <script type="application/javascript">
-  <![CDATA[
-    ////////////////////////////////////////////////////////////////////////////
-    // Invoker checkers.
-    function stateBusyChecker(aIsEnabled)
-    {
-      this.type = EVENT_STATE_CHANGE;
-      this.__defineGetter__("target", currentTabDocument);
-
-      this.check = function stateBusyChecker_check(aEvent)
-      {
-        var event = null;
-        try {
-          var event = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
-        } catch (e) {
-          ok(false, "State change event was expected");
-        }
-
-        if (!event)
-          return;
-
-        is(event.state, STATE_BUSY, "Wrong state of statechange event.");
-        is(event.isEnabled, aIsEnabled,
-           "Wrong value of state of statechange event");
-
-        testStates(event.accessible, (aIsEnabled ? STATE_BUSY : 0), 0,
-                   (aIsEnabled ? 0 : STATE_BUSY), 0);
-      }
-    }
-
-    function documentReloadChecker(aIsFromUserInput)
-    {
-      this.type = EVENT_DOCUMENT_RELOAD;
-      this.__defineGetter__("target", currentTabDocument);
-
-      this.check = function documentReloadChecker_check(aEvent)
-      {
-        is(aEvent.isFromUserInput, aIsFromUserInput,
-           "Wrong value of isFromUserInput");
-      }
-    }
-
-    ////////////////////////////////////////////////////////////////////////////
-    // Invokers.
-
-    /**
-     * Load URI.
-     */
-    function loadURIInvoker(aURI)
-    {
-      this.invoke = function loadURIInvoker_invoke()
-      {
-        tabBrowser().loadURI(aURI);
-      }
-
-      this.eventSeq = [
-        // We don't expect state change event for busy true since things happen
-        // quickly and it's coalesced.
-        new asyncInvokerChecker(EVENT_REORDER, currentBrowser),
-        new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument),
-        new stateBusyChecker(false)
-      ];
-
-      this.getID = function loadURIInvoker_getID()
-      {
-        return "load uri " + aURI;
-      }
-    }
-
-    /**
-     * Load the document having sub document. No document loading events for
-     * nested document.
-     */
-    function loadNestedDocURIInvoker(aNestedDocURI)
-    {
-      this.__proto__ = new loadURIInvoker(aNestedDocURI);
-
-      // Remove reorder event checker since the event is likely coalesced by
-      // reorder event on Firefox UI (refer to bug 759670 for details).
-      this.eventSeq.shift();
-
-      this.unexpectedEventSeq = [
-        new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, getNestedDoc),
-        new invokerChecker(EVENT_STATE_CHANGE, getNestedDoc)
-      ];
-
-      function getNestedDoc()
-      {
-        var iframeNodes = currentTabDocument().getElementsByTagName("iframe");
-        return iframeNodes && iframeNodes.length > 0 ?
-          iframeNodes[0].firstChild : null;
-      }
-    }
-
-    /**
-     * Reload the page by F5 (isFromUserInput flag is true).
-     */
-    function userReloadInvoker()
-    {
-      this.invoke = function userReloadInvoker_invoke()
-      {
-        synthesizeKey("VK_F5", {}, browserWindow());
-      }
-
-      this.eventSeq = [
-        new documentReloadChecker(true),
-        new asyncInvokerChecker(EVENT_REORDER, currentBrowser),
-        new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument),
-        new stateBusyChecker(false)
-      ];
-
-      this.getID = function userReloadInvoker_getID()
-      {
-        return "user reload page";
-      }
-    }
-
-    /**
-     * Reload the page (isFromUserInput flag is false).
-     */
-    function reloadInvoker()
-    {
-      this.invoke = function reloadInvoker_invoke()
-      {
-        tabBrowser().reload();
-      }
-
-      this.eventSeq = [
-        new documentReloadChecker(false),
-        new asyncInvokerChecker(EVENT_REORDER, currentBrowser),
-        new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument),
-        new stateBusyChecker(false)
-      ];
-
-      this.getID = function reloadInvoker_getID()
-      {
-        return "reload page";
-      }
-    }
-
-    /**
-     * Load wrong URI what results in error page loading.
-     */
-    function loadErrorPageInvoker(aURL, aURLDescr)
-    {
-      this.invoke = function loadErrorPageInvoker_invoke()
-      {
-        tabBrowser().loadURI(aURL);
-      }
-
-      this.eventSeq = [
-        // We don't expect state change for busy true, load stopped events since
-        // things happen quickly and it's coalesced.
-        new asyncInvokerChecker(EVENT_REORDER, currentBrowser),
-        new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument),
-        new stateBusyChecker(false)
-      ];
-
-      this.getID = function loadErrorPageInvoker_getID()
-      {
-        return "load error page: '" + aURLDescr + "'";
-      }
-    }
-
-    ////////////////////////////////////////////////////////////////////////////
-    // Tests
-
-    //gA11yEventDumpToConsole = true; // debug
-    //gA11yEventDumpFeature = "parentchain:reorder";
-
-    var gQueue = null;
-    function doTests()
-    {
-      gQueue = new eventQueue();
-
-      var dataURL =
-        "data:text/html,<html><body><iframe src='http://example.com'></iframe></body></html>";
-      gQueue.push(new loadNestedDocURIInvoker(dataURL));
-
-      gQueue.push(new loadURIInvoker("about:"));
-      gQueue.push(new userReloadInvoker());
-      gQueue.push(new loadURIInvoker("about:mozilla"));
-      gQueue.push(new reloadInvoker());
-      gQueue.push(new loadErrorPageInvoker("www.wronguri.wronguri",
-                                           "Server not found"));
-      gQueue.push(new loadErrorPageInvoker("https://nocert.example.com:443",
-                                          "Untrusted Connection"));
-
-      gQueue.onFinish = function() { closeBrowserWindow(); }
-      gQueue.invoke();
-    }
-
-    SimpleTest.waitForExplicitFinish();
-    openBrowserWindow(doTests);
-  ]]>
-  </script>
-
-  <vbox flex="1" style="overflow: auto;">
-  <body xmlns="http://www.w3.org/1999/xhtml">
-    <a target="_blank"
-       href="https://bugzilla.mozilla.org/show_bug.cgi?id=566103"
-       title=" reorganize accessible document handling">
-      Mozilla Bug 566103
-    </a>
-    <a target="_blank"
-       href="https://bugzilla.mozilla.org/show_bug.cgi?id=754165"
-       title="Fire document load events on iframes too">
-      Mozilla Bug 754165
-    </a>
-    <p id="display"></p>
-    <div id="content" style="display: none">
-    </div>
-    <pre id="test">
-    </pre>
-  </body>
-
-  <vbox id="eventdump"></vbox>
-  </vbox>
-</window>
deleted file mode 100644
--- a/accessible/tests/mochitest/events/test_focus_browserui.xul
+++ /dev/null
@@ -1,149 +0,0 @@
-<?xml version="1.0"?>
-<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
-<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
-                 type="text/css"?>
-
-<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        title="Accessibility Loading Document Events Test.">
-
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
-
-  <script type="application/javascript"
-          src="../common.js"></script>
-  <script type="application/javascript"
-          src="../role.js"></script>
-  <script type="application/javascript"
-          src="../states.js"></script>
-  <script type="application/javascript"
-          src="../events.js"></script>
-  <script type="application/javascript"
-          src="../browser.js"></script>
-
-  <script type="application/javascript">
-  <![CDATA[
-    ////////////////////////////////////////////////////////////////////////////
-    // Helpers
-
-    function inputInDocument()
-    {
-      var tabdoc = currentTabDocument();
-      return tabdoc.getElementById("input");
-    }
-
-    ////////////////////////////////////////////////////////////////////////////
-    // Invokers
-
-    function loadURI(aURI)
-    {
-      this.invoke = function loadURI_invoke()
-      {
-        tabBrowser().loadURI(aURI);
-      }
-
-      this.eventSeq = [
-        new focusChecker(currentTabDocument)
-      ];
-
-      this.getID = function loadURI_getID()
-      {
-        return "load uri " + aURI;
-      }
-    }
-
-    function goBack()
-    {
-      this.invoke = function goBack_invoke()
-      {
-        tabBrowser().goBack();
-      }
-
-      this.eventSeq = [
-        new focusChecker(inputInDocument)
-      ];
-
-      this.getID = function goBack_getID()
-      {
-        return "go back one page in history ";
-      }
-    }
-
-    ////////////////////////////////////////////////////////////////////////////
-    // Testing
-
-    var gInputDocURI = "data:text/html,<html><input id='input'></html>";
-    var gButtonDocURI = "data:text/html,<html><input id='input' type='button' value='button'></html>";
-
-    //gA11yEventDumpToConsole = true; // debug
-
-    var gQueue = null;
-    function doTests()
-    {
-      gQueue = new eventQueue();
-
-      var tabDocument = currentTabDocument();
-      var input = inputInDocument();
-
-      // move focus to input inside tab document
-      gQueue.push(new synthTab(tabDocument, new focusChecker(input),
-                               browserWindow()));
-
-      // open new url, focus moves to new document
-      gQueue.push(new loadURI(gButtonDocURI));
-
-      // back one page in history, moves moves on input of tab document
-      gQueue.push(new goBack());
-
-      // open new tab, focus moves to urlbar
-      gQueue.push(new synthKey(tabDocument, "t", { accelKey: true, window: browserWindow() },
-                               new focusChecker(urlbarInput)));
-
-      // close open tab, focus goes on input of tab document
-      gQueue.push(new synthKey(tabDocument, "w", { accelKey: true, window: browserWindow() },
-                               new focusChecker(inputInDocument)));
-
-      gQueue.onFinish = function()
-      {
-        closeBrowserWindow();
-      }
-      gQueue.invoke();
-    }
-
-    if (navigator.oscpu.startsWith("Windows NT 6.1") || navigator.oscpu.startsWith("Windows NT 6.2")) {
-      todo(false, "fix the leak!");
-      } else {
-      SimpleTest.waitForExplicitFinish();
-      openBrowserWindow(doTests, gInputDocURI);
-    }
-  ]]>
-  </script>
-
-  <vbox flex="1" style="overflow: auto;">
-  <body xmlns="http://www.w3.org/1999/xhtml">
-    <a target="_blank"
-       href="https://bugzilla.mozilla.org/show_bug.cgi?id=644452"
-       title="Focus not set when switching to cached document with back or forward if anything other than the document was last focused">
-      Mozilla Bug 644452
-    </a>
-    <a target="_blank"
-       href="https://bugzilla.mozilla.org/show_bug.cgi?id=665412"
-       title="Broken focus when returning to editable text field after closing a tab while focused in the Navigation toolbar">
-      Mozilla Bug 665412
-    </a>
-    <a target="_blank"
-       href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958"
-       title="Rework accessible focus handling">
-      Mozilla Bug 673958
-    </a>
-    <p id="display"></p>
-    <div id="content" style="display: none">
-    </div>
-    <pre id="test">
-    </pre>
-  </body>
-
-  <vbox id="eventdump"></vbox>
-  </vbox>
-</window>
deleted file mode 100644
--- a/accessible/tests/mochitest/events/test_focus_dialog.html
+++ /dev/null
@@ -1,164 +0,0 @@
-<html>
-
-<head>
-  <title>Accessible focus testing</title>
-
-  <link rel="stylesheet" type="text/css"
-        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
-
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
-
-  <script type="application/javascript"
-          src="../common.js"></script>
-  <script type="application/javascript"
-          src="../events.js"></script>
-  <script type="application/javascript"
-          src="../role.js"></script>
-  <script type="application/javascript"
-          src="../states.js"></script>
-
-  <script type="application/javascript">
-    function openCloseDialog(aID)
-    {
-      this.eventSeq = [
-        new focusChecker(getNode(aID))
-      ];
-
-      this.invoke = function openCloseDialog_invoke()
-      {
-        var wnd = window.open("focus.html");
-        wnd.close();
-      }
-
-      this.getID = function openCloseDialog_getID()
-      {
-        return "Open close dialog while focus on " + prettyName(aID);
-      }
-    }
-
-    var gDialogWnd = null;
-    function getDialogDocument()
-    {
-      return gDialogWnd.document;
-    }
-
-    function openDialog(aID)
-    {
-      this.eventSeq = [
-        new focusChecker(getDialogDocument)
-      ];
-
-      this.invoke = function openDialog_invoke()
-      {
-        gDialogWnd = window.open("focus.html");
-      }
-
-      this.getID = function openDialog_getID()
-      {
-        return "Open dialog while focus on " + prettyName(aID);
-      }
-    }
-
-    function closeDialog(aID)
-    {
-      this.eventSeq = [
-        new focusChecker(aID)
-      ];
-
-      this.invoke = function closeDialog_invoke()
-      {
-        gDialogWnd.close();
-      }
-
-      this.getID = function closeDialog_getID()
-      {
-        return "Close dialog while focus on " + prettyName(aID);
-      }
-    }
-
-    function showNFocusAlertDialog()
-    {
-      this.ID = "alertdialog";
-      this.DOMNode = getNode(this.ID);
-
-      this.invoke = function showNFocusAlertDialog_invoke()
-      {
-        document.getElementById(this.ID).style.display = 'block';
-        document.getElementById(this.ID).focus();
-      }
-
-      this.eventSeq = [
-        new invokerChecker(EVENT_SHOW, this.DOMNode),
-        new focusChecker(this.DOMNode)
-      ];
-
-      this.getID = function showNFocusAlertDialog_getID()
-      {
-        return "Show and focus alert dialog " + prettyName(this.ID);
-      }
-    }
-
-    /**
-     * Do tests.
-     */
-
-    //gA11yEventDumpID = "eventdump"; // debug stuff
-    //gA11yEventDumpToConsole = true;
-
-    var gQueue = null;
-
-    function doTests()
-    {
-      gQueue = new eventQueue(EVENT_FOCUS);
-
-      gQueue.push(new synthFocus("button"));
-      gQueue.push(new openDialog("button"));
-      gQueue.push(new closeDialog("button"));
-
-      var frameNode = getNode("editabledoc");
-      gQueue.push(new synthFocusOnFrame(frameNode));
-      gQueue.push(new openCloseDialog(frameNode.contentDocument));
-
-      gQueue.push(new showNFocusAlertDialog());
-
-      gQueue.invoke(); // Will call SimpleTest.finish();
-    }
-
-    SimpleTest.waitForExplicitFinish();
-    addA11yLoadEvent(doTests);
-  </script>
-</head>
-
-<body>
-
-  <a target="_blank"
-     href="https://bugzilla.mozilla.org/show_bug.cgi?id=551679"
-     title="focus is not fired for focused document when switching between windows">
-    Mozilla Bug 551679
-  </a>
-  <a target="_blank"
-     href="https://bugzilla.mozilla.org/show_bug.cgi?id=580464"
-     title="Accessible focus incorrect after JS focus() but correct after switching apps or using menu bar">
-    Mozilla Bug 580464
-  </a>
-  <p id="display"></p>
-  <div id="content" style="display: none"></div>
-  <pre id="test">
-  </pre>
-
-  <button id="button">button</button>
-  <iframe id="editabledoc" src="focus.html"></iframe>
-
-  <div id="alertdialog" style="display: none" tabindex="-1" role="alertdialog" aria-labelledby="title2" aria-describedby="desc2">
-    <div id="title2">Blah blah</div>
-    <div id="desc2">Woof woof woof.</div>
-    <button>Close</button>
-  </div>
-
-
-  <div id="eventdump"></div>
-</body>
-</html>
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1699,16 +1699,18 @@ pref("browser.suppress_first_window_anim
 pref("browser.onboarding.enabled", true);
 // Mark this as an upgraded profile so we don't offer the initial new user onboarding tour.
 pref("browser.onboarding.tourset-version", 1);
 pref("browser.onboarding.hidden", false);
 // On the Activity-Stream page, the snippet's position overlaps with our notification.
 // So use `browser.onboarding.notification.finished` to let the AS page know
 // if our notification is finished and safe to show their snippet.
 pref("browser.onboarding.notification.finished", false);
+pref("browser.onboarding.newtour", "private,addons,customize,search,default,sync");
+pref("browser.onboarding.updatetour", "");
 
 // Preferences for the Screenshots feature:
 // Temporarily disable Screenshots in Beta & Release, so that we can gradually
 // roll out the feature using SHIELD pref flipping.
 #ifdef NIGHTLY_BUILD
 pref("extensions.screenshots.system-disabled", false);
 #else
 pref("extensions.screenshots.system-disabled", true);
--- a/browser/base/content/test/general/browser_aboutHome.js
+++ b/browser/base/content/test/general/browser_aboutHome.js
@@ -19,16 +19,24 @@ registerCleanupFunction(function() {
   // Ensure we don't pollute prefs for next tests.
   Services.prefs.clearUserPref("network.cookies.cookieBehavior");
   Services.prefs.clearUserPref("network.cookie.lifetimePolicy");
   Services.prefs.clearUserPref("browser.rights.override");
   Services.prefs.clearUserPref("browser.rights." + gRightsVersion + ".shown");
 });
 
 add_task(async function() {
+  // The onboarding tour's notification would affect tests
+  // and it isn't out test target so make sure disabling it
+  // before any tests start.
+  await promiseDisableOnboardingTours();
+  is(false, Services.prefs.getBoolPref("browser.onboarding.enabled"), "Should disable the onboarding tours");
+});
+
+add_task(async function() {
   info("Check that clearing cookies does not clear storage");
 
   await withSnippetsMap(
     () => {
       Cc["@mozilla.org/observer-service;1"]
         .getService(Ci.nsIObserverService)
         .notifyObservers(null, "cookie-changed", "cleared");
     },
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -824,8 +824,12 @@ function getCertExceptionDialog(aLocatio
 
       if (childDoc.location.href == aLocation) {
         return childDoc;
       }
     }
   }
   return undefined;
 }
+
+function promiseDisableOnboardingTours() {
+  return SpecialPowers.pushPrefEnv({set: [["browser.onboarding.enabled", false]]});
+}
--- a/browser/base/content/test/newtab/browser_newtab_focus.js
+++ b/browser/base/content/test/newtab/browser_newtab_focus.js
@@ -59,17 +59,17 @@ function countFocus(aExpectedCount) {
 /**
  * Wait for the onboarding tour notification opens
  */
 function promiseTourNotificationOpened(browser) {
   let condition = () => {
     return ContentTask.spawn(browser, {}, function() {
       return new Promise(resolve => {
         let bar = content.document.querySelector("#onboarding-notification-bar");
-        if (bar && bar.classList.contains("onboarding-opened") && bar.dataset.cssTransition == "end") {
+        if (bar && bar.classList.contains("onboarding-opened")) {
           resolve(true);
           return;
         }
         resolve(false);
       });
     })
   };
   return BrowserTestUtils.waitForCondition(
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -125,18 +125,16 @@ var whitelist = [
   {file: "chrome://browser/locale/taskbar.properties",
    platforms: ["linux", "macosx"]},
   // Bug 1316187
   {file: "chrome://global/content/customizeToolbar.xul"},
   // Bug 1343837
   {file: "chrome://global/content/findUtils.js"},
   // Bug 1343843
   {file: "chrome://global/content/url-classifier/unittests.xul"},
-  // Bug 1343839
-  {file: "chrome://global/locale/headsUpDisplay.properties"},
   // Bug 1348362
   {file: "chrome://global/skin/icons/warning-64.png", platforms: ["linux", "win"]},
   // Bug 1348525
   {file: "chrome://global/skin/splitter/grip-bottom.gif", platforms: ["linux"]},
   {file: "chrome://global/skin/splitter/grip-left.gif", platforms: ["linux"]},
   {file: "chrome://global/skin/splitter/grip-right.gif", platforms: ["linux"]},
   {file: "chrome://global/skin/splitter/grip-top.gif", platforms: ["linux"]},
   // Bug 1348526
--- a/browser/components/customizableui/PanelMultiView.jsm
+++ b/browser/components/customizableui/PanelMultiView.jsm
@@ -4,16 +4,18 @@
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["PanelMultiView"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+  "resource://gre/modules/AppConstants.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableWidgets",
   "resource:///modules/CustomizableWidgets.jsm");
 
 /**
  * Simple implementation of the sliding window pattern; panels are added to a
  * linked list, in-order, and the currently shown panel is remembered using a
  * marker. The marker shifts as navigation between panels is continued, where
  * the panel at index 0 is always the starting point:
@@ -875,21 +877,34 @@ this.PanelMultiView = class {
 
         // When using block-in-box layout inside a scrollable frame, like in the
         // main menu contents scroller, if we allow the contents to scroll then
         // it will not cause its container to expand. Thus, we layout first
         // without any scrolling (using "display: flex;"), and only if the view
         // exceeds the available space we set the height explicitly and enable
         // scrolling.
         if (this._mainView.hasAttribute("blockinboxworkaround")) {
-          let mainViewHeight =
-              this._dwu.getBoundsWithoutFlushing(this._mainView).height;
-          if (mainViewHeight > maxHeight) {
-            this._mainView.style.height = maxHeight + "px";
-            this._mainView.setAttribute("exceeding", "true");
+          let blockInBoxWorkaround = () => {
+            let mainViewHeight =
+                this._dwu.getBoundsWithoutFlushing(this._mainView).height;
+            if (mainViewHeight > maxHeight) {
+              this._mainView.style.height = maxHeight + "px";
+              this._mainView.setAttribute("exceeding", "true");
+            }
+          };
+          // On Windows, we cannot measure the full height of the main view
+          // until it is visible. Unfortunately, this causes a visible jump when
+          // the view needs to scroll, but there is no easy way around this.
+          if (AppConstants.platform == "win") {
+            // We register a "once" listener so we don't need to store the value
+            // of maxHeight elsewhere on the object.
+            this._panel.addEventListener("popupshown", blockInBoxWorkaround,
+                                         { once: true });
+          } else {
+            blockInBoxWorkaround();
           }
         }
         break;
       case "popupshown":
         // Now that the main view is visible, we can check the height of the
         // description elements it contains.
         this.descriptionHeightWorkaround();
         break;
--- a/browser/components/sessionstore/SessionCookies.jsm
+++ b/browser/components/sessionstore/SessionCookies.jsm
@@ -88,31 +88,31 @@ var SessionCookiesInternal = {
         break;
       case "cleared":
         CookieStore.clear();
         break;
       case "batch-deleted":
         this._removeCookies(subject);
         break;
       default:
-        throw new Error("Unhandled cookie-changed notification.");
+        throw new Error("Unhandled session-cookie-changed notification.");
     }
   },
 
   /**
    * If called for the first time in a session, iterates all cookies in the
    * cookies service and puts them into the store if they're session cookies.
    */
   _ensureInitialized() {
     if (this._initialized) {
       return;
     }
     this._reloadCookies();
     this._initialized = true;
-    Services.obs.addObserver(this, "cookie-changed");
+    Services.obs.addObserver(this, "session-cookie-changed");
 
     // Listen for privacy level changes to reload cookies when needed.
     Services.prefs.addObserver("browser.sessionstore.privacy_level", () => {
       this._reloadCookies();
     });
   },
 
   /**
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -3893,16 +3893,18 @@ var SessionStoreInternal = {
    * @param aSidebar
    *        Sidebar command
    */
   restoreDimensions: function ssi_restoreDimensions(aWindow, aWidth, aHeight, aLeft, aTop, aSizeMode, aSidebar) {
     var win = aWindow;
     var _this = this;
     function win_(aName) { return _this._getWindowDimension(win, aName); }
 
+    const dwu = win.QueryInterface(Ci.nsIInterfaceRequestor)
+                   .getInterface(Ci.nsIDOMWindowUtils);
     // find available space on the screen where this window is being placed
     let screen = gScreenManager.screenForRect(aLeft, aTop, aWidth, aHeight);
     if (screen) {
       let screenLeft = {}, screenTop = {}, screenWidth = {}, screenHeight = {};
       screen.GetAvailRectDisplayPix(screenLeft, screenTop, screenWidth, screenHeight);
       // screenX/Y are based on the origin of the screen's desktop-pixel coordinate space
       let screenLeftCss = screenLeft.value;
       let screenTopCss = screenTop.value;
@@ -3940,48 +3942,57 @@ var SessionStoreInternal = {
         bottom = screenBottomCss;
         if (aTop > screenTopCss) {
           aTop = Math.max(bottom - aHeight, screenTopCss);
         }
       }
       aHeight = bottom - aTop;
     }
 
-    // only modify those aspects which aren't correct yet
-    if (!isNaN(aLeft) && !isNaN(aTop) && (aLeft != win_("screenX") || aTop != win_("screenY"))) {
-      aWindow.moveTo(aLeft, aTop);
-    }
-    if (aWidth && aHeight && (aWidth != win_("width") || aHeight != win_("height")) && !gResistFingerprintingEnabled) {
-      // Don't resize the window if it's currently maximized and we would
-      // maximize it again shortly after.
-      if (aSizeMode != "maximized" || win_("sizemode") != "maximized") {
-        aWindow.resizeTo(aWidth, aHeight);
+    // Suppress animations.
+    dwu.suppressAnimation(true);
+
+    // We want to make sure users will get their animations back in case an exception is thrown.
+    try {
+      // only modify those aspects which aren't correct yet
+      if (!isNaN(aLeft) && !isNaN(aTop) && (aLeft != win_("screenX") || aTop != win_("screenY"))) {
+        aWindow.moveTo(aLeft, aTop);
+      }
+      if (aWidth && aHeight && (aWidth != win_("width") || aHeight != win_("height")) && !gResistFingerprintingEnabled) {
+        // Don't resize the window if it's currently maximized and we would
+        // maximize it again shortly after.
+        if (aSizeMode != "maximized" || win_("sizemode") != "maximized") {
+          aWindow.resizeTo(aWidth, aHeight);
+        }
       }
-    }
-    if (aSizeMode && win_("sizemode") != aSizeMode && !gResistFingerprintingEnabled) {
-      switch (aSizeMode) {
-      case "maximized":
-        aWindow.maximize();
-        break;
-      case "minimized":
-        aWindow.minimize();
-        break;
-      case "normal":
-        aWindow.restore();
-        break;
+      if (aSizeMode && win_("sizemode") != aSizeMode && !gResistFingerprintingEnabled) {
+        switch (aSizeMode) {
+        case "maximized":
+          aWindow.maximize();
+          break;
+        case "minimized":
+          aWindow.minimize();
+          break;
+        case "normal":
+          aWindow.restore();
+          break;
+        }
       }
-    }
-    var sidebar = aWindow.document.getElementById("sidebar-box");
-    if (sidebar.getAttribute("sidebarcommand") != aSidebar) {
-      aWindow.SidebarUI.show(aSidebar);
-    }
-    // since resizing/moving a window brings it to the foreground,
-    // we might want to re-focus the last focused window
-    if (this.windowToFocus) {
-      this.windowToFocus.focus();
+      var sidebar = aWindow.document.getElementById("sidebar-box");
+      if (sidebar.getAttribute("sidebarcommand") != aSidebar) {
+        aWindow.SidebarUI.show(aSidebar);
+      }
+      // since resizing/moving a window brings it to the foreground,
+      // we might want to re-focus the last focused window
+      if (this.windowToFocus) {
+        this.windowToFocus.focus();
+      }
+    } finally {
+      // Enable animations.
+      dwu.suppressAnimation(false);
     }
   },
 
   /* ........ Disk Access .............. */
 
   /**
    * Save the current session state to disk, after a delay.
    *
--- a/browser/components/sessionstore/test/browser_cookies.js
+++ b/browser/components/sessionstore/test/browser_cookies.js
@@ -9,17 +9,17 @@ function promiseSetCookie(cookie) {
   ]);
 }
 
 function waitForCookieChanged() {
   return new Promise(resolve => {
     Services.obs.addObserver(function observer(subj, topic, data) {
       Services.obs.removeObserver(observer, topic);
       resolve();
-    }, "cookie-changed");
+    }, "session-cookie-changed");
   });
 }
 
 function cookieExists(host, name, value) {
   let {cookies: [c]} = JSON.parse(ss.getBrowserState());
   return c && c.host == host && c.name == name && c.value == value;
 }
 
--- a/browser/components/sessionstore/test/browser_cookies_legacy.js
+++ b/browser/components/sessionstore/test/browser_cookies_legacy.js
@@ -30,17 +30,17 @@ function waitForNewCookie({host, name, v
       }
 
       let cookie = subj.QueryInterface(Ci.nsICookie2);
       if (cookie.host == host && cookie.name == name && cookie.value == value) {
         ok(true, "cookie added by the cookie service");
         Services.obs.removeObserver(observer, topic);
         resolve();
       }
-    }, "cookie-changed");
+    }, "session-cookie-changed");
   });
 }
 
 // Setup and cleanup.
 add_task(async function test_setup() {
   Services.cookies.removeAll();
 
   registerCleanupFunction(() => {
--- a/browser/components/sessionstore/test/browser_sessionStoreContainer.js
+++ b/browser/components/sessionstore/test/browser_sessionStoreContainer.js
@@ -86,17 +86,17 @@ async function openTabInUserContext(user
 
 function waitForNewCookie() {
   return new Promise(resolve => {
     Services.obs.addObserver(function observer(subj, topic, data) {
       if (data == "added") {
         Services.obs.removeObserver(observer, topic);
         resolve();
       }
-    }, "cookie-changed");
+    }, "session-cookie-changed");
   });
 }
 
 add_task(async function test() {
   const USER_CONTEXTS = [
     "default",
     "personal",
     "work",
new file mode 100644
--- /dev/null
+++ b/browser/config/mozconfigs/linux32/opt-dmd
@@ -0,0 +1,3 @@
+ac_add_options --enable-dmd
+
+. "$topsrcdir/browser/config/mozconfigs/linux32/nightly"
new file mode 100644
--- /dev/null
+++ b/browser/config/mozconfigs/linux64/opt-dmd
@@ -0,0 +1,3 @@
+ac_add_options --enable-dmd
+
+. "$topsrcdir/browser/config/mozconfigs/linux64/nightly"
new file mode 100644
--- /dev/null
+++ b/browser/config/mozconfigs/macosx64/opt-dmd
@@ -0,0 +1,3 @@
+ac_add_options --enable-dmd
+
+. "$topsrcdir/browser/config/mozconfigs/macosx64/nightly"
new file mode 100644
--- /dev/null
+++ b/browser/config/mozconfigs/win32/opt-dmd
@@ -0,0 +1,3 @@
+ac_add_options --enable-dmd
+
+. "$topsrcdir/browser/config/mozconfigs/win32/nightly"
new file mode 100644
--- /dev/null
+++ b/browser/config/mozconfigs/win64/opt-dmd
@@ -0,0 +1,3 @@
+ac_add_options --enable-dmd
+
+. "$topsrcdir/browser/config/mozconfigs/win64/nightly"
--- a/browser/extensions/onboarding/README.md
+++ b/browser/extensions/onboarding/README.md
@@ -9,16 +9,20 @@ Everytime `about:home` or `about:newtab`
 ## Landing rules
 
 We would apply some rules:
 
 * Avoid `chrome://` in `onbaording.js` since onboarding is intented to be injected into a normal content process page.
 * All styles and ids should be formated as `onboarding-*` to avoid conflict with the origin page.
 * All strings in `locales` should be formated as `onboarding.*` for consistency.
 
+## How to change the order of tours
+
+Edit `browser/app/profile/firefox.js` and modify `browser.onboarding.newtour` for the new user tour or `browser.onboarding.updatetour` for the update user tour. You can change the tour list and the order by concate `tourIds` with `,` sign. You can find available `tourId` from `onboardingTourset` in `onboarding.js`.
+
 ## How to pump tour set version after update tours
 
 The tourset version is used to track the last major tourset change version. The `tourset-version` pref store the major tourset version (ex: `1`) but not the current browser version. When browser update to the next version (ex: 58, 59) the tourset pref is still `1` if we didn't do any major tourset update.
 
 Once the tour set version is updated (ex: `2`), onboarding overlay should show the update tour to the updated user (ex: update from v56 -> v57), even when user have watched the previous tours or preferred to hide the previous tours.
 
 Edit `browser/app/profile/firefox.js` and set `browser.onboarding.tourset-version` as `[tourset version]` (in integer format).
 
--- a/browser/extensions/onboarding/content/onboarding.css
+++ b/browser/extensions/onboarding/content/onboarding.css
@@ -348,16 +348,17 @@
   min-width: 640px;
   background: rgba(255, 255, 255, 0.97);
   border-top: 2px solid #e9e9e9;
   transition: transform 0.8s;
   transform: translateY(122px);
 }
 
 #onboarding-notification-bar.onboarding-opened {
+  transition: none;
   transform: translateY(0px);
 }
 
 #onboarding-notification-icon {
   height: 36px;
   background: url("img/overlay-icon.svg") no-repeat;
   background-size: 36px;
   background-position: 34px;
@@ -368,28 +369,29 @@
 }
 
 #onboarding-notification-icon:dir(rtl) {
   background-position: right 34px center;
 }
 
 #onboarding-notification-icon::after {
   --height: 22px;
+  --vpadding: 3px;
   content: attr(data-tooltip);
   background: #5ce6e6;
   position: absolute;
   top: 0;
   offset-inline-start: 68px;
   color: #10404a;
   font-size: 12px;
-  min-height: var(--height);
-  line-height: var(--height);
+  line-height: calc(var(--height) - var(--vpadding) * 2);
+  max-width: 90px;
   border-radius: calc(var(--height) / 2);
   border: 1px solid #fff;
-  padding: 0 10px;
+  padding: var(--vpadding) 10px;
   text-align: center;
 }
 
 #onboarding-notification-close-btn {
   background-color: rgba(255, 255, 255, 0.97);
   border: none;
   position: absolute;
   offset-block-start: 50%;
@@ -422,17 +424,17 @@
   font-size: 13px
 }
 
 #onboarding-notification-tour-title {
   margin: 0;
 }
 
 #onboarding-notification-tour-icon {
-  width: 64px;
+  min-width: 64px;
   height: 64px;
   background-size: 64px;
   background-repeat: no-repeat;
 }
 
 #onboarding-notification-action-btn {
   background: #0d96ff;
   border: none;
--- a/browser/extensions/onboarding/content/onboarding.js
+++ b/browser/extensions/onboarding/content/onboarding.js
@@ -17,17 +17,17 @@ const BUNDLE_URI = "chrome://onboarding/
 const UITOUR_JS_URI = "resource://onboarding/lib/UITour-lib.js";
 const TOUR_AGENT_JS_URI = "resource://onboarding/onboarding-tour-agent.js";
 const BRAND_SHORT_NAME = Services.strings
                      .createBundle("chrome://branding/locale/brand.properties")
                      .GetStringFromName("brandShortName");
 
 /**
  * Add any number of tours, following the format
- * {
+ * "tourId": { // The short tour id which could be saved in pref
  *   // The unique tour id
  *   id: "onboarding-tour-addons",
  *   // The string id of tour name which would be displayed on the navigation bar
  *   tourNameId: "onboarding.tour-addon",
  *   // The method returing strings used on tour notification
  *   getNotificationStrings(bundle):
  *     - title: // The string of tour notification title
  *     - message: // The string of tour notification message
@@ -35,18 +35,18 @@ const BRAND_SHORT_NAME = Services.string
  *   // Return a div appended with elements for this tours.
  *   // Each tour should contain the following 3 sections in the div:
  *   // .onboarding-tour-description, .onboarding-tour-content, .onboarding-tour-button-container.
  *   // Add onboarding-no-button css class in the div if this tour does not need a button container.
  *   // If there was a .onboarding-tour-action-button present and was clicked, tour would be marked as completed.
  *   getPage() {},
  * },
  **/
-var onboardingTours = [
-  {
+var onboardingTourset = {
+  "private": {
     id: "onboarding-tour-private-browsing",
     tourNameId: "onboarding.tour-private-browsing",
     getNotificationStrings(bundle) {
       return {
         title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-private-browsing.title"),
         message: bundle.GetStringFromName("onboarding.notification.onboarding-tour-private-browsing.message"),
         button: bundle.GetStringFromName("onboarding.button.learnMore"),
       };
@@ -63,17 +63,17 @@ var onboardingTours = [
         </section>
         <aside class="onboarding-tour-button-container">
           <button id="onboarding-tour-private-browsing-button" class="onboarding-tour-action-button" data-l10n-id="onboarding.tour-private-browsing.button"></button>
         </aside>
       `;
       return div;
     },
   },
-  {
+  "addons": {
     id: "onboarding-tour-addons",
     tourNameId: "onboarding.tour-addons",
     getNotificationStrings(bundle) {
       return {
         title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-addons.title"),
         message: bundle.formatStringFromName("onboarding.notification.onboarding-tour-addons.message", [BRAND_SHORT_NAME], 1),
         button: bundle.GetStringFromName("onboarding.button.learnMore"),
       };
@@ -90,17 +90,17 @@ var onboardingTours = [
         </section>
         <aside class="onboarding-tour-button-container">
           <button id="onboarding-tour-addons-button" class="onboarding-tour-action-button" data-l10n-id="onboarding.tour-addons.button"></button>
         </aside>
       `;
       return div;
     },
   },
-  {
+  "customize": {
     id: "onboarding-tour-customize",
     tourNameId: "onboarding.tour-customize",
     getNotificationStrings(bundle) {
       return {
         title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-customize.title"),
         message: bundle.formatStringFromName("onboarding.notification.onboarding-tour-customize.message", [BRAND_SHORT_NAME], 1),
         button: bundle.GetStringFromName("onboarding.button.learnMore"),
       };
@@ -117,17 +117,17 @@ var onboardingTours = [
         </section>
         <aside class="onboarding-tour-button-container">
           <button id="onboarding-tour-customize-button" class="onboarding-tour-action-button" data-l10n-id="onboarding.tour-customize.button"></button>
         </aside>
       `;
       return div;
     },
   },
-  {
+  "search": {
     id: "onboarding-tour-search",
     tourNameId: "onboarding.tour-search2",
     getNotificationStrings(bundle) {
       return {
         title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-search.title"),
         message: bundle.GetStringFromName("onboarding.notification.onboarding-tour-search.message"),
         button: bundle.GetStringFromName("onboarding.button.learnMore"),
       };
@@ -144,17 +144,17 @@ var onboardingTours = [
         </section>
         <aside class="onboarding-tour-button-container">
           <button id="onboarding-tour-search-button" class="onboarding-tour-action-button" data-l10n-id="onboarding.tour-search.button"></button>
         </aside>
       `;
       return div;
     },
   },
-  {
+  "default": {
     id: "onboarding-tour-default-browser",
     tourNameId: "onboarding.tour-default-browser",
     getNotificationStrings(bundle) {
       return {
         title: bundle.formatStringFromName("onboarding.notification.onboarding-tour-default-browser.title", [BRAND_SHORT_NAME], 1),
         message: bundle.formatStringFromName("onboarding.notification.onboarding-tour-default-browser.message", [BRAND_SHORT_NAME], 1),
         button: bundle.GetStringFromName("onboarding.button.learnMore"),
       };
@@ -174,17 +174,17 @@ var onboardingTours = [
         </section>
         <aside class="onboarding-tour-button-container">
           <button id="onboarding-tour-default-browser-button" class="onboarding-tour-action-button" data-l10n-id="${defaultBrowserButtonId}"></button>
         </aside>
       `;
       return div;
     },
   },
-  {
+  "sync": {
     id: "onboarding-tour-sync",
     tourNameId: "onboarding.tour-sync2",
     getNotificationStrings(bundle) {
       return {
         title: bundle.GetStringFromName("onboarding.notification.onboarding-tour-sync.title"),
         message: bundle.GetStringFromName("onboarding.notification.onboarding-tour-sync.message"),
         button: bundle.GetStringFromName("onboarding.button.learnMore"),
       };
@@ -207,34 +207,41 @@ var onboardingTours = [
           <img src="resource://onboarding/img/figure_sync.svg" />
         </section>
       `;
       div.querySelector("#onboarding-tour-sync-email-input").placeholder =
         bundle.GetStringFromName("onboarding.tour-sync.email-input.placeholder");
       return div;
     },
   },
-];
+};
 
 /**
  * The script won't be initialized if we turned off onboarding by
  * setting "browser.onboarding.enabled" to false.
  */
 class Onboarding {
   constructor(contentWindow) {
     this.init(contentWindow);
   }
 
   async init(contentWindow) {
     this._window = contentWindow;
     this._tourItems = [];
     this._tourPages = [];
+    this._tours = [];
 
-    // we only support the new user tour at this moment
-    if (Services.prefs.getStringPref("browser.onboarding.tour-type", "update") !== "new") {
+    let tourIds = this._getTourIDList(Services.prefs.getStringPref("browser.onboarding.tour-type", "update"));
+    tourIds.forEach(tourId => {
+      if (onboardingTourset[tourId]) {
+        this._tours.push(onboardingTourset[tourId]);
+      }
+    });
+
+    if (this._tours.length === 0) {
       return;
     }
 
     // We want to create and append elements after CSS is loaded so
     // no flash of style changes and no additional reflow.
     await this._loadCSS();
     this._bundle = Services.strings.createBundle(BUNDLE_URI);
 
@@ -251,16 +258,21 @@ class Onboarding {
     // Destroy on unload. This is to ensure we remove all the stuff we left.
     // No any leak out there.
     this._window.addEventListener("unload", () => this.destroy());
 
     this._initPrefObserver();
     this._initNotification();
   }
 
+  _getTourIDList(tourType) {
+    let tours = Services.prefs.getStringPref(`browser.onboarding.${tourType}tour`, "");
+    return tours.split(",").filter(tourId => tourId !== "").map(tourId => tourId.trim());
+  }
+
   _initNotification() {
     let doc = this._window.document;
     if (doc.hidden) {
       // When the preloaded-browser feature is on,
       // it would preload an hidden about:newtab in the background.
       // We don't want to show notification in that hidden state.
       let onVisible = () => {
         if (!doc.hidden) {
@@ -280,17 +292,17 @@ class Onboarding {
     }
 
     this._prefsObserved = new Map();
     this._prefsObserved.set("browser.onboarding.hidden", prefValue => {
       if (prefValue) {
         this.destroy();
       }
     });
-    onboardingTours.forEach(tour => {
+    this._tours.forEach(tour => {
       let tourId = tour.id;
       this._prefsObserved.set(`browser.onboarding.tour.${tourId}.completed`, () => {
         this.markTourCompletionState(tourId);
       });
     });
     for (let [name, callback] of this._prefsObserved) {
       Preferences.observe(name, callback);
     }
@@ -350,17 +362,17 @@ class Onboarding {
     if (this._notificationBar) {
       this._notificationBar.remove();
     }
   }
 
   toggleOverlay() {
     if (this._tourItems.length == 0) {
       // Lazy loading until first toggle.
-      this._loadTours(onboardingTours);
+      this._loadTours(this._tours);
     }
 
     this.hideNotification();
     this._overlay.classList.toggle("onboarding-opened");
 
     let hiddenCheckbox = this._window.document.getElementById("onboarding-tour-hidden-checkbox");
     if (hiddenCheckbox.checked) {
       this.hide();
@@ -413,34 +425,34 @@ class Onboarding {
       return;
     }
 
     // Pick out the next target tour to show
     let targetTour = null;
 
     // Take the last tour as the default last prompted
     // so below would start from the 1st one if found no the last prompted from the pref.
-    let lastPromptedId = onboardingTours[onboardingTours.length - 1].id;
+    let lastPromptedId = this._tours[this._tours.length - 1].id;
     lastPromptedId = Preferences.get("browser.onboarding.notification.lastPrompted", lastPromptedId);
 
-    let lastTourIndex = onboardingTours.findIndex(tour => tour.id == lastPromptedId);
+    let lastTourIndex = this._tours.findIndex(tour => tour.id == lastPromptedId);
     if (lastTourIndex < 0) {
       // Couldn't find the tour.
       // This could be because the pref was manually modified into unknown value
       // or the tour version has been updated so have an new tours set.
       // Take the last tour as the last prompted so would start from the 1st one below.
-      lastTourIndex = onboardingTours.length - 1;
+      lastTourIndex = this._tours.length - 1;
     }
 
     // Form tours to notify into the order we want.
     // For example, There are tour #0 ~ #5 and the #3 is the last prompted.
     // This would form [#4, #5, #0, #1, #2, #3].
     // So the 1st met incomplete tour in #4 ~ #2 would be the one to show.
     // Or #3 would be the one to show if #4 ~ #2 are all completed.
-    let toursToNotify = [ ...onboardingTours.slice(lastTourIndex + 1), ...onboardingTours.slice(0, lastTourIndex + 1) ];
+    let toursToNotify = [ ...this._tours.slice(lastTourIndex + 1), ...this._tours.slice(0, lastTourIndex + 1) ];
     targetTour = toursToNotify.find(tour => !this.isTourCompleted(tour.id));
 
 
     if (!targetTour) {
       this.sendMessageToChrome("set-prefs", [{
         name: "browser.onboarding.notification.finished",
         value: true
       }]);
@@ -455,37 +467,27 @@ class Onboarding {
     this._notificationBar.dataset.targetTourId = targetTour.id;
     let notificationStrings = targetTour.getNotificationStrings(this._bundle);
     let actionBtn = this._notificationBar.querySelector("#onboarding-notification-action-btn");
     actionBtn.textContent = notificationStrings.button;
     let tourTitle = this._notificationBar.querySelector("#onboarding-notification-tour-title");
     tourTitle.textContent = notificationStrings.title;
     let tourMessage = this._notificationBar.querySelector("#onboarding-notification-tour-message");
     tourMessage.textContent = notificationStrings.message;
-
-    this._notificationBar.addEventListener("transitionend", () => {
-      this._notificationBar.dataset.cssTransition = "end";
-    }, { once: true });
-    this._window.requestAnimationFrame(() => {
-      // Request the 2nd animation frame.
-      // This is to make sure the appending operation above and the css operation happen
-      // in the different layout tick so as to make sure the transition happens.
-      this._window.requestAnimationFrame(() => this._notificationBar.classList.add("onboarding-opened"));
-    });
+    this._notificationBar.classList.add("onboarding-opened");
 
     this.sendMessageToChrome("set-prefs", [{
       name: "browser.onboarding.notification.lastPrompted",
       value: targetTour.id
     }]);
   }
 
   hideNotification() {
     if (this._notificationBar) {
       this._notificationBar.classList.remove("onboarding-opened");
-      delete this._notificationBar.dataset.cssTransition;
     }
   }
 
   _renderNotificationBar() {
     let div = this._window.document.createElement("div");
     div.id = "onboarding-notification-bar";
     // We use `innerHTML` for more friendly reading.
     // The security should be fine because this is not from an external input.
@@ -502,17 +504,17 @@ class Onboarding {
       <button id="onboarding-notification-close-btn"></button>
     `;
     let toolTip = this._bundle.formatStringFromName("onboarding.notification-icon-tool-tip", [BRAND_SHORT_NAME], 1);
     div.querySelector("#onboarding-notification-icon").setAttribute("data-tooltip", toolTip);
     return div;
   }
 
   hide() {
-    this.setToursCompleted(onboardingTours.map(tour => tour.id));
+    this.setToursCompleted(this._tours.map(tour => tour.id));
     this.sendMessageToChrome("set-prefs", [
       {
         name: "browser.onboarding.hidden",
         value: true
       },
       {
         name: "browser.onboarding.notification.finished",
         value: true
--- a/browser/extensions/onboarding/test/browser/browser.ini
+++ b/browser/extensions/onboarding/test/browser/browser.ini
@@ -1,6 +1,7 @@
 [DEFAULT]
 support-files =
   head.js
 
 [browser_onboarding_notification.js]
 [browser_onboarding_tours.js]
+[browser_onboarding_tourset.js]
--- a/browser/extensions/onboarding/test/browser/browser_onboarding_notification.js
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_notification.js
@@ -1,16 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 add_task(async function test_show_tour_notifications_in_order() {
   resetOnboardingDefaultState();
-  await SpecialPowers.pushPrefEnv({set: [["browser.onboarding.enabled", true]]});
 
   let tourIds = TOUR_IDs;
   let tab = null;
   let targetTourId = null;
   let reloadPromise = null;
   let expectedPrefUpdate = null;
   for (let i = 0; i < tourIds.length; ++i) {
     expectedPrefUpdate = promisePrefUpdated("browser.onboarding.notification.lastPrompted", tourIds[i]);
@@ -38,17 +37,16 @@ add_task(async function test_show_tour_n
   targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
   is(targetTourId, tourIds[0], "Should loop back to the 1st tour notification after showing all notifications");
   await expectedPrefUpdate;
   await BrowserTestUtils.removeTab(tab);
 });
 
 add_task(async function test_open_target_tour_from_notification() {
   resetOnboardingDefaultState();
-  await SpecialPowers.pushPrefEnv({set: [["browser.onboarding.enabled", true]]});
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
   await BrowserTestUtils.loadURI(tab.linkedBrowser, ABOUT_NEWTAB_URL);
   await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
   await promiseTourNotificationOpened(tab.linkedBrowser);
   let targetTourId = await getCurrentNotificationTargetTourId(tab.linkedBrowser);
   await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-notification-action-btn", {}, tab.linkedBrowser);
   await promiseOnboardingOverlayOpened(tab.linkedBrowser);
@@ -56,17 +54,16 @@ add_task(async function test_open_target
 
   is(targetTourId, activeNavItemId, "Should navigate to the target tour item.");
   is(`${targetTourId}-page`, activePageId, "Should display the target tour page.");
   await BrowserTestUtils.removeTab(tab);
 });
 
 add_task(async function test_not_show_notification_for_completed_tour() {
   resetOnboardingDefaultState();
-  await SpecialPowers.pushPrefEnv({set: [["browser.onboarding.enabled", true]]});
 
   let tourIds = TOUR_IDs;
   // Make only the last tour uncompleted
   let lastTourId = tourIds[tourIds.length - 1];
   for (let id of tourIds) {
     if (id != lastTourId) {
       setTourCompletedState(id, true);
     }
--- a/browser/extensions/onboarding/test/browser/browser_onboarding_tours.js
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_tours.js
@@ -24,21 +24,20 @@ function assertTourCompletedStyle(tourId
     } else {
       ok(!item.classList.contains("onboarding-complete"), `Should not set the incomplete #${args.tourId} tour with the complete style`);
     }
   });
 }
 
 add_task(async function test_hide_onboarding_tours() {
   resetOnboardingDefaultState();
-  await SpecialPowers.pushPrefEnv({set: [["browser.onboarding.enabled", true]]});
 
   let tourIds = TOUR_IDs;
   let tabs = [];
-  for (let url of [ABOUT_NEWTAB_URL, ABOUT_HOME_URL]) {
+  for (let url of URLs) {
     let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
     await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
     await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
     await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-icon", {}, tab.linkedBrowser);
     await promiseOnboardingOverlayOpened(tab.linkedBrowser);
     tabs.push(tab);
   }
 
@@ -56,21 +55,20 @@ add_task(async function test_hide_onboar
     let tab = tabs[i];
     await assertOnboardingDestroyed(tab.linkedBrowser);
     await BrowserTestUtils.removeTab(tab);
   }
 });
 
 add_task(async function test_click_action_button_to_set_tour_completed() {
   resetOnboardingDefaultState();
-  await SpecialPowers.pushPrefEnv({set: [["browser.onboarding.enabled", true]]});
 
   let tourIds = TOUR_IDs;
   let tabs = [];
-  for (let url of [ABOUT_NEWTAB_URL, ABOUT_HOME_URL]) {
+  for (let url of URLs) {
     let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
     await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
     await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
     await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-icon", {}, tab.linkedBrowser);
     await promiseOnboardingOverlayOpened(tab.linkedBrowser);
     tabs.push(tab);
   }
 
@@ -86,26 +84,25 @@ add_task(async function test_click_actio
     }
     await BrowserTestUtils.removeTab(tab);
   }
 });
 
 
 add_task(async function test_set_right_tour_completed_style_on_overlay() {
   resetOnboardingDefaultState();
-  await SpecialPowers.pushPrefEnv({set: [["browser.onboarding.enabled", true]]});
 
   let tourIds = TOUR_IDs;
   // Make the tours of even number as completed
   for (let i = 0; i < tourIds.length; ++i) {
     setTourCompletedState(tourIds[i], i % 2 == 0);
   }
 
   let tabs = [];
-  for (let url of [ABOUT_NEWTAB_URL, ABOUT_HOME_URL]) {
+  for (let url of URLs) {
     let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
     await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
     await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
     await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-icon", {}, tab.linkedBrowser);
     await promiseOnboardingOverlayOpened(tab.linkedBrowser);
     tabs.push(tab);
   }
 
new file mode 100644
--- /dev/null
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_tourset.js
@@ -0,0 +1,104 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_onboarding_default_new_tourset() {
+  resetOnboardingDefaultState();
+  let tabs = [];
+  for (let url of URLs) {
+    let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+    await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
+    await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+    await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-icon", {}, tab.linkedBrowser);
+    await promiseOnboardingOverlayOpened(tab.linkedBrowser);
+    tabs.push(tab);
+  }
+
+  let doc = content && content.document;
+  let doms = doc.querySelectorAll(".onboarding-tour-item");
+  is(doms.length, TOUR_IDs.length, "has exact tour numbers");
+  doms.forEach((dom, idx) => {
+    is(TOUR_IDs[idx], dom.id, "contain defined onboarding id");
+  });
+
+  for (let i = tabs.length - 1; i >= 0; --i) {
+    let tab = tabs[i];
+    await BrowserTestUtils.removeTab(tab);
+  }
+});
+
+add_task(async function test_onboarding_custom_new_tourset() {
+  const CUSTOM_NEW_TOURs = [
+    "onboarding-tour-private-browsing",
+    "onboarding-tour-addons",
+    "onboarding-tour-customize",
+  ];
+
+  resetOnboardingDefaultState();
+  await SpecialPowers.pushPrefEnv({set: [
+    ["browser.onboarding.tour-type", "new"],
+    ["browser.onboarding.tourset-version", 1],
+    ["browser.onboarding.seen-tourset-version", 1],
+    ["browser.onboarding.newtour", "private,addons,customize"],
+  ]});
+
+  let tabs = [];
+  for (let url of URLs) {
+    let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+    await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
+    await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+    await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-icon", {}, tab.linkedBrowser);
+    await promiseOnboardingOverlayOpened(tab.linkedBrowser);
+    tabs.push(tab);
+  }
+
+  let doc = content && content.document;
+  let doms = doc.querySelectorAll(".onboarding-tour-item");
+  is(doms.length, CUSTOM_NEW_TOURs.length, "has exact tour numbers");
+  doms.forEach((dom, idx) => {
+    is(CUSTOM_NEW_TOURs[idx], dom.id, "contain defined onboarding id");
+  });
+
+  for (let i = tabs.length - 1; i >= 0; --i) {
+    let tab = tabs[i];
+    await BrowserTestUtils.removeTab(tab);
+  }
+});
+
+add_task(async function test_onboarding_custom_update_tourset() {
+  const CUSTOM_UPDATE_TOURs = [
+    "onboarding-tour-customize",
+    "onboarding-tour-private-browsing",
+    "onboarding-tour-addons",
+  ];
+  resetOnboardingDefaultState();
+  await SpecialPowers.pushPrefEnv({set: [
+    ["browser.onboarding.tour-type", "update"],
+    ["browser.onboarding.tourset-version", 1],
+    ["browser.onboarding.seen-tourset-version", 1],
+    ["browser.onboarding.updatetour", "customize,private,addons"],
+  ]});
+
+  let tabs = [];
+  for (let url of URLs) {
+    let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+    await BrowserTestUtils.loadURI(tab.linkedBrowser, url);
+    await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+    await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-icon", {}, tab.linkedBrowser);
+    await promiseOnboardingOverlayOpened(tab.linkedBrowser);
+    tabs.push(tab);
+  }
+
+  let doc = content && content.document;
+  let doms = doc.querySelectorAll(".onboarding-tour-item");
+  is(doms.length, CUSTOM_UPDATE_TOURs.length, "has exact tour numbers");
+  doms.forEach((dom, idx) => {
+    is(CUSTOM_UPDATE_TOURs[idx], dom.id, "contain defined onboarding id");
+  });
+
+  for (let i = tabs.length - 1; i >= 0; --i) {
+    let tab = tabs[i];
+    await BrowserTestUtils.removeTab(tab);
+  }
+});
--- a/browser/extensions/onboarding/test/browser/head.js
+++ b/browser/extensions/onboarding/test/browser/head.js
@@ -1,27 +1,30 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 let { Preferences } = Cu.import("resource://gre/modules/Preferences.jsm", {});
 
 const ABOUT_HOME_URL = "about:home";
 const ABOUT_NEWTAB_URL = "about:newtab";
+const URLs = [ABOUT_HOME_URL, ABOUT_NEWTAB_URL];
 const TOUR_IDs = [
   "onboarding-tour-private-browsing",
   "onboarding-tour-addons",
   "onboarding-tour-customize",
   "onboarding-tour-search",
   "onboarding-tour-default-browser",
   "onboarding-tour-sync",
 ];
+const UPDATE_TOUR_IDs = [];
 
 function resetOnboardingDefaultState() {
   // All the prefs should be reset to the default states
   // and no need to revert back so we don't use `SpecialPowers.pushPrefEnv` here.
+  Preferences.set("browser.onboarding.enabled", true);
   Preferences.set("browser.onboarding.hidden", false);
   Preferences.set("browser.onboarding.notification.finished", false);
   Preferences.set("browser.onboarding.notification.lastPrompted", "");
   TOUR_IDs.forEach(id => Preferences.set(`browser.onboarding.tour.${id}.completed`, false));
 }
 
 function setTourCompletedState(tourId, state) {
   Preferences.set(`browser.onboarding.tour.${tourId}.completed`, state);
@@ -82,17 +85,17 @@ function promisePrefUpdated(name, expect
   });
 }
 
 function promiseTourNotificationOpened(browser) {
   let condition = () => {
     return ContentTask.spawn(browser, {}, function() {
       return new Promise(resolve => {
         let bar = content.document.querySelector("#onboarding-notification-bar");
-        if (bar && bar.classList.contains("onboarding-opened") && bar.dataset.cssTransition == "end") {
+        if (bar && bar.classList.contains("onboarding-opened")) {
           resolve(true);
           return;
         }
         resolve(false);
       });
     })
   };
   return BrowserTestUtils.waitForCondition(
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -820,11 +820,19 @@ bin/libfreebl_32int64_3.so
 @RESPATH@/gmp-clearkey/0.1/manifest.json
 
 ; gfx
 #ifdef XP_WIN
 @RESPATH@/components/GfxSanityTest.manifest
 @RESPATH@/components/SanityTest.js
 #endif
 
-#ifdef MOZ_MULET
-#include ../../b2g/installer/package-manifest.in
+#ifdef MOZ_DMD
+; DMD
+@RESPATH@/dmd.py
+@RESPATH@/fix_stack_using_bpsyms.py
+#ifdef XP_MACOSX
+@RESPATH@/fix_macosx_stack.py
 #endif
+#ifdef XP_LINUX
+@RESPATH@/fix_linux_stack.py
+#endif
+#endif
--- a/devtools/client/inspector/grids/components/GridOutline.js
+++ b/devtools/client/inspector/grids/components/GridOutline.js
@@ -2,25 +2,29 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { addons, createClass, DOM: dom, PropTypes } =
   require("devtools/client/shared/vendor/react");
 
+const Services = require("Services");
 const Types = require("../types");
 const { getStr } = require("../utils/l10n");
 
 // The delay prior to executing the grid cell highlighting.
 const GRID_HIGHLIGHTING_DEBOUNCE = 50;
 
-// Minimum height/width a grid cell can be
-const MIN_CELL_HEIGHT = 5;
-const MIN_CELL_WIDTH = 5;
+// Prefs for the max number of rows/cols a grid container can have for
+// the outline to display.
+const GRID_OUTLINE_MAX_ROWS_PREF =
+  Services.prefs.getIntPref("devtools.gridinspector.gridOutlineMaxRows");
+const GRID_OUTLINE_MAX_COLUMNS_PREF =
+  Services.prefs.getIntPref("devtools.gridinspector.gridOutlineMaxColumns");
 
 // Move SVG grid to the right 100 units, so that it is not flushed against the edge of
 // layout border
 const TRANSLATE_X = 0;
 const TRANSLATE_Y = 0;
 
 const GRID_CELL_SCALE_FACTOR = 50;
 
@@ -52,18 +56,28 @@ module.exports = createClass({
     let selectedGrid = grids.find(grid => grid.highlighted);
 
     // Store the height of the grid container in the component state to prevent overflow
     // issues. We want to store the width of the grid container as well so that the
     // viewbox is only the calculated width of the grid outline.
     let { width, height } = selectedGrid
                             ? this.getTotalWidthAndHeight(selectedGrid)
                             : { width: 0, height: 0 };
+    let showOutline;
 
-    this.setState({ height, width, selectedGrid, showOutline: true });
+    if (selectedGrid) {
+      const { cols, rows } = selectedGrid.gridFragments[0];
+
+      // Show the grid outline if both the rows/columns are less than or equal
+      // to their max prefs.
+      showOutline = (cols.lines.length <= GRID_OUTLINE_MAX_COLUMNS_PREF) &&
+                    (rows.lines.length <= GRID_OUTLINE_MAX_ROWS_PREF);
+    }
+
+    this.setState({ height, width, selectedGrid, showOutline });
   },
 
   /**
    * Get the width and height of a given grid.
    *
    * @param  {Object} grid
    *         A single grid container in the document.
    * @return {Object} An object like { width, height }
@@ -215,24 +229,16 @@ module.exports = createClass({
 
     // Draw the cells contained within the grid outline border.
     for (let rowNumber = 1; rowNumber <= numberOfRows; rowNumber++) {
       height = GRID_CELL_SCALE_FACTOR * (rows.tracks[rowNumber - 1].breadth / 100);
 
       for (let columnNumber = 1; columnNumber <= numberOfColumns; columnNumber++) {
         width = GRID_CELL_SCALE_FACTOR * (cols.tracks[columnNumber - 1].breadth / 100);
 
-        // If a grid cell is less than the minimum pixels in width or height,
-        // do not render the outline at all.
-        if (width < MIN_CELL_WIDTH || height < MIN_CELL_HEIGHT) {
-          this.setState({ showOutline: false });
-
-          return [];
-        }
-
         const gridAreaName = this.getGridAreaName(columnNumber, rowNumber, areas);
         const gridCell = this.renderGridCell(id, gridFragmentIndex, x, y,
                                              rowNumber, columnNumber, color, gridAreaName,
                                              width, height);
 
         rectangles.push(gridCell);
         x += width;
       }
--- a/devtools/client/inspector/grids/test/browser_grids_grid-outline-cannot-show-outline.js
+++ b/devtools/client/inspector/grids/test/browser_grids_grid-outline-cannot-show-outline.js
@@ -5,25 +5,23 @@
 
 // Tests that grid outline does not show when cells are too small to be drawn and that
 // "Cannot show outline for this grid." message is displayed.
 
 const TEST_URI = `
   <style type='text/css'>
     #grid {
       display: grid;
-      grid-template-columns: 2px;
-    }
-    .cell {
-      grid-template-columns: 2px;
+      grid-template-columns: repeat(51, 20px);
+      grid-template-rows: repeat(51, 20px);
     }
   </style>
   <div id="grid">
-    <div id="cellA" className="cell">cell A</div>
-    <div id="cellB" className="cell">cell B</div>
+    <div id="cellA">cell A</div>
+    <div id="cellB">cell B</div>
   </div>
 `;
 
 add_task(function* () {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
 
   let { inspector, gridInspector } = yield openLayoutView();
   let { document: doc } = gridInspector;
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -71,16 +71,18 @@ pref("devtools.fontinspector.enabled", t
 // Counter to promote the inspector layout view.
 // @remove after release 56 (See Bug 1355747)
 pref("devtools.promote.layoutview", 1);
 // Whether or not to show the promote bar in the layout view
 // @remove after release 56 (See Bug 1355747)
 pref("devtools.promote.layoutview.showPromoteBar", true);
 
 // Grid highlighter preferences
+pref("devtools.gridinspector.gridOutlineMaxColumns", 50);
+pref("devtools.gridinspector.gridOutlineMaxRows", 50);
 pref("devtools.gridinspector.showGridAreas", false);
 pref("devtools.gridinspector.showGridLineNumbers", false);
 pref("devtools.gridinspector.showInfiniteLines", false);
 
 // Whether or not the box model panel is opened in the computed view
 pref("devtools.computed.boxmodel.opened", true);
 // Whether or not the box model panel is opened in the layout view
 pref("devtools.layout.boxmodel.opened", true);
--- a/dom/animation/KeyframeUtils.cpp
+++ b/dom/animation/KeyframeUtils.cpp
@@ -562,17 +562,18 @@ KeyframeUtils::ParseProperty(nsCSSProper
   // FIXME this is using the wrong base uri (bug 1343919)
   RefPtr<URLExtraData> data = new URLExtraData(aDocument->GetDocumentURI(),
                                                aDocument->GetDocumentURI(),
                                                aDocument->NodePrincipal());
   return Servo_ParseProperty(aProperty,
                              &value,
                              data,
                              ParsingMode::Default,
-                             aDocument->GetCompatibilityMode()).Consume();
+                             aDocument->GetCompatibilityMode(),
+                             aDocument->CSSLoader()).Consume();
 }
 
 // ------------------------------------------------------------------
 //
 // Internal helpers
 //
 // ------------------------------------------------------------------
 
--- a/dom/base/Link.cpp
+++ b/dom/base/Link.cpp
@@ -18,16 +18,17 @@
 
 #include "nsEscape.h"
 #include "nsGkAtoms.h"
 #include "nsHTMLDNSPrefetch.h"
 #include "nsString.h"
 #include "mozAutoDocUpdate.h"
 
 #include "mozilla/Services.h"
+#include "nsAttrValueInlines.h"
 
 namespace mozilla {
 namespace dom {
 
 Link::Link(Element *aElement)
   : mElement(aElement)
   , mHistory(services::GetHistoryService())
   , mLinkState(eLinkState_NotLink)
@@ -89,44 +90,86 @@ Link::CancelDNSPrefetch(nsWrapperCache::
     mElement->UnsetFlags(aRequestedFlag);
     // Possible that hostname could have changed since binding, but since this
     // covers common cases, most DNS prefetch requests will be canceled
     nsHTMLDNSPrefetch::CancelPrefetchLow(this, NS_ERROR_ABORT);
   }
 }
 
 void
-Link::TryDNSPrefetchPreconnectOrPrefetchOrPrerender()
+Link::GetContentPolicyMimeTypeMedia(nsAttrValue& aAsAttr,
+                                    nsContentPolicyType& aPolicyType,
+                                    nsString& aMimeType,
+                                    nsAString& aMedia)
+{
+  nsAutoString as;
+  mElement->GetAttr(kNameSpaceID_None, nsGkAtoms::as, as);
+  Link::ParseAsValue(as, aAsAttr);
+  aPolicyType = AsValueToContentPolicy(aAsAttr);
+
+  nsAutoString type;
+  mElement->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type);
+  nsAutoString notUsed;
+  nsContentUtils::SplitMimeType(type, aMimeType, notUsed);
+
+  mElement->GetAttr(kNameSpaceID_None, nsGkAtoms::media, aMedia);
+}
+
+void
+Link::TryDNSPrefetchOrPreconnectOrPrefetchOrPreloadOrPrerender()
 {
   MOZ_ASSERT(mElement->IsInComposedDoc());
   if (!ElementHasHref()) {
     return;
   }
 
   nsAutoString rel;
   if (!mElement->GetAttr(kNameSpaceID_None, nsGkAtoms::rel, rel)) {
     return;
   }
 
-  if (!nsContentUtils::PrefetchEnabled(mElement->OwnerDoc()->GetDocShell())) {
+  if (!nsContentUtils::PrefetchPreloadEnabled(mElement->OwnerDoc()->GetDocShell())) {
     return;
   }
 
   uint32_t linkTypes = nsStyleLinkElement::ParseLinkTypes(rel);
 
   if ((linkTypes & nsStyleLinkElement::ePREFETCH) ||
-      (linkTypes & nsStyleLinkElement::eNEXT)){
+      (linkTypes & nsStyleLinkElement::eNEXT) ||
+      (linkTypes & nsStyleLinkElement::ePRELOAD)){
     nsCOMPtr<nsIPrefetchService> prefetchService(do_GetService(NS_PREFETCHSERVICE_CONTRACTID));
     if (prefetchService) {
       nsCOMPtr<nsIURI> uri(GetURI());
       if (uri) {
         nsCOMPtr<nsIDOMNode> domNode = GetAsDOMNode(mElement);
-        prefetchService->PrefetchURI(uri,
-                                     mElement->OwnerDoc()->GetDocumentURI(),
-                                     domNode, linkTypes & nsStyleLinkElement::ePREFETCH);
+        if (linkTypes & nsStyleLinkElement::ePRELOAD) {
+          nsAttrValue asAttr;
+          nsContentPolicyType policyType;
+          nsAutoString mimeType;
+          nsAutoString media;
+          GetContentPolicyMimeTypeMedia(asAttr, policyType, mimeType, media);
+
+          if (policyType == nsIContentPolicy::TYPE_INVALID) {
+            // Ignore preload with a wrong or empty as attribute.
+            return;
+          }
+
+          if (!nsStyleLinkElement::CheckPreloadAttrs(asAttr, mimeType, media,
+                                                     mElement->OwnerDoc())) {
+            policyType = nsIContentPolicy::TYPE_INVALID;
+          }
+
+          prefetchService->PreloadURI(uri,
+                                      mElement->OwnerDoc()->GetDocumentURI(),
+                                      domNode, policyType);
+        } else {
+          prefetchService->PrefetchURI(uri,
+                                       mElement->OwnerDoc()->GetDocumentURI(),
+                                       domNode, linkTypes & nsStyleLinkElement::ePREFETCH);
+        }
         return;
       }
     }
   }
 
   if (linkTypes & nsStyleLinkElement::ePRECONNECT) {
     nsCOMPtr<nsIURI> uri(GetURI());
     if (uri && mElement->OwnerDoc()) {
@@ -147,24 +190,151 @@ Link::TryDNSPrefetchPreconnectOrPrefetch
   if (linkTypes & nsStyleLinkElement::eDNS_PREFETCH) {
     if (nsHTMLDNSPrefetch::IsAllowed(mElement->OwnerDoc())) {
       nsHTMLDNSPrefetch::PrefetchLow(this);
     }
   }
 }
 
 void
-Link::CancelPrefetch()
+Link::UpdatePreload(nsIAtom* aName, const nsAttrValue* aValue,
+                    const nsAttrValue* aOldValue)
+{
+  MOZ_ASSERT(mElement->IsInComposedDoc());
+
+  if (!ElementHasHref()) {
+     return;
+  }
+
+  nsAutoString rel;
+  if (!mElement->GetAttr(kNameSpaceID_None, nsGkAtoms::rel, rel)) {
+    return;
+  }
+
+  if (!nsContentUtils::PrefetchPreloadEnabled(mElement->OwnerDoc()->GetDocShell())) {
+    return;
+  }
+
+  uint32_t linkTypes = nsStyleLinkElement::ParseLinkTypes(rel);
+
+  if (!(linkTypes & nsStyleLinkElement::ePRELOAD)) {
+    return;
+  }
+
+  nsCOMPtr<nsIPrefetchService> prefetchService(do_GetService(NS_PREFETCHSERVICE_CONTRACTID));
+  if (!prefetchService) {
+    return;
+  }
+
+  nsCOMPtr<nsIURI> uri(GetURI());
+  if (!uri) {
+    return;
+  }
+
+  nsCOMPtr<nsIDOMNode> domNode = GetAsDOMNode(mElement);
+
+  nsAttrValue asAttr;
+  nsContentPolicyType asPolicyType;
+  nsAutoString mimeType;
+  nsAutoString media;
+  GetContentPolicyMimeTypeMedia(asAttr, asPolicyType, mimeType, media);
+
+  if (asPolicyType == nsIContentPolicy::TYPE_INVALID) {
+    // Ignore preload with a wrong or empty as attribute, but be sure to cancel
+    // the old one.
+    prefetchService->CancelPrefetchPreloadURI(uri, domNode);
+    return;
+  }
+
+  nsContentPolicyType policyType = asPolicyType;
+  if (!nsStyleLinkElement::CheckPreloadAttrs(asAttr, mimeType, media,
+                                             mElement->OwnerDoc())) {
+    policyType = nsIContentPolicy::TYPE_INVALID;
+  }
+
+  if (aName == nsGkAtoms::crossorigin) {
+    CORSMode corsMode = Element::AttrValueToCORSMode(aValue);
+    CORSMode oldCorsMode = Element::AttrValueToCORSMode(aOldValue);
+    if (corsMode != oldCorsMode) {
+      prefetchService->CancelPrefetchPreloadURI(uri, domNode);
+      prefetchService->PreloadURI(uri, mElement->OwnerDoc()->GetDocumentURI(),
+                                  domNode, policyType);
+    }
+    return;
+  }
+
+  nsContentPolicyType oldPolicyType;
+
+  if (aName == nsGkAtoms::as) {
+    if (aOldValue) {
+      oldPolicyType = AsValueToContentPolicy(*aOldValue);
+      if (!nsStyleLinkElement::CheckPreloadAttrs(*aOldValue, mimeType, media,
+                                                 mElement->OwnerDoc())) {
+        oldPolicyType = nsIContentPolicy::TYPE_INVALID;
+      }
+    } else {
+      oldPolicyType = nsIContentPolicy::TYPE_INVALID;
+    }    
+  } else if (aName == nsGkAtoms::type) {
+    nsAutoString oldType;
+    nsAutoString notUsed;
+    if (aOldValue) {
+      aOldValue->ToString(oldType);
+    } else {
+      oldType = EmptyString();
+    }
+    nsAutoString oldMimeType;
+    nsContentUtils::SplitMimeType(oldType, oldMimeType, notUsed);
+    if (nsStyleLinkElement::CheckPreloadAttrs(asAttr, oldMimeType, media,
+                                              mElement->OwnerDoc())) {
+      oldPolicyType = asPolicyType;
+    } else {
+      oldPolicyType = nsIContentPolicy::TYPE_INVALID;
+    }
+  } else {
+    MOZ_ASSERT(aName == nsGkAtoms::media);
+    nsAutoString oldMedia;
+    if (aOldValue) {
+      aOldValue->ToString(oldMedia);
+    } else {
+      oldMedia = EmptyString();
+    }
+    if (nsStyleLinkElement::CheckPreloadAttrs(asAttr, mimeType, oldMedia,
+                                              mElement->OwnerDoc())) {
+      oldPolicyType = asPolicyType;
+    } else {
+      oldPolicyType = nsIContentPolicy::TYPE_INVALID;
+    }
+  }
+
+  if ((policyType != oldPolicyType) &&
+      (oldPolicyType != nsIContentPolicy::TYPE_INVALID)) {
+    prefetchService->CancelPrefetchPreloadURI(uri, domNode);
+
+  }
+
+  // Trigger a new preload if the policy type has changed.
+  // Also trigger load if the new policy type is invalid, this will only
+  // trigger an error event.
+  if ((policyType != oldPolicyType) ||
+      (policyType == nsIContentPolicy::TYPE_INVALID)) {
+    prefetchService->PreloadURI(uri, mElement->OwnerDoc()->GetDocumentURI(),
+                                domNode, policyType);
+  }
+}
+
+void
+Link::CancelPrefetchOrPreload()
 {
   nsCOMPtr<nsIPrefetchService> prefetchService(do_GetService(NS_PREFETCHSERVICE_CONTRACTID));
   if (prefetchService) {
     nsCOMPtr<nsIURI> uri(GetURI());
     if (uri) {
       nsCOMPtr<nsIDOMNode> domNode = GetAsDOMNode(mElement);
-      prefetchService->CancelPrefetchURI(uri, domNode);
+      prefetchService->CancelPrefetchPreloadURI(uri, domNode);
     }
   }
 }
 
 void
 Link::SetLinkState(nsLinkState aState)
 {
   NS_ASSERTION(mRegistered,
@@ -673,10 +843,61 @@ Link::SizeOfExcludingThis(mozilla::Mallo
 
   // The following members don't need to be measured:
   // - mElement, because it is a pointer-to-self used to avoid QIs
   // - mHistory, because it is non-owning
 
   return n;
 }
 
+static const nsAttrValue::EnumTable kAsAttributeTable[] = {
+  { "",              DESTINATION_INVALID       },
+  { "audio",         DESTINATION_AUDIO         },
+  { "font",          DESTINATION_FONT          },
+  { "image",         DESTINATION_IMAGE         },
+  { "script",        DESTINATION_SCRIPT        },
+  { "style",         DESTINATION_STYLE         },
+  { "track",         DESTINATION_TRACK         },
+  { "video",         DESTINATION_VIDEO         },
+  { "fetch",         DESTINATION_FETCH         },
+  { nullptr,         0 }
+};
+
+
+/* static */ void
+Link::ParseAsValue(const nsAString& aValue,
+                   nsAttrValue& aResult)
+{
+  DebugOnly<bool> success =
+  aResult.ParseEnumValue(aValue, kAsAttributeTable, false,
+                         // default value is a empty string
+                         // if aValue is not a value we
+                         // understand
+                         &kAsAttributeTable[0]);
+  MOZ_ASSERT(success);
+}
+
+/* static */ nsContentPolicyType
+Link::AsValueToContentPolicy(const nsAttrValue& aValue)
+{
+  switch(aValue.GetEnumValue()) {
+  case DESTINATION_INVALID:
+    return nsIContentPolicy::TYPE_INVALID;
+  case DESTINATION_AUDIO:
+  case DESTINATION_TRACK:
+  case DESTINATION_VIDEO:
+    return nsIContentPolicy::TYPE_MEDIA;
+  case DESTINATION_FONT:
+    return nsIContentPolicy::TYPE_FONT;
+  case DESTINATION_IMAGE:
+    return nsIContentPolicy::TYPE_IMAGE;
+  case DESTINATION_SCRIPT:
+    return nsIContentPolicy::TYPE_SCRIPT;
+  case DESTINATION_STYLE:
+    return nsIContentPolicy::TYPE_STYLESHEET;
+  case DESTINATION_FETCH:
+    return nsIContentPolicy::TYPE_OTHER;
+  }
+  return nsIContentPolicy::TYPE_INVALID;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/Link.h
+++ b/dom/base/Link.h
@@ -9,16 +9,17 @@
  */
 
 #ifndef mozilla_dom_Link_h__
 #define mozilla_dom_Link_h__
 
 #include "mozilla/IHistory.h"
 #include "mozilla/MemoryReporting.h"
 #include "nsIContent.h" // for nsLinkState
+#include "nsIContentPolicyBase.h"
 
 namespace mozilla {
 
 class EventStates;
 
 namespace dom {
 
 class Element;
@@ -118,33 +119,37 @@ public:
   virtual bool ElementHasHref() const;
 
   // This is called by HTMLAnchorElement.
   void TryDNSPrefetch();
   void CancelDNSPrefetch(nsWrapperCache::FlagsType aDeferredFlag,
                          nsWrapperCache::FlagsType aRequestedFlag);
 
   // This is called by HTMLLinkElement.
-  void TryDNSPrefetchPreconnectOrPrefetchOrPrerender();
-  void CancelPrefetch();
+  void TryDNSPrefetchOrPreconnectOrPrefetchOrPreloadOrPrerender();
+  void UpdatePreload(nsIAtom* aName, const nsAttrValue* aValue,
+                     const nsAttrValue* aOldValue);
+  void CancelPrefetchOrPreload();
 
   bool HasPendingLinkUpdate() const { return mHasPendingLinkUpdate; }
   void SetHasPendingLinkUpdate() { mHasPendingLinkUpdate = true; }
   void ClearHasPendingLinkUpdate() { mHasPendingLinkUpdate = false; }
 
   // To ensure correct mHasPendingLinkUpdate handling, we have this method
   // similar to the one in Element. Overriders must call
   // ClearHasPendingLinkUpdate().
   // If you change this, change also the method in Element.
   virtual void NodeInfoChanged(nsIDocument* aOldDoc) = 0;
 
   bool IsInDNSPrefetch() { return mInDNSPrefetch; }
   void SetIsInDNSPrefetch() { mInDNSPrefetch = true; }
   void ClearIsInDNSPrefetch() { mInDNSPrefetch = false; }
 
+  static void ParseAsValue(const nsAString& aValue, nsAttrValue& aResult);
+  static nsContentPolicyType AsValueToContentPolicy(const nsAttrValue& aValue);
 protected:
   virtual ~Link();
 
   /**
    * Return true if the link has associated URI.
    */
   bool HasURI() const
   {
@@ -163,16 +168,21 @@ private:
    * Unregisters from History so this node no longer gets notifications about
    * changes to visitedness.
    */
   void UnregisterFromHistory();
 
   already_AddRefed<nsIURI> GetURIToMutate();
   void SetHrefAttribute(nsIURI *aURI);
 
+  void GetContentPolicyMimeTypeMedia(nsAttrValue& aAsAttr,
+                                     nsContentPolicyType& aPolicyType,
+                                     nsString& aMimeType,
+                                     nsAString& aMedia);
+
   mutable nsCOMPtr<nsIURI> mCachedURI;
 
   Element * const mElement;
 
   // Strong reference to History.  The link has to unregister before History
   // can disappear.
   nsCOMPtr<IHistory> mHistory;
 
@@ -184,12 +194,33 @@ private:
 
   bool mHasPendingLinkUpdate : 1;
 
   bool mInDNSPrefetch : 1;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(Link, MOZILLA_DOM_LINK_IMPLEMENTATION_IID)
 
+enum ASDestination : uint8_t {
+  DESTINATION_INVALID,
+  DESTINATION_AUDIO,
+  DESTINATION_DOCUMENT,
+  DESTINATION_EMBED,
+  DESTINATION_FONT,
+  DESTINATION_IMAGE,
+  DESTINATION_MANIFEST,
+  DESTINATION_OBJECT,
+  DESTINATION_REPORT,
+  DESTINATION_SCRIPT,
+  DESTINATION_SERVICEWORKER,
+  DESTINATION_SHAREDWORKER,
+  DESTINATION_STYLE,
+  DESTINATION_TRACK,
+  DESTINATION_VIDEO,
+  DESTINATION_WORKER,
+  DESTINATION_XSLT,
+  DESTINATION_FETCH
+};
+
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_Link_h__
--- a/dom/base/StructuredCloneHolder.cpp
+++ b/dom/base/StructuredCloneHolder.cpp
@@ -441,56 +441,58 @@ StructuredCloneHolder::ReadFullySerializ
   return nullptr;
 }
 
 /* static */ bool
 StructuredCloneHolder::WriteFullySerializableObjects(JSContext* aCx,
                                                      JSStructuredCloneWriter* aWriter,
                                                      JS::Handle<JSObject*> aObj)
 {
+  JS::Rooted<JSObject*> obj(aCx, aObj);
+
   // See if this is a ImageData object.
   {
     ImageData* imageData = nullptr;
-    if (NS_SUCCEEDED(UNWRAP_OBJECT(ImageData, aObj, imageData))) {
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(ImageData, &obj, imageData))) {
       return WriteStructuredCloneImageData(aCx, aWriter, imageData);
     }
   }
 
   // Handle URLSearchParams cloning
   {
     URLSearchParams* usp = nullptr;
-    if (NS_SUCCEEDED(UNWRAP_OBJECT(URLSearchParams, aObj, usp))) {
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(URLSearchParams, &obj, usp))) {
       return JS_WriteUint32Pair(aWriter, SCTAG_DOM_URLSEARCHPARAMS, 0) &&
              usp->WriteStructuredClone(aWriter);
     }
   }
 
   // Handle Key cloning
   {
     CryptoKey* key = nullptr;
-    if (NS_SUCCEEDED(UNWRAP_OBJECT(CryptoKey, aObj, key))) {
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(CryptoKey, &obj, key))) {
       return JS_WriteUint32Pair(aWriter, SCTAG_DOM_WEBCRYPTO_KEY, 0) &&
              key->WriteStructuredClone(aWriter);
     }
   }
 
 #ifdef MOZ_WEBRTC
   {
     // Handle WebRTC Certificate cloning
     RTCCertificate* cert = nullptr;
-    if (NS_SUCCEEDED(UNWRAP_OBJECT(RTCCertificate, aObj, cert))) {
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(RTCCertificate, &obj, cert))) {
       MOZ_ASSERT(NS_IsMainThread());
       return JS_WriteUint32Pair(aWriter, SCTAG_DOM_RTC_CERTIFICATE, 0) &&
              cert->WriteStructuredClone(aWriter);
     }
   }
 #endif
 
-  if (NS_IsMainThread() && xpc::IsReflector(aObj)) {
-    nsCOMPtr<nsISupports> base = xpc::UnwrapReflectorToISupports(aObj);
+  if (NS_IsMainThread() && xpc::IsReflector(obj)) {
+    nsCOMPtr<nsISupports> base = xpc::UnwrapReflectorToISupports(obj);
     nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(base);
     if (principal) {
       auto nsjsprincipals = nsJSPrincipals::get(principal);
       return nsjsprincipals->write(aCx, aWriter);
     }
   }
 
   // Don't know what this is
@@ -1006,72 +1008,74 @@ bool
 StructuredCloneHolder::CustomWriteHandler(JSContext* aCx,
                                           JSStructuredCloneWriter* aWriter,
                                           JS::Handle<JSObject*> aObj)
 {
   if (!mSupportsCloning) {
     return false;
   }
 
+  JS::Rooted<JSObject*> obj(aCx, aObj);
+
   // See if this is a File/Blob object.
   {
     Blob* blob = nullptr;
-    if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob))) {
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, &obj, blob))) {
       return WriteBlob(aWriter, blob, this);
     }
   }
 
   // See if this is a Directory object.
   {
     Directory* directory = nullptr;
-    if (NS_SUCCEEDED(UNWRAP_OBJECT(Directory, aObj, directory))) {
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(Directory, &obj, directory))) {
       return WriteDirectory(aWriter, directory);
     }
   }
 
   // See if this is a FileList object.
   {
     FileList* fileList = nullptr;
-    if (NS_SUCCEEDED(UNWRAP_OBJECT(FileList, aObj, fileList))) {
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(FileList, &obj, fileList))) {
       return WriteFileList(aWriter, fileList, this);
     }
   }
 
   // See if this is a FormData object.
   {
     FormData* formData = nullptr;
-    if (NS_SUCCEEDED(UNWRAP_OBJECT(FormData, aObj, formData))) {
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(FormData, &obj, formData))) {
       return WriteFormData(aWriter, formData, this);
     }
   }
 
   // See if this is an ImageBitmap object.
   if (mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread ||
       mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread) {
     ImageBitmap* imageBitmap = nullptr;
-    if (NS_SUCCEEDED(UNWRAP_OBJECT(ImageBitmap, aObj, imageBitmap))) {
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(ImageBitmap, &obj, imageBitmap))) {
       return ImageBitmap::WriteStructuredClone(aWriter,
                                                GetSurfaces(),
                                                imageBitmap);
     }
   }
 
   // See if this is a StructuredCloneBlob object.
   {
     StructuredCloneBlob* holder = nullptr;
-    if (NS_SUCCEEDED(UNWRAP_OBJECT(StructuredCloneHolder, aObj, holder))) {
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(StructuredCloneHolder, &obj, holder))) {
       return holder->WriteStructuredClone(aCx, aWriter, this);
     }
   }
 
   // See if this is a WasmModule.
   if ((mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread ||
        mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread) &&
-      JS::IsWasmModuleObject(aObj)) {
-    RefPtr<JS::WasmModule> module = JS::GetWasmModule(aObj);
+      JS::IsWasmModuleObject(obj)) {
+    RefPtr<JS::WasmModule> module = JS::GetWasmModule(obj);
     MOZ_ASSERT(module);
 
     return WriteWasmModule(aWriter, module, this);
   }
 
   {
     nsCOMPtr<nsISupports> base = xpc::UnwrapReflectorToISupports(aObj);
     nsCOMPtr<nsIInputStream> inputStream = do_QueryInterface(base);
@@ -1169,19 +1173,21 @@ StructuredCloneHolder::CustomWriteTransf
                                                   JS::TransferableOwnership* aOwnership,
                                                   void** aContent,
                                                   uint64_t* aExtraData)
 {
   if (!mSupportsTransferring) {
     return false;
   }
 
+  JS::Rooted<JSObject*> obj(aCx, aObj);
+
   {
     MessagePort* port = nullptr;
-    nsresult rv = UNWRAP_OBJECT(MessagePort, aObj, port);
+    nsresult rv = UNWRAP_OBJECT(MessagePort, &obj, port);
     if (NS_SUCCEEDED(rv)) {
       // We use aExtraData to store the index of this new port identifier.
       *aExtraData = mPortIdentifiers.Length();
       MessagePortIdentifier* identifier = mPortIdentifiers.AppendElement();
 
       port->CloneAndDisentangle(*identifier);
 
       *aTag = SCTAG_DOM_MAP_MESSAGEPORT;
@@ -1189,32 +1195,32 @@ StructuredCloneHolder::CustomWriteTransf
       *aContent = nullptr;
 
       return true;
     }
 
     if (mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread ||
         mStructuredCloneScope == StructuredCloneScope::SameProcessDifferentThread) {
       OffscreenCanvas* canvas = nullptr;
-      rv = UNWRAP_OBJECT(OffscreenCanvas, aObj, canvas);
+      rv = UNWRAP_OBJECT(OffscreenCanvas, &obj, canvas);
       if (NS_SUCCEEDED(rv)) {
         MOZ_ASSERT(canvas);
 
         *aExtraData = 0;
         *aTag = SCTAG_DOM_CANVAS;
         *aOwnership = JS::SCTAG_TMO_CUSTOM;
         *aContent = canvas->ToCloneData();
         MOZ_ASSERT(*aContent);
         canvas->SetNeutered();
 
         return true;
       }
 
       ImageBitmap* bitmap = nullptr;
-      rv = UNWRAP_OBJECT(ImageBitmap, aObj, bitmap);
+      rv = UNWRAP_OBJECT(ImageBitmap, &obj, bitmap);
       if (NS_SUCCEEDED(rv)) {
         MOZ_ASSERT(bitmap);
 
         *aExtraData = 0;
         *aTag = SCTAG_DOM_IMAGEBITMAP;
         *aOwnership = JS::SCTAG_TMO_CUSTOM;
         *aContent = bitmap->ToCloneData().release();
         MOZ_ASSERT(*aContent);
--- a/dom/base/WebSocket.cpp
+++ b/dom/base/WebSocket.cpp
@@ -615,16 +615,20 @@ void
 WebSocketImpl::Disconnect()
 {
   if (mDisconnectingOrDisconnected) {
     return;
   }
 
   AssertIsOnTargetThread();
 
+  // DontKeepAliveAnyMore() and DisconnectInternal() can release the object. So
+  // hold a reference to this until the end of the method.
+  RefPtr<WebSocketImpl> kungfuDeathGrip = this;
+
   // Disconnect can be called from some control event (such as Notify() of
   // WorkerHolder). This will be schedulated before any other sync/async
   // runnable. In order to prevent some double Disconnect() calls, we use this
   // boolean.
   mDisconnectingOrDisconnected = true;
 
   // DisconnectInternal touches observers and nsILoadGroup and it must run on
   // the main thread.
@@ -636,20 +640,16 @@ WebSocketImpl::Disconnect()
       new DisconnectInternalRunnable(this);
     ErrorResult rv;
     runnable->Dispatch(Killing, rv);
     // XXXbz this seems totally broken.  We should be propagating this out, but
     // where to, exactly?
     rv.SuppressException();
   }
 
-  // DontKeepAliveAnyMore() can release the object. So hold a reference to this
-  // until the end of the method.
-  RefPtr<WebSocketImpl> kungfuDeathGrip = this;
-
   NS_ReleaseOnMainThread("WebSocketImpl::mChannel", mChannel.forget());
   NS_ReleaseOnMainThread("WebSocketImpl::mService", mService.forget());
 
   mWebSocket->DontKeepAliveAnyMore();
   mWebSocket->mImpl = nullptr;
 
   if (mWorkerPrivate && mWorkerHolder) {
     UnregisterWorkerHolder();
--- a/dom/base/nsAttrValue.cpp
+++ b/dom/base/nsAttrValue.cpp
@@ -1752,17 +1752,18 @@ nsAttrValue::ParseStyleAttribute(const n
     }
   }
 
   RefPtr<DeclarationBlock> decl;
   if (ownerDoc->GetStyleBackendType() == StyleBackendType::Servo) {
     RefPtr<URLExtraData> data = new URLExtraData(baseURI, docURI,
                                                  aElement->NodePrincipal());
     decl = ServoDeclarationBlock::FromCssText(aString, data,
-                                              ownerDoc->GetCompatibilityMode());
+                                              ownerDoc->GetCompatibilityMode(),
+                                              ownerDoc->CSSLoader());
   } else {
     css::Loader* cssLoader = ownerDoc->CSSLoader();
     nsCSSParser cssParser(cssLoader);
     decl = cssParser.ParseStyleAttribute(aString, docURI, baseURI,
                                          aElement->NodePrincipal());
   }
   if (!decl) {
     return false;
--- a/dom/base/nsContentSink.cpp
+++ b/dom/base/nsContentSink.cpp
@@ -46,18 +46,20 @@
 #include "nsIWebNavigation.h"
 #include "nsGenericHTMLElement.h"
 #include "nsHTMLDNSPrefetch.h"
 #include "nsIObserverService.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/ScriptLoader.h"
 #include "nsParserConstants.h"
 #include "nsSandboxFlags.h"
+#include "Link.h"
 
 using namespace mozilla;
+using namespace mozilla::dom;
 
 LazyLogModule gContentSinkLogModuleInfo("nscontentsink");
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsContentSink)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsContentSink)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsContentSink)
   NS_INTERFACE_MAP_ENTRY(nsICSSLoaderObserver)
@@ -451,16 +453,17 @@ nsContentSink::ProcessLinkHeader(const n
   nsAutoString href;
   nsAutoString rel;
   nsAutoString title;
   nsAutoString titleStar;
   nsAutoString type;
   nsAutoString media;
   nsAutoString anchor;
   nsAutoString crossOrigin;
+  nsAutoString as;
 
   crossOrigin.SetIsVoid(true);
 
   // copy to work buffer
   nsAutoString stringList(aLinkData);
 
   // put an extra null at the end
   stringList.Append(kNullCh);
@@ -633,30 +636,35 @@ nsContentSink::ProcessLinkHeader(const n
               anchor.StripWhitespace();
             }
           } else if (attr.LowerCaseEqualsLiteral("crossorigin")) {
             if (crossOrigin.IsVoid()) {
               crossOrigin.SetIsVoid(false);
               crossOrigin = value;
               crossOrigin.StripWhitespace();
             }
+          } else if (attr.LowerCaseEqualsLiteral("as")) {
+            if (as.IsEmpty()) {
+              as = value;
+              as.CompressWhitespace();
+            }
           }
         }
       }
     }
 
     if (endCh == kComma) {
       // hit a comma, process what we've got so far
 
       href.Trim(" \t\n\r\f"); // trim HTML5 whitespace
       if (!href.IsEmpty() && !rel.IsEmpty()) {
         rv = ProcessLink(anchor, href, rel,
                          // prefer RFC 5987 variant over non-I18zed version
                          titleStar.IsEmpty() ? title : titleStar,
-                         type, media, crossOrigin);
+                         type, media, crossOrigin, as);
       }
 
       href.Truncate();
       rel.Truncate();
       title.Truncate();
       type.Truncate();
       media.Truncate();
       anchor.Truncate();
@@ -668,48 +676,50 @@ nsContentSink::ProcessLinkHeader(const n
     start = ++end;
   }
 
   href.Trim(" \t\n\r\f"); // trim HTML5 whitespace
   if (!href.IsEmpty() && !rel.IsEmpty()) {
     rv = ProcessLink(anchor, href, rel,
                      // prefer RFC 5987 variant over non-I18zed version
                      titleStar.IsEmpty() ? title : titleStar,
-                     type, media, crossOrigin);
+                     type, media, crossOrigin, as);
   }
 
   return rv;
 }
 
 
 nsresult
 nsContentSink::ProcessLink(const nsAString& aAnchor, const nsAString& aHref,
                            const nsAString& aRel, const nsAString& aTitle,
                            const nsAString& aType, const nsAString& aMedia,
-                           const nsAString& aCrossOrigin)
+                           const nsAString& aCrossOrigin,
+                           const nsAString& aAs)
 {
   uint32_t linkTypes =
     nsStyleLinkElement::ParseLinkTypes(aRel);
 
   // The link relation may apply to a different resource, specified
   // in the anchor parameter. For the link relations supported so far,
   // we simply abort if the link applies to a resource different to the
   // one we've loaded
   if (!LinkContextIsOurDocument(aAnchor)) {
     return NS_OK;
   }
 
-  if (!nsContentUtils::PrefetchEnabled(mDocShell)) {
+  if (!nsContentUtils::PrefetchPreloadEnabled(mDocShell)) {
     return NS_OK;
   }
 
-  bool hasPrefetch = linkTypes & nsStyleLinkElement::ePREFETCH;
   // prefetch href if relation is "next" or "prefetch"
-  if (hasPrefetch || (linkTypes & nsStyleLinkElement::eNEXT)) {
-    PrefetchHref(aHref, mDocument, hasPrefetch);
+  if ((linkTypes & nsStyleLinkElement::eNEXT) ||
+      (linkTypes & nsStyleLinkElement::ePREFETCH) ||
+      (linkTypes & nsStyleLinkElement::ePRELOAD)) {
+    PrefetchPreloadHref(aHref, mDocument, linkTypes, aAs, aType, aMedia);
   }
 
   if (linkTypes & nsStyleLinkElement::ePRERENDER) {
     nsCOMPtr<nsIURI> href;
     nsresult rv = NS_NewURI(getter_AddRefs(href), aHref);
     if (NS_SUCCEEDED(rv)) {
       mDocument->PrerenderHref(href);
     }
@@ -834,30 +844,55 @@ nsContentSink::ProcessMETATag(nsIContent
     }
   }
 
   return rv;
 }
 
 
 void
-nsContentSink::PrefetchHref(const nsAString &aHref,
-                            nsINode *aSource,
-                            bool aExplicit)
+nsContentSink::PrefetchPreloadHref(const nsAString &aHref,
+                                   nsINode *aSource,
+                                   uint32_t aLinkTypes,
+                                   const nsAString& aAs,
+                                   const nsAString& aType,
+                                   const nsAString& aMedia)
 {
   nsCOMPtr<nsIPrefetchService> prefetchService(do_GetService(NS_PREFETCHSERVICE_CONTRACTID));
   if (prefetchService) {
     // construct URI using document charset
     auto encoding = mDocument->GetDocumentCharacterSet();
     nsCOMPtr<nsIURI> uri;
     NS_NewURI(getter_AddRefs(uri), aHref, encoding,
               mDocument->GetDocBaseURI());
     if (uri) {
       nsCOMPtr<nsIDOMNode> domNode = do_QueryInterface(aSource);
-      prefetchService->PrefetchURI(uri, mDocumentURI, domNode, aExplicit);
+      if (aLinkTypes & nsStyleLinkElement::ePRELOAD) {
+        nsAttrValue asAttr;
+        Link::ParseAsValue(aAs, asAttr);
+        nsContentPolicyType policyType = Link::AsValueToContentPolicy(asAttr);
+
+        if (policyType == nsIContentPolicy::TYPE_INVALID) {
+          // Ignore preload with a wrong or empty as attribute.
+          return;
+        }
+
+        nsAutoString mimeType;
+        nsAutoString notUsed;
+        nsContentUtils::SplitMimeType(aType, mimeType, notUsed);
+        if (!nsStyleLinkElement::CheckPreloadAttrs(asAttr, mimeType,
+                                                   aMedia,mDocument)) {
+          policyType = nsIContentPolicy::TYPE_INVALID;
+        }
+
+        prefetchService->PreloadURI(uri, mDocumentURI, domNode, policyType);
+      } else {
+        prefetchService->PrefetchURI(uri, mDocumentURI, domNode,
+                                     aLinkTypes & nsStyleLinkElement::ePREFETCH);
+      }
     }
   }
 }
 
 void
 nsContentSink::PrefetchDNS(const nsAString &aHref)
 {
   nsAutoString hostname;
--- a/dom/base/nsContentSink.h
+++ b/dom/base/nsContentSink.h
@@ -149,27 +149,30 @@ protected:
 
   nsresult ProcessHTTPHeaders(nsIChannel* aChannel);
   nsresult ProcessHeaderData(nsIAtom* aHeader, const nsAString& aValue,
                              nsIContent* aContent = nullptr);
   nsresult ProcessLinkHeader(const nsAString& aLinkData);
   nsresult ProcessLink(const nsAString& aAnchor,
                        const nsAString& aHref, const nsAString& aRel,
                        const nsAString& aTitle, const nsAString& aType,
-                       const nsAString& aMedia, const nsAString& aCrossOrigin);
+                       const nsAString& aMedia, const nsAString& aCrossOrigin,
+                       const nsAString& aAs);
 
   virtual nsresult ProcessStyleLink(nsIContent* aElement,
                                     const nsAString& aHref,
                                     bool aAlternate,
                                     const nsAString& aTitle,
                                     const nsAString& aType,
                                     const nsAString& aMedia);
 
-  void PrefetchHref(const nsAString &aHref, nsINode *aSource,
-                    bool aExplicit);
+  void PrefetchPreloadHref(const nsAString &aHref, nsINode *aSource,
+                           uint32_t aLinkTypes, const nsAString& aAs,
+                           const nsAString& aType,
+                           const nsAString& aMedia);
 
   // For PrefetchDNS() aHref can either be the usual
   // URI format or of the form "//www.hostname.com" without a scheme.
   void PrefetchDNS(const nsAString &aHref);
 
   // Gets the cache key (used to identify items in a cache) of the channel.
   nsresult GetChannelCacheKey(nsIChannel* aChannel, nsACString& aCacheKey);
 
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -7719,37 +7719,37 @@ nsContentUtils::GenerateUUIDInPlace(nsID
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
 bool
-nsContentUtils::PrefetchEnabled(nsIDocShell* aDocShell)
+nsContentUtils::PrefetchPreloadEnabled(nsIDocShell* aDocShell)
 {
   //
-  // SECURITY CHECK: disable prefetching from mailnews!
+  // SECURITY CHECK: disable prefetching and preloading from mailnews!
   //
   // walk up the docshell tree to see if any containing
   // docshell are of type MAIL.
   //
 
   if (!aDocShell) {
     return false;
   }
 
   nsCOMPtr<nsIDocShell> docshell = aDocShell;
   nsCOMPtr<nsIDocShellTreeItem> parentItem;
 
   do {
     uint32_t appType = 0;
     nsresult rv = docshell->GetAppType(&appType);
     if (NS_FAILED(rv) || appType == nsIDocShell::APP_TYPE_MAIL) {
-      return false; // do not prefetch, preconnect from mailnews
+      return false; // do not prefetch, preload, preconnect from mailnews
     }
 
     docshell->GetParent(getter_AddRefs(parentItem));
     if (parentItem) {
       docshell = do_QueryInterface(parentItem);
       if (!docshell) {
         NS_ERROR("cannot get a docshell from a treeItem!");
         return false;
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -1052,17 +1052,17 @@ public:
    */
   static void SandboxFlagsToString(uint32_t aFlags, nsAString& aString);
 
   /**
    * Helper function that generates a UUID.
    */
   static nsresult GenerateUUIDInPlace(nsID& aUUID);
 
-  static bool PrefetchEnabled(nsIDocShell* aDocShell);
+  static bool PrefetchPreloadEnabled(nsIDocShell* aDocShell);
 
   /**
    * Fill (with the parameters given) the localized string named |aKey| in
    * properties file |aFile|.
    */
 private:
   static nsresult FormatLocalizedString(PropertiesFile aFile,
                                         const char* aKey,
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -817,36 +817,26 @@ nsDOMClassInfo::PostCreatePrototype(JSCo
 
   // Make prototype delegation work correctly. Consider if a site sets
   // HTMLElement.prototype.foopy = function () { ... } Now, calling
   // document.body.foopy() needs to ensure that looking up foopy on
   // document.body's prototype will find the right function.
   JS::Rooted<JSObject*> global(cx, ::JS_GetGlobalForObject(cx, proto));
 
   // Only do this if the global object is a window.
-  // XXX Is there a better way to check this?
-  nsISupports *globalNative = XPConnect()->GetNativeOfWrapper(cx, global);
-  nsCOMPtr<nsPIDOMWindowInner> piwin = do_QueryInterface(globalNative);
-  if (!piwin) {
+  nsGlobalWindow* win;
+  if (NS_FAILED(UNWRAP_OBJECT(Window, &global, win))) {
+    // Not a window.
     return NS_OK;
   }
 
-  nsGlobalWindow *win = nsGlobalWindow::Cast(piwin);
   if (win->IsClosedOrClosing()) {
     return NS_OK;
   }
 
-  // If the window is in a different compartment than the global object, then
-  // it's likely that global is a sandbox object whose prototype is a window.
-  // Don't do anything in this case.
-  if (win->FastGetGlobalJSObject() &&
-      js::GetObjectCompartment(global) != js::GetObjectCompartment(win->FastGetGlobalJSObject())) {
-    return NS_OK;
-  }
-
   // Don't overwrite a property set by content.
   bool contentDefinedProperty;
   if (!::JS_AlreadyHasOwnUCProperty(cx, global, reinterpret_cast<const char16_t*>(mData->mNameUTF16),
                                     NS_strlen(mData->mNameUTF16),
                                     &contentDefinedProperty)) {
     return NS_ERROR_FAILURE;
   }
 
--- a/dom/base/nsDOMClassInfo.h
+++ b/dom/base/nsDOMClassInfo.h
@@ -138,35 +138,16 @@ const nsQueryInterfaceWithError
 do_QueryWrappedNative(nsIXPConnectWrappedNative *wrapper, JSObject *obj,
                       nsresult *aError)
 
 {
   return nsQueryInterfaceWithError(nsDOMClassInfo::GetNative(wrapper, obj),
                                    aError);
 }
 
-inline
-nsQueryInterface
-do_QueryWrapper(JSContext *cx, JSObject *obj)
-{
-  nsISupports *native =
-    nsDOMClassInfo::XPConnect()->GetNativeOfWrapper(cx, obj);
-  return nsQueryInterface(native);
-}
-
-inline
-nsQueryInterfaceWithError
-do_QueryWrapper(JSContext *cx, JSObject *obj, nsresult* error)
-{
-  nsISupports *native =
-    nsDOMClassInfo::XPConnect()->GetNativeOfWrapper(cx, obj);
-  return nsQueryInterfaceWithError(native, error);
-}
-
-
 typedef nsDOMClassInfo nsDOMGenericSH;
 
 // Makes sure that the wrapper is preserved if new properties are added.
 class nsEventTargetSH : public nsDOMGenericSH
 {
 protected:
   explicit nsEventTargetSH(nsDOMClassInfoData* aData) : nsDOMGenericSH(aData)
   {
--- a/dom/base/nsDOMNavigationTiming.h
+++ b/dom/base/nsDOMNavigationTiming.h
@@ -77,16 +77,24 @@ public:
   DOMTimeMilliSec GetLoadEventStart() const
   {
     return mLoadEventStart;
   }
   DOMTimeMilliSec GetLoadEventEnd() const
   {
     return mLoadEventEnd;
   }
+  DOMTimeMilliSec GetTimeToNonBlankPaint() const
+  {
+    if (mNonBlankPaintTimeStamp.IsNull()) {
+      return 0;
+    }
+
+    return TimeStampToDOMHighRes(mNonBlankPaintTimeStamp);
+  }
 
   enum class DocShellState : uint8_t {
     eActive,
     eInactive
   };
 
   void NotifyNavigationStart(DocShellState aDocShellState);
   void NotifyFetchStart(nsIURI* aURI, Type aNavigationType);
@@ -105,17 +113,17 @@ public:
   void NotifyDOMContentLoadedStart(nsIURI* aURI);
   void NotifyDOMContentLoadedEnd(nsIURI* aURI);
 
   void NotifyNonBlankPaintForRootContentDocument();
   void NotifyDocShellStateChanged(DocShellState aDocShellState);
 
   DOMTimeMilliSec TimeStampToDOM(mozilla::TimeStamp aStamp) const;
 
-  inline DOMHighResTimeStamp TimeStampToDOMHighRes(mozilla::TimeStamp aStamp)
+  inline DOMHighResTimeStamp TimeStampToDOMHighRes(mozilla::TimeStamp aStamp) const
   {
     mozilla::TimeDuration duration = aStamp - mNavigationStartTimeStamp;
     return duration.ToMilliseconds();
   }
 
 private:
   nsDOMNavigationTiming(const nsDOMNavigationTiming &) = delete;
   ~nsDOMNavigationTiming();
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -1276,16 +1276,26 @@ nsDOMWindowUtils::SendNativeTouchTap(int
       &nsIWidget::SynthesizeNativeTouchTap,
       LayoutDeviceIntPoint(aScreenX, aScreenY),
       aLongTap,
       aObserver));
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDOMWindowUtils::SuppressAnimation(bool aSuppress)
+{
+  nsIWidget* widget = GetWidget();
+  if (widget) {
+    widget->SuppressAnimation(aSuppress);
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDOMWindowUtils::ClearNativeTouchSequence(nsIObserver* aObserver)
 {
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget) {
     return NS_ERROR_FAILURE;
   }
 
   NS_DispatchToMainThread(
@@ -3212,26 +3222,26 @@ NS_IMETHODIMP
 nsDOMWindowUtils::GetFileId(JS::Handle<JS::Value> aFile, JSContext* aCx,
                             int64_t* _retval)
 {
   if (aFile.isPrimitive()) {
     *_retval = -1;
     return NS_OK;
   }
 
-  JSObject* obj = aFile.toObjectOrNull();
+  JS::Rooted<JSObject*> obj(aCx, aFile.toObjectOrNull());
 
   IDBMutableFile* mutableFile = nullptr;
-  if (NS_SUCCEEDED(UNWRAP_OBJECT(IDBMutableFile, obj, mutableFile))) {
+  if (NS_SUCCEEDED(UNWRAP_OBJECT(IDBMutableFile, &obj, mutableFile))) {
     *_retval = mutableFile->GetFileId();
     return NS_OK;
   }
 
   Blob* blob = nullptr;
-  if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, obj, blob))) {
+  if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, &obj, blob))) {
     *_retval = blob->GetFileId();
     return NS_OK;
   }
 
   *_retval = -1;
   return NS_OK;
 }
 
@@ -3239,20 +3249,20 @@ NS_IMETHODIMP
 nsDOMWindowUtils::GetFilePath(JS::HandleValue aFile, JSContext* aCx,
                               nsAString& _retval)
 {
   if (aFile.isPrimitive()) {
     _retval.Truncate();
     return NS_OK;
   }
 
-  JSObject* obj = aFile.toObjectOrNull();
+  JS::Rooted<JSObject*> obj(aCx, aFile.toObjectOrNull());
 
   File* file = nullptr;
-  if (NS_SUCCEEDED(UNWRAP_OBJECT(File, obj, file))) {
+  if (NS_SUCCEEDED(UNWRAP_OBJECT(File, &obj, file))) {
     nsString filePath;
     ErrorResult rv;
     file->GetMozFullPathInternal(filePath, rv);
     if (NS_WARN_IF(rv.Failed())) {
       return rv.StealNSResult();
     }
 
     _retval = filePath;
@@ -4226,17 +4236,17 @@ nsDOMWindowUtils::TriggerDeviceReset()
   ContentChild* cc = ContentChild::GetSingleton();
   if (cc) {
     cc->SendDeviceReset();
     return NS_OK;
   }
 
   GPUProcessManager* pm = GPUProcessManager::Get();
   if (pm) {
-    pm->TriggerDeviceResetForTesting();
+    pm->SimulateDeviceReset();
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::ForceUseCounterFlush(nsIDOMNode *aNode)
 {
   NS_ENSURE_ARG_POINTER(aNode);
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -2787,20 +2787,24 @@ nsDocument::InitCSP(nsIChannel* aChannel
   // directive, intersect the CSP sandbox flags with the existing flags. This
   // corresponds to the _least_ permissive policy.
   uint32_t cspSandboxFlags = SANDBOXED_NONE;
   rv = csp->GetCSPSandboxFlags(&cspSandboxFlags);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mSandboxFlags |= cspSandboxFlags;
 
-  if (cspSandboxFlags & SANDBOXED_ORIGIN) {
-    // If the new CSP sandbox flags do not have the allow-same-origin flag
-    // reset the document principal to a null principal
-    principal = NullPrincipal::Create();
+  // Probably the iframe sandbox attribute already caused the creation of a
+  // new NullPrincipal. Only create a new NullPrincipal if CSP requires so
+  // and no one has been created yet.
+  bool needNewNullPrincipal =
+    (cspSandboxFlags & SANDBOXED_ORIGIN) && !(mSandboxFlags & SANDBOXED_ORIGIN);
+  if (needNewNullPrincipal) {
+    principal = NullPrincipal::CreateWithInheritedAttributes(principal);
+    principal->SetCsp(csp);
     SetPrincipal(principal);
   }
 
   // ----- Enforce frame-ancestor policy on any applied policies
   nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
   if (docShell) {
     bool safeAncestry = false;
 
@@ -6017,17 +6021,18 @@ nsIDocument::CreateAttributeNS(const nsA
 
 bool
 nsDocument::CustomElementConstructor(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
 {
   JS::CallArgs args = JS::CallArgsFromVp(aArgc, aVp);
 
   JS::Rooted<JSObject*> global(aCx,
     JS_GetGlobalForObject(aCx, &args.callee()));
-  nsCOMPtr<nsPIDOMWindowInner> window = do_QueryWrapper(aCx, global);
+  RefPtr<nsGlobalWindow> window;
+  UNWRAP_OBJECT(Window, global, window);
   MOZ_ASSERT(window, "Should have a non-null window");
 
   nsDocument* document = static_cast<nsDocument*>(window->GetDoc());
 
   // Function name is the type of the custom element.
   JSString* jsFunName =
     JS_GetFunctionId(JS_ValueToFunction(aCx, args.calleev()));
   nsAutoJSString elemName;
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -136,16 +136,17 @@ GK_ATOM(aria_required, "aria-required")
 GK_ATOM(aria_selected, "aria-selected")
 GK_ATOM(aria_setsize, "aria-setsize")
 GK_ATOM(aria_sort, "aria-sort")
 GK_ATOM(aria_valuemax, "aria-valuemax")
 GK_ATOM(aria_valuemin, "aria-valuemin")
 GK_ATOM(aria_valuenow, "aria-valuenow")
 GK_ATOM(arrow, "arrow")
 GK_ATOM(article, "article")
+GK_ATOM(as, "as")
 GK_ATOM(ascending, "ascending")
 GK_ATOM(aside, "aside")
 GK_ATOM(aspectRatio, "aspect-ratio")
 GK_ATOM(assign, "assign")
 GK_ATOM(async, "async")
 GK_ATOM(attribute, "attribute")
 GK_ATOM(attributes, "attributes")
 GK_ATOM(attributeSet, "attribute-set")
--- a/dom/base/nsStyleLinkElement.cpp
+++ b/dom/base/nsStyleLinkElement.cpp
@@ -28,16 +28,24 @@
 #include "nsIDOMStyleSheet.h"
 #include "nsUnicharUtils.h"
 #include "nsCRT.h"
 #include "nsXPCOMCIDInternal.h"
 #include "nsUnicharInputStream.h"
 #include "nsContentUtils.h"
 #include "nsStyleUtil.h"
 #include "nsQueryObject.h"
+#include "nsIContentPolicyBase.h"
+#include "nsMimeTypes.h"
+#include "imgLoader.h"
+#include "MediaContainerType.h"
+#include "DecoderDoctorDiagnostics.h"
+#include "DecoderTraits.h"
+#include "MediaList.h"
+#include "nsAttrValueInlines.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 nsStyleLinkElement::nsStyleLinkElement()
   : mDontLoadStyle(false)
   , mUpdatesEnabled(true)
   , mLineNumber(1)
@@ -138,16 +146,18 @@ static uint32_t ToLinkMask(const nsAStri
   else if (aLink.EqualsLiteral("stylesheet"))
     return nsStyleLinkElement::eSTYLESHEET;
   else if (aLink.EqualsLiteral("next"))
     return nsStyleLinkElement::eNEXT;
   else if (aLink.EqualsLiteral("alternate"))
     return nsStyleLinkElement::eALTERNATE;
   else if (aLink.EqualsLiteral("preconnect"))
     return nsStyleLinkElement::ePRECONNECT;
+  else if (aLink.EqualsLiteral("preload"))
+    return nsStyleLinkElement::ePRELOAD;
   else if (aLink.EqualsLiteral("prerender"))
     return nsStyleLinkElement::ePRERENDER;
   else
     return 0;
 }
 
 uint32_t nsStyleLinkElement::ParseLinkTypes(const nsAString& aTypes)
 {
@@ -180,16 +190,136 @@ uint32_t nsStyleLinkElement::ParseLinkTy
   }
   if (inString) {
     nsContentUtils::ASCIIToLower(Substring(start, current), subString);
     linkMask |= ToLinkMask(subString);
   }
   return linkMask;
 }
 
+// We will use official mime-types from:
+// https://www.iana.org/assignments/media-types/media-types.xhtml#font
+// We do not support old deprecated mime-types for preload feature.
+// (We currectly do not support font/collection)
+static uint32_t StyleLinkElementFontMimeTypesNum = 5;
+static const char* StyleLinkElementFontMimeTypes[] = {
+  "font/otf",
+  "font/sfnt",
+  "font/ttf",
+  "font/woff",
+  "font/woff2"
+};
+
+bool
+IsFontMimeType(const nsAString& aType)
+{
+  if (aType.IsEmpty()) {
+    return true;
+  }
+  for (uint32_t i = 0; i < StyleLinkElementFontMimeTypesNum; i++) {
+    if (aType.EqualsASCII(StyleLinkElementFontMimeTypes[i])) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool
+nsStyleLinkElement::CheckPreloadAttrs(const nsAttrValue& aAs,
+                                      const nsAString& aType,
+                                      const nsAString& aMedia,
+                                      nsIDocument* aDocument)
+{
+  nsContentPolicyType policyType = Link::AsValueToContentPolicy(aAs);
+  if (policyType == nsIContentPolicy::TYPE_INVALID) {
+    return false;
+  }
+
+  // Check if media attribute is valid.
+  if (!aMedia.IsEmpty()) {
+    RefPtr<MediaList> mediaList = MediaList::Create(aDocument->GetStyleBackendType(),
+                                                    aMedia);
+    nsIPresShell* shell = aDocument->GetShell();
+    if (!shell) {
+      return false;
+    }
+
+    nsPresContext* presContext = shell->GetPresContext();
+    if (!presContext) {
+      return false;
+    }
+    if (!mediaList->Matches(presContext)) {
+      return false;
+    }
+  }
+
+  if (aType.IsEmpty()) {
+    return true;
+  }
+
+  nsString type = nsString(aType);
+  ToLowerCase(type);
+
+  if (policyType == nsIContentPolicy::TYPE_OTHER) {
+    return true;
+
+  } else if (policyType == nsIContentPolicy::TYPE_MEDIA) {
+    if (aAs.GetEnumValue() == DESTINATION_TRACK) {
+      if (type.EqualsASCII("text/vtt")) {
+        return true;
+      } else {
+        return false;
+      }
+    }
+    Maybe<MediaContainerType> mimeType = MakeMediaContainerType(aType);
+    if (!mimeType) {
+      return false;
+    }
+    DecoderDoctorDiagnostics diagnostics;
+    CanPlayStatus status = DecoderTraits::CanHandleContainerType(*mimeType,
+                                                                 &diagnostics);
+    // Preload if this return CANPLAY_YES and CANPLAY_MAYBE.
+    if (status == CANPLAY_NO) {
+      return false;
+    } else {
+      return true;
+    }
+
+  } else if (policyType == nsIContentPolicy::TYPE_FONT) {
+    if (IsFontMimeType(type)) {
+      return true;
+    } else {
+      return false;
+    }
+
+  } else if (policyType == nsIContentPolicy::TYPE_IMAGE) {
+    if (imgLoader::SupportImageWithMimeType(NS_ConvertUTF16toUTF8(type).get(),
+                                            AcceptedMimeTypes::IMAGES_AND_DOCUMENTS)) {
+      return true;
+    } else {
+      return false;
+    }
+
+  } else if (policyType == nsIContentPolicy::TYPE_SCRIPT) {
+    if (nsContentUtils::IsJavascriptMIMEType(type)) {
+      return true;
+    } else {
+      return false;
+    }
+
+  } else if (policyType == nsIContentPolicy::TYPE_STYLESHEET) {
+    if (type.EqualsASCII("text/css")) {
+      return true;
+    } else {
+      return false;
+    }
+  }
+  return false;
+}
+
 NS_IMETHODIMP
 nsStyleLinkElement::UpdateStyleSheet(nsICSSLoaderObserver* aObserver,
                                      bool* aWillNotify,
                                      bool* aIsAlternate,
                                      bool aForceReload)
 {
   if (aForceReload) {
     // We remove this stylesheet from the cache to load a new version.
--- a/dom/base/nsStyleLinkElement.h
+++ b/dom/base/nsStyleLinkElement.h
@@ -15,16 +15,17 @@
 
 #include "mozilla/Attributes.h"
 #include "mozilla/CORSMode.h"
 #include "mozilla/StyleSheetInlines.h"
 #include "mozilla/net/ReferrerPolicy.h"
 #include "nsCOMPtr.h"
 #include "nsIStyleSheetLinkingElement.h"
 #include "nsTArray.h"
+#include "nsAttrValue.h"
 
 class nsIDocument;
 class nsIURI;
 
 namespace mozilla {
 class CSSStyleSheet;
 namespace dom {
 class ShadowRoot;
@@ -58,22 +59,26 @@ public:
 
   enum RelValue {
     ePREFETCH =     0x00000001,
     eDNS_PREFETCH = 0x00000002,
     eSTYLESHEET =   0x00000004,
     eNEXT =         0x00000008,
     eALTERNATE =    0x00000010,
     ePRECONNECT =   0x00000020,
-    ePRERENDER =    0x00000040
+    ePRERENDER =    0x00000040,
+    ePRELOAD =      0x00000080
   };
 
   // The return value is a bitwise or of 0 or more RelValues.
   static uint32_t ParseLinkTypes(const nsAString& aTypes);
 
+  static bool CheckPreloadAttrs(const nsAttrValue& aAs, const nsAString& aType,
+                                const nsAString& aMedia, nsIDocument* aDocument);
+
   void UpdateStyleSheetInternal()
   {
     UpdateStyleSheetInternal(nullptr, nullptr);
   }
 protected:
   /**
    * @param aOldDocument should be non-null only if we're updating because we
    *                     removed the node from the document.
--- a/dom/base/nsTreeSanitizer.cpp
+++ b/dom/base/nsTreeSanitizer.cpp
@@ -147,16 +147,17 @@ nsIAtom** const kElementsHTML[] = {
 
 nsIAtom** const kAttributesHTML[] = {
   &nsGkAtoms::abbr,
   &nsGkAtoms::accept,
   &nsGkAtoms::acceptcharset,
   &nsGkAtoms::accesskey,
   &nsGkAtoms::action,
   &nsGkAtoms::alt,
+  &nsGkAtoms::as,
   &nsGkAtoms::autocomplete,
   &nsGkAtoms::autofocus,
   &nsGkAtoms::autoplay,
   &nsGkAtoms::axis,
   &nsGkAtoms::_char,
   &nsGkAtoms::charoff,
   &nsGkAtoms::charset,
   &nsGkAtoms::checked,
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -604,16 +604,18 @@ skip-if = toolkit == 'android'
 [test_bug1163743.html]
 [test_bug1165501.html]
 [test_bug1187157.html]
 [test_bug1198095.html]
 [test_bug1238440.html]
 [test_bug1250148.html]
 [test_bug1259588.html]
 [test_bug1268962.html]
+[test_bug1222633.html]
+[test_bug1222633_link_update.html]
 [test_bug1274806.html]
 [test_bug1281963.html]
 [test_bug1295852.html]
 [test_bug1307730.html]
 [test_bug1308069.html]
 [test_bug1314032.html]
 [test_bug1318303.html]
 [test_caretPositionFromPoint.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_bug1222633.html
@@ -0,0 +1,130 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1222633
+-->
+<head>
+  <title>Test for Bug 1222633</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1222633">Mozilla Bug 1222633</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 1222633 **/
+
+function testPreloadEvent(url, crossorigin, expectLoad) {
+  return new Promise((resolve) => {
+    var link = document.createElement("LINK");
+    link.setAttribute("rel", "preload");
+    link.setAttribute("href", url);
+    link.setAttribute("as", "fetch");
+    if (crossorigin) {
+      link.setAttribute("crossorigin", "");
+    }
+
+    link.addEventListener("load", () => {
+      ok(expectLoad, "not expecting load event for " + url);
+      link.remove();
+      resolve();
+    });
+    link.addEventListener("error", () => {
+      ok(!expectLoad, "not expecting error event for " + url);
+      link.remove();
+      resolve();
+    });
+    document.head.appendChild(link);
+  });
+}
+
+function testCancelPreloadNotCrash(url) {
+  var ios = SpecialPowers.Cc["@mozilla.org/network/io-service;1"].
+            getService(SpecialPowers.Ci.nsIIOService);
+  var prefetch = SpecialPowers.Cc["@mozilla.org/prefetch-service;1"].
+                   getService(SpecialPowers.Ci.nsIPrefetchService);
+
+  var link = document.createElement("LINK");
+  link.setAttribute("rel", "preload");
+  link.setAttribute("href", url);
+  link.setAttribute("as", "fetch");
+  document.head.appendChild(link);
+
+  // Not actually verifying any value, just to ensure cancelPrefetchPreload
+  // won't cause crash.
+  prefetch.cancelPrefetchPreloadURI(ios.newURI(url, null, null), link);
+}
+
+function testChangePrefetchToPreload(url) {
+  return new Promise((resolve) => {
+    var preloaded = false;
+    var link = document.createElement("LINK");
+    link.setAttribute("rel", "prefetch");
+    link.setAttribute("href", url);
+    link.setAttribute("as", "fetch");
+
+    link.addEventListener("load", () => {
+      ok(preloaded, "this will happen only on a preload");
+      ok(true, "not expecting load event for " + url);
+      link.remove();
+      resolve();
+    });
+    link.addEventListener("error", () => {
+      ok(false, "not expecting error event for " + url);
+      link.remove();
+      resolve();
+    });
+    document.head.appendChild(link);
+    preloaded = true;
+    link.setAttribute("rel", "preload");
+  })
+};
+
+const SJS_PATH = window.location.pathname.replace(/[^/]+$/, "file_bug1268962.sjs");
+const SAME_ORIGIN = "http://mochi.test:8888" + SJS_PATH;
+const CROSS_ORIGIN = "http://example.com" + SJS_PATH;
+
+SimpleTest.waitForExplicitFinish();
+
+// test same origin
+testPreloadEvent(SAME_ORIGIN + "?statusCode=200&cacheControl=no-cache", false, false)
+.then(() => testPreloadEvent(SAME_ORIGIN + "?statusCode=404&cacheControl=no-cache", false, false))
+.then(() => testPreloadEvent(SAME_ORIGIN + "?statusCode=200&cacheControl=max-age%3D120", false, true))
+.then(() => testPreloadEvent(SAME_ORIGIN + "?statusCode=404&cacheControl=max-age%3D120", false, false))
+
+// test cross origin without CORS
+.then(() => testPreloadEvent(CROSS_ORIGIN + "?statusCode=200&cacheControl=no-cache", false, true))
+.then(() => testPreloadEvent(CROSS_ORIGIN + "?statusCode=404&cacheControl=no-cache", false, true))
+.then(() => testPreloadEvent(CROSS_ORIGIN + "?statusCode=200&cacheControl=max-age%3D120", false, true))
+.then(() => testPreloadEvent(CROSS_ORIGIN + "?statusCode=404&cacheControl=max-age%3D120", false, true))
+
+// test cross origin by redirection without CORS
+.then(() => testPreloadEvent(SAME_ORIGIN + "?redirect=crossorigin&statusCode=200&cacheControl=no-cache", false, true))
+.then(() => testPreloadEvent(SAME_ORIGIN + "?redirect=crossorigin&statusCode=404&cacheControl=no-cache", false, true))
+.then(() => testPreloadEvent(SAME_ORIGIN + "?redirect=crossorigin&statusCode=200&cacheControl=max-age%3D120", false, true))
+.then(() => testPreloadEvent(SAME_ORIGIN + "?redirect=crossorigin&statusCode=404&cacheControl=max-age%3D120", false, true))
+
+// test cross origin with CORS request but no CORS response
+.then(() => testPreloadEvent(CROSS_ORIGIN + "?statusCode=200&cacheControl=no-cache", true, true))
+.then(() => testPreloadEvent(CROSS_ORIGIN + "?statusCode=404&cacheControl=no-cache", true, true))
+.then(() => testPreloadEvent(CROSS_ORIGIN + "?statusCode=200&cacheControl=max-age%3D120", true, true))
+.then(() => testPreloadEvent(CROSS_ORIGIN + "?statusCode=404&cacheControl=max-age%3D120", true, true))
+
+// test cross origin with CORS request and CORS response
+.then(() => testPreloadEvent(CROSS_ORIGIN + "?statusCode=200&cacheControl=no-cache&allowOrigin=*", true, false))
+.then(() => testPreloadEvent(CROSS_ORIGIN + "?statusCode=404&cacheControl=no-cache&allowOrigin=*", true, false))
+.then(() => testPreloadEvent(CROSS_ORIGIN + "?statusCode=200&cacheControl=max-age%3D120&allowOrigin=*", true, true))
+.then(() => testPreloadEvent(CROSS_ORIGIN + "?statusCode=404&cacheControl=max-age%3D120&allowOrigin=*", true, false))
+.then(() => testChangePrefetchToPreload(SAME_ORIGIN + "?statusCode=200&cacheControl=max-age%3D120", false, true))
+
+// test the crash issue: https://bugzilla.mozilla.org/show_bug.cgi?id=1294159
+.then(() => testCancelPreloadNotCrash(SAME_ORIGIN + "?statusCode=200&cacheControl=max-age%3D120"))
+
+.catch((err) => ok(false, "promise rejected: " + err))
+.then(() => SimpleTest.finish());
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_bug1222633_link_update.html
@@ -0,0 +1,139 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1222633
+-->
+<head>
+  <title>Test for Bug 1222633</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1222633">Mozilla Bug 1222633</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 1222633 **/
+
+// Test changing as attribute from an empty value to "foo" to "image". The empty value and "foo" will
+// not trigger any event and "image" should trigger an load event.
+function testPreloadEventAsAttributeChange(url) {
+  return new Promise((resolve) => {
+    var link = document.createElement("LINK");
+    link.setAttribute("rel", "preload");
+    link.setAttribute("href", url);
+
+    link.addEventListener("load", () => {
+      ok(link.as == "image", "Only image will trigger a load event");
+      link.remove();
+      resolve();
+    });
+    link.addEventListener("error", () => {
+      ok(false, "We should not get an error event.");
+    });
+
+    document.head.appendChild(link);
+    link.setAttribute("as", "foo");
+    link.setAttribute("as", "image");
+  });
+}
+
+function testPreloadEventAttributeChange(url, attr, value1, value2, expectLoad1, expectLoad2) {
+  return new Promise((resolve) => {
+    var count = 0;
+    var link = document.createElement("LINK");
+    link.setAttribute("rel", "preload");
+    link.setAttribute("href", url);
+    link.setAttribute("as", "image");
+
+    link.setAttribute(attr, value1);
+
+    link.addEventListener("load", () => {
+      count++;
+      if (count == 1) {
+        ok(expectLoad1, "expecting first load event for " + url);
+        link.setAttribute(attr, value2);
+      } else {
+        ok(expectLoad2, "expecting second load event for " + url);
+      }
+
+      if (count == 2) {
+        link.remove();
+        resolve();
+      }
+    });
+    link.addEventListener("error", () => {
+      count++;
+      if (count == 1) {
+        ok(!expectLoad1, "expecting first error event for " + url);
+        link.setAttribute(attr, value2);
+      } else {
+        ok(!expectLoad2, "expecting second error event for " + url);
+      }
+      if (count == 2) {
+        link.remove();
+        resolve();
+      }
+    });
+    document.head.appendChild(link);
+  });
+}
+
+function testPreloadEventSetCrossOrigin(url) {
+  return new Promise((resolve) => {
+    var link = document.createElement("LINK");
+    link.setAttribute("rel", "preload");
+    link.setAttribute("href", url);
+    link.setAttribute("as", "fetch");
+    count = 0;
+
+    link.addEventListener("load", () => {
+      count++;
+      if ((count == 1) || (count == 3) || (count == 5)) {
+        ok(true, "expecting " + count + ". load event for " + url);
+      } else {
+        ok(false, "expecting " + count + ". event for " + url);
+      }
+      if ((count == 1) || (count == 3)) {
+        link.setAttribute("crossorigin", "");
+      } else {
+        link.remove();
+        resolve();
+      }
+    });
+
+    link.addEventListener("error", () => {
+      count++;
+      if ((count == 2) || (count == 4)) {
+        ok(true, "expecting " + count + ". error event for " + url);
+      } else {
+        ok(false, "expecting " + count + ". error event for " + url);
+      }
+      link.removeAttribute("crossorigin");
+    });
+    document.head.appendChild(link);
+  });
+}
+
+const SJS_PATH = window.location.pathname.replace(/[^/]+$/, "file_bug1268962.sjs");
+const SAME_ORIGIN = "http://mochi.test:8888" + SJS_PATH;
+const CROSS_ORIGIN = "http://example.com" + SJS_PATH;
+
+SimpleTest.waitForExplicitFinish();
+
+// Test changing as parameter from a wrong to a correct one.
+testPreloadEventAsAttributeChange(SAME_ORIGIN + "?statusCode=200&cacheControl=max-age%3D120")
+// Test changing type parameter from a wrong to a correct one for given as parameter.
+.then(() => testPreloadEventAttributeChange(SAME_ORIGIN + "?statusCode=200&cacheControl=max-age%3D120", "type", "text/vtt", "image/png", false, true))
+// Test changing media parameter from a wrong to a correct one.
+.then(() => testPreloadEventAttributeChange(SAME_ORIGIN + "?statusCode=200&cacheControl=max-age%3D120", "media", "foo", "all", false, true))
+// Test changing crossorigin parameter.
+.then(() => testPreloadEventSetCrossOrigin(CROSS_ORIGIN + "?statusCode=404&cacheControl=max-age%3D120&allowOrigin=*"))
+
+.catch((err) => ok(false, "promise rejected: " + err))
+.then(() => SimpleTest.finish());
+
+</script>
+</body>
+</html>
--- a/dom/base/test/test_bug1268962.html
+++ b/dom/base/test/test_bug1268962.html
@@ -45,19 +45,19 @@ function testCancelPrefetchNotCrash(url)
   var prefetch = SpecialPowers.Cc["@mozilla.org/prefetch-service;1"].
                    getService(SpecialPowers.Ci.nsIPrefetchService);
 
   var link = document.createElement("LINK");
   link.setAttribute("rel", "prefetch");
   link.setAttribute("href", url);
   document.head.appendChild(link);
 
-  // Not actually verifying any value, just to ensure cancelPrefetch
+  // Not actually verifying any value, just to ensure cancelPrefetchPreload
   // won't cause crash.
-  prefetch.cancelPrefetchURI(ios.newURI(url), link);
+  prefetch.cancelPrefetchPreloadURI(ios.newURI(url), link);
 }
 
 const SJS_PATH = window.location.pathname.replace(/[^/]+$/, "file_bug1268962.sjs");
 const SAME_ORIGIN = "http://mochi.test:8888" + SJS_PATH;
 const CROSS_ORIGIN = "http://example.com" + SJS_PATH;
 
 SimpleTest.waitForExplicitFinish();
 
--- a/dom/base/test/unit/test_cancelPrefetch.js
+++ b/dom/base/test/unit/test_cancelPrefetch.js
@@ -49,86 +49,86 @@ add_test(function test_cancel1() {
   }
 
   do_check_true(didFail == 1, 'Prefetching the same request with the same ' +
                               'node fails.');
 
   do_check_true(prefetch.hasMoreElements(), 'There is still request in ' +
                                             'the queue');
 
-  prefetch.cancelPrefetchURI(uri, node1);
+  prefetch.cancelPrefetchPreloadURI(uri, node1);
 
   do_check_false(prefetch.hasMoreElements(), 'There is no request in the ' +
                                              'queue');
   run_next_test();
 });
 
 add_test(function test_cancel2() {
   // Prefetch a uri with 2 different nodes. There should be 2 request
   // in the queue and canceling one will not cancel the other.
 
   var uri = ios.newURI("http://localhost/1");
   prefetch.prefetchURI(uri, uri, node1, true);
   prefetch.prefetchURI(uri, uri, node2, true);
 
   do_check_true(prefetch.hasMoreElements(), 'There are requests in the queue');
 
-  prefetch.cancelPrefetchURI(uri, node1);
+  prefetch.cancelPrefetchPreloadURI(uri, node1);
 
   do_check_true(prefetch.hasMoreElements(), 'There is still one more request ' +
                                             'in the queue');
 
-  prefetch.cancelPrefetchURI(uri, node2);
+  prefetch.cancelPrefetchPreloadURI(uri, node2);
 
   do_check_false(prefetch.hasMoreElements(), 'There is no request in the queue');
   run_next_test();
 });
 
 add_test(function test_cancel3() {
   // Request a prefetch of a uri. Trying to cancel a prefetch for the same uri
   // with a different node will fail.
   var uri = ios.newURI("http://localhost/1");
   prefetch.prefetchURI(uri, uri, node1, true);
 
   do_check_true(prefetch.hasMoreElements(), 'There is a request in the queue');
 
   var didFail = 0;
 
   try {
-    prefetch.cancelPrefetchURI(uri, node2);
+    prefetch.cancelPrefetchPreloadURI(uri, node2, true);
   } catch(e) {
     didFail = 1;
   }
   do_check_true(didFail == 1, 'Canceling the request failed');
 
   do_check_true(prefetch.hasMoreElements(), 'There is still a request ' +
                                             'in the queue');
 
-  prefetch.cancelPrefetchURI(uri, node1);
+  prefetch.cancelPrefetchPreloadURI(uri, node1);
   do_check_false(prefetch.hasMoreElements(), 'There is no request in the queue');
   run_next_test();
 });
 
 add_test(function test_cancel4() {
   // Request a prefetch of a uri. Trying to cancel a prefetch for a different uri
   // with the same node will fail.
   var uri1 = ios.newURI("http://localhost/1");
   var uri2 = ios.newURI("http://localhost/2");
   prefetch.prefetchURI(uri1, uri1, node1, true);
 
   do_check_true(prefetch.hasMoreElements(), 'There is a request in the queue');
 
   var didFail = 0;
 
   try {
-    prefetch.cancelPrefetchURI(uri2, node1);
+    prefetch.cancelPrefetchPreloadURI(uri2, node1);
   } catch(e) {
     didFail = 1;
   }
   do_check_true(didFail == 1, 'Canceling the request failed');
 
   do_check_true(prefetch.hasMoreElements(), 'There is still a request ' +
                                             'in the queue');
 
-  prefetch.cancelPrefetchURI(uri1, node1);
+  prefetch.cancelPrefetchPreloadURI(uri1, node1);
   do_check_false(prefetch.hasMoreElements(), 'There is no request in the queue');
   run_next_test();
 });
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -1711,19 +1711,20 @@ XrayResolveOwnProperty(JSContext* cx, JS
     // explicitly. So we check if we're running in such a scope, and if so,
     // whether the wrappee is a bound element. If it is, we do a lookup via
     // specialized XBL machinery.
     //
     // While we have to do some sketchy walking through content land, we should
     // be protected by read-only/non-configurable properties, and any functions
     // we end up with should _always_ be living in our own scope (the XBL scope).
     // Make sure to assert that.
+    JS::Rooted<JSObject*> maybeElement(cx, obj);
     Element* element;
     if (xpc::ObjectScope(wrapper)->IsContentXBLScope() &&
-        NS_SUCCEEDED(UNWRAP_OBJECT(Element, obj, element))) {
+        NS_SUCCEEDED(UNWRAP_OBJECT(Element, &maybeElement, element))) {
       if (!nsContentUtils::LookupBindingMember(cx, element, id, desc)) {
         return false;
       }
 
       DEBUG_CheckXBLLookup(cx, desc.address());
 
       if (desc.object()) {
         // XBL properties shouldn't be cached on the holder, as they might be
@@ -2301,24 +2302,25 @@ ReparentWrapper(JSContext* aCx, JS::Hand
       copyTo = aObj;
     }
 
     if (!copyTo || !JS_CopyPropertiesFrom(aCx, copyTo, propertyHolder)) {
       MOZ_CRASH();
     }
   }
 
+  JS::Rooted<JSObject*> maybeObjLC(aCx, aObj);
   nsObjectLoadingContent* htmlobject;
-  nsresult rv = UNWRAP_OBJECT(HTMLObjectElement, aObj, htmlobject);
+  nsresult rv = UNWRAP_OBJECT(HTMLObjectElement, &maybeObjLC, htmlobject);
   if (NS_FAILED(rv)) {
     rv = UnwrapObject<prototypes::id::HTMLEmbedElement,
-                      HTMLSharedObjectElement>(aObj, htmlobject);
+                      HTMLSharedObjectElement>(&maybeObjLC, htmlobject);
     if (NS_FAILED(rv)) {
       rv = UnwrapObject<prototypes::id::HTMLAppletElement,
-                        HTMLSharedObjectElement>(aObj, htmlobject);
+                        HTMLSharedObjectElement>(&maybeObjLC, htmlobject);
       if (NS_FAILED(rv)) {
         htmlobject = nullptr;
       }
     }
   }
   if (htmlobject) {
     htmlobject->SetupProtoChain(aCx, aObj);
   }
@@ -2373,18 +2375,20 @@ GlobalObject::GetAsSupports() const
   // Remove everything below here once all our global objects are using new
   // bindings.  If that ever happens; it would need to include Sandbox and
   // BackstagePass.
 
   // See whether mGlobalJSObject is an XPCWrappedNative.  This will redo the
   // IsWrapper bit above and the UnwrapDOMObjectToISupports in the case when
   // we're not actually an XPCWrappedNative, but this should be a rare-ish case
   // anyway.
-  mGlobalObject = xpc::UnwrapReflectorToISupports(mGlobalJSObject);
-  if (mGlobalObject) {
+  nsCOMPtr<nsISupports> supp = xpc::UnwrapReflectorToISupports(mGlobalJSObject);
+  if (supp) {
+    // See documentation for mGlobalJSObject for why this assignment is OK.
+    mGlobalObject = supp;
     return mGlobalObject;
   }
 
   // And now a final hack.  Sandbox is not a reflector, but it does have an
   // nsIGlobalObject hanging out in its private slot.  Handle that case here,
   // (though again, this will do the useless UnwrapDOMObjectToISupports if we
   // got here for something that is somehow not a DOM object, not an
   // XPCWrappedNative _and_ not a Sandbox).
@@ -2898,19 +2902,26 @@ GenericBindingGetter(JSContext* cx, unsi
   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
   const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
   prototypes::ID protoID = static_cast<prototypes::ID>(info->protoID);
   if (!args.thisv().isObject()) {
     return ThrowInvalidThis(cx, args, false, protoID);
   }
   JS::Rooted<JSObject*> obj(cx, &args.thisv().toObject());
 
+  // NOTE: we want to leave obj in its initial compartment, so don't want to
+  // pass it to UnwrapObject.
+  JS::Rooted<JSObject*> rootSelf(cx, obj);
   void* self;
   {
-    nsresult rv = UnwrapObject<void>(obj, self, protoID, info->depth);
+    binding_detail::MutableObjectHandleWrapper wrapper(&rootSelf);
+    nsresult rv = binding_detail::UnwrapObjectInternal<void, true>(wrapper,
+                                                                   self,
+                                                                   protoID,
+                                                                   info->depth);
     if (NS_FAILED(rv)) {
       return ThrowInvalidThis(cx, args,
                               rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO,
                               protoID);
     }
   }
 
   MOZ_ASSERT(info->type() == JSJitInfo::Getter);
@@ -2937,19 +2948,26 @@ GenericPromiseReturningBindingGetter(JSC
   prototypes::ID protoID = static_cast<prototypes::ID>(info->protoID);
   if (!args.thisv().isObject()) {
     ThrowInvalidThis(cx, args, false, protoID);
     return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee),
                                      args.rval());
   }
   JS::Rooted<JSObject*> obj(cx, &args.thisv().toObject());
 
+  // NOTE: we want to leave obj in its initial compartment, so don't want to
+  // pass it to UnwrapObject.
+  JS::Rooted<JSObject*> rootSelf(cx, obj);
   void* self;
   {
-    nsresult rv = UnwrapObject<void>(obj, self, protoID, info->depth);
+    binding_detail::MutableObjectHandleWrapper wrapper(&rootSelf);
+    nsresult rv = binding_detail::UnwrapObjectInternal<void, true>(wrapper,
+                                                                   self,
+                                                                   protoID,
+                                                                   info->depth);
     if (NS_FAILED(rv)) {
       ThrowInvalidThis(cx, args, rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO,
                        protoID);
       return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee),
                                        args.rval());
     }
   }
   MOZ_ASSERT(info->type() == JSJitInfo::Getter);
@@ -2974,19 +2992,26 @@ GenericBindingSetter(JSContext* cx, unsi
   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
   const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
   prototypes::ID protoID = static_cast<prototypes::ID>(info->protoID);
   if (!args.thisv().isObject()) {
     return ThrowInvalidThis(cx, args, false, protoID);
   }
   JS::Rooted<JSObject*> obj(cx, &args.thisv().toObject());
 
+  // NOTE: we want to leave obj in its initial compartment, so don't want to
+  // pass it to UnwrapObject.
+  JS::Rooted<JSObject*> rootSelf(cx, obj);
   void* self;
   {
-    nsresult rv = UnwrapObject<void>(obj, self, protoID, info->depth);
+    binding_detail::MutableObjectHandleWrapper wrapper(&rootSelf);
+    nsresult rv = binding_detail::UnwrapObjectInternal<void, true>(wrapper,
+                                                                   self,
+                                                                   protoID,
+                                                                   info->depth);
     if (NS_FAILED(rv)) {
       return ThrowInvalidThis(cx, args,
                               rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO,
                               protoID);
     }
   }
   if (args.length() == 0) {
     return ThrowNoSetterArg(cx, protoID);
@@ -3009,19 +3034,26 @@ GenericBindingMethod(JSContext* cx, unsi
   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
   const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(args.calleev());
   prototypes::ID protoID = static_cast<prototypes::ID>(info->protoID);
   if (!args.thisv().isObject()) {
     return ThrowInvalidThis(cx, args, false, protoID);
   }
   JS::Rooted<JSObject*> obj(cx, &args.thisv().toObject());
 
+  // NOTE: we want to leave obj in its initial compartment, so don't want to
+  // pass it to UnwrapObject.
+  JS::Rooted<JSObject*> rootSelf(cx, obj);
   void* self;
   {
-    nsresult rv = UnwrapObject<void>(obj, self, protoID, info->depth);
+    binding_detail::MutableObjectHandleWrapper wrapper(&rootSelf);
+    nsresult rv = binding_detail::UnwrapObjectInternal<void, true>(wrapper,
+                                                                   self,
+                                                                   protoID,
+                                                                   info->depth);
     if (NS_FAILED(rv)) {
       return ThrowInvalidThis(cx, args,
                               rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO,
                               protoID);
     }
   }
   MOZ_ASSERT(info->type() == JSJitInfo::Method);
   JSJitMethodOp method = info->method;
@@ -3047,19 +3079,26 @@ GenericPromiseReturningBindingMethod(JSC
   prototypes::ID protoID = static_cast<prototypes::ID>(info->protoID);
   if (!args.thisv().isObject()) {
     ThrowInvalidThis(cx, args, false, protoID);
     return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee),
                                      args.rval());
   }
   JS::Rooted<JSObject*> obj(cx, &args.thisv().toObject());
 
+  // NOTE: we want to leave obj in its initial compartment, so don't want to
+  // pass it to UnwrapObject.
+  JS::Rooted<JSObject*> rootSelf(cx, obj);
   void* self;
   {
-    nsresult rv = UnwrapObject<void>(obj, self, protoID, info->depth);
+    binding_detail::MutableObjectHandleWrapper wrapper(&rootSelf);
+    nsresult rv = binding_detail::UnwrapObjectInternal<void, true>(wrapper,
+                                                                   self,
+                                                                   protoID,
+                                                                   info->depth);
     if (NS_FAILED(rv)) {
       ThrowInvalidThis(cx, args, rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO,
                        protoID);
       return ConvertExceptionToPromise(cx, xpc::XrayAwareCalleeGlobal(callee),
                                        args.rval());
     }
   }
   MOZ_ASSERT(info->type() == JSJitInfo::Method);
@@ -3227,17 +3266,17 @@ UnwrapArgImpl(JSContext* cx,
               JS::Handle<JSObject*> src,
               const nsIID &iid,
               void **ppArg)
 {
   if (!NS_IsMainThread()) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  nsISupports *iface = xpc::UnwrapReflectorToISupports(src);
+  nsCOMPtr<nsISupports> iface = xpc::UnwrapReflectorToISupports(src);
   if (iface) {
     if (NS_FAILED(iface->QueryInterface(iid, ppArg))) {
       return NS_ERROR_XPC_BAD_CONVERT_JS;
     }
 
     return NS_OK;
   }
 
@@ -3257,16 +3296,50 @@ UnwrapArgImpl(JSContext* cx,
   // We need to go through the QueryInterface logic to make this return
   // the right thing for the various 'special' interfaces; e.g.
   // nsIPropertyBag. We must use AggregatedQueryInterface in cases where
   // there is an outer to avoid nasty recursion.
   return wrappedJS->QueryInterface(iid, ppArg);
 }
 
 nsresult
+UnwrapXPConnectImpl(JSContext* cx,
+                    JS::MutableHandle<JS::Value> src,
+                    const nsIID &iid,
+                    void **ppArg)
+{
+  if (!NS_IsMainThread()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  MOZ_ASSERT(src.isObject());
+  // Unwrap ourselves, because we're going to want access to the unwrapped
+  // object.
+  JS::Rooted<JSObject*> obj(cx,
+                            js::CheckedUnwrap(&src.toObject(),
+                                              /* stopAtWindowProxy = */ false));
+  if (!obj) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsCOMPtr<nsISupports> iface = xpc::UnwrapReflectorToISupports(obj);
+  if (!iface) {
+    return NS_ERROR_XPC_BAD_CONVERT_JS;
+  }
+
+  if (NS_FAILED(iface->QueryInterface(iid, ppArg))) {
+    return NS_ERROR_XPC_BAD_CONVERT_JS;
+  }
+
+  // Now update our source to keep rooting our object.
+  src.setObject(*obj);
+  return NS_OK;
+}
+
+nsresult
 UnwrapWindowProxyImpl(JSContext* cx,
                       JS::Handle<JSObject*> src,
                       nsPIDOMWindowOuter** ppArg)
 {
   nsCOMPtr<nsPIDOMWindowInner> inner;
   nsresult rv = UnwrapArg<nsPIDOMWindowInner>(cx, src, getter_AddRefs(inner));
   NS_ENSURE_SUCCESS(rv, rv);
 
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -55,33 +55,57 @@ template<typename KeyType, typename Valu
 nsresult
 UnwrapArgImpl(JSContext* cx, JS::Handle<JSObject*> src, const nsIID& iid,
               void** ppArg);
 
 nsresult
 UnwrapWindowProxyImpl(JSContext* cx, JS::Handle<JSObject*> src,
                       nsPIDOMWindowOuter** ppArg);
 
-/** Convert a jsval to an XPCOM pointer. */
+/** Convert a jsval to an XPCOM pointer. Caller must not assume that src will
+    keep the XPCOM pointer rooted. */
 template <class Interface>
 inline nsresult
 UnwrapArg(JSContext* cx, JS::Handle<JSObject*> src, Interface** ppArg)
 {
   return UnwrapArgImpl(cx, src, NS_GET_TEMPLATE_IID(Interface),
                        reinterpret_cast<void**>(ppArg));
 }
 
 template <>
 inline nsresult
 UnwrapArg<nsPIDOMWindowOuter>(JSContext* cx, JS::Handle<JSObject*> src,
                               nsPIDOMWindowOuter** ppArg)
 {
   return UnwrapWindowProxyImpl(cx, src, ppArg);
 }
 
+nsresult
+UnwrapXPConnectImpl(JSContext* cx, JS::MutableHandle<JS::Value> src,
+                    const nsIID& iid, void** ppArg);
+
+/*
+ * Convert a jsval being used as a Web IDL interface implementation to an XPCOM
+ * pointer; this is only used for Web IDL interfaces that specify
+ * hasXPConnectImpls.  This is not the same as UnwrapArg because caller _can_
+ * assume that if unwrapping succeeds "val" will be updated so it's rooting the
+ * XPCOM pointer.  Also, UnwrapXPConnect doesn't need to worry about doing
+ * XPCWrappedJS things.
+ *
+ * val must be an ObjectValue.
+ */
+template<class Interface>
+inline nsresult
+UnwrapXPConnect(JSContext* cx, JS::MutableHandle<JS::Value> val,
+                Interface** ppThis)
+{
+  return UnwrapXPConnectImpl(cx, val, NS_GET_TEMPLATE_IID(Interface),
+                             reinterpret_cast<void**>(ppThis));
+}
+
 bool
 ThrowInvalidThis(JSContext* aCx, const JS::CallArgs& aArgs,
                  bool aSecurityError, const char* aInterfaceName);
 
 bool
 ThrowInvalidThis(JSContext* aCx, const JS::CallArgs& aArgs,
                  bool aSecurityError, prototypes::ID aProtoId);
 
@@ -181,68 +205,227 @@ UnwrapDOMObjectToISupports(JSObject* aOb
 }
 
 inline bool
 IsDOMObject(JSObject* obj)
 {
   return IsDOMClass(js::GetObjectClass(obj));
 }
 
+// There are two valid ways to use UNWRAP_OBJECT: Either obj needs to
+// be a MutableHandle<JSObject*>, or value needs to be a strong-reference
+// smart pointer type (OwningNonNull or RefPtr or nsCOMPtr), in which case obj
+// can be anything that converts to JSObject*.
 #define UNWRAP_OBJECT(Interface, obj, value)                                 \
   mozilla::dom::UnwrapObject<mozilla::dom::prototypes::id::Interface,        \
     mozilla::dom::Interface##Binding::NativeType>(obj, value)
 
+// Test whether the given object is an instance of the given interface.
+#define IS_INSTANCE_OF(Interface, obj)                                  \
+  mozilla::dom::IsInstanceOf<mozilla::dom::prototypes::id::Interface,   \
+                             mozilla::dom::Interface##Binding::NativeType>(obj)
+
+// Unwrap the given non-wrapper object.  This can be used with any obj that
+// converts to JSObject*; as long as that JSObject* is live the return value
+// will be valid.
+#define UNWRAP_NON_WRAPPER_OBJECT(Interface, obj, value)                        \
+  mozilla::dom::UnwrapNonWrapperObject<mozilla::dom::prototypes::id::Interface, \
+    mozilla::dom::Interface##Binding::NativeType>(obj, value)
+
 // Some callers don't want to set an exception when unwrapping fails
 // (for example, overload resolution uses unwrapping to tell what sort
 // of thing it's looking at).
 // U must be something that a T* can be assigned to (e.g. T* or an RefPtr<T>).
-template <class T, typename U>
+//
+// The obj argument will be mutated to point to CheckedUnwrap of itself if the
+// passed-in value is not a DOM object and CheckedUnwrap succeeds.
+//
+// If mayBeWrapper is true, there are three valid ways to invoke
+// UnwrapObjectInternal: Either obj needs to be a class wrapping a
+// MutableHandle<JSObject*>, with an assignment operator that sets the handle to
+// the given object, or U needs to be a strong-reference smart pointer type
+// (OwningNonNull or RefPtr or nsCOMPtr), or the value being stored in "value"
+// must not escape past being tested for falsiness immediately after the
+// UnwrapObjectInternal call.
+//
+// If mayBeWrapper is false, obj can just be a JSObject*, and U anything that a
+// T* can be assigned to.
+namespace binding_detail {
+template <class T, bool mayBeWrapper, typename U, typename V>
 MOZ_ALWAYS_INLINE nsresult
-UnwrapObject(JSObject* obj, U& value, prototypes::ID protoID,
-             uint32_t protoDepth)
+UnwrapObjectInternal(V& obj, U& value, prototypes::ID protoID,
+                     uint32_t protoDepth)
 {
   /* First check to see whether we have a DOM object */
   const DOMJSClass* domClass = GetDOMClass(obj);
-  if (!domClass) {
-    /* Maybe we have a security wrapper or outer window? */
-    if (!js::IsWrapper(obj)) {
-      /* Not a DOM object, not a wrapper, just bail */
-      return NS_ERROR_XPC_BAD_CONVERT_JS;
-    }
-
-    obj = js::CheckedUnwrap(obj, /* stopAtWindowProxy = */ false);
-    if (!obj) {
-      return NS_ERROR_XPC_SECURITY_MANAGER_VETO;
-    }
-    MOZ_ASSERT(!js::IsWrapper(obj));
-    domClass = GetDOMClass(obj);
-    if (!domClass) {
-      /* We don't have a DOM object */
-      return NS_ERROR_XPC_BAD_CONVERT_JS;
+  if (domClass) {
+    /* This object is a DOM object.  Double-check that it is safely
+       castable to T by checking whether it claims to inherit from the
+       class identified by protoID. */
+    if (domClass->mInterfaceChain[protoDepth] == protoID) {
+      value = UnwrapDOMObject<T>(obj);
+      return NS_OK;
     }
   }
 
-  /* This object is a DOM object.  Double-check that it is safely
-     castable to T by checking whether it claims to inherit from the
-     class identified by protoID. */
-  if (domClass->mInterfaceChain[protoDepth] == protoID) {
-    value = UnwrapDOMObject<T>(obj);
+  /* Maybe we have a security wrapper or outer window? */
+  if (!mayBeWrapper || !js::IsWrapper(obj)) {
+    /* Not a DOM object, not a wrapper, just bail */
+    return NS_ERROR_XPC_BAD_CONVERT_JS;
+  }
+
+  JSObject* unwrappedObj =
+    js::CheckedUnwrap(obj, /* stopAtWindowProxy = */ false);
+  if (!unwrappedObj) {
+    return NS_ERROR_XPC_SECURITY_MANAGER_VETO;
+  }
+  MOZ_ASSERT(!js::IsWrapper(unwrappedObj));
+  // Recursive call is OK, because now we're using false for mayBeWrapper and
+  // we never reach this code if that boolean is false, so can't keep calling
+  // ourselves.
+  //
+  // Unwrap into a temporary pointer, because in general unwrapping into
+  // something of type U might trigger GC (e.g. release the value currently
+  // stored in there, with arbitrary consequences) and invalidate the
+  // "unwrappedObj" pointer.
+  T* tempValue;
+  nsresult rv = UnwrapObjectInternal<T, false>(unwrappedObj, tempValue,
+                                               protoID, protoDepth);
+  if (NS_SUCCEEDED(rv)) {
+    // It's very important to not update "obj" with the "unwrappedObj" value
+    // until we know the unwrap has succeeded.  Otherwise, in a situation in
+    // which we have an overload of object and primitive we could end up
+    // converting to the primitive from the unwrappedObj, whereas we want to do
+    // it from the original object.
+    obj = unwrappedObj;
+    // And now assign to "value"; at this point we don't care if a GC happens
+    // and invalidates unwrappedObj.
+    value = tempValue;
     return NS_OK;
   }
 
   /* It's the wrong sort of DOM object */
   return NS_ERROR_XPC_BAD_CONVERT_JS;
 }
 
-template <prototypes::ID PrototypeID, class T, typename U>
+struct MutableObjectHandleWrapper {
+  explicit MutableObjectHandleWrapper(JS::MutableHandle<JSObject*> aHandle)
+    : mHandle(aHandle)
+  {
+  }
+
+  void operator=(JSObject* aObject)
+  {
+    MOZ_ASSERT(aObject);
+    mHandle.set(aObject);
+  }
+
+  operator JSObject*() const
+  {
+    return mHandle;
+  }
+
+private:
+  JS::MutableHandle<JSObject*> mHandle;
+};
+
+struct MutableValueHandleWrapper {
+  explicit MutableValueHandleWrapper(JS::MutableHandle<JS::Value> aHandle)
+    : mHandle(aHandle)
+  {
+  }
+
+  void operator=(JSObject* aObject)
+  {
+    MOZ_ASSERT(aObject);
+    mHandle.setObject(*aObject);
+  }
+
+  operator JSObject*() const
+  {
+    return &mHandle.toObject();
+  }
+
+private:
+  JS::MutableHandle<JS::Value> mHandle;
+};
+
+} // namespace binding_detail
+
+// UnwrapObject overloads that ensure we have a MutableHandle to keep it alive.
+template<prototypes::ID PrototypeID, class T, typename U>
+MOZ_ALWAYS_INLINE nsresult
+UnwrapObject(JS::MutableHandle<JSObject*> obj, U& value)
+{
+  binding_detail::MutableObjectHandleWrapper wrapper(obj);
+  return binding_detail::UnwrapObjectInternal<T, true>(
+    wrapper, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth);
+}
+
+template<prototypes::ID PrototypeID, class T, typename U>
 MOZ_ALWAYS_INLINE nsresult
-UnwrapObject(JSObject* obj, U& value)
+UnwrapObject(JS::MutableHandle<JS::Value> obj, U& value)
+{
+  MOZ_ASSERT(obj.isObject());
+  binding_detail::MutableValueHandleWrapper wrapper(obj);
+  return binding_detail::UnwrapObjectInternal<T, true>(
+    wrapper, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth);
+}
+
+// UnwrapObject overloads that ensure we have a strong ref to keep it alive.
+template<prototypes::ID PrototypeID, class T, typename U>
+MOZ_ALWAYS_INLINE nsresult
+UnwrapObject(JSObject* obj, RefPtr<U>& value)
+{
+  return binding_detail::UnwrapObjectInternal<T, true>(
+    obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth);
+}
+
+template<prototypes::ID PrototypeID, class T, typename U>
+MOZ_ALWAYS_INLINE nsresult
+UnwrapObject(JSObject* obj, nsCOMPtr<U>& value)
+{
+  return binding_detail::UnwrapObjectInternal<T, true>(
+    obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth);
+}
+
+template<prototypes::ID PrototypeID, class T, typename U>
+MOZ_ALWAYS_INLINE nsresult
+UnwrapObject(JSObject* obj, OwningNonNull<U>& value)
 {
-  return UnwrapObject<T>(obj, value, PrototypeID,
-                         PrototypeTraits<PrototypeID>::Depth);
+  return binding_detail::UnwrapObjectInternal<T, true>(
+    obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth);
+}
+
+// An UnwrapObject overload that just calls one of the JSObject* ones.
+template<prototypes::ID PrototypeID, class T, typename U>
+MOZ_ALWAYS_INLINE nsresult
+UnwrapObject(JS::Handle<JS::Value> obj, U& value)
+{
+  MOZ_ASSERT(obj.isObject());
+  return UnwrapObject<PrototypeID, T>(&obj.toObject(), value);
+}
+
+template<prototypes::ID PrototypeID, class T>
+MOZ_ALWAYS_INLINE bool
+IsInstanceOf(JSObject* obj)
+{
+  void* ignored;
+  nsresult unwrapped = binding_detail::UnwrapObjectInternal<T, true>(
+    obj, ignored, PrototypeID, PrototypeTraits<PrototypeID>::Depth);
+  return NS_SUCCEEDED(unwrapped);
+}
+
+template<prototypes::ID PrototypeID, class T, typename U>
+MOZ_ALWAYS_INLINE nsresult
+UnwrapNonWrapperObject(JSObject* obj, U& value)
+{
+  MOZ_ASSERT(!js::IsWrapper(obj));
+  return binding_detail::UnwrapObjectInternal<T, false>(
+    obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth);
 }
 
 MOZ_ALWAYS_INLINE bool
 IsConvertibleToDictionary(JS::Handle<JS::Value> val)
 {
   return val.isNullOrUndefined() || val.isObject();
 }
 
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -2004,19 +2004,18 @@ class CGHasInstanceHook(CGAbstractStatic
                                   "HasInstance only works for nsISupports-based classes.");
 
                     bool ok = InterfaceHasInstance(cx, argc, vp);
                     if (!ok || args.rval().toBoolean()) {
                       return ok;
                     }
 
                     // FIXME Limit this to chrome by checking xpc::AccessCheck::isChrome(obj).
-                    nsISupports* native =
-                      nsContentUtils::XPConnect()->GetNativeOfWrapper(cx,
-                                                                      js::UncheckedUnwrap(instance, /* stopAtWindowProxy = */ false));
+                    nsCOMPtr<nsISupports> native =
+                      xpc::UnwrapReflectorToISupports(js::UncheckedUnwrap(instance, /* stopAtWindowProxy = */ false));
                     nsCOMPtr<nsIDOM${name}> qiResult = do_QueryInterface(native);
                     args.rval().setBoolean(!!qiResult);
                     return true;
                     """,
                     nativeType=self.descriptor.nativeType,
                     name=self.descriptor.interface.identifier.name))
 
         hasInstanceCode = dedent("""
@@ -4307,69 +4306,78 @@ def numericValue(t, v):
             return "mozilla::NegativeInfinity<%s>()" % typeName
         if math.isnan(v):
             return "mozilla::UnspecifiedNaN<%s>()" % typeName
     return "%s%s" % (v, numericSuffixes[t])
 
 
 class CastableObjectUnwrapper():
     """
-    A class for unwrapping an object named by the "source" argument
-    based on the passed-in descriptor and storing it in a variable
-    called by the name in the "target" argument.
+    A class for unwrapping an object stored in a JS Value (or
+    MutableHandle<Value> or Handle<Value>) named by the "source" and
+    "mutableSource" arguments based on the passed-in descriptor and storing it
+    in a variable called by the name in the "target" argument.  The "source"
+    argument should be able to produce a Value or Handle<Value>; the
+    "mutableSource" argument should be able to produce a MutableHandle<Value>
 
     codeOnFailure is the code to run if unwrapping fails.
 
     If isCallbackReturnValue is "JSImpl" and our descriptor is also
     JS-implemented, fall back to just creating the right object if what we
     have isn't one already.
 
     If allowCrossOriginObj is True, then we'll first do an
     UncheckedUnwrap and then operate on the result.
     """
-    def __init__(self, descriptor, source, target, codeOnFailure,
+    def __init__(self, descriptor, source, mutableSource, target, codeOnFailure,
                  exceptionCode=None, isCallbackReturnValue=False,
                  allowCrossOriginObj=False):
         self.substitution = {
             "type": descriptor.nativeType,
             "protoID": "prototypes::id::" + descriptor.name,
             "target": target,
             "codeOnFailure": codeOnFailure,
         }
+        # Supporting both the "cross origin object" case and the "has
+        # XPConnect impls" case at the same time is a pain, so let's
+        # not do that.  That allows us to assume that our source is
+        # always a Handle or MutableHandle.
+        if allowCrossOriginObj and descriptor.hasXPConnectImpls:
+            raise TypeError("Interface %s both allows a cross-origin 'this' "
+                            "and has XPConnect impls.  We don't support that" %
+                            descriptor.name)
         if allowCrossOriginObj:
             self.substitution["uncheckedObjDecl"] = fill(
                 """
-                JS::Rooted<JSObject*> maybeUncheckedObj(cx);
-                if (xpc::WrapperFactory::IsXrayWrapper(${source})) {
-                  maybeUncheckedObj = js::UncheckedUnwrap(${source});
+                JS::Rooted<JSObject*> maybeUncheckedObj(cx, &${source}.toObject());
+                """,
+                source=source)
+            self.substitution["uncheckedObjGet"] = fill(
+                """
+                if (xpc::WrapperFactory::IsXrayWrapper(maybeUncheckedObj)) {
+                  maybeUncheckedObj = js::UncheckedUnwrap(maybeUncheckedObj);
                 } else {
-                  maybeUncheckedObj = js::CheckedUnwrap(${source});
+                  maybeUncheckedObj = js::CheckedUnwrap(maybeUncheckedObj);
                   if (!maybeUncheckedObj) {
                     $*{codeOnFailure}
                   }
                 }
                 """,
-                source=source,
                 codeOnFailure=(codeOnFailure % { 'securityError': 'true'}))
             self.substitution["source"] = "maybeUncheckedObj"
-            xpconnectUnwrap = dedent("""
-                nsresult rv;
-                { // Scope for the JSAutoCompartment, because we only
-                  // want to be in that compartment for the UnwrapArg call.
-                  JS::Rooted<JSObject*> source(cx, ${source});
-                  JSAutoCompartment ac(cx, ${source});
-                  rv = UnwrapArg<${type}>(cx, source, getter_AddRefs(objPtr));
-                }
-                """)
+            self.substitution["mutableSource"] = "&maybeUncheckedObj"
+            # No need to set up xpconnectUnwrap, since it won't be
+            # used in the allowCrossOriginObj case.
         else:
             self.substitution["uncheckedObjDecl"] = ""
+            self.substitution["uncheckedObjGet"] = ""
             self.substitution["source"] = source
+            self.substitution["mutableSource"] = mutableSource
             xpconnectUnwrap = (
-                "JS::Rooted<JSObject*> source(cx, ${source});\n"
-                "nsresult rv = UnwrapArg<${type}>(cx, source, getter_AddRefs(objPtr));\n")
+                "nsresult rv = UnwrapXPConnect<${type}>(cx, ${mutableSource}, getter_AddRefs(objPtr));\n")
 
         if descriptor.hasXPConnectImpls:
             self.substitution["codeOnFailure"] = string.Template(
                 "RefPtr<${type}> objPtr;\n" +
                 xpconnectUnwrap +
                 "if (NS_FAILED(rv)) {\n"
                 "${indentedCodeOnFailure}"
                 "}\n"
@@ -4382,24 +4390,24 @@ class CastableObjectUnwrapper():
               descriptor.interface.isJSImplemented()):
             exceptionCode = exceptionCode or codeOnFailure
             self.substitution["codeOnFailure"] = fill(
                 """
                 // Be careful to not wrap random DOM objects here, even if
                 // they're wrapped in opaque security wrappers for some reason.
                 // XXXbz Wish we could check for a JS-implemented object
                 // that already has a content reflection...
-                if (!IsDOMObject(js::UncheckedUnwrap(${source}))) {
+                if (!IsDOMObject(js::UncheckedUnwrap(&${source}.toObject()))) {
                   nsCOMPtr<nsIGlobalObject> contentGlobal;
                   JS::Handle<JSObject*> callback = CallbackOrNull();
                   if (!callback ||
                       !GetContentGlobalForJSImplementedObject(cx, callback, getter_AddRefs(contentGlobal))) {
                     $*{exceptionCode}
                   }
-                  JS::Rooted<JSObject*> jsImplSourceObj(cx, ${source});
+                  JS::Rooted<JSObject*> jsImplSourceObj(cx, &${source}.toObject());
                   ${target} = new ${type}(jsImplSourceObj, contentGlobal);
                 } else {
                   $*{codeOnFailure}
                 }
                 """,
                 exceptionCode=exceptionCode,
                 **self.substitution)
         else:
@@ -4407,35 +4415,36 @@ class CastableObjectUnwrapper():
 
     def __str__(self):
         substitution = self.substitution.copy()
         substitution["codeOnFailure"] %= {
             'securityError': 'rv == NS_ERROR_XPC_SECURITY_MANAGER_VETO'
         }
         return fill(
             """
+            $*{uncheckedObjDecl}
             {
-              $*{uncheckedObjDecl}
-              nsresult rv = UnwrapObject<${protoID}, ${type}>(${source}, ${target});
+              $*{uncheckedObjGet}
+              nsresult rv = UnwrapObject<${protoID}, ${type}>(${mutableSource}, ${target});
               if (NS_FAILED(rv)) {
                 $*{codeOnFailure}
               }
             }
             """,
             **substitution)
 
 
 class FailureFatalCastableObjectUnwrapper(CastableObjectUnwrapper):
     """
     As CastableObjectUnwrapper, but defaulting to throwing if unwrapping fails
     """
-    def __init__(self, descriptor, source, target, exceptionCode,
+    def __init__(self, descriptor, source, mutableSource, target, exceptionCode,
                  isCallbackReturnValue, sourceDescription):
         CastableObjectUnwrapper.__init__(
-            self, descriptor, source, target,
+            self, descriptor, source, mutableSource, target,
             'ThrowErrorMessage(cx, MSG_DOES_NOT_IMPLEMENT_INTERFACE, "%s", "%s");\n'
             '%s' % (sourceDescription, descriptor.interface.identifier.name,
                     exceptionCode),
             exceptionCode,
             isCallbackReturnValue)
 
 
 def getCallbackConversionInfo(type, idlObject, isMember, isCallbackReturnValue,
@@ -4490,16 +4499,19 @@ class JSToNativeConversionInfo():
     def __init__(self, template, declType=None, holderType=None,
                  dealWithOptional=False, declArgs=None,
                  holderArgs=None):
         """
         template: A string representing the conversion code.  This will have
                   template substitution performed on it as follows:
 
           ${val} is a handle to the JS::Value in question
+          ${maybeMutableVal} May be a mutable handle to the JS::Value in
+                             question. This is only OK to use if ${val} is
+                             known to not be undefined.
           ${holderName} replaced by the holder's name, if any
           ${declName} replaced by the declaration's name
           ${haveValue} replaced by an expression that evaluates to a boolean
                        for whether we have a JS::Value.  Only used when
                        defaultValue is not None or when True is passed for
                        checkForValue to instantiateJSToNativeConversion.
                        This expression may not be already-parenthesized, so if
                        you use it with && or || make sure to put parens
@@ -4885,16 +4897,17 @@ def getJSToNativeConversionInfo(type, de
         if nullable:
             typeName = CGTemplatedType("Nullable", typeName)
             arrayRef = "${declName}.SetValue()"
         else:
             arrayRef = "${declName}"
 
         elementConversion = string.Template(elementInfo.template).substitute({
             "val": "temp" + str(nestingLevel),
+            "maybeMutableVal": "&temp" + str(nestingLevel),
             "declName": "slot" + str(nestingLevel),
             # We only need holderName here to handle isExternal()
             # interfaces, which use an internal holder for the
             # conversion even when forceOwningType ends up true.
             "holderName": "tempHolder" + str(nestingLevel),
             "passedToJSImpl": "${passedToJSImpl}"
         })
 
@@ -4994,16 +5007,17 @@ def getJSToNativeConversionInfo(type, de
         if nullable:
             declType = CGTemplatedType("Nullable", declType)
             recordRef = "${declName}.SetValue()"
         else:
             recordRef = "${declName}"
 
         valueConversion = string.Template(valueInfo.template).substitute({
             "val": "temp",
+            "maybeMutableVal": "&temp",
             "declName": "slot",
             # We only need holderName here to handle isExternal()
             # interfaces, which use an internal holder for the
             # conversion even when forceOwningType ends up true.
             "holderName": "tempHolder",
             "passedToJSImpl": "${passedToJSImpl}"
         })
 
@@ -5665,23 +5679,25 @@ def getJSToNativeConversionInfo(type, de
         if forceOwningType:
             templateBody += 'static_assert(IsRefcounted<%s>::value, "We can only store refcounted classes.");' % typeName
 
         if (not descriptor.interface.isConsequential() and
             not descriptor.interface.isExternal()):
             if failureCode is not None:
                 templateBody += str(CastableObjectUnwrapper(
                     descriptor,
-                    "&${val}.toObject()",
+                    "${val}",
+                    "${maybeMutableVal}",
                     "${declName}",
                     failureCode))
             else:
                 templateBody += str(FailureFatalCastableObjectUnwrapper(
                     descriptor,
-                    "&${val}.toObject()",
+                    "${val}",
+                    "${maybeMutableVal}",
                     "${declName}",
                     exceptionCode,
                     isCallbackReturnValue,
                     firstCap(sourceDescription)))
         else:
             # Either external, or new-binding non-castable.  We always have a
             # holder for these, because we don't actually know whether we have
             # to addref when unwrapping or not.  So we just pass an
@@ -6318,16 +6334,19 @@ def instantiateJSToNativeConversion(info
                                        pre="(", post=")")
         else:
             holderCtorArgs = None
         result.append(
             CGList([holderType, CGGeneric(" "),
                     CGGeneric(originalHolderName),
                     holderCtorArgs, CGGeneric(";\n")]))
 
+    if "maybeMutableVal" not in replacements:
+        replacements["maybeMutableVal"] = replacements["val"]
+
     conversion = CGGeneric(
         string.Template(templateBody).substitute(replacements))
 
     if checkForValue:
         if dealWithOptional:
             declConstruct = CGIndenter(
                 CGGeneric("%s.Construct(%s);\n" %
                           (originalDeclName,
@@ -6411,16 +6430,18 @@ class CGArgumentConverter(CGThing):
         # interface, arguments can possibly be undefined, but will need to be
         # converted to the key/value type of the backing object. In this case,
         # use .get() instead of direct access to the argument. This won't
         # matter for iterable since generated functions for those interface
         # don't take arguments.
         if member.isMethod() and member.isMaplikeOrSetlikeOrIterableMethod():
             self.replacementVariables["val"] = string.Template(
                 "args.get(${index})").substitute(replacer)
+            self.replacementVariables["maybeMutableVal"] = string.Template(
+                "args[${index}]").substitute(replacer)
         else:
             self.replacementVariables["val"] = string.Template(
                 "args[${index}]").substitute(replacer)
         haveValueCheck = string.Template(
             "args.hasDefined(${index})").substitute(replacer)
         self.replacementVariables["haveValue"] = haveValueCheck
         self.descriptorProvider = descriptorProvider
         if self.argument.canHaveMissingValue():
@@ -6482,16 +6503,17 @@ class CGArgumentConverter(CGThing):
                     ${elemType}& slot = *${declName}.AppendElement(mozilla::fallible);
                 """)
         ).substitute(replacer)
 
         val = string.Template("args[variadicArg]").substitute(replacer)
         variadicConversion += indent(
             string.Template(typeConversion.template).substitute({
                 "val": val,
+                "maybeMutableVal": val,
                 "declName": "slot",
                 # We only need holderName here to handle isExternal()
                 # interfaces, which use an internal holder for the
                 # conversion even when forceOwningType ends up true.
                 "holderName": "tempHolder",
                 # Use the same ${obj} as for the variadic arg itself
                 "obj": replacer["obj"],
                 "passedToJSImpl": toStringBool(isJSImplementedDescriptor(self.descriptorProvider))
@@ -8644,24 +8666,31 @@ class CGAbstractBindingMethod(CGAbstract
         self.callArgs = callArgs
         self.allowCrossOriginThis = allowCrossOriginThis
 
     def definition_body(self):
         body = self.callArgs
         if self.getThisObj is not None:
             body += self.getThisObj.define() + "\n"
         body += "%s* self;\n" % self.descriptor.nativeType
+        body += dedent(
+            """
+            JS::Rooted<JS::Value> rootSelf(cx, JS::ObjectValue(*obj));
+            """)
 
         # Our descriptor might claim that we're not castable, simply because
         # we're someone's consequential interface.  But for this-unwrapping, we
         # know that we're the real deal.  So fake a descriptor here for
         # consumption by CastableObjectUnwrapper.
         body += str(CastableObjectUnwrapper(
             self.descriptor,
-            "obj", "self", self.unwrapFailureCode,
+            "rootSelf",
+            "&rootSelf",
+            "self",
+            self.unwrapFailureCode,
             allowCrossOriginObj=self.allowCrossOriginThis))
 
         return body + self.generate_code().define()
 
     def generate_code(self):
         assert False  # Override me
 
 
@@ -10183,16 +10212,17 @@ def getUnionTypeTemplateVars(unionType, 
                 holderArgs = ""
             initHolder = "%s.emplace(%s);\n" % (holderName, holderArgs)
         else:
             initHolder = ""
 
         jsConversion = fill(
             initHolder + conversionInfo.template,
             val="value",
+            maybeMutableVal="value",
             declName="memberSlot",
             holderName=(holderName if ownsMembers else "%s.ref()" % holderName),
             destroyHolder=destroyHolder,
             passedToJSImpl="passedToJSImpl")
 
         jsConversion = fill(
             """
             tryNext = false;
@@ -10202,19 +10232,24 @@ def getUnionTypeTemplateVars(unionType, 
             }
             return true;
             """,
             structType=structType,
             name=name,
             ctorArgs=ctorArgs,
             jsConversion=jsConversion)
 
+        if ownsMembers:
+            handleType = "JS::Handle<JS::Value>"
+        else:
+            handleType = "JS::MutableHandle<JS::Value>"
+
         setter = ClassMethod("TrySetTo" + name, "bool",
                              [Argument("JSContext*", "cx"),
-                              Argument("JS::Handle<JS::Value>", "value"),
+                              Argument(handleType, "value"),
                               Argument("bool&", "tryNext"),
                               Argument("bool", "passedToJSImpl", default="false")],
                              inline=not ownsMembers,
                              bodyInHeader=not ownsMembers,
                              body=jsConversion)
 
     return {
         "name": name,
@@ -11333,24 +11368,33 @@ class CGProxySpecialOperation(CGPerSigna
             argument = arguments[1]
             info = getJSToNativeConversionInfo(
                 argument.type, descriptor,
                 treatNullAs=argument.treatNullAs,
                 sourceDescription=("value being assigned to %s setter" %
                                    descriptor.interface.identifier.name))
             if argumentHandleValue is None:
                 argumentHandleValue = "desc.value()"
+            rootedValue = fill(
+                """
+                JS::Rooted<JS::Value> rootedValue(cx, ${argumentHandleValue});
+                """,
+                argumentHandleValue = argumentHandleValue)
             templateValues = {
                 "declName": argument.identifier.name,
                 "holderName": argument.identifier.name + "_holder",
                 "val": argumentHandleValue,
+                "maybeMutableVal": "&rootedValue",
                 "obj": "obj",
                 "passedToJSImpl": "false"
             }
             self.cgRoot.prepend(instantiateJSToNativeConversion(info, templateValues))
+            # rootedValue needs to come before the conversion, so we
+            # need to prepend it last.
+            self.cgRoot.prepend(CGGeneric(rootedValue))
         elif operation.isGetter() or operation.isDeleter():
             if foundVar is None:
                 self.cgRoot.prepend(CGGeneric("bool found = false;\n"))
 
     def getArguments(self):
         args = [(a, a.identifier.name) for a in self.arguments]
         if self.idlNode.isGetter() or self.idlNode.isDeleter():
             args.append((FakeArgument(BuiltinTypes[IDLBuiltinType.Types.boolean],
@@ -13393,16 +13437,17 @@ class CGDictionary(CGThing):
         optional with default value, or optional without default
         value.  We set up a template in the 'conversion' variable for
         exactly how to do this, then substitute into it from the
         conversionReplacements dictionary.
         """
         member, conversionInfo = memberInfo
         replacements = {
             "val": "temp.ref()",
+            "maybeMutableVal": "temp.ptr()",
             "declName": self.makeMemberName(member.identifier.name),
             # We need a holder name for external interfaces, but
             # it's scoped down to the conversion so we can just use
             # anything we want.
             "holderName": "holder",
             "passedToJSImpl": "passedToJSImpl"
         }
         # We can't handle having a holderType here
--- a/dom/bindings/FakeString.h
+++ b/dom/bindings/FakeString.h
@@ -14,28 +14,29 @@
 namespace mozilla {
 namespace dom {
 namespace binding_detail {
 // A struct that has the same layout as an nsString but much faster
 // constructor and destructor behavior. FakeString uses inline storage
 // for small strings and a nsStringBuffer for longer strings.
 struct FakeString {
   FakeString() :
-    mFlags(nsString::F_TERMINATED)
+    mDataFlags(nsString::DataFlags::TERMINATED),
+    mClassFlags(nsString::ClassFlags(0))
   {
   }
 
   ~FakeString() {
-    if (mFlags & nsString::F_SHARED) {
+    if (mDataFlags & nsString::DataFlags::SHARED) {
       nsStringBuffer::FromData(mData)->Release();
     }
   }
 
   void Rebind(const nsString::char_type* aData, nsString::size_type aLength) {
-    MOZ_ASSERT(mFlags == nsString::F_TERMINATED);
+    MOZ_ASSERT(mDataFlags == nsString::DataFlags::TERMINATED);
     mData = const_cast<nsString::char_type*>(aData);
     mLength = aLength;
   }
 
   // Share aString's string buffer, if it has one; otherwise, make this string
   // depend upon aString's data.  aString should outlive this instance of
   // FakeString.
   void ShareOrDependUpon(const nsAString& aString) {
@@ -44,26 +45,26 @@ struct FakeString {
       Rebind(aString.Data(), aString.Length());
     } else {
       AssignFromStringBuffer(sharedBuffer.forget());
       mLength = aString.Length();
     }
   }
 
   void Truncate() {
-    MOZ_ASSERT(mFlags == nsString::F_TERMINATED);
+    MOZ_ASSERT(mDataFlags == nsString::DataFlags::TERMINATED);
     mData = nsString::char_traits::sEmptyBuffer;
     mLength = 0;
   }
 
   void SetIsVoid(bool aValue) {
     MOZ_ASSERT(aValue,
                "We don't support SetIsVoid(false) on FakeString!");
     Truncate();
-    mFlags |= nsString::F_VOIDED;
+    mDataFlags |= nsString::DataFlags::VOIDED;
   }
 
   const nsString::char_type* Data() const
   {
     return mData;
   }
 
   nsString::char_type* BeginWriting()
@@ -106,31 +107,32 @@ operator const nsAString& () const {
 
 private:
   nsAString* ToAStringPtr() {
     return reinterpret_cast<nsString*>(this);
   }
 
   nsString::char_type* mData;
   nsString::size_type mLength;
-  uint32_t mFlags;
+  nsString::DataFlags mDataFlags;
+  nsString::ClassFlags mClassFlags;
 
   static const size_t sInlineCapacity = 64;
   nsString::char_type mInlineStorage[sInlineCapacity];
 
   FakeString(const FakeString& other) = delete;
   void operator=(const FakeString& other) = delete;
 
   void SetData(nsString::char_type* aData) {
-    MOZ_ASSERT(mFlags == nsString::F_TERMINATED);
+    MOZ_ASSERT(mDataFlags == nsString::DataFlags::TERMINATED);
     mData = const_cast<nsString::char_type*>(aData);
   }
   void AssignFromStringBuffer(already_AddRefed<nsStringBuffer> aBuffer) {
     SetData(static_cast<nsString::char_type*>(aBuffer.take()->Data()));
-    mFlags = nsString::F_SHARED | nsString::F_TERMINATED;
+    mDataFlags = nsString::DataFlags::SHARED | nsString::DataFlags::TERMINATED;
   }
 
   friend class NonNull<nsAString>;
 
   // A class to use for our static asserts to ensure our object layout
   // matches that of nsString.
   class StringAsserter;
   friend class StringAsserter;
@@ -142,19 +144,22 @@ private:
                       sizeof(nsString),
                     "FakeString should include all nsString members");
       static_assert(offsetof(FakeString, mData) ==
                       offsetof(StringAsserter, mData),
                     "Offset of mData should match");
       static_assert(offsetof(FakeString, mLength) ==
                       offsetof(StringAsserter, mLength),
                     "Offset of mLength should match");
-      static_assert(offsetof(FakeString, mFlags) ==
-                      offsetof(StringAsserter, mFlags),
-                    "Offset of mFlags should match");
+      static_assert(offsetof(FakeString, mDataFlags) ==
+                      offsetof(StringAsserter, mDataFlags),
+                    "Offset of mDataFlags should match");
+      static_assert(offsetof(FakeString, mClassFlags) ==
+                      offsetof(StringAsserter, mClassFlags),
+                    "Offset of mClassFlags should match");
     }
   };
 };
 } // namespace binding_detail
 } // namespace dom
 } // namespace mozilla
 
-#endif /* mozilla_dom_FakeString_h__ */
\ No newline at end of file
+#endif /* mozilla_dom_FakeString_h__ */
--- a/dom/bindings/WebIDLGlobalNameHash.cpp
+++ b/dom/bindings/WebIDLGlobalNameHash.cpp
@@ -214,18 +214,24 @@ WebIDLGlobalNameHash::DefineIfEnabled(JS
   // or the window being touched.
   JS::Rooted<JSObject*> global(aCx,
     js::CheckedUnwrap(aObj, /* stopAtWindowProxy = */ false));
   if (!global) {
     return Throw(aCx, NS_ERROR_DOM_SECURITY_ERR);
   }
 
   {
+    // It's safe to pass "&global" here, because we've already unwrapped it, but
+    // for general sanity better to not have debug code even having the
+    // appearance of mutating things that opt code uses.
+#ifdef DEBUG
+    JS::Rooted<JSObject*> temp(aCx, global);
     DebugOnly<nsGlobalWindow*> win;
-    MOZ_ASSERT(NS_SUCCEEDED(UNWRAP_OBJECT(Window, global, win)));
+    MOZ_ASSERT(NS_SUCCEEDED(UNWRAP_OBJECT(Window, &temp, win)));
+#endif
   }
 
   if (checkEnabledForScope && !checkEnabledForScope(aCx, global)) {
     return true;
   }
 
   // The DOM constructor resolve machinery interacts with Xrays in tricky
   // ways, and there are some asymmetries that are important to understand.
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -2807,34 +2807,36 @@ CreateDeclarationForServo(nsCSSPropertyI
 
   NS_ConvertUTF16toUTF8 value(aPropertyValue);
 
   RefPtr<RawServoDeclarationBlock> servoDeclarations =
     Servo_ParseProperty(aProperty,
                         &value,
                         data,
                         ParsingMode::Default,
-                        aDocument->GetCompatibilityMode()).Consume();
+                        aDocument->GetCompatibilityMode(),
+                        aDocument->CSSLoader()).Consume();
 
   if (!servoDeclarations) {
     // We got a syntax error.  The spec says this value must be ignored.
     return nullptr;
   }
 
   // From canvas spec, force to set line-height property to 'normal' font
   // property.
   if (aProperty == eCSSProperty_font) {
     const nsCString normalString = NS_LITERAL_CSTRING("normal");
     Servo_DeclarationBlock_SetPropertyById(servoDeclarations,
                                            eCSSProperty_line_height,
                                            &normalString,
                                            false,
                                            data,
                                            ParsingMode::Default,
-                                           aDocument->GetCompatibilityMode());
+                                           aDocument->GetCompatibilityMode(),
+                                           aDocument->CSSLoader());
   }
 
   return servoDeclarations.forget();
 }
 
 static already_AddRefed<RawServoDeclarationBlock>
 CreateFontDeclarationForServo(const nsAString& aFont,
                               nsIDocument* aDocument)
--- a/dom/events/test/mochitest.ini
+++ b/dom/events/test/mochitest.ini
@@ -175,9 +175,8 @@ skip-if = toolkit == 'android' #CRASH_DU
 [test_wheel_default_action.html]
 [test_bug687787.html]
 [test_bug1305458.html]
 [test_bug1298970.html]
 [test_bug1304044.html]
 [test_bug1332699.html]
 [test_bug1339758.html]
 [test_dnd_with_modifiers.html]
-[test_submitevent_on_form.html]
deleted file mode 100644
--- a/dom/events/test/test_submitevent_on_form.html
+++ /dev/null
@@ -1,37 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Test submit event on form</title>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-  <form action="javascript:doDefault()" id="form">
-    <input type="submit" value="Do Default Action">
-  </form>
-  <pre id="test">
-  <script type="application/javascript">
-SimpleTest.waitForExplicitFinish();
-SimpleTest.waitForFocus(runTests);
-
-var doDefaultAction = false;
-
-function doDefault()
-{
-  doDefaultAction = true;
-}
-
-function runTests()
-{
-  let form = document.getElementById("form");
-  form.dispatchEvent(new Event('submit'));
-  setTimeout(() => {
-    ok(!doDefaultAction, "untrusted submit event shouldn't trigger form default action");
-    SimpleTest.finish();
-  });
-}
-  </script>
-  </pre>
-</body>
-</html>
--- a/dom/fetch/FetchConsumer.cpp
+++ b/dom/fetch/FetchConsumer.cpp
@@ -206,32 +206,34 @@ public:
   OnStreamComplete(nsIStreamLoader* aLoader,
                    nsISupports* aCtxt,
                    nsresult aStatus,
                    uint32_t aResultLength,
                    const uint8_t* aResult) override
   {
     MOZ_ASSERT(NS_IsMainThread());
 
+    // The loading is completed. Let's nullify the pump before continuing the
+    // consuming of the body.
+    mFetchBodyConsumer->NullifyConsumeBodyPump();
+
     // If the binding requested cancel, we don't need to call
     // ContinueConsumeBody, since that is the originator.
     if (aStatus == NS_BINDING_ABORTED) {
       return NS_OK;
     }
 
     uint8_t* nonconstResult = const_cast<uint8_t*>(aResult);
     if (mFetchBodyConsumer->GetWorkerPrivate()) {
       RefPtr<ContinueConsumeBodyRunnable<Derived>> r =
         new ContinueConsumeBodyRunnable<Derived>(mFetchBodyConsumer,
                                                  aStatus,
                                                  aResultLength,
                                                  nonconstResult);
       if (!r->Dispatch()) {
-        // XXXcatalinb: The worker is shutting down, the pump will be canceled
-        // by FetchBodyWorkerHolder::Notify.
         NS_WARNING("Could not dispatch ConsumeBodyRunnable");
         // Return failure so that aResult is freed.
         return NS_ERROR_FAILURE;
       }
     } else {
       mFetchBodyConsumer->ContinueConsumeBody(aStatus, aResultLength,
                                               nonconstResult);
     }
@@ -277,31 +279,31 @@ template <class Derived>
 NS_IMPL_RELEASE(ConsumeBodyDoneObserver<Derived>)
 template <class Derived>
 NS_INTERFACE_MAP_BEGIN(ConsumeBodyDoneObserver<Derived>)
   NS_INTERFACE_MAP_ENTRY(nsIStreamLoaderObserver)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamLoaderObserver)
 NS_INTERFACE_MAP_END
 
 template <class Derived>
-class CancelPumpRunnable final : public WorkerMainThreadRunnable
+class ShutDownMainThreadConsumingRunnable final : public WorkerMainThreadRunnable
 {
   RefPtr<FetchBodyConsumer<Derived>> mBodyConsumer;
 
 public:
-  explicit CancelPumpRunnable(FetchBodyConsumer<Derived>* aBodyConsumer)
+  explicit ShutDownMainThreadConsumingRunnable(FetchBodyConsumer<Derived>* aBodyConsumer)
     : WorkerMainThreadRunnable(aBodyConsumer->GetWorkerPrivate(),
                                NS_LITERAL_CSTRING("Fetch :: Cancel Pump"))
     , mBodyConsumer(aBodyConsumer)
   {}
 
   bool
   MainThreadRun() override
   {
-    mBodyConsumer->CancelPump();
+    mBodyConsumer->ShutDownMainThreadConsuming();
     return true;
   }
 };
 
 } // anonymous
 
 template <class Derived>
 /* static */ already_AddRefed<Promise>
@@ -391,27 +393,26 @@ FetchBodyConsumer<Derived>::FetchBodyCon
   : mTargetThread(NS_GetCurrentThread())
   , mMainThreadEventTarget(aMainThreadEventTarget)
   , mBody(aBody)
   , mGlobal(aGlobalObject)
   , mWorkerPrivate(aWorkerPrivate)
   , mConsumeType(aType)
   , mConsumePromise(aPromise)
   , mBodyConsumed(false)
+  , mShuttingDown(false)
 {
   MOZ_ASSERT(aMainThreadEventTarget);
   MOZ_ASSERT(aBody);
   MOZ_ASSERT(aPromise);
 }
 
 template <class Derived>
 FetchBodyConsumer<Derived>::~FetchBodyConsumer()
 {
-  NS_ProxyRelease("FetchBodyConsumer::mBody",
-                  mTargetThread, mBody.forget());
 }
 
 template <class Derived>
 void
 FetchBodyConsumer<Derived>::AssertIsOnTargetThread() const
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mTargetThread);
 }
@@ -441,16 +442,21 @@ FetchBodyConsumer<Derived>::RegisterWork
  * reflected in a lack of error return code.
  */
 template <class Derived>
 void
 FetchBodyConsumer<Derived>::BeginConsumeBodyMainThread()
 {
   AssertIsOnMainThread();
 
+  // Nothing to do.
+  if (mShuttingDown) {
+    return;
+  }
+
   AutoFailConsumeBody<Derived> autoReject(this);
 
   nsresult rv;
   nsCOMPtr<nsIInputStream> stream;
   mBody->DerivedClass()->GetBody(getter_AddRefs(stream));
   if (!stream) {
     rv = NS_NewCStringInputStream(getter_AddRefs(stream), EmptyCString());
     if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -498,20 +504,19 @@ FetchBodyConsumer<Derived>::BeginConsume
   }
 
   rv = pump->AsyncRead(listener, nullptr);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
   // Now that everything succeeded, we can assign the pump to a pointer that
-  // stays alive for the lifetime of the FetchBody.
-  mConsumeBodyPump =
-    new nsMainThreadPtrHolder<nsIInputStreamPump>("FetchBodyConsumer::mConsumeBodyPump",
-                                                  pump, mMainThreadEventTarget);
+  // stays alive for the lifetime of the FetchConsumer.
+  mConsumeBodyPump = pump;
+
   // It is ok for retargeting to fail and reads to happen on the main thread.
   autoReject.DontFail();
 
   // Try to retarget, otherwise fall back to main thread.
   nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(pump);
   if (rr) {
     nsCOMPtr<nsIEventTarget> sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
     rv = rr->RetargetDeliveryTo(sts);
@@ -523,73 +528,46 @@ FetchBodyConsumer<Derived>::BeginConsume
 
 template <class Derived>
 void
 FetchBodyConsumer<Derived>::ContinueConsumeBody(nsresult aStatus,
                                                 uint32_t aResultLength,
                                                 uint8_t* aResult)
 {
   AssertIsOnTargetThread();
-  // Just a precaution to ensure ContinueConsumeBody is not called out of
-  // sync with a body read.
-  MOZ_ASSERT(mBody->BodyUsed());
 
   if (mBodyConsumed) {
     return;
   }
   mBodyConsumed = true;
 
+  // Just a precaution to ensure ContinueConsumeBody is not called out of
+  // sync with a body read.
+  MOZ_ASSERT(mBody->BodyUsed());
+
   auto autoFree = mozilla::MakeScopeExit([&] {
     free(aResult);
   });
 
   MOZ_ASSERT(mConsumePromise);
   RefPtr<Promise> localPromise = mConsumePromise.forget();
 
   RefPtr<FetchBodyConsumer<Derived>> self = this;
   auto autoReleaseObject = mozilla::MakeScopeExit([&] {
     self->ReleaseObject();
   });
 
   if (NS_WARN_IF(NS_FAILED(aStatus))) {
     localPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
-
-    // If binding aborted, cancel the pump. We can't assert mConsumeBodyPump.
-    // In the (admittedly rare) situation that BeginConsumeBodyMainThread()
-    // context switches out, and the worker thread gets canceled before the
-    // pump is setup, mConsumeBodyPump will be null.
-    // We've to use the !! form since non-main thread pointer access on
-    // a nsMainThreadPtrHandle is not permitted.
-    if (aStatus == NS_BINDING_ABORTED && !!mConsumeBodyPump) {
-      if (NS_IsMainThread()) {
-        CancelPump();
-      } else {
-        MOZ_ASSERT(mWorkerPrivate);
-        // In case of worker thread, we block the worker while the request is
-        // canceled on the main thread. This ensures that OnStreamComplete has
-        // a valid FetchBody around to call CancelPump and we don't release the
-        // FetchBody on the main thread.
-        RefPtr<CancelPumpRunnable<Derived>> r =
-          new CancelPumpRunnable<Derived>(this);
-        ErrorResult rv;
-        r->Dispatch(Terminating, rv);
-        if (rv.Failed()) {
-          NS_WARNING("Could not dispatch CancelPumpRunnable. Nothing we can do here");
-          // None of our callers are callled directly from JS, so there is no
-          // point in trying to propagate this failure out of here.  And
-          // localPromise is already rejected.  Just suppress the failure.
-          rv.SuppressException();
-        }
-      }
-    }
   }
 
-  // Release the pump and then early exit if there was an error.
-  // Uses NS_ProxyRelease internally, so this is safe.
-  mConsumeBodyPump = nullptr;
+  // We need to nullify mConsumeBodyPump on the main-thread and, in case it has
+  // not been created yet, we need to block the creation setting mShuttingDown
+  // to true.
+  ShutDownMainThreadConsuming();
 
   // Don't warn here since we warned above.
   if (NS_FAILED(aStatus)) {
     return;
   }
 
   // Finish successfully consuming body according to type.
   MOZ_ASSERT(aResult);
@@ -663,49 +641,73 @@ FetchBodyConsumer<Derived>::ContinueCons
   }
 }
 
 template <class Derived>
 void
 FetchBodyConsumer<Derived>::ContinueConsumeBlobBody(BlobImpl* aBlobImpl)
 {
   AssertIsOnTargetThread();
-  // Just a precaution to ensure ContinueConsumeBody is not called out of
-  // sync with a body read.
-  MOZ_ASSERT(mBody->BodyUsed());
   MOZ_ASSERT(mConsumeType == CONSUME_BLOB);
 
   if (mBodyConsumed) {
     return;
   }
   mBodyConsumed = true;
 
+  // Just a precaution to ensure ContinueConsumeBody is not called out of
+  // sync with a body read.
+  MOZ_ASSERT(mBody->BodyUsed());
+
   MOZ_ASSERT(mConsumePromise);
   RefPtr<Promise> localPromise = mConsumePromise.forget();
 
-  // Release the pump and then early exit if there was an error.
-  // Uses NS_ProxyRelease internally, so this is safe.
-  mConsumeBodyPump = nullptr;
+  // Release the pump.
+  ShutDownMainThreadConsuming();
 
   RefPtr<dom::Blob> blob =
     dom::Blob::Create(mBody->DerivedClass()->GetParentObject(), aBlobImpl);
   MOZ_ASSERT(blob);
 
   localPromise->MaybeResolve(blob);
 
   ReleaseObject();
 }
 
 template <class Derived>
 void
-FetchBodyConsumer<Derived>::CancelPump()
+FetchBodyConsumer<Derived>::ShutDownMainThreadConsuming()
 {
-  AssertIsOnMainThread();
-  MOZ_ASSERT(mConsumeBodyPump);
-  mConsumeBodyPump->Cancel(NS_BINDING_ABORTED);
+  if (!NS_IsMainThread()) {
+    MOZ_ASSERT(mWorkerPrivate);
+    // In case of worker thread, we block the worker while the request is
+    // canceled on the main thread. This ensures that OnStreamComplete has a
+    // valid FetchConsumer around to call ShutDownMainThreadConsuming and we
+    // don't release the FetchConsumer on the main thread.
+    RefPtr<ShutDownMainThreadConsumingRunnable<Derived>> r =
+      new ShutDownMainThreadConsumingRunnable<Derived>(this);
+
+    IgnoredErrorResult rv;
+    r->Dispatch(Terminating, rv);
+    if (NS_WARN_IF(rv.Failed())) {
+      return;
+    }
+
+    MOZ_DIAGNOSTIC_ASSERT(mShuttingDown);
+    return;
+  }
+
+  // We need this because maybe, mConsumeBodyPump has not been created yet. We
+  // must be sure that we don't try to do it.
+  mShuttingDown = true;
+
+  if (mConsumeBodyPump) {
+    mConsumeBodyPump->Cancel(NS_BINDING_ABORTED);
+    mConsumeBodyPump = nullptr;
+  }
 }
 
 template <class Derived>
 NS_IMETHODIMP
 FetchBodyConsumer<Derived>::Observe(nsISupports* aSubject,
                                     const char* aTopic,
                                     const char16_t* aData)
 {
--- a/dom/fetch/FetchConsumer.h
+++ b/dom/fetch/FetchConsumer.h
@@ -57,24 +57,30 @@ public:
 
   void
   ContinueConsumeBody(nsresult aStatus, uint32_t aLength, uint8_t* aResult);
 
   void
   ContinueConsumeBlobBody(BlobImpl* aBlobImpl);
 
   void
-  CancelPump();
+  ShutDownMainThreadConsuming();
 
   workers::WorkerPrivate*
   GetWorkerPrivate() const
   {
     return mWorkerPrivate;
   }
 
+  void
+  NullifyConsumeBodyPump()
+  {
+    mConsumeBodyPump = nullptr;
+  }
+
 private:
   FetchBodyConsumer(nsIEventTarget* aMainThreadEventTarget,
                     nsIGlobalObject* aGlobalObject,
                     workers::WorkerPrivate* aWorkerPrivate,
                     FetchBody<Derived>* aBody,
                     Promise* aPromise,
                     FetchConsumeType aType);
 
@@ -95,21 +101,26 @@ private:
   // This WorkerHolder keeps alive the consumer via a cycle.
   UniquePtr<workers::WorkerHolder> mWorkerHolder;
 
   nsCOMPtr<nsIGlobalObject> mGlobal;
 
   // Always set whenever the FetchBodyConsumer is created on the worker thread.
   workers::WorkerPrivate* mWorkerPrivate;
 
-  nsMainThreadPtrHandle<nsIInputStreamPump> mConsumeBodyPump;
+  // Touched on the main-thread only.
+  nsCOMPtr<nsIInputStreamPump> mConsumeBodyPump;
 
   // Only ever set once, always on target thread.
   FetchConsumeType mConsumeType;
   RefPtr<Promise> mConsumePromise;
 
+  // touched only on the target thread.
   bool mBodyConsumed;
+
+  // touched only on the main-thread.
+  bool mShuttingDown;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_FetchConsumer_h
--- a/dom/html/HTMLFormElement.cpp
+++ b/dom/html/HTMLFormElement.cpp
@@ -469,20 +469,17 @@ HTMLFormElement::UnbindFromTree(bool aDe
   }
   ForgetCurrentSubmission();
 }
 
 nsresult
 HTMLFormElement::GetEventTargetParent(EventChainPreVisitor& aVisitor)
 {
   aVisitor.mWantsWillHandleEvent = true;
-  // According to the UI events spec section "Trusted events", we shouldn't
-  // trigger UA default action with an untrusted event except click.
-  if (aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this) &&
-      aVisitor.mEvent->IsTrusted()) {
+  if (aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this)) {
     uint32_t msg = aVisitor.mEvent->mMessage;
     if (msg == eFormSubmit) {
       if (mGeneratingSubmit) {
         aVisitor.mCanHandle = false;
         return NS_OK;
       }
       mGeneratingSubmit = true;
 
@@ -514,20 +511,17 @@ HTMLFormElement::WillHandleEvent(EventCh
     aVisitor.mEvent->StopPropagation();
   }
   return NS_OK;
 }
 
 nsresult
 HTMLFormElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
 {
-  // According to the UI events spec section "Trusted events", we shouldn't
-  // trigger UA default action with an untrusted event except click.
-  if (aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this) &&
-      aVisitor.mEvent->IsTrusted()) {
+  if (aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this)) {
     EventMessage msg = aVisitor.mEvent->mMessage;
     if (msg == eFormSubmit) {
       // let the form know not to defer subsequent submissions
       mDeferSubmission = false;
     }
 
     if (aVisitor.mEventStatus == nsEventStatus_eIgnore) {
       switch (msg) {
--- a/dom/html/HTMLLinkElement.cpp
+++ b/dom/html/HTMLLinkElement.cpp
@@ -162,17 +162,17 @@ HTMLLinkElement::BindToTree(nsIDocument*
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Link must be inert in ShadowRoot.
   if (aDocument && !GetContainingShadow()) {
     aDocument->RegisterPendingLinkUpdate(this);
   }
 
   if (IsInComposedDoc()) {
-    TryDNSPrefetchPreconnectOrPrefetchOrPrerender();
+    TryDNSPrefetchOrPreconnectOrPrefetchOrPreloadOrPrerender();
   }
 
   void (HTMLLinkElement::*update)() = &HTMLLinkElement::UpdateStyleSheetInternal;
   nsContentUtils::AddScriptRunner(
     NewRunnableMethod("dom::HTMLLinkElement::BindToTree", this, update));
 
   CreateAndDispatchEvent(aDocument, NS_LITERAL_STRING("DOMLinkAdded"));
 
@@ -194,17 +194,17 @@ HTMLLinkElement::LinkRemoved()
 void
 HTMLLinkElement::UnbindFromTree(bool aDeep, bool aNullParent)
 {
   // Cancel any DNS prefetches
   // Note: Must come before ResetLinkState.  If called after, it will recreate
   // mCachedURI based on data that is invalid - due to a call to GetHostname.
   CancelDNSPrefetch(HTML_LINK_DNS_PREFETCH_DEFERRED,
                     HTML_LINK_DNS_PREFETCH_REQUESTED);
-  CancelPrefetch();
+  CancelPrefetchOrPreload();
 
   // If this link is ever reinserted into a document, it might
   // be under a different xml:base, so forget the cached state now.
   Link::ResetLinkState(false, Link::ElementHasHref());
 
   // If this is reinserted back into the document it will not be
   // from the parser.
   nsCOMPtr<nsIDocument> oldDoc = GetUncomposedDoc();
@@ -227,16 +227,21 @@ HTMLLinkElement::ParseAttribute(int32_t 
                                 nsAttrValue& aResult)
 {
   if (aNamespaceID == kNameSpaceID_None) {
     if (aAttribute == nsGkAtoms::crossorigin) {
       ParseCORSValue(aValue, aResult);
       return true;
     }
 
+    if (aAttribute == nsGkAtoms::as) {
+      ParseAsValue(aValue, aResult);
+      return true;
+    }
+
     if (aAttribute == nsGkAtoms::sizes) {
       aResult.ParseAtomArray(aValue);
       return true;
     }
 
     if (aAttribute == nsGkAtoms::integrity) {
       aResult.ParseStringOrAtom(aValue);
       return true;
@@ -279,17 +284,17 @@ HTMLLinkElement::CreateAndDispatchEvent(
 nsresult
 HTMLLinkElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                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();
+    CancelPrefetchOrPreload();
   }
 
   return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName,
                                              aValue, aNotify);
 }
 
 nsresult
 HTMLLinkElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
@@ -310,30 +315,38 @@ HTMLLinkElement::AfterSetAttr(int32_t aN
   }
 
   if (aValue) {
     if (aNameSpaceID == kNameSpaceID_None &&
         (aName == nsGkAtoms::href ||
          aName == nsGkAtoms::rel ||
          aName == nsGkAtoms::title ||
          aName == nsGkAtoms::media ||
-         aName == nsGkAtoms::type)) {
+         aName == nsGkAtoms::type ||
+         aName == nsGkAtoms::as ||
+         aName == nsGkAtoms::crossorigin)) {
       bool dropSheet = false;
       if (aName == nsGkAtoms::rel) {
         nsAutoString value;
         aValue->ToString(value);
         uint32_t linkTypes = nsStyleLinkElement::ParseLinkTypes(value);
         if (GetSheet()) {
           dropSheet = !(linkTypes & nsStyleLinkElement::eSTYLESHEET);
         }
       }
 
       if ((aName == nsGkAtoms::rel || aName == nsGkAtoms::href) &&
           IsInComposedDoc()) {
-        TryDNSPrefetchPreconnectOrPrefetchOrPrerender();
+        TryDNSPrefetchOrPreconnectOrPrefetchOrPreloadOrPrerender();
+      }
+
+      if ((aName == nsGkAtoms::as || aName == nsGkAtoms::type ||
+           aName == nsGkAtoms::crossorigin || aName == nsGkAtoms::media) &&
+          IsInComposedDoc()) {
+        UpdatePreload(aName, aValue, aOldValue);
       }
 
       UpdateStyleSheetInternal(nullptr, nullptr,
                                dropSheet ||
                                (aName == nsGkAtoms::title ||
                                 aName == nsGkAtoms::media ||
                                 aName == nsGkAtoms::type));
     }
@@ -343,16 +356,21 @@ HTMLLinkElement::AfterSetAttr(int32_t aN
     if (aNameSpaceID == kNameSpaceID_None) {
       if (aName == nsGkAtoms::href ||
           aName == nsGkAtoms::rel ||
           aName == nsGkAtoms::title ||
           aName == nsGkAtoms::media ||
           aName == nsGkAtoms::type) {
         UpdateStyleSheetInternal(nullptr, nullptr, true);
       }
+      if ((aName == nsGkAtoms::as || aName == nsGkAtoms::type ||
+           aName == nsGkAtoms::crossorigin || aName == nsGkAtoms::media) &&
+          IsInComposedDoc()) {
+        UpdatePreload(aName, aValue, aOldValue);
+      }
     }
   }
 
   return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue,
                                             aOldValue, aNotify);
 }
 
 nsresult
@@ -388,16 +406,17 @@ static const DOMTokenListSupportedToken 
   "prefetch",
   "dns-prefetch",
   "stylesheet",
   "next",
   "alternate",
   "preconnect",
   "icon",
   "search",
+  "preload",
   nullptr
 };
 
 nsDOMTokenList*
 HTMLLinkElement::RelList()
 {
   if (!mRelList) {
     mRelList = new nsDOMTokenList(this, nsGkAtoms::rel, sSupportedRelValues);
@@ -499,10 +518,16 @@ HTMLLinkElement::SizeOfExcludingThis(moz
 }
 
 JSObject*
 HTMLLinkElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return HTMLLinkElementBinding::Wrap(aCx, this, aGivenProto);
 }
 
+void
+HTMLLinkElement::GetAs(nsAString& aResult)
+{
+  GetEnumAttr(nsGkAtoms::as, EmptyCString().get(), aResult);
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/html/HTMLLinkElement.h
+++ b/dom/html/HTMLLinkElement.h
@@ -113,16 +113,21 @@ public:
   {
     SetHTMLAttr(nsGkAtoms::media, aMedia, aRv);
   }
   // XPCOM GetHreflang is fine.
   void SetHreflang(const nsAString& aHreflang, ErrorResult& aRv)
   {
     SetHTMLAttr(nsGkAtoms::hreflang, aHreflang, aRv);
   }
+  void GetAs(nsAString& aResult);
+  void SetAs(const nsAString& aAs, ErrorResult& aRv)
+  {
+    SetAttr(nsGkAtoms::as ,aAs, aRv);
+  }
   nsDOMTokenList* Sizes()
   {
     return GetTokenList(nsGkAtoms::sizes);
   }
   // XPCOM GetType is fine.
   void SetType(const nsAString& aType, ErrorResult& aRv)
   {
     SetHTMLAttr(nsGkAtoms::type, aType, aRv);
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -227,18 +227,18 @@ RejectPromises(const nsTArray<RefPtr<Pro
 // since if we neglect to add a self-reference, this element might be
 // garbage collected while there are still event listeners that should
 // receive events. If we neglect to remove the self-reference then the element
 // just lives longer than it needs to.
 
 class nsMediaEvent : public Runnable
 {
 public:
-  explicit nsMediaEvent(HTMLMediaElement* aElement)
-    : Runnable("dom::nsMediaEvent")
+  explicit nsMediaEvent(const char* aName, HTMLMediaElement* aElement)
+    : Runnable(aName)
     , mElement(aElement)
     , mLoadID(mElement->GetCurrentLoadID())
   {
   }
   ~nsMediaEvent() {}
 
   NS_IMETHOD Run() = 0;
 
@@ -253,17 +253,17 @@ protected:
 
 class HTMLMediaElement::nsAsyncEventRunner : public nsMediaEvent
 {
 private:
   nsString mName;
 
 public:
   nsAsyncEventRunner(const nsAString& aName, HTMLMediaElement* aElement) :
-    nsMediaEvent(aElement), mName(aName)
+    nsMediaEvent("HTMLMediaElement::nsAsyncEventRunner", aElement), mName(aName)
   {
   }
 
   NS_IMETHOD Run() override
   {
     // Silently cancel if our load has been cancelled.
     if (IsCancelled())
       return NS_OK;
@@ -286,17 +286,17 @@ class HTMLMediaElement::nsResolveOrRejec
 {
   nsTArray<RefPtr<Promise>> mPromises;
   nsresult mError;
 
 public:
   nsResolveOrRejectPendingPlayPromisesRunner(HTMLMediaElement* aElement,
                                              nsTArray<RefPtr<Promise>>&& aPromises,
                                              nsresult aError = NS_OK)
-  : nsMediaEvent(aElement)
+  : nsMediaEvent("HTMLMediaElement::nsResolveOrRejectPendingPlayPromisesRunner", aElement)
   , mPromises(Move(aPromises))
   , mError(aError)
   {
     mElement->mPendingPlayPromisesRunners.AppendElement(this);
   }
 
   void ResolveOrReject()
   {
@@ -342,17 +342,17 @@ public:
 
 class nsSourceErrorEventRunner : public nsMediaEvent
 {
 private:
   nsCOMPtr<nsIContent> mSource;
 public:
   nsSourceErrorEventRunner(HTMLMediaElement* aElement,
                            nsIContent* aSource)
-    : nsMediaEvent(aElement),
+    : nsMediaEvent("dom::nsSourceErrorEventRunner", aElement),
       mSource(aSource)
   {
   }
 
   NS_IMETHOD Run() override {
     // Silently cancel if our load has been cancelled.
     if (IsCancelled())
       return NS_OK;
@@ -368,18 +368,22 @@ public:
 /**
  * This listener observes the first video frame to arrive with a non-empty size,
  * and calls HTMLMediaElement::UpdateInitialMediaSize() with that size.
  */
 class HTMLMediaElement::StreamSizeListener : public DirectMediaStreamTrackListener {
 public:
   explicit StreamSizeListener(HTMLMediaElement* aElement) :
     mElement(aElement),
+    mMainThreadEventTarget(aElement->MainThreadEventTarget()),
     mInitialSizeFound(false)
-  {}
+  {
+    MOZ_ASSERT(mElement);
+    MOZ_ASSERT(mMainThreadEventTarget);
+  }
 
   void Forget() { mElement = nullptr; }
 
   void ReceivedSize(gfx::IntSize aSize)
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     if (!mElement) {
@@ -402,34 +406,36 @@ public:
       MOZ_ASSERT(false, "Should only lock on to a video track");
       return;
     }
 
     const VideoSegment& video = static_cast<const VideoSegment&>(aMedia);
     for (VideoSegment::ConstChunkIterator c(video); !c.IsEnded(); c.Next()) {
       if (c->mFrame.GetIntrinsicSize() != gfx::IntSize(0,0)) {
         mInitialSizeFound = true;
-        nsCOMPtr<nsIRunnable> event = NewRunnableMethod<gfx::IntSize>(
-          "dom::HTMLMediaElement::StreamSizeListener::ReceivedSize",
-          this,
-          &StreamSizeListener::ReceivedSize,
-          c->mFrame.GetIntrinsicSize());
         // This is fine to dispatch straight to main thread (instead of via
         // ...AfterStreamUpdate()) since it reflects state of the element,
         // not the stream. Events reflecting stream or track state should be
         // dispatched so their order is preserved.
-        NS_DispatchToMainThread(event.forget());
+        mMainThreadEventTarget->Dispatch(NewRunnableMethod<gfx::IntSize>(
+          "dom::HTMLMediaElement::StreamSizeListener::ReceivedSize",
+          this,
+          &StreamSizeListener::ReceivedSize,
+          c->mFrame.GetIntrinsicSize()));
         return;
       }
     }
   }
 
 private:
   // These fields may only be accessed on the main thread
   HTMLMediaElement* mElement;
+  // We hold mElement->MainThreadEventTarget() here because the mElement could
+  // be reset in Forget().
+  nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget;
 
   // These fields may only be accessed on the MSG's appending thread.
   // (this is a direct listener so we get called by whoever is producing
   // this track's data)
   bool mInitialSizeFound;
 };
 
 /**
@@ -969,17 +975,17 @@ private:
       return;
     }
 
     if (!IsPlayingStarted()) {
       return;
     }
 
     uint64_t windowID = mAudioChannelAgent->WindowID();
-    NS_DispatchToMainThread(NS_NewRunnableFunction(
+    mOwner->MainThreadEventTarget()->Dispatch(NS_NewRunnableFunction(
       "dom::HTMLMediaElement::AudioChannelAgentCallback::"
       "MaybeNotifyMediaResumed",
       [windowID]() -> void {
         nsCOMPtr<nsIObserverService> observerService =
           services::GetObserverService();
         if (NS_WARN_IF(!observerService)) {
           return;
         }
@@ -1226,21 +1232,22 @@ public:
 
     // loadListener will be unregistered either on shutdown or when
     // OnStartRequest for the channel we just opened fires.
     nsContentUtils::RegisterShutdownObserver(loadListener);
   }
 
   nsresult Load(HTMLMediaElement* aElement)
   {
+    MOZ_ASSERT(aElement);
     // Per bug 1235183 comment 8, we can't spin the event loop from stable
     // state. Defer NS_NewChannel() to a new regular runnable.
-    return NS_DispatchToMainThread(NewRunnableMethod<HTMLMediaElement*>(
-      "ChannelLoader::LoadInternal",
-      this, &ChannelLoader::LoadInternal, aElement));
+    return aElement->MainThreadEventTarget()->Dispatch(
+      NewRunnableMethod<HTMLMediaElement*>("ChannelLoader::LoadInternal",
+        this, &ChannelLoader::LoadInternal, aElement));
   }
 
   void Cancel()
   {
     mCancelled = true;
     if (mChannel) {
       mChannel->Cancel(NS_BINDING_ABORTED);
       mChannel = nullptr;
@@ -1536,17 +1543,17 @@ HTMLMediaElement::MozRequestDebugInfo(Er
     GetEMEInfo(EMEInfo);
     result.AppendLiteral("EME Info: ");
     result.Append(EMEInfo);
     result.AppendLiteral("\n");
   }
 
   if (mDecoder) {
     mDecoder->RequestDebugInfo()->Then(
-      AbstractThread::MainThread(), __func__,
+      mAbstractMainThread, __func__,
       [promise, result] (const nsACString& aString) {
         promise->MaybeResolve(result + NS_ConvertUTF8toUTF16(aString));
       },
       [promise, result] () {
         promise->MaybeResolve(result);
       });
   } else {
     promise->MaybeResolve(result);
@@ -1838,17 +1845,17 @@ typedef void (HTMLMediaElement::*SyncSec
 // http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#synchronous-section
 class nsSyncSection : public nsMediaEvent
 {
 private:
   nsCOMPtr<nsIRunnable> mRunnable;
 public:
   nsSyncSection(HTMLMediaElement* aElement,
                 nsIRunnable* aRunnable) :
-    nsMediaEvent(aElement),
+    nsMediaEvent("dom::nsSyncSection", aElement),
     mRunnable(aRunnable)
   {
   }
 
   NS_IMETHOD Run() override {
     // Silently cancel if our load has been cancelled.
     if (IsCancelled())
       return NS_OK;
@@ -2024,20 +2031,19 @@ void HTMLMediaElement::SelectResource()
       }
     } else {
       const char16_t* params[] = { src.get() };
       ReportLoadError("MediaLoadInvalidURI", params, ArrayLength(params));
     }
     // The media element has neither a src attribute nor a source element child:
     // set the networkState to NETWORK_EMPTY, and abort these steps; the
     // synchronous section ends.
-    nsCOMPtr<nsIRunnable> event =
-      NewRunnableMethod<nsCString>("HTMLMediaElement::NoSupportedMediaSourceError",
-                                   this, &HTMLMediaElement::NoSupportedMediaSourceError, nsCString());
-    NS_DispatchToMainThread(event);
+    mMainThreadEventTarget->Dispatch(NewRunnableMethod<nsCString>(
+      "HTMLMediaElement::NoSupportedMediaSourceError",
+      this, &HTMLMediaElement::NoSupportedMediaSourceError, nsCString()));
   } else {
     // Otherwise, the source elements will be used.
     mIsLoadingFromSourceChildren = true;
     LoadFromSourceChildren();
   }
 }
 
 void HTMLMediaElement::NotifyLoadError()
@@ -2184,19 +2190,19 @@ void HTMLMediaElement::NotifyMediaTrackD
         // If we bounce it to the MediaStreamGraph it might not be picked up,
         // for instance if the MediaInputPort was destroyed in the same
         // iteration as it was added.
         MediaStreamTrack* outputTrack = ms.mStream->FindOwnedDOMTrack(
             ms.mTrackPorts[i].second()->GetDestination(),
             ms.mTrackPorts[i].second()->GetDestinationTrackId());
         MOZ_ASSERT(outputTrack);
         if (outputTrack) {
-          NS_DispatchToMainThread(
-            NewRunnableMethod("MediaStreamTrack::OverrideEnded",
-                              outputTrack, &MediaStreamTrack::OverrideEnded));
+          mMainThreadEventTarget->Dispatch(NewRunnableMethod(
+            "MediaStreamTrack::OverrideEnded",
+            outputTrack, &MediaStreamTrack::OverrideEnded));
         }
 
         ms.mTrackPorts[i].second()->Destroy();
         ms.mTrackPorts.RemoveElementAt(i);
         break;
       }
     }
 #ifdef DEBUG
@@ -2231,20 +2237,19 @@ void HTMLMediaElement::NotifyMediaStream
 
 void HTMLMediaElement::DealWithFailedElement(nsIContent* aSourceElement)
 {
   if (mShuttingDown) {
     return;
   }
 
   DispatchAsyncSourceError(aSourceElement);
-  nsCOMPtr<nsIRunnable> event =
-    NewRunnableMethod("HTMLMediaElement::QueueLoadFromSourceTask",
-                      this, &HTMLMediaElement::QueueLoadFromSourceTask);
-  NS_DispatchToMainThread(event);
+  mMainThreadEventTarget->Dispatch(NewRunnableMethod(
+    "HTMLMediaElement::QueueLoadFromSourceTask",
+    this, &HTMLMediaElement::QueueLoadFromSourceTask));
 }
 
 void
 HTMLMediaElement::NotifyOutputTrackStopped(DOMMediaStream* aOwningStream,
                                            TrackID aDestinationTrackID)
 {
   for (OutputMediaStream& ms : mOutputStreams) {
     if (!ms.mCapturingMediaStream) {
@@ -3364,17 +3369,17 @@ HTMLMediaElement::AddCaptureMediaTrackTo
   MediaSegment::Type type = inputTrack->AsAudioStreamTrack()
                           ? MediaSegment::AUDIO
                           : MediaSegment::VIDEO;
 
   RefPtr<MediaStreamTrack> track =
     aOutputStream.mStream->CreateDOMTrack(destinationTrackID, type, source);
 
   if (aAsyncAddtrack) {
-    NS_DispatchToMainThread(
+    mMainThreadEventTarget->Dispatch(
       NewRunnableMethod<StoreRefPtrPassByPtr<MediaStreamTrack>>(
         "DOMMediaStream::AddTrackInternal",
         aOutputStream.mStream, &DOMMediaStream::AddTrackInternal, track));
   } else {
     aOutputStream.mStream->AddTrackInternal(track);
   }
 
   // Track is muted initially, so we don't leak data if it's added while paused
@@ -3770,16 +3775,17 @@ private:
   HTMLMediaElement* mWeak = nullptr;
   Phase mPhase = Phase::Init;
 };
 
 NS_IMPL_ISUPPORTS(HTMLMediaElement::ShutdownObserver, nsIObserver)
 
 HTMLMediaElement::HTMLMediaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
   : nsGenericHTMLElement(aNodeInfo),
+    mMainThreadEventTarget(OwnerDoc()->EventTargetFor(TaskCategory::Other)),
     mAbstractMainThread(OwnerDoc()->AbstractMainThreadFor(TaskCategory::Other)),
     mWatchManager(this, mAbstractMainThread),
     mSrcStreamTracksAvailable(false),
     mSrcStreamPausedCurrentTime(-1),
     mShutdownObserver(new ShutdownObserver),
     mCurrentLoadID(0),
     mSourcePointer(0),
     mNetworkState(nsIDOMHTMLMediaElement::NETWORK_EMPTY),
@@ -3833,16 +3839,19 @@ HTMLMediaElement::HTMLMediaElement(alrea
     mDefaultPlaybackStartPosition(0.0),
     mIsAudioTrackAudible(false),
     mHasSuspendTaint(false),
     mMediaTracksConstructed(false),
     mVisibilityState(Visibility::UNTRACKED),
     mErrorSink(new ErrorSink(this)),
     mAudioChannelWrapper(new AudioChannelAgentCallback(this, mAudioChannel))
 {
+  MOZ_ASSERT(mMainThreadEventTarget);
+  MOZ_ASSERT(mAbstractMainThread);
+
   ErrorResult rv;
 
   double defaultVolume = Preferences::GetFloat("media.default_volume", 1.0);
   SetVolume(defaultVolume, rv);
 
   mPaused.SetOuter(this);
 
   RegisterActivityObserver();
@@ -4143,16 +4152,18 @@ HTMLMediaElement::WakeLockBoolWrapper::S
 {
   mCanPlay = aCanPlay;
   UpdateWakeLock();
 }
 
 void
 HTMLMediaElement::WakeLockBoolWrapper::UpdateWakeLock()
 {
+  MOZ_ASSERT(NS_IsMainThread());
+
   if (!mOuter) {
     return;
   }
 
   bool playing = (!mValue && mCanPlay);
 
   if (playing) {
     if (mTimer) {
@@ -4161,16 +4172,17 @@ HTMLMediaElement::WakeLockBoolWrapper::U
     }
     mOuter->WakeLockCreate();
   } else if (!mTimer) {
     // Don't release the wake lock immediately; instead, release it after a
     // grace period.
     int timeout = Preferences::GetInt("media.wakelock_timeout", 2000);
     mTimer = do_CreateInstance("@mozilla.org/timer;1");
     if (mTimer) {
+      mTimer->SetTarget(mOuter->MainThreadEventTarget());
       mTimer->InitWithNamedFuncCallback(
         TimerCallback,
         this,
         timeout,
         nsITimer::TYPE_ONE_SHOT,
         "dom::HTMLMediaElement::WakeLockBoolWrapper::UpdateWakeLock");
     }
   }
@@ -4392,16 +4404,17 @@ void HTMLMediaElement::HiddenVideoStart(
 {
   MOZ_ASSERT(NS_IsMainThread());
   mHiddenPlayTime.Start();
   if (mVideoDecodeSuspendTimer) {
     // Already started, just keep it running.
     return;
   }
   mVideoDecodeSuspendTimer = do_CreateInstance("@mozilla.org/timer;1");
+  mVideoDecodeSuspendTimer->SetTarget(mMainThreadEventTarget);
   mVideoDecodeSuspendTimer->InitWithNamedFuncCallback(
     VideoDecodeSuspendTimerCallback, this,
     MediaPrefs::MDSMSuspendBackgroundVideoDelay(), nsITimer::TYPE_ONE_SHOT,
     "HTMLMediaElement::VideoDecodeSuspendTimerCallback");
 }
 
 void HTMLMediaElement::HiddenVideoStop()
 {
@@ -5588,16 +5601,17 @@ void HTMLMediaElement::ProgressTimerCall
 
 void HTMLMediaElement::StartProgressTimer()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING);
   NS_ASSERTION(!mProgressTimer, "Already started progress timer.");
 
   mProgressTimer = do_CreateInstance("@mozilla.org/timer;1");
+  mProgressTimer->SetTarget(mMainThreadEventTarget);
   mProgressTimer->InitWithNamedFuncCallback(
     ProgressTimerCallback, this, PROGRESS_MS, nsITimer::TYPE_REPEATING_SLACK,
     "HTMLMediaElement::ProgressTimerCallback");
 }
 
 void HTMLMediaElement::StartProgress()
 {
   // Record the time now for detecting stalled.
@@ -6134,19 +6148,17 @@ nsresult HTMLMediaElement::DispatchAsync
   nsCOMPtr<nsIRunnable> event;
 
   if (aName.EqualsLiteral("playing")) {
     event = new nsNotifyAboutPlayingRunner(this, TakePendingPlayPromises());
   } else {
     event = new nsAsyncEventRunner(aName, this);
   }
 
-  OwnerDoc()->Dispatch("HTMLMediaElement::DispatchAsyncEvent",
-                       TaskCategory::Other,
-                       event.forget());
+  mMainThreadEventTarget->Dispatch(event.forget());
 
   if ((aName.EqualsLiteral("play") || aName.EqualsLiteral("playing"))) {
     mPlayTime.Start();
     if (IsHidden()) {
       HiddenVideoStart();
     }
   } else if (aName.EqualsLiteral("waiting")) {
     mPlayTime.Pause();
@@ -6380,21 +6392,20 @@ void HTMLMediaElement::AddRemoveSelfRefe
     if (needSelfReference) {
       // The shutdown observer will hold a strong reference to us. This
       // will do to keep us alive. We need to know about shutdown so that
       // we can release our self-reference.
       mShutdownObserver->AddRefMediaElement();
     } else {
       // Dispatch Release asynchronously so that we don't destroy this object
       // inside a call stack of method calls on this object
-      nsCOMPtr<nsIRunnable> event =
-        NewRunnableMethod("dom::HTMLMediaElement::DoRemoveSelfReference",
-                          this,
-                          &HTMLMediaElement::DoRemoveSelfReference);
-      NS_DispatchToMainThread(event);
+      mMainThreadEventTarget->Dispatch(NewRunnableMethod(
+        "dom::HTMLMediaElement::DoRemoveSelfReference",
+        this,
+        &HTMLMediaElement::DoRemoveSelfReference));
     }
   }
 }
 
 void HTMLMediaElement::DoRemoveSelfReference()
 {
   mShutdownObserver->ReleaseMediaElement();
 }
@@ -6412,17 +6423,17 @@ HTMLMediaElement::IsNodeOfType(uint32_t 
   return !(aFlags & ~(eCONTENT | eMEDIA));
 }
 
 void HTMLMediaElement::DispatchAsyncSourceError(nsIContent* aSourceElement)
 {
   LOG_EVENT(LogLevel::Debug, ("%p Queuing simple source error event", this));
 
   nsCOMPtr<nsIRunnable> event = new nsSourceErrorEventRunner(this, aSourceElement);
-  NS_DispatchToMainThread(event);
+  mMainThreadEventTarget->Dispatch(event.forget());
 }
 
 void HTMLMediaElement::NotifyAddedSource()
 {
   // If a source element is inserted as a child of a media element
   // that has no src attribute and whose networkState has the value
   // NETWORK_EMPTY, the user agent must invoke the media element's
   // resource selection algorithm.
@@ -7402,36 +7413,32 @@ HTMLMediaElement::AsyncResolvePendingPla
   if (mShuttingDown) {
     return;
   }
 
   nsCOMPtr<nsIRunnable> event
     = new nsResolveOrRejectPendingPlayPromisesRunner(this,
                                                      TakePendingPlayPromises());
 
-  OwnerDoc()->Dispatch("nsResolveOrRejectPendingPlayPromisesRunner",
-                       TaskCategory::Other,
-                       event.forget());
+  mMainThreadEventTarget->Dispatch(event.forget());
 }
 
 void
 HTMLMediaElement::AsyncRejectPendingPlayPromises(nsresult aError)
 {
   if (mShuttingDown) {
     return;
   }
 
   nsCOMPtr<nsIRunnable> event
     = new nsResolveOrRejectPendingPlayPromisesRunner(this,
                                                      TakePendingPlayPromises(),
                                                      aError);
 
-  OwnerDoc()->Dispatch("nsResolveOrRejectPendingPlayPromisesRunner",
-                       TaskCategory::Other,
-                       event.forget());
+  mMainThreadEventTarget->Dispatch(event.forget());
 }
 
 void
 HTMLMediaElement::GetEMEInfo(nsString& aEMEInfo)
 {
   if (!mMediaKeys) {
     return;
   }
@@ -7583,33 +7590,35 @@ HTMLMediaElement::ReportCanPlayTelemetry
   LOG(LogLevel::Debug, ("%s", __func__));
 
   RefPtr<nsIThread> thread;
   nsresult rv = NS_NewNamedThread("MediaTelemetry", getter_AddRefs(thread));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
+  RefPtr<AbstractThread> abstractThread = mAbstractMainThread;
+
   thread->Dispatch(
     NS_NewRunnableFunction(
       "dom::HTMLMediaElement::ReportCanPlayTelemetry",
-      [thread]() {
+      [thread, abstractThread]() {
 #if XP_WIN
         // Windows Media Foundation requires MSCOM to be inited.
         HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
         MOZ_ASSERT(hr == S_OK);
 #endif
         bool aac = MP4Decoder::IsSupportedType(
           MediaContainerType(MEDIAMIMETYPE("audio/mp4")), nullptr);
         bool h264 = MP4Decoder::IsSupportedType(
           MediaContainerType(MEDIAMIMETYPE("video/mp4")), nullptr);
 #if XP_WIN
         CoUninitialize();
 #endif
-        AbstractThread::MainThread()->Dispatch(NS_NewRunnableFunction(
+        abstractThread->Dispatch(NS_NewRunnableFunction(
           "dom::HTMLMediaElement::ReportCanPlayTelemetry",
           [thread, aac, h264]() {
             LOG(LogLevel::Debug, ("MediaTelemetry aac=%d h264=%d", aac, h264));
             Telemetry::Accumulate(
               Telemetry::HistogramID::VIDEO_CAN_CREATE_AAC_DECODER, aac);
             Telemetry::Accumulate(
               Telemetry::HistogramID::VIDEO_CAN_CREATE_H264_DECODER, h264);
             thread->AsyncShutdown();
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -60,16 +60,17 @@ class MediaStreamTrack;
 class VideoStreamTrack;
 } // namespace dom
 } // namespace mozilla
 
 class nsIChannel;
 class nsIHttpChannel;
 class nsILoadGroup;
 class nsIRunnable;
+class nsISerialEventTarget;
 class nsITimer;
 class nsRange;
 
 namespace mozilla {
 namespace dom {
 
 // Number of milliseconds between timeupdate events as defined by spec
 #define TIMEUPDATE_MS 250
@@ -770,16 +771,21 @@ public:
   // At the time we are going to resolve/reject a promise, the "seeking" event
   // task should already be queued but might yet be processed, so we queue one
   // more task to file the promise resolving/rejection micro-tasks
   // asynchronously to make sure that the micro-tasks are processed after the
   // "seeking" event task.
   void AsyncResolveSeekDOMPromiseIfExists() override;
   void AsyncRejectSeekDOMPromiseIfExists() override;
 
+  nsISerialEventTarget* MainThreadEventTarget()
+  {
+    return mMainThreadEventTarget;
+  }
+
 protected:
   virtual ~HTMLMediaElement();
 
   class AudioChannelAgentCallback;
   class ChannelLoader;
   class ErrorSink;
   class MediaLoadListener;
   class MediaStreamTracksAvailableCallback;
@@ -1312,16 +1318,20 @@ protected:
   virtual nsresult OnAttrSetButNotChanged(int32_t aNamespaceID, nsIAtom* aName,
                                           const nsAttrValueOrString& aValue,
                                           bool aNotify) override;
 
   // The current decoder. Load() has been called on this decoder.
   // At most one of mDecoder and mSrcStream can be non-null.
   RefPtr<MediaDecoder> mDecoder;
 
+  // The DocGroup-specific nsISerialEventTarget of this HTML element on the main
+  // thread.
+  nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget;
+
   // The DocGroup-specific AbstractThread::MainThread() of this HTML element.
   RefPtr<AbstractThread> mAbstractMainThread;
 
   // Observers listening to changes to the mDecoder principal.
   // Used by streams captured from this element.
   nsTArray<DecoderPrincipalChangeObserver*> mDecoderPrincipalChangeObservers;
 
   // State-watching manager.
--- a/dom/html/HTMLTableElement.cpp
+++ b/dom/html/HTMLTableElement.cpp
@@ -19,209 +19,229 @@ NS_IMPL_NS_NEW_HTML_ELEMENT(Table)
 namespace mozilla {
 namespace dom {
 
 /* ------------------------------ TableRowsCollection -------------------------------- */
 /**
  * This class provides a late-bound collection of rows in a table.
  * mParent is NOT ref-counted to avoid circular references
  */
-class TableRowsCollection final : public nsIHTMLCollection,
-                                  public nsWrapperCache
+class TableRowsCollection final : public nsIHTMLCollection
+                                , public nsStubMutationObserver
+                                , public nsWrapperCache
 {
 public:
   explicit TableRowsCollection(HTMLTableElement* aParent);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_NSIDOMHTMLCOLLECTION
 
+  NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+  NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+  NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+  NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
+
   virtual Element* GetElementAt(uint32_t aIndex) override;
   virtual nsINode* GetParentObject() override
   {
     return mParent;
   }
 
   virtual Element*
   GetFirstNamedElement(const nsAString& aName, bool& aFound) override;
   virtual void GetSupportedNames(nsTArray<nsString>& aNames) override;
 
   NS_IMETHOD    ParentDestroyed();
 
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TableRowsCollection)
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(TableRowsCollection, nsIHTMLCollection)
 
   // nsWrapperCache
   using nsWrapperCache::GetWrapperPreserveColor;
   using nsWrapperCache::PreserveWrapper;
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 protected:
-  virtual ~TableRowsCollection();
+  // Unregister ourselves as a mutation observer, and clear our internal state.
+  void CleanUp();
+  void LastRelease()
+  {
+    CleanUp();
+  }
+  virtual ~TableRowsCollection()
+  {
+    // we do NOT have a ref-counted reference to mParent, so do NOT
+    // release it!  this is to avoid circular references.  The
+    // instantiator who provided mParent is responsible for managing our
+    // reference for us.
+    CleanUp();
+  }
 
   virtual JSObject* GetWrapperPreserveColorInternal() override
   {
     return nsWrapperCache::GetWrapperPreserveColor();
   }
   virtual void PreserveWrapperInternal(nsISupports* aScriptObjectHolder) override
   {
     nsWrapperCache::PreserveWrapper(aScriptObjectHolder);
   }
 
-  // Those rows that are not in table sections
+  // Ensure that HTMLTableElement is in a valid state. This must be called
+  // before inspecting the mRows object.
+  void EnsureInitialized();
+
+  // Checks if the passed-in container is interesting for the purposes of
+  // invalidation due to a mutation observer.
+  bool InterestingContainer(nsIContent* aContainer);
+
+  // Check if the passed-in nsIContent is a <tr> within the section defined by
+  // `aSection`. The root of the table is considered to be part of the `<tbody>`
+  // section.
+  bool IsAppropriateRow(nsIAtom* aSection, nsIContent* aContent);
+
+  // Scan backwards starting from `aCurrent` in the table, looking for the
+  // previous row in the table which is within the section `aSection`.
+  nsIContent* PreviousRow(nsIAtom* aSection, nsIContent* aCurrent);
+
+  // Handle the insertion of the child `aChild` into the container `aContainer`
+  // within the tree. The container must be an `InterestingContainer`. This
+  // method updates the mRows, mBodyStart, and mFootStart member variables.
+  //
+  // HandleInsert returns an integer which can be passed to the next call of the
+  // method in a loop inserting children into the same container. This will
+  // optimize subsequent insertions to require less work. This can either be -1,
+  // in which case we don't know where to insert the next row, and When passed
+  // to HandleInsert, it will use `PreviousRow` to locate the index to insert.
+  // Or, it can be an index to insert the next <tr> in the same container at.
+  int32_t HandleInsert(nsIContent* aContainer,
+                       nsIContent* aChild,
+                       int32_t aIndexGuess = -1);
+
+  // The HTMLTableElement which this TableRowsCollection tracks the rows for.
   HTMLTableElement* mParent;
+
+  // The current state of the TableRowsCollection. mBodyStart and mFootStart are
+  // indices into mRows which represent the location of the first row in the
+  // body or foot section. If there are no rows in a section, the index points
+  // at the location where the first element in that section would be inserted.
+  nsTArray<nsCOMPtr<nsIContent>> mRows;
+  uint32_t mBodyStart;
+  uint32_t mFootStart;
+  bool mInitialized;
 };
 
 
 TableRowsCollection::TableRowsCollection(HTMLTableElement *aParent)
   : mParent(aParent)
+  , mBodyStart(0)
+  , mFootStart(0)
+  , mInitialized(false)
 {
+  MOZ_ASSERT(mParent);
 }
 
-TableRowsCollection::~TableRowsCollection()
+void
+TableRowsCollection::EnsureInitialized()
 {
-  // we do NOT have a ref-counted reference to mParent, so do NOT
-  // release it!  this is to avoid circular references.  The
-  // instantiator who provided mParent is responsible for managing our
-  // reference for us.
+  if (mInitialized) {
+    return;
+  }
+  mInitialized = true;
+
+  // Initialize mRows as the TableRowsCollection is created. The mutation
+  // observer should keep it up to date.
+  //
+  // It should be extremely unlikely that anyone creates a TableRowsCollection
+  // without calling a method on it, so lazily performing this initialization
+  // seems unnecessary.
+  AutoTArray<nsCOMPtr<nsIContent>, 32> body;
+  AutoTArray<nsCOMPtr<nsIContent>, 32> foot;
+  mRows.Clear();
+
+  auto addRowChildren = [&] (nsTArray<nsCOMPtr<nsIContent>>& aArray, nsIContent* aNode) {
+    for (nsIContent* inner = aNode->nsINode::GetFirstChild();
+         inner; inner = inner->GetNextSibling()) {
+      if (inner->IsHTMLElement(nsGkAtoms::tr)) {
+        aArray.AppendElement(inner);
+      }
+    }
+  };
+
+  for (nsIContent* node = mParent->nsINode::GetFirstChild();
+       node; node = node->GetNextSibling()) {
+    if (node->IsHTMLElement(nsGkAtoms::thead)) {
+      addRowChildren(mRows, node);
+    } else if (node->IsHTMLElement(nsGkAtoms::tbody)) {
+      addRowChildren(body, node);
+    } else if (node->IsHTMLElement(nsGkAtoms::tfoot)) {
+      addRowChildren(foot, node);
+    } else if (node->IsHTMLElement(nsGkAtoms::tr)) {
+      body.AppendElement(node);
+    }
+  }
+
+  mBodyStart = mRows.Length();
+  mRows.AppendElements(Move(body));
+  mFootStart = mRows.Length();
+  mRows.AppendElements(Move(foot));
+
+  mParent->AddMutationObserver(this);
+}
+
+void
+TableRowsCollection::CleanUp()
+{
+  // Unregister ourselves as a mutation observer.
+  if (mInitialized && mParent)  {
+    mParent->RemoveMutationObserver(this);
+  }
+
+  // Clean up all of our internal state and make it empty in case someone looks
+  // at us.
+  mRows.Clear();
+  mBodyStart = 0;
+  mFootStart = 0;
+
+  // We set mInitialized to true in case someone still has a reference to us, as
+  // we don't need to try to initialize first.
+  mInitialized = true;
+  mParent = nullptr;
 }
 
 JSObject*
 TableRowsCollection::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return HTMLCollectionBinding::Wrap(aCx, this, aGivenProto);
 }
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(TableRowsCollection)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TableRowsCollection, mRows)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(TableRowsCollection)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(TableRowsCollection)
+NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(TableRowsCollection,
+                                                   LastRelease())
 
 NS_INTERFACE_TABLE_HEAD(TableRowsCollection)
   NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY
   NS_INTERFACE_TABLE(TableRowsCollection, nsIHTMLCollection,
-                     nsIDOMHTMLCollection)
+                     nsIDOMHTMLCollection, nsIMutationObserver)
   NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(TableRowsCollection)
 NS_INTERFACE_MAP_END
 
-// Macro that can be used to avoid copy/pasting code to iterate over the
-// rowgroups.  _code should be the code to execute for each rowgroup.  The
-// rowgroup's rows will be in the nsIDOMHTMLCollection* named "rows".
-// _trCode should be the code to execute for each tr row.  Note that
-// this may be null at any time.  This macro assumes an nsresult named
-// |rv| is in scope.
-  #define DO_FOR_EACH_BY_ORDER(_code, _trCode)                       \
-    do {                                                             \
-      if (mParent) {                                                 \
-        HTMLTableSectionElement* rowGroup;                           \
-        nsIHTMLCollection* rows;                                     \
-        /* THead */                                                  \
-        for (nsIContent* _node = mParent->nsINode::GetFirstChild();  \
-             _node; _node = _node->GetNextSibling()) {               \
-           if (_node->IsHTMLElement(nsGkAtoms::thead)) {             \
-             rowGroup = static_cast<HTMLTableSectionElement*>(_node);\
-             rows = rowGroup->Rows();                                \
-             do { /* gives scoping */                                \
-               _code                                                 \
-             } while (0);                                            \
-           }                                                         \
-        }                                                            \
-        /* TBodies */                                                \
-        for (nsIContent* _node = mParent->nsINode::GetFirstChild();  \
-             _node; _node = _node->GetNextSibling()) {               \
-          if (_node->IsHTMLElement(nsGkAtoms::tr)) {                 \
-            do {                                                     \
-              _trCode                                                \
-            } while (0);                                             \
-          } else if (_node->IsHTMLElement(nsGkAtoms::tbody)) {       \
-            rowGroup = static_cast<HTMLTableSectionElement*>(_node); \
-            rows = rowGroup->Rows();                                 \
-            do { /* gives scoping */                                 \
-              _code                                                  \
-            } while (0);                                             \
-          }                                                          \
-        }                                                            \
-        /* TFoot */                                                  \
-        for (nsIContent* _node = mParent->nsINode::GetFirstChild();  \
-             _node; _node = _node->GetNextSibling()) {               \
-           if (_node->IsHTMLElement(nsGkAtoms::tfoot)) {             \
-             rowGroup = static_cast<HTMLTableSectionElement*>(_node);\
-             rows = rowGroup->Rows();                                \
-             do { /* gives scoping */                                \
-               _code                                                 \
-             } while (0);                                            \
-           }                                                         \
-        }                                                            \
-      }                                                              \
-    } while (0)
-
-static uint32_t
-CountRowsInRowGroup(nsIDOMHTMLCollection* rows)
-{
-  uint32_t length = 0;
-
-  if (rows) {
-    rows->GetLength(&length);
-  }
-
-  return length;
-}
-
-// we re-count every call.  A better implementation would be to set
-// ourselves up as an observer of contentAppended, contentInserted,
-// and contentDeleted
 NS_IMETHODIMP
 TableRowsCollection::GetLength(uint32_t* aLength)
 {
-  *aLength=0;
-
-  DO_FOR_EACH_BY_ORDER({
-    *aLength += CountRowsInRowGroup(rows);
-  }, {
-    (*aLength)++;
-  });
-
+  EnsureInitialized();
+  *aLength = mRows.Length();
   return NS_OK;
 }
 
-// Returns the item at index aIndex if available. If null is returned,
-// then aCount will be set to the number of rows in this row collection.
-// Otherwise, the value of aCount is undefined.
-static Element*
-GetItemOrCountInRowGroup(nsIDOMHTMLCollection* rows,
-                         uint32_t aIndex, uint32_t* aCount)
-{
-  *aCount = 0;
-
-  if (rows) {
-    rows->GetLength(aCount);
-    if (aIndex < *aCount) {
-      nsIHTMLCollection* list = static_cast<nsIHTMLCollection*>(rows);
-      return list->GetElementAt(aIndex);
-    }
-  }
-
-  return nullptr;
-}
-
 Element*
 TableRowsCollection::GetElementAt(uint32_t aIndex)
 {
-  DO_FOR_EACH_BY_ORDER({
-    uint32_t count;
-    Element* node = GetItemOrCountInRowGroup(rows, aIndex, &count);
-    if (node) {
-      return node;
-    }
-
-    NS_ASSERTION(count <= aIndex, "GetItemOrCountInRowGroup screwed up");
-    aIndex -= count;
-  },{
-    if (aIndex == 0) {
-      return _node->AsElement();
-    }
-    aIndex--;
-  });
-
+  EnsureInitialized();
+  if (aIndex < mRows.Length()) {
+    return mRows[aIndex]->AsElement();
+  }
   return nullptr;
 }
 
 NS_IMETHODIMP
 TableRowsCollection::Item(uint32_t aIndex, nsIDOMNode** aReturn)
 {
   nsISupports* node = GetElementAt(aIndex);
   if (!node) {
@@ -231,76 +251,63 @@ TableRowsCollection::Item(uint32_t aInde
   }
 
   return CallQueryInterface(node, aReturn);
 }
 
 Element*
 TableRowsCollection::GetFirstNamedElement(const nsAString& aName, bool& aFound)
 {
+  EnsureInitialized();
   aFound = false;
   nsCOMPtr<nsIAtom> nameAtom = NS_Atomize(aName);
   NS_ENSURE_TRUE(nameAtom, nullptr);
-  DO_FOR_EACH_BY_ORDER({
-    Element* item = rows->NamedGetter(aName, aFound);
-    if (aFound) {
-      return item;
+
+  for (auto& node : mRows) {
+    if (node->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
+                          nameAtom, eCaseMatters) ||
+        node->AttrValueIs(kNameSpaceID_None, nsGkAtoms::id,
+                          nameAtom, eCaseMatters)) {
+      aFound = true;
+      return node->AsElement();
     }
-  }, {
-    if (_node->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
-                           nameAtom, eCaseMatters) ||
-        _node->AttrValueIs(kNameSpaceID_None, nsGkAtoms::id,
-                           nameAtom, eCaseMatters)) {
-      aFound = true;
-      return _node->AsElement();
-    }
-  });
+  }
 
   return nullptr;
 }
 
 void
 TableRowsCollection::GetSupportedNames(nsTArray<nsString>& aNames)
 {
-  DO_FOR_EACH_BY_ORDER({
-    nsTArray<nsString> names;
-    nsCOMPtr<nsIHTMLCollection> coll = do_QueryInterface(rows);
-    if (coll) {
-      coll->GetSupportedNames(names);
-      for (uint32_t i = 0; i < names.Length(); ++i) {
-        if (!aNames.Contains(names[i])) {
-          aNames.AppendElement(names[i]);
-        }
-      }
-    }
-  }, {
-    if (_node->HasID()) {
-      nsIAtom* idAtom = _node->GetID();
+  EnsureInitialized();
+  for (auto& node : mRows) {
+    if (node->HasID()) {
+      nsIAtom* idAtom = node->GetID();
       MOZ_ASSERT(idAtom != nsGkAtoms::_empty,
                  "Empty ids don't get atomized");
       nsDependentAtomString idStr(idAtom);
       if (!aNames.Contains(idStr)) {
         aNames.AppendElement(idStr);
       }
     }
 
-    nsGenericHTMLElement* el = nsGenericHTMLElement::FromContent(_node);
+    nsGenericHTMLElement* el = nsGenericHTMLElement::FromContent(node);
     if (el) {
       const nsAttrValue* val = el->GetParsedAttr(nsGkAtoms::name);
       if (val && val->Type() == nsAttrValue::eAtom) {
         nsIAtom* nameAtom = val->GetAtomValue();
         MOZ_ASSERT(nameAtom != nsGkAtoms::_empty,
                    "Empty names don't get atomized");
         nsDependentAtomString nameStr(nameAtom);
         if (!aNames.Contains(nameStr)) {
           aNames.AppendElement(nameStr);
         }
       }
     }
-  });
+  }
 }
 
 
 NS_IMETHODIMP
 TableRowsCollection::NamedItem(const nsAString& aName,
                                nsIDOMNode** aReturn)
 {
   bool found;
@@ -312,20 +319,265 @@ TableRowsCollection::NamedItem(const nsA
   }
 
   return CallQueryInterface(node, aReturn);
 }
 
 NS_IMETHODIMP
 TableRowsCollection::ParentDestroyed()
 {
-  // see comment in destructor, do NOT release mParent!
-  mParent = nullptr;
+  CleanUp();
+  return NS_OK;
+}
+
+bool
+TableRowsCollection::InterestingContainer(nsIContent* aContainer)
+{
+  return mParent && aContainer &&
+    (aContainer == mParent ||
+     (aContainer->GetParent() == mParent &&
+      aContainer->IsAnyOfHTMLElements(nsGkAtoms::thead,
+                                      nsGkAtoms::tbody,
+                                      nsGkAtoms::tfoot)));
+}
+
+bool
+TableRowsCollection::IsAppropriateRow(nsIAtom* aSection, nsIContent* aContent)
+{
+  if (!aContent->IsHTMLElement(nsGkAtoms::tr)) {
+    return false;
+  }
+  // If it's in the root, then we consider it to be in a tbody.
+  nsIContent* parent = aContent->GetParent();
+  if (aSection == nsGkAtoms::tbody && parent == mParent) {
+    return true;
+  }
+  return parent->IsHTMLElement(aSection);
+}
+
+nsIContent*
+TableRowsCollection::PreviousRow(nsIAtom* aSection, nsIContent* aCurrent)
+{
+  // Keep going backwards until we've found a `tr` element. We want to always
+  // run at least once, as we don't want to find ourselves.
+  //
+  // Each spin of the loop we step backwards one element. If we're at the top of
+  // a section, we step out of it into the root, and if we step onto a section
+  // matching `aSection`, we step into it. We keep spinning the loop until
+  // either we reach the first element in mParent, or find a <tr> in an
+  // appropriate section.
+  nsIContent* prev = aCurrent;
+  do {
+    nsIContent* parent = prev->GetParent();
+    prev = prev->GetPreviousSibling();
+
+    // Ascend out of any sections we're currently in, if we've run out of
+    // elements.
+    if (!prev && parent != mParent) {
+      prev = parent->GetPreviousSibling();
+    }
+
+    // Descend into a section if we stepped onto one.
+    if (prev && prev->GetParent() == mParent && prev->IsHTMLElement(aSection)) {
+      prev = prev->GetLastChild();
+    }
+  } while (prev && !IsAppropriateRow(aSection, prev));
+  return prev;
+}
+
+int32_t
+TableRowsCollection::HandleInsert(nsIContent* aContainer,
+                                  nsIContent* aChild,
+                                  int32_t aIndexGuess)
+{
+  if (!nsContentUtils::IsInSameAnonymousTree(mParent, aChild)) {
+    return aIndexGuess; // Nothing inserted, guess hasn't changed.
+  }
+
+  // If we're adding a section to the root, add each of the rows in that section
+  // individually.
+  if (aContainer == mParent &&
+      aChild->IsAnyOfHTMLElements(nsGkAtoms::thead,
+                                  nsGkAtoms::tbody,
+                                  nsGkAtoms::tfoot)) {
+    // If we're entering a tbody, we can persist the index guess we were passed,
+    // as the newly added items are in the same section as us, however, if we're
+    // entering thead or tfoot we will have to re-scan.
+    bool isTBody = aChild->IsHTMLElement(nsGkAtoms::tbody);
+    int32_t indexGuess = isTBody ? aIndexGuess : -1;
+
+    for (nsIContent* inner = aChild->GetFirstChild();
+         inner; inner = inner->GetNextSibling()) {
+      indexGuess = HandleInsert(aChild, inner, indexGuess);
+    }
+
+    return isTBody ? indexGuess : -1;
+  }
+  if (!aChild->IsHTMLElement(nsGkAtoms::tr)) {
+    return aIndexGuess; // Nothing inserted, guess hasn't changed.
+  }
+
+  // We should have only been passed an insertion from an interesting container,
+  // so we can get the container we're inserting to fairly easily.
+  nsIAtom* section = aContainer == mParent
+    ? nsGkAtoms::tbody
+    : aContainer->NodeInfo()->NameAtom();
+
+  // Determine the default index we would to insert after if we don't find any
+  // previous row, and offset our section boundaries based on the section we're
+  // planning to insert into.
+  size_t index = 0;
+  if (section == nsGkAtoms::thead) {
+    mBodyStart++;
+    mFootStart++;
+  } else if (section == nsGkAtoms::tbody) {
+    index = mBodyStart;
+    mFootStart++;
+  } else if (section == nsGkAtoms::tfoot) {
+    index = mFootStart;
+  } else {
+    MOZ_ASSERT(false, "section should be one of thead, tbody, or tfoot");
+  }
 
-  return NS_OK;
+  // If we already have an index guess, we can skip scanning for the previous row.
+  if (aIndexGuess >= 0) {
+    index = aIndexGuess;
+  } else {
+    // Find the previous row in the section we're inserting into. If we find it,
+    // we can use it to override our insertion index. We don't need to modify
+    // mBodyStart or mFootStart anymore, as they have already been correctly
+    // updated based only on section.
+    nsIContent* insertAfter = PreviousRow(section, aChild);
+    if (insertAfter) {
+      // NOTE: We want to ensure that appending elements is quick, so we search
+      // from the end rather than from the beginning.
+      index = mRows.LastIndexOf(insertAfter) + 1;
+      MOZ_ASSERT(index != nsTArray<nsCOMPtr<nsIContent>>::NoIndex);
+    }
+  }
+
+#ifdef DEBUG
+  // Assert that we're inserting into the correct section.
+  if (section == nsGkAtoms::thead) {
+    MOZ_ASSERT(index < mBodyStart);
+  } else if (section == nsGkAtoms::tbody) {
+    MOZ_ASSERT(index >= mBodyStart);
+    MOZ_ASSERT(index < mFootStart);
+  } else if (section == nsGkAtoms::tfoot) {
+    MOZ_ASSERT(index >= mFootStart);
+    MOZ_ASSERT(index <= mRows.Length());
+  }
+
+  MOZ_ASSERT(mBodyStart <= mFootStart);
+  MOZ_ASSERT(mFootStart <= mRows.Length() + 1);
+#endif
+
+  mRows.InsertElementAt(index, aChild);
+  return index + 1;
+}
+
+// nsIMutationObserver
+
+void
+TableRowsCollection::ContentAppended(nsIDocument* aDocument,
+                                     nsIContent* aContainer,
+                                     nsIContent* aFirstNewContent,
+                                     int32_t aNewIndexInContainer)
+{
+  if (!nsContentUtils::IsInSameAnonymousTree(mParent, aFirstNewContent) ||
+      !InterestingContainer(aContainer)) {
+    return;
+  }
+
+  // We usually can't guess where we need to start inserting, unless we're
+  // appending into mParent, in which case we can provide the guess that we
+  // should insert at the end of the body, which can help us avoid potentially
+  // expensive work in the common case.
+  int32_t indexGuess = mParent == aContainer ? mFootStart : -1;
+
+  // Insert each of the newly added content one at a time. The indexGuess should
+  // make insertions of a large number of elements cheaper.
+  for (nsIContent* content = aFirstNewContent;
+       content; content = content->GetNextSibling()) {
+    indexGuess = HandleInsert(aContainer, content, indexGuess);
+  }
+}
+
+void
+TableRowsCollection::ContentInserted(nsIDocument* aDocument,
+                                     nsIContent* aContainer,
+                                     nsIContent* aChild,
+                                     int32_t aIndexInContainer)
+{
+  if (!nsContentUtils::IsInSameAnonymousTree(mParent, aChild) ||
+      !InterestingContainer(aContainer)) {
+    return;
+  }
+
+  HandleInsert(aContainer, aChild);
+}
+
+void
+TableRowsCollection::ContentRemoved(nsIDocument* aDocument,
+                                    nsIContent* aContainer,
+                                    nsIContent* aChild,
+                                    int32_t aIndexInContainer,
+                                    nsIContent* aPreviousSibling)
+{
+  if (!nsContentUtils::IsInSameAnonymousTree(mParent, aChild) ||
+      !InterestingContainer(aContainer)) {
+    return;
+  }
+
+  // If the element being removed is a `tr`, we can just remove it from our
+  // list. It shouldn't change the order of anything.
+  if (aChild->IsHTMLElement(nsGkAtoms::tr)) {
+    size_t index = mRows.IndexOf(aChild);
+    if (index != nsTArray<nsCOMPtr<nsIContent>>::NoIndex) {
+      mRows.RemoveElementAt(index);
+      if (mBodyStart > index) {
+        mBodyStart--;
+      }
+      if (mFootStart > index) {
+        mFootStart--;
+      }
+    }
+    return;
+  }
+
+  // If the element being removed is a `thead`, `tbody`, or `tfoot`, we can
+  // remove any `tr`s in our list which have that element as its parent node. In
+  // any other situation, the removal won't affect us, so we can ignore it.
+  if (!aChild->IsAnyOfHTMLElements(nsGkAtoms::thead, nsGkAtoms::tbody, nsGkAtoms::tfoot)) {
+    return;
+  }
+
+  size_t beforeLength = mRows.Length();
+  mRows.RemoveElementsBy([&] (nsIContent* element) {
+    return element->GetParent() == aChild;
+  });
+  size_t removed = beforeLength - mRows.Length();
+  if (aChild->IsHTMLElement(nsGkAtoms::thead)) {
+    // NOTE: Need to move both tbody and tfoot, as we removed from head.
+    mBodyStart -= removed;
+    mFootStart -= removed;
+  } else if (aChild->IsHTMLElement(nsGkAtoms::tbody)) {
+    // NOTE: Need to move tfoot, as we removed from body.
+    mFootStart -= removed;
+  }
+}
+
+void
+TableRowsCollection::NodeWillBeDestroyed(const nsINode* aNode)
+{
+  // Set mInitialized to false so CleanUp doesn't try to remove our mutation
+  // observer, as we're going away. CleanUp() will reset mInitialized to true as
+  // it returns.
+  mInitialized = false;
+  CleanUp();
 }
 
 /* --------------------------- HTMLTableElement ---------------------------- */
 
 HTMLTableElement::HTMLTableElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
   : nsGenericHTMLElement(aNodeInfo),
     mTableInheritedAttributes(nullptr)
 {
--- a/dom/html/test/test_link_attributes_reflection.html
+++ b/dom/html/test/test_link_attributes_reflection.html
@@ -73,12 +73,24 @@ reflectString({
 });
 
 // .target (String)
 reflectString({
   element: document.createElement("link"),
   attribute: "target",
 });
 
+// .as (String)
+reflectLimitedEnumerated({
+  element: document.createElement("link"),
+  attribute: "as",
+  validValues: [ "fetch", "audio", "font", "image", "script", "style",  "track", "video" ],
+  invalidValues: [
+    "", "audi", "doc", "Emb", "foobar", "FOOBAR", " fOoBaR  ", "OBJ", "document", "embed", "manifest", "object", "report", "serviceworker", "sharedworker", "worker", "xslt"
+  ],
+  defaultValue: { invalid: "", missing: "" },
+  nullable: false,
+})
+
 </script>
 </pre>
 </body>
 </html>
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -206,18 +206,21 @@ StructuredCloneWriteCallback(JSContext* 
     MOZ_ASSERT(!cloneWriteInfo->mOffsetToKeyProp);
     cloneWriteInfo->mOffsetToKeyProp = js::GetSCOffset(aWriter);
 
     uint64_t value = 0;
     // Omit endian swap
     return JS_WriteBytes(aWriter, &value, sizeof(value));
   }
 
+  // UNWRAP_OBJECT calls might mutate this.
+  JS::Rooted<JSObject*> obj(aCx, aObj);
+
   IDBMutableFile* mutableFile;
-  if (NS_SUCCEEDED(UNWRAP_OBJECT(IDBMutableFile, aObj, mutableFile))) {
+  if (NS_SUCCEEDED(UNWRAP_OBJECT(IDBMutableFile, &obj, mutableFile))) {
     if (cloneWriteInfo->mDatabase->IsFileHandleDisabled()) {
       return false;
     }
 
     IDBDatabase* database = mutableFile->Database();
     MOZ_ASSERT(database);
 
     // Throw when trying to store IDBMutableFile objects that live in a
@@ -276,17 +279,17 @@ StructuredCloneWriteCallback(JSContext* 
     newFile->mMutableFile = mutableFile;
     newFile->mType = StructuredCloneFile::eMutableFile;
 
     return true;
   }
 
   {
     Blob* blob = nullptr;
-    if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob))) {
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, &obj, blob))) {
       ErrorResult rv;
       uint64_t size = blob->GetSize(rv);
       MOZ_ASSERT(!rv.Failed());
 
       size = NativeEndian::swapToLittleEndian(size);
 
       nsString type;
       blob->GetType(type);
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -633,16 +633,22 @@ interface nsIDOMWindowUtils : nsISupport
    * See nsIWidget::SynthesizeNativeMouseMove and sendNativeMouseEvent
    */
   void sendNativeMouseMove(in long aScreenX,
                            in long aScreenY,
                            in nsIDOMElement aElement,
                            [optional] in nsIObserver aObserver);
 
   /**
+   * Suppress animations that are applied to a window by OS when
+   * resizing, moving, changing size mode, ...
+   */
+  void suppressAnimation(in boolean aSuppress);
+
+  /**
    * The values for sendNativeMouseScrollEvent's aAdditionalFlags.
    */
 
   /**
    * If MOUSESCROLL_PREFER_WIDGET_AT_POINT is set, widget will dispatch
    * the event to a widget which is under the cursor.  Otherwise, dispatch to
    * a default target on the platform.  E.g., on Windows, it's focused window.
    */
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -1321,16 +1321,17 @@ ContentChild::RecvReinitRendering(Endpoi
     return IPC_FAIL_NO_REASON(this);
   }
   if (!ImageBridgeChild::ReinitForContent(Move(aImageBridge), namespaces[2])) {
     return IPC_FAIL_NO_REASON(this);
   }
   if (!gfx::VRManagerChild::ReinitForContent(Move(aVRBridge))) {
     return IPC_FAIL_NO_REASON(this);
   }
+  gfxPlatform::GetPlatform()->CompositorUpdated();
 
   // Establish new PLayerTransactions.
   for (const auto& tabChild : tabs) {
     if (tabChild->LayersId()) {
       tabChild->ReinitRendering();
     }
   }
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -5427,13 +5427,13 @@ ContentParent::RecvMaybeReloadPlugins()
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 ContentParent::RecvDeviceReset()
 {
   GPUProcessManager* pm = GPUProcessManager::Get();
   if (pm) {
-    pm->TriggerDeviceResetForTesting();
+    pm->SimulateDeviceReset();
   }
 
   return IPC_OK();
 }
--- a/dom/media/AbstractMediaDecoder.h
+++ b/dom/media/AbstractMediaDecoder.h
@@ -41,21 +41,16 @@ typedef nsDataHashtable<nsCStringHashKey
 class AbstractMediaDecoder : public nsIObserver
 {
 public:
   // Increments the parsed, decoded and dropped frame counters by the passed in
   // counts.
   // Can be called on any thread.
   virtual void NotifyDecodedFrames(const FrameStatisticsData& aStats) = 0;
 
-  virtual AbstractCanonical<media::NullableTimeUnit>* CanonicalDurationOrNull()
-  {
-    return nullptr;
-  };
-
   // Returns an event that will be notified when the owning document changes state
   // and we might have a new compositor. If this new compositor requires us to
   // recreate our decoders, then we expect the existing decoderis to return an
   // error independently of this.
   virtual MediaEventSource<RefPtr<layers::KnowsCompositor>>*
   CompositorUpdatedEvent()
   {
     return nullptr;
--- a/dom/media/FileBlockCache.cpp
+++ b/dom/media/FileBlockCache.cpp
@@ -1,15 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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 "FileBlockCache.h"
+#include "MediaCache.h"
+#include "MediaPrefs.h"
 #include "mozilla/SharedThreadPool.h"
 #include "VideoUtils.h"
 #include "prio.h"
 #include <algorithm>
 #include "nsAnonymousTemporaryFile.h"
 #include "mozilla/dom/ContentChild.h"
 #include "nsXULAppAPI.h"
 
@@ -116,16 +118,43 @@ FileBlockCache::Init()
 
   if (NS_FAILED(rv)) {
     Close();
   }
 
   return rv;
 }
 
+int32_t
+FileBlockCache::GetMaxBlocks() const
+{
+  // We look up the cache size every time. This means dynamic changes
+  // to the pref are applied.
+  const uint32_t cacheSizeKb =
+    std::min(MediaPrefs::MediaCacheSizeKb(), uint32_t(INT32_MAX) * 2);
+  // Ensure we can divide BLOCK_SIZE by 1024.
+  static_assert(MediaCacheStream::BLOCK_SIZE % 1024 == 0,
+                "BLOCK_SIZE should be a multiple of 1024");
+  // Ensure BLOCK_SIZE/1024 is at least 2.
+  static_assert(MediaCacheStream::BLOCK_SIZE / 1024 >= 2,
+                "BLOCK_SIZE / 1024 should be at least 2");
+  // Ensure we can convert BLOCK_SIZE/1024 to a uint32_t without truncation.
+  static_assert(MediaCacheStream::BLOCK_SIZE / 1024 <= int64_t(UINT32_MAX),
+                "BLOCK_SIZE / 1024 should be at most UINT32_MAX");
+  // Since BLOCK_SIZE is a strict multiple of 1024,
+  // cacheSizeKb * 1024 / BLOCK_SIZE == cacheSizeKb / (BLOCK_SIZE / 1024),
+  // but the latter formula avoids a potential overflow from `* 1024`.
+  // And because BLOCK_SIZE/1024 is at least 2, the maximum cache size
+  // INT32_MAX*2 will give a maxBlocks that can fit in an int32_t.
+  constexpr uint32_t blockSizeKb =
+    uint32_t(MediaCacheStream::BLOCK_SIZE / 1024);
+  const int32_t maxBlocks = int32_t(cacheSizeKb / blockSizeKb);
+  return std::max(maxBlocks, int32_t(1));
+}
+
 FileBlockCache::FileBlockCache()
   : mFileMutex("MediaCache.Writer.IO.Mutex")
   , mFD(nullptr)
   , mFDCurrentPos(0)
   , mDataMutex("MediaCache.Writer.Data.Mutex")
   , mIsWriteScheduled(false)
   , mIsReading(false)
 {
--- a/dom/media/FileBlockCache.h
+++ b/dom/media/FileBlockCache.h
@@ -60,16 +60,20 @@ public:
 protected:
   virtual ~FileBlockCache();
 
 public:
   // Launch thread and open temporary file.
   // If re-initializing, just discard pending writes if any.
   nsresult Init() override;
 
+  // Maximum number of blocks allowed in this block cache.
+  // Calculated from "media.cache_size" pref.
+  int32_t GetMaxBlocks() const override;
+
   // Can be called on any thread. This defers to a non-main thread.
   nsresult WriteBlock(uint32_t aBlockIndex,
                       Span<const uint8_t> aData1,
                       Span<const uint8_t> aData2) override;
 
   // Synchronously reads data from file. May read from file or memory
   // depending on whether written blocks have been flushed to file yet.
   // Not recommended to be called from the main thread, as can cause jank.
--- a/dom/media/MediaBlockCacheBase.h
+++ b/dom/media/MediaBlockCacheBase.h
@@ -47,16 +47,20 @@ public:
 protected:
   virtual ~MediaBlockCacheBase() {}
 
 public:
   // Initialize this cache.
   // If called again, re-initialize cache with minimal chance of failure.
   virtual nsresult Init() = 0;
 
+  // Maximum number of blocks expected in this block cache. (But allow overflow
+  // to accomodate incoming traffic before MediaCache can handle it.)
+  virtual int32_t GetMaxBlocks() const = 0;
+
   // Can be called on any thread. This defers to a non-main thread.
   virtual nsresult WriteBlock(uint32_t aBlockIndex,
                               Span<const uint8_t> aData1,
                               Span<const uint8_t> aData2) = 0;
 
   // Synchronously reads data from file. May read from file or memory
   // depending on whether written blocks have been flushed to file yet.
   // Not recommended to be called from the main thread, as can cause jank.
--- a/dom/media/MediaCache.cpp
+++ b/dom/media/MediaCache.cpp
@@ -3,16 +3,17 @@
 /* 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 "MediaCache.h"
 
 #include "FileBlockCache.h"
 #include "MediaBlockCacheBase.h"
+#include "MediaPrefs.h"
 #include "MediaResource.h"
 #include "MemoryBlockCache.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Logging.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
@@ -732,41 +733,16 @@ MediaCache::ReadCacheFile(
     // Since the monitor might be acquired on the main thread, we need to drop
     // the monitor while doing IO in order not to block the main thread.
     ReentrantMonitorAutoExit unlock(mReentrantMonitor);
     return blockCache->Read(
       aOffset, reinterpret_cast<uint8_t*>(aData), aLength, aBytes);
   }
 }
 
-static int32_t GetMaxBlocks()
-{
-  // We look up the cache size every time. This means dynamic changes
-  // to the pref are applied.
-  const uint32_t cacheSizeKb =
-    std::min(MediaPrefs::MediaCacheSizeKb(), uint32_t(INT32_MAX) * 2);
-  // Ensure we can divide BLOCK_SIZE by 1024.
-  static_assert(MediaCache::BLOCK_SIZE % 1024 == 0,
-                "BLOCK_SIZE should be a multiple of 1024");
-  // Ensure BLOCK_SIZE/1024 is at least 2.
-  static_assert(MediaCache::BLOCK_SIZE / 1024 >= 2,
-                "BLOCK_SIZE / 1024 should be at least 2");
-  // Ensure we can convert BLOCK_SIZE/1024 to a uint32_t without truncation.
-  static_assert(MediaCache::BLOCK_SIZE / 1024 <= int64_t(UINT32_MAX),
-                "BLOCK_SIZE / 1024 should be at most UINT32_MAX");
-  // Since BLOCK_SIZE is a strict multiple of 1024,
-  // cacheSizeKb * 1024 / BLOCK_SIZE == cacheSizeKb / (BLOCK_SIZE / 1024),
-  // but the latter formula avoids a potential overflow from `* 1024`.
-  // And because BLOCK_SIZE/1024 is at least 2, the maximum cache size
-  // INT32_MAX*2 will give a maxBlocks that can fit in an int32_t.
-  constexpr uint32_t blockSizeKb = uint32_t(MediaCache::BLOCK_SIZE / 1024);
-  const int32_t maxBlocks = int32_t(cacheSizeKb / blockSizeKb);
-  return std::max(maxBlocks, int32_t(1));
-}
-
 // Allowed range is whatever can be accessed with an int32_t block index.
 static bool
 IsOffsetAllowed(int64_t aOffset)
 {
   return aOffset < (int64_t(INT32_MAX) + 1) * MediaCache::BLOCK_SIZE &&
          aOffset >= 0;
 }
 
@@ -813,18 +789,20 @@ MediaCache::FindBlockForIncomingData(Tim
                       INT32_MAX);
 
   if (blockIndex < 0 || !IsBlockFree(blockIndex)) {
     // The block returned is already allocated.
     // Don't reuse it if a) there's room to expand the cache or
     // b) the data we're going to store in the free block is not higher
     // priority than the data already stored in the free block.
     // The latter can lead us to go over the cache limit a bit.
-    if ((mIndex.Length() < uint32_t(GetMaxBlocks()) || blockIndex < 0 ||
-         PredictNextUseForIncomingData(aStream) >= PredictNextUse(aNow, blockIndex))) {
+    if ((mIndex.Length() < uint32_t(mBlockCache->GetMaxBlocks()) ||
+         blockIndex < 0 ||
+         PredictNextUseForIncomingData(aStream) >=
+           PredictNextUse(aNow, blockIndex))) {
       blockIndex = mIndex.Length();
       if (!mIndex.AppendElement())
         return -1;
       mIndexWatermark = std::max(mIndexWatermark, blockIndex + 1);
       mFreeBlocks.AddFirstBlock(blockIndex);
       return blockIndex;
     }
   }
@@ -1158,17 +1136,17 @@ MediaCache::Update()
 
   {
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     mUpdateQueued = false;
 #ifdef DEBUG
     mInUpdate = true;
 #endif
 
-    int32_t maxBlocks = GetMaxBlocks();
+    int32_t maxBlocks = mBlockCache->GetMaxBlocks();
     TimeStamp now = TimeStamp::Now();
 
     int32_t freeBlockCount = mFreeBlocks.GetCount();
     TimeDuration latestPredictedUseForOverflow = 0;
     if (mIndex.Length() > uint32_t(maxBlocks)) {
       // Try to trim back the cache to its desired maximum size. The cache may
       // have overflowed simply due to data being received when we have
       // no blocks in the main part of the cache that are free or lower
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -192,23 +192,16 @@ MediaDecoder::RemoveOutputStream(MediaSt
 
 double
 MediaDecoder::GetDuration()
 {
   MOZ_ASSERT(NS_IsMainThread());
   return mDuration;
 }
 
-AbstractCanonical<media::NullableTimeUnit>*
-MediaDecoder::CanonicalDurationOrNull()
-{
-  MOZ_ASSERT(mDecoderStateMachine);
-  return mDecoderStateMachine->CanonicalDuration();
-}
-
 void
 MediaDecoder::SetInfinite(bool aInfinite)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
   mInfiniteStream = aInfinite;
   DurationChanged();
 }
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -775,17 +775,16 @@ protected:
 
   // Current decoding position in the stream. This is where the decoder
   // is up to consuming the stream. This is not adjusted during decoder
   // seek operations, but it's updated at the end when we start playing
   // back again.
   Canonical<int64_t> mDecoderPosition;
 
 public:
-  AbstractCanonical<media::NullableTimeUnit>* CanonicalDurationOrNull() override;
   AbstractCanonical<double>* CanonicalVolume() { return &mVolume; }
   AbstractCanonical<bool>* CanonicalPreservesPitch()
   {
     return &mPreservesPitch;
   }
   AbstractCanonical<bool>* CanonicalLooping()
   {
     return &mLooping;
--- a/dom/media/MediaDecoderReader.cpp
+++ b/dom/media/MediaDecoderReader.cpp
@@ -70,53 +70,32 @@ public:
 
 MediaDecoderReader::MediaDecoderReader(const MediaDecoderReaderInit& aInit)
   : mAudioCompactor(mAudioQueue)
   , mDecoder(aInit.mDecoder)
   , mTaskQueue(new TaskQueue(
       GetMediaThreadPool(MediaThreadType::PLAYBACK),
       "MediaDecoderReader::mTaskQueue",
       /* aSupportsTailDispatch = */ true))
-  , mWatchManager(this, mTaskQueue)
   , mBuffered(mTaskQueue, TimeIntervals(), "MediaDecoderReader::mBuffered (Canonical)")
-  , mDuration(mTaskQueue, NullableTimeUnit(), "MediaDecoderReader::mDuration (Mirror)")
   , mIgnoreAudioOutputFormat(false)
   , mHitAudioDecodeError(false)
   , mShutdown(false)
   , mResource(aInit.mResource)
 {
   MOZ_COUNT_CTOR(MediaDecoderReader);
   MOZ_ASSERT(NS_IsMainThread());
 }
 
 nsresult
 MediaDecoderReader::Init()
 {
-  // Dispatch initialization that needs to happen on that task queue.
-  mTaskQueue->Dispatch(
-    NewRunnableMethod("MediaDecoderReader::InitializationTask",
-                      this,
-                      &MediaDecoderReader::InitializationTask));
   return InitInternal();
 }
 
-void
-MediaDecoderReader::InitializationTask()
-{
-  if (!mDecoder) {
-    return;
-  }
-  if (mDecoder->CanonicalDurationOrNull()) {
-    mDuration.Connect(mDecoder->CanonicalDurationOrNull());
-  }
-
-  // Initialize watchers.
-  mWatchManager.Watch(mDuration, &MediaDecoderReader::UpdateBuffered);
-}
-
 MediaDecoderReader::~MediaDecoderReader()
 {
   MOZ_ASSERT(mShutdown);
   MOZ_COUNT_DTOR(MediaDecoderReader);
 }
 
 size_t MediaDecoderReader::SizeOfVideoQueueInBytes() const
 {
@@ -137,16 +116,24 @@ size_t MediaDecoderReader::SizeOfVideoQu
   return mVideoQueue.GetSize();
 }
 
 size_t MediaDecoderReader::SizeOfAudioQueueInFrames()
 {
   return mAudioQueue.GetSize();
 }
 
+void
+MediaDecoderReader::UpdateDuration(const media::TimeUnit& aDuration)
+{
+  MOZ_ASSERT(OnTaskQueue());
+  mDuration = Some(aDuration);
+  UpdateBuffered();
+}
+
 nsresult MediaDecoderReader::ResetDecode(TrackSet aTracks)
 {
   if (aTracks.contains(TrackInfo::kVideoTrack)) {
     VideoQueue().Reset();
     mBaseVideoPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
   }
 
   if (aTracks.contains(TrackInfo::kAudioTrack)) {
@@ -198,23 +185,22 @@ void
 MediaDecoderReader::VisibilityChanged()
 {}
 
 media::TimeIntervals
 MediaDecoderReader::GetBuffered()
 {
   MOZ_ASSERT(OnTaskQueue());
 
-  AutoPinned<MediaResource> stream(mResource);
-
-  if (!mDuration.Ref().isSome()) {
+  if (mDuration.isNothing()) {
     return TimeIntervals();
   }
 
-  return GetEstimatedBufferedTimeRanges(stream, mDuration.Ref().ref().ToMicroseconds());
+  AutoPinned<MediaResource> stream(mResource);
+  return GetEstimatedBufferedTimeRanges(stream, mDuration->ToMicroseconds());
 }
 
 RefPtr<MediaDecoderReader::MetadataPromise>
 MediaDecoderReader::AsyncReadMetadata()
 {
   MOZ_ASSERT(OnTaskQueue());
   DECODER_LOG("MediaDecoderReader::AsyncReadMetadata");
 
@@ -366,20 +352,16 @@ MediaDecoderReader::Shutdown()
 {
   MOZ_ASSERT(OnTaskQueue());
   mShutdown = true;
 
   mBaseAudioPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__);
   mBaseVideoPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__);
 
   ReleaseResources();
-  mDuration.DisconnectIfConnected();
   mBuffered.DisconnectAll();
 
-  // Shut down the watch manager before shutting down our task queue.
-  mWatchManager.Shutdown();
-
   mDecoder = nullptr;
 
   return mTaskQueue->BeginShutdown();
 }
 
 } // namespace mozilla
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -127,16 +127,18 @@ public:
   // thread.
   virtual RefPtr<ShutdownPromise> Shutdown();
 
   virtual bool OnTaskQueue() const
   {
     return OwnerThread()->IsCurrentThreadIn();
   }
 
+  void UpdateDuration(const media::TimeUnit& aDuration);
+
   // Resets all state related to decoding, emptying all buffers etc.
   // Cancels all pending Request*Data() request callbacks, rejects any
   // outstanding seek promises, and flushes the decode pipeline. The
   // decoder must not call any of the callbacks for outstanding
   // Request*Data() calls after this is called. Calls to Request*Data()
   // made after this should be processed as usual.
   //
   // Normally this call preceedes a Seek() call, or shutdown.
@@ -296,27 +298,23 @@ protected:
   AudioCompactor mAudioCompactor;
 
   // Reference to the owning decoder object.
   AbstractMediaDecoder* mDecoder;
 
   // Decode task queue.
   RefPtr<TaskQueue> mTaskQueue;
 
-  // State-watching manager.
-  WatchManager<MediaDecoderReader> mWatchManager;
-
   // Buffered range.
   Canonical<media::TimeIntervals> mBuffered;
 
   // Stores presentation info required for playback.
   MediaInfo mInfo;
 
-  // Duration, mirrored from the state machine task queue.
-  Mirror<media::NullableTimeUnit> mDuration;
+  media::NullableTimeUnit mDuration;
 
   // Whether we should accept media that we know we can't play
   // directly, because they have a number of channel higher than
   // what we support.
   bool mIgnoreAudioOutputFormat;
 
   // This is a quick-and-dirty way for DecodeAudioData implementations to
   // communicate the presence of a decoding error to RequestAudioData. We should
@@ -334,22 +332,16 @@ protected:
   // Notify if we are waiting for a decryption key.
   MediaEventProducer<TrackInfo::TrackType> mOnTrackWaitingForKey;
 
   RefPtr<MediaResource> mResource;
 
 private:
   virtual nsresult InitInternal() { return NS_OK; }
 
-  // Does any spinup that needs to happen on this task queue. This runs on a
-  // different thread than Init, and there should not be ordering dependencies
-  // between the two (even though in practice, Init will always run first right
-  // now thanks to the tail dispatcher).
-  void InitializationTask();
-
   // Read header data for all bitstreams in the file. Fills aInfo with
   // the data required to present the media, and optionally fills *aTags
   // with tag metadata from the file.
   // Returns NS_OK on success, or NS_ERROR_FAILURE on failure.
   virtual nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags)
   {
     MOZ_CRASH();
   }
--- a/dom/media/MediaDecoderReaderWrapper.cpp
+++ b/dom/media/MediaDecoderReaderWrapper.cpp
@@ -8,16 +8,20 @@
 #include "MediaDecoderReaderWrapper.h"
 
 namespace mozilla {
 
 MediaDecoderReaderWrapper::MediaDecoderReaderWrapper(AbstractThread* aOwnerThread,
                                                      MediaDecoderReader* aReader)
   : mOwnerThread(aOwnerThread)
   , mReader(aReader)
+  , mWatchManager(this, aReader->OwnerThread())
+  , mDuration(aReader->OwnerThread(),
+              NullableTimeUnit(),
+              "MediaDecoderReaderWrapper::mDuration (Mirror)")
 {
   // Must support either heuristic buffering or WaitForData().
   MOZ_ASSERT(mReader->UseBufferingHeuristics() ||
              mReader->IsWaitForDataSupported());
 }
 
 MediaDecoderReaderWrapper::~MediaDecoderReaderWrapper()
 {}
@@ -129,18 +133,22 @@ MediaDecoderReaderWrapper::ResetDecode(T
   mReader->OwnerThread()->Dispatch(r.forget());
 }
 
 RefPtr<ShutdownPromise>
 MediaDecoderReaderWrapper::Shutdown()
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   mShutdown = true;
-  return InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__,
-                     &MediaDecoderReader::Shutdown);
+  RefPtr<MediaDecoderReaderWrapper> self = this;
+  return InvokeAsync(mReader->OwnerThread(), __func__, [self]() {
+    self->mDuration.DisconnectIfConnected();
+    self->mWatchManager.Shutdown();
+    return self->mReader->Shutdown();
+  });
 }
 
 RefPtr<MediaDecoderReaderWrapper::MetadataPromise>
 MediaDecoderReaderWrapper::OnMetadataRead(MetadataHolder&& aMetadata)
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   if (mShutdown) {
     return MetadataPromise::CreateAndReject(
@@ -166,9 +174,33 @@ MediaDecoderReaderWrapper::SetVideoBlank
   nsCOMPtr<nsIRunnable> r =
     NewRunnableMethod<bool>("MediaDecoderReader::SetVideoNullDecode",
                             mReader,
                             &MediaDecoderReader::SetVideoNullDecode,
                             aIsBlankDecode);
   mReader->OwnerThread()->Dispatch(r.forget());
 }
 
+void
+MediaDecoderReaderWrapper::UpdateDuration()
+{
+  MOZ_ASSERT(mReader->OwnerThread()->IsCurrentThreadIn());
+  mReader->UpdateDuration(mDuration.Ref().ref());
+}
+
+void
+MediaDecoderReaderWrapper::SetCanonicalDuration(
+  AbstractCanonical<media::NullableTimeUnit>* aCanonical)
+{
+  using DurationT = AbstractCanonical<media::NullableTimeUnit>;
+  RefPtr<MediaDecoderReaderWrapper> self = this;
+  RefPtr<DurationT> canonical = aCanonical;
+  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+    "MediaDecoderReaderWrapper::SetCanonicalDuration",
+    [this, self, canonical]() {
+      mDuration.Connect(canonical);
+      mWatchManager.Watch(mDuration,
+                          &MediaDecoderReaderWrapper::UpdateDuration);
+    });
+  mReader->OwnerThread()->Dispatch(r.forget());
+}
+
 } // namespace mozilla
--- a/dom/media/MediaDecoderReaderWrapper.h
+++ b/dom/media/MediaDecoderReaderWrapper.h
@@ -83,23 +83,33 @@ public:
   AbstractCanonical<media::TimeIntervals>* CanonicalBuffered() {
     return mReader->CanonicalBuffered();
   }
 
   void SetCDMProxy(CDMProxy* aProxy) { mReader->SetCDMProxy(aProxy); }
 
   void SetVideoBlankDecode(bool aIsBlankDecode);
 
+  void SetCanonicalDuration(
+    AbstractCanonical<media::NullableTimeUnit>* aCanonical);
+
 private:
   ~MediaDecoderReaderWrapper();
   RefPtr<MetadataPromise> OnMetadataRead(MetadataHolder&& aMetadata);
   RefPtr<MetadataPromise> OnMetadataNotRead(const MediaResult& aError);
+  void UpdateDuration();
 
   const RefPtr<AbstractThread> mOwnerThread;
   const RefPtr<MediaDecoderReader> mReader;
 
   bool mShutdown = false;
   Maybe<media::TimeUnit> mStartTime;
+
+  // State-watching manager.
+  WatchManager<MediaDecoderReaderWrapper> mWatchManager;
+
+  // Duration, mirrored from the state machine task queue.
+  Mirror<media::NullableTimeUnit> mDuration;
 };
 
 } // namespace mozilla
 
 #endif // MediaDecoderReaderWrapper_h_
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -2956,16 +2956,18 @@ nsresult MediaDecoderStateMachine::Init(
     OwnerThread(), __func__, this,
     &MediaDecoderStateMachine::OnCDMProxyReady,
     &MediaDecoderStateMachine::OnCDMProxyNotReady)
   ->Track(mCDMProxyPromise);
 
   nsresult rv = mReader->Init();
   NS_ENSURE_SUCCESS(rv, rv);
 
+  mReader->SetCanonicalDuration(&mDuration);
+
   return NS_OK;
 }
 
 void
 MediaDecoderStateMachine::StopPlayback()
 {
   MOZ_ASSERT(OnTaskQueue());
   LOG("StopPlayback()");
--- a/dom/media/MemoryBlockCache.cpp
+++ b/dom/media/MemoryBlockCache.cpp
@@ -132,19 +132,33 @@ enum MemoryBlockCacheTelemetryErrors
   ReadOverrun = 2,
   WriteBlockOverflow = 3,
   WriteBlockCannotGrow = 4,
   MoveBlockSourceOverrun = 5,
   MoveBlockDestOverflow = 6,
   MoveBlockCannotGrow = 7,
 };
 
+static int32_t
+CalculateMaxBlocks(int64_t aContentLength)
+{
+  // Note: It doesn't matter if calculations overflow, Init() would later fail.
+  // We want at least enough blocks to contain the original content length.
+  const int32_t requiredBlocks =
+    int32_t((aContentLength - 1) / MediaBlockCacheBase::BLOCK_SIZE + 1);
+  // Allow at least 1s of ultra HD (25Mbps).
+  const int32_t workableBlocks =
+    25 * 1024 * 1024 / 8 / MediaBlockCacheBase::BLOCK_SIZE;
+  return std::max(requiredBlocks, workableBlocks);
+}
+
 MemoryBlockCache::MemoryBlockCache(int64_t aContentLength)
   // Buffer whole blocks.
   : mInitialContentLength((aContentLength >= 0) ? size_t(aContentLength) : 0)
+  , mMaxBlocks(CalculateMaxBlocks(aContentLength))
   , mMutex("MemoryBlockCache")
   , mHasGrown(false)
 {
   if (aContentLength <= 0) {
     LOG("MemoryBlockCache() MEMORYBLOCKCACHE_ERRORS='InitUnderuse'");
     Telemetry::Accumulate(Telemetry::HistogramID::MEMORYBLOCKCACHE_ERRORS,
                           InitUnderuse);
   }
@@ -170,41 +184,46 @@ MemoryBlockCache::EnsureBufferCanContain
   const size_t desiredLength =
     ((aContentLength - 1) / BLOCK_SIZE + 1) * BLOCK_SIZE;
   if (initialLength >= desiredLength) {
     // Already large enough.
     return true;
   }
   // Need larger buffer. If we are allowed more memory, attempt to re-allocate.
   const size_t extra = desiredLength - initialLength;
-  // Note: There is a small race between testing `atomic + extra > limit` and
-  // committing to it with `atomic += extra` below; but this is acceptable, as
-  // in the worst case it may allow a small number of buffers to go past the
-  // limit.
-  // The alternative would have been to reserve the space first with
-  // `atomic += extra` and then undo it with `atomic -= extra` in case of
-  // failure; but this would have meant potentially preventing other (small but
-  // successful) allocations.
-  static const size_t sysmem =
-    std::max<size_t>(PR_GetPhysicalMemorySize(), 32 * 1024 * 1024);
-  const size_t limit = std::min(
-    size_t(MediaPrefs::MediaMemoryCachesCombinedLimitKb()) * 1024,
-    sysmem * MediaPrefs::MediaMemoryCachesCombinedLimitPcSysmem() / 100);
-  const size_t currentSizes = static_cast<size_t>(gCombinedSizes);
-  if (currentSizes + extra > limit) {
-    LOG("EnsureBufferCanContain(%zu) - buffer size %zu, wanted + %zu = %zu;"
-        " combined sizes %zu + %zu > limit %zu",
-        aContentLength,
-        initialLength,
-        extra,
-        desiredLength,
-        currentSizes,
-        extra,
-        limit);
-    return false;
+  // Only check the very first allocation against the combined MemoryBlockCache
+  // limit. Further growths will always be allowed, assuming MediaCache won't
+  // go over GetMaxBlocks() by too much.
+  if (initialLength == 0) {
+    // Note: There is a small race between testing `atomic + extra > limit` and
+    // committing to it with `atomic += extra` below; but this is acceptable, as
+    // in the worst case it may allow a small number of buffers to go past the
+    // limit.
+    // The alternative would have been to reserve the space first with
+    // `atomic += extra` and then undo it with `atomic -= extra` in case of
+    // failure; but this would have meant potentially preventing other (small
+    // but successful) allocations.
+    static const size_t sysmem =
+      std::max<size_t>(PR_GetPhysicalMemorySize(), 32 * 1024 * 1024);
+    const size_t limit = std::min(
+      size_t(MediaPrefs::MediaMemoryCachesCombinedLimitKb()) * 1024,
+      sysmem * MediaPrefs::MediaMemoryCachesCombinedLimitPcSysmem() / 100);
+    const size_t currentSizes = static_cast<size_t>(gCombinedSizes);
+    if (currentSizes + extra > limit) {
+      LOG("EnsureBufferCanContain(%zu) - buffer size %zu, wanted + %zu = %zu;"
+          " combined sizes %zu + %zu > limit %zu",
+          aContentLength,
+          initialLength,
+          extra,
+          desiredLength,
+          currentSizes,
+          extra,
+          limit);
+      return false;
+    }
   }
   if (!mBuffer.SetLength(desiredLength, mozilla::fallible)) {
     LOG("EnsureBufferCanContain(%zu) - buffer size %zu, wanted + %zu = %zu, "
         "allocation failed",
         aContentLength,
         initialLength,
         extra,
         desiredLength);
--- a/dom/media/MemoryBlockCache.h
+++ b/dom/media/MemoryBlockCache.h
@@ -37,16 +37,20 @@ public:
 protected:
   virtual ~MemoryBlockCache();
 
 public:
   // Allocate initial buffer.
   // If re-initializing, clear buffer.
   virtual nsresult Init() override;
 
+  // Maximum number of blocks allowed in this block cache.
+  // Based on initial content length, and minimum usable block cache.
+  int32_t GetMaxBlocks() const override { return mMaxBlocks; }
+
   // Can be called on any thread.
   virtual nsresult WriteBlock(uint32_t aBlockIndex,
                               Span<const uint8_t> aData1,
                               Span<const uint8_t> aData2) override;
 
   // Synchronously reads data from buffer.
   virtual nsresult Read(int64_t aOffset,
                         uint8_t* aData,
@@ -66,16 +70,19 @@ private:
   // Ensure the buffer has at least a multiple of BLOCK_SIZE that can contain
   // aContentLength bytes. Buffer size can only grow.
   // Returns false if allocation failed.
   bool EnsureBufferCanContain(size_t aContentLength);
 
   // Initial content length.
   const size_t mInitialContentLength;
 
+  // Maximum number of blocks that this MemoryBlockCache expects.
+  const int32_t mMaxBlocks;
+
   // Mutex which controls access to all members below.
   Mutex mMutex;
 
   nsTArray<uint8_t> mBuffer;
   bool mHasGrown;
 };
 
 } // End namespace mozilla.
--- a/dom/media/WebVTTListener.cpp
+++ b/dom/media/WebVTTListener.cpp
@@ -152,18 +152,19 @@ WebVTTListener::OnDataAvailable(nsIReque
 
 NS_IMETHODIMP
 WebVTTListener::OnCue(JS::Handle<JS::Value> aCue, JSContext* aCx)
 {
   if (!aCue.isObject()) {
     return NS_ERROR_FAILURE;
   }
 
+  JS::Rooted<JSObject*> obj(aCx, &aCue.toObject());
   TextTrackCue* cue = nullptr;
-  nsresult rv = UNWRAP_OBJECT(VTTCue, &aCue.toObject(), cue);
+  nsresult rv = UNWRAP_OBJECT(VTTCue, &obj, cue);
   NS_ENSURE_SUCCESS(rv, rv);
 
   cue->SetTrackElement(mElement);
   mElement->mTrack->AddCue(*cue);
 
   return NS_OK;
 }
 
--- a/dom/messagechannel/MessagePort.cpp
+++ b/dom/messagechannel/MessagePort.cpp
@@ -398,23 +398,23 @@ MessagePort::PostMessage(JSContext* aCx,
                          ErrorResult& aRv)
 {
   // We *must* clone the data here, or the JS::Value could be modified
   // by script
 
   // Here we want to check if the transerable object list contains
   // this port.
   for (uint32_t i = 0; i < aTransferable.Length(); ++i) {
-    JSObject* object = aTransferable[i];
+    JS::Rooted<JSObject*> object(aCx, aTransferable[i]);
     if (!object) {
       continue;
     }
 
     MessagePort* port = nullptr;
-    nsresult rv = UNWRAP_OBJECT(MessagePort, object, port);
+    nsresult rv = UNWRAP_OBJECT(MessagePort, &object, port);
     if (NS_SUCCEEDED(rv) && port == this) {
       aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
       return;
     }
   }
 
   JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedValue());
 
--- a/dom/notification/Notification.cpp
+++ b/dom/notification/Notification.cpp
@@ -984,17 +984,17 @@ Notification::SetAlertName()
 // static
 already_AddRefed<Notification>
 Notification::Constructor(const GlobalObject& aGlobal,
                           const nsAString& aTitle,
                           const NotificationOptions& aOptions,
                           ErrorResult& aRv)
 {
   // FIXME(nsm): If the sticky flag is set, throw an error.
-  ServiceWorkerGlobalScope* scope = nullptr;
+  RefPtr<ServiceWorkerGlobalScope> scope;
   UNWRAP_OBJECT(ServiceWorkerGlobalScope, aGlobal.Get(), scope);
   if (scope) {
     aRv.ThrowTypeError<MSG_NOTIFICATION_NO_CONSTRUCTOR_IN_SERVICEWORKER>();
     return nullptr;
   }
 
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
   RefPtr<Notification> notification =
--- a/dom/performance/PerformanceTiming.h
+++ b/dom/performance/PerformanceTiming.h
@@ -239,16 +239,25 @@ public:
   {
     if (!nsContentUtils::IsPerformanceTimingEnabled() ||
         nsContentUtils::ShouldResistFingerprinting()) {
       return 0;
     }
     return GetDOMTiming()->GetLoadEventEnd();
   }
 
+  DOMTimeMilliSec TimeToNonBlankPaint() const
+  {
+    if (!nsContentUtils::IsPerformanceTimingEnabled() ||
+        nsContentUtils::ShouldResistFingerprinting()) {
+      return 0;
+    }
+    return GetDOMTiming()->GetTimeToNonBlankPaint();
+  }
+
 private:
   ~PerformanceTiming();
 
   bool IsInitialized() const;
   void InitializeTimingInfo(nsITimedChannel* aChannel);
 
   bool IsTopLevelContentDocument() const;
 
--- a/dom/presentation/PresentationSessionInfo.cpp
+++ b/dom/presentation/PresentationSessionInfo.cpp
@@ -1606,17 +1606,17 @@ PresentationPresentingInfo::ResolvedCall
   if (NS_WARN_IF(!obj)) {
     ReplyError(NS_ERROR_DOM_OPERATION_ERR);
     return;
   }
 
   // Start to listen to document state change event |STATE_TRANSFERRING|.
   // Use Element to support both HTMLIFrameElement and nsXULElement.
   Element* frame = nullptr;
-  nsresult rv = UNWRAP_OBJECT(Element, obj, frame);
+  nsresult rv = UNWRAP_OBJECT(Element, &obj, frame);
   if (NS_WARN_IF(!frame)) {
     ReplyError(NS_ERROR_DOM_OPERATION_ERR);
     return;
   }
 
   nsCOMPtr<nsIFrameLoaderOwner> owner = do_QueryInterface((nsIFrameLoaderOwner*) frame);
   if (NS_WARN_IF(!owner)) {
     ReplyError(NS_ERROR_DOM_OPERATION_ERR);
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -279,24 +279,23 @@ enum class NativeHandlerTask : int32_t {
   Reject
 };
 
 static bool
 NativeHandlerCallback(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
 {
   JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
 
-  JS::Rooted<JS::Value> v(aCx,
-                          js::GetFunctionNativeReserved(&args.callee(),
-                                                        SLOT_NATIVEHANDLER));
+  JS::Value v = js::GetFunctionNativeReserved(&args.callee(),
+                                              SLOT_NATIVEHANDLER);
   MOZ_ASSERT(v.isObject());
 
+  JS::Rooted<JSObject*> obj(aCx, &v.toObject());
   PromiseNativeHandler* handler = nullptr;
-  if (NS_FAILED(UNWRAP_OBJECT(PromiseNativeHandler, &v.toObject(),
-                              handler))) {
+  if (NS_FAILED(UNWRAP_OBJECT(PromiseNativeHandler, &obj, handler))) {
     return Throw(aCx, NS_ERROR_UNEXPECTED);
   }
 
   v = js::GetFunctionNativeReserved(&args.callee(), SLOT_NATIVEHANDLER_TASK);
   NativeHandlerTask task = static_cast<NativeHandlerTask>(v.toInt32());
 
   if (task == NativeHandlerTask::Resolve) {
     handler->ResolvedCallback(aCx, args.get(0));
--- a/dom/security/test/csp/test_sandbox.html
+++ b/dom/security/test/csp/test_sandbox.html
@@ -101,16 +101,25 @@ var testCases = [
   },
   {
     // Test 12: same as Test 6
     csp: "sandbox allow-same-origin allow-scripts; default-src 'self' 'unsafe-inline';",
     file: "file_sandbox_12.html",
     results: { img12_bad: -1, script12_bad: -1 },
     nrOKmessages: 4 // sends 4 ok message
   },
+  {
+    // Test 13: same as Test 5 and Test 11, but:
+    // * using sandbox flag 'allow-scripts' in CSP and not as iframe attribute
+    // * not using allow-same-origin in CSP (so a new NullPrincipal is created).
+    csp: "default-src 'none'; script-src 'unsafe-inline'; sandbox allow-scripts",
+    file: "file_sandbox_5.html",
+    results: { img13_bad: -1, img13a_bad: -1, script13_bad: -1, script13a_bad: -1 },
+    nrOKmessages: 2 // sends 2 ok message
+  },
 ];
 
 // a postMessage handler that is used by sandboxed iframes without
 // 'allow-same-origin' to communicate pass/fail back to this main page.
 // it expects to be called with an object like:
 //  { ok: true/false,
 //    desc: <description of the test> which it then forwards to ok() }
 window.addEventListener("message", receiveMessage);
--- a/dom/smil/nsSMILCSSValueType.cpp
+++ b/dom/smil/nsSMILCSSValueType.cpp
@@ -689,17 +689,18 @@ ValueFromStringHelper(nsCSSPropertyID aP
                                                doc->NodePrincipal());
   NS_ConvertUTF16toUTF8 value(aString);
   RefPtr<RawServoDeclarationBlock> servoDeclarationBlock =
     Servo_ParseProperty(aPropID,
                         &value,
                         data,
                         ParsingMode::AllowUnitlessLength |
                         ParsingMode::AllowAllNumericValues,
-                        doc->GetCompatibilityMode()).Consume();
+                        doc->GetCompatibilityMode(),
+                        doc->CSSLoader()).Consume();
   if (!servoDeclarationBlock) {
     return result;
   }
 
   // Get a suitable style context for Servo
   const ServoComputedValues* currentStyle =
     aStyleContext->ComputedValues();
 
--- a/dom/svg/nsSVGElement.cpp
+++ b/dom/svg/nsSVGElement.cpp
@@ -1188,16 +1188,17 @@ public:
   // already_AddRefed css::Declaration that incorporates the parsed
   // values. Otherwise, this method returns null.
   already_AddRefed<DeclarationBlock> GetDeclarationBlock();
 
 private:
   // MEMBER DATA
   // -----------
   nsCSSParser       mParser;
+  css::Loader*      mLoader;
 
   // Arguments for nsCSSParser::ParseProperty
   nsIURI*           mDocURI;
   nsCOMPtr<nsIURI>  mBaseURI;
 
   // Declaration for storing parsed values (lazily initialized)
   RefPtr<DeclarationBlock> mDecl;
 
@@ -1207,17 +1208,17 @@ private:
   StyleBackendType mBackend;
 };
 
 MappedAttrParser::MappedAttrParser(css::Loader* aLoader,
                                    nsIURI* aDocURI,
                                    already_AddRefed<nsIURI> aBaseURI,
                                    nsSVGElement* aElement,
                                    StyleBackendType aBackend)
-  : mParser(aLoader), mDocURI(aDocURI), mBaseURI(aBaseURI),
+  : mParser(aLoader), mLoader(aLoader), mDocURI(aDocURI), mBaseURI(aBaseURI),
     mElement(aElement), mBackend(aBackend)
 {
 }
 
 MappedAttrParser::~MappedAttrParser()
 {
   MOZ_ASSERT(!mDecl,
              "If mDecl was initialized, it should have been returned via "
@@ -1248,17 +1249,17 @@ MappedAttrParser::ParseMappedAttrValue(n
                             mElement->NodePrincipal(), mDecl->AsGecko(), &changed, false, true);
     } else {
       NS_ConvertUTF16toUTF8 value(aMappedAttrValue);
       // FIXME (bug 1343964): Figure out a better solution for sending the base uri to servo
       RefPtr<URLExtraData> data = new URLExtraData(mBaseURI, mDocURI,
                                                    mElement->NodePrincipal());
       changed = Servo_DeclarationBlock_SetPropertyById(
         mDecl->AsServo()->Raw(), propertyID, &value, false, data,
-        ParsingMode::AllowUnitlessLength, mElement->OwnerDoc()->GetCompatibilityMode());
+        ParsingMode::AllowUnitlessLength, mElement->OwnerDoc()->GetCompatibilityMode(), mLoader);
     }
 
     if (changed) {
       // The normal reporting of use counters by the nsCSSParser won't happen
       // since it doesn't have a sheet.
       if (nsCSSProps::IsShorthand(propertyID)) {
         CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, propertyID,
                                              CSSEnabledState::eForAllContent) {
--- a/dom/tests/mochitest/webcomponents/test_link_prefetch.html
+++ b/dom/tests/mochitest/webcomponents/test_link_prefetch.html
@@ -55,27 +55,27 @@ https://bugzilla.mozilla.org/show_bug.cg
   is(prefetch.hasMoreElements(), true,
      "Changing href, a new prefetch has been started.");
   // To check if "https://example.com/1" prefetch has been canceled, we try to
   // cancel it using PrefetService. Since the prefetch for
   // "https://example.com/1" does not exist, the cancel will throw.
   var cancelError = 0;
   try {
     var uri = ios.newURI("https://example.com/1");
-    prefetch.cancelPrefetchURI(uri, linkElem);
+    prefetch.cancelPrefetchPreloadURI(uri, linkElem);
   } catch(e) {
     cancelError = 1;
   }
   is(cancelError, 1, "This prefetch has aleady been canceled");
 
   // Now cancel the right uri.
   cancelError = 0;
   try {
     var uri = ios.newURI("https://example.com/2");
-    prefetch.cancelPrefetchURI(uri, linkElem);
+    prefetch.cancelPrefetchPreloadURI(uri, linkElem);
   } catch(e) {
     cancelError = 1;
   }
   is(cancelError, 0, "This prefetch has been canceled successfully");
 
   is(prefetch.hasMoreElements(), false, "The prefetch has already been canceled.");
 
   // Removing the link will do nothing regarding prefetch service.
--- a/dom/webidl/HTMLLinkElement.webidl
+++ b/dom/webidl/HTMLLinkElement.webidl
@@ -46,8 +46,14 @@ partial interface HTMLLinkElement {
            attribute DOMString target;
 };
 
 // https://w3c.github.io/webappsec/specs/subresourceintegrity/#htmllinkelement-1
 partial interface HTMLLinkElement {
   [CEReactions, SetterThrows]
   attribute DOMString integrity;
 };
+
+//https://w3c.github.io/preload/
+partial interface HTMLLinkElement {
+  [SetterThrows, Pure]
+           attribute DOMString as;
+};
--- a/dom/webidl/PerformanceTiming.webidl
+++ b/dom/webidl/PerformanceTiming.webidl
@@ -29,10 +29,16 @@ interface PerformanceTiming {
   readonly attribute unsigned long long domLoading;
   readonly attribute unsigned long long domInteractive;
   readonly attribute unsigned long long domContentLoadedEventStart;
   readonly attribute unsigned long long domContentLoadedEventEnd;
   readonly attribute unsigned long long domComplete;
   readonly attribute unsigned long long loadEventStart;
   readonly attribute unsigned long long loadEventEnd;
 
+  // This is a Chrome proprietary extension and not part of the
+  // performance/navigation timing specification.
+  // Returns 0 if a non-blank paint has not happened.
+  [Pref="dom.performance.time_to_non_blank_paint.enabled"]
+  readonly attribute unsigned long long timeToNonBlankPaint;
+
   jsonifier;
 };
--- a/dom/workers/ScriptLoader.cpp
+++ b/dom/workers/ScriptLoader.cpp
@@ -1520,17 +1520,17 @@ CacheCreator::ResolvedCallback(JSContext
 
   if (!aValue.isObject()) {
     FailLoaders(NS_ERROR_FAILURE);
     return;
   }
 
   JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
   Cache* cache = nullptr;
-  nsresult rv = UNWRAP_OBJECT(Cache, obj, cache);
+  nsresult rv = UNWRAP_OBJECT(Cache, &obj, cache);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     FailLoaders(NS_ERROR_FAILURE);
     return;
   }
 
   mCache = cache;
   MOZ_DIAGNOSTIC_ASSERT(mCache);
 
@@ -1666,17 +1666,17 @@ CacheScriptLoader::ResolvedCallback(JSCo
     }
     return;
   }
 
   MOZ_ASSERT(aValue.isObject());
 
   JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
   mozilla::dom::Response* response = nullptr;
-  rv = UNWRAP_OBJECT(Response, obj, response);
+  rv = UNWRAP_OBJECT(Response, &obj, response);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     Fail(rv);
     return;
   }
 
   InternalHeaders* headers = response->GetInternalHeaders();
 
   IgnoredErrorResult ignored;
--- a/dom/workers/ServiceWorker.cpp
+++ b/dom/workers/ServiceWorker.cpp
@@ -30,19 +30,17 @@ namespace workers {
 
 bool
 ServiceWorkerVisible(JSContext* aCx, JSObject* aObj)
 {
   if (NS_IsMainThread()) {
     return Preferences::GetBool("dom.serviceWorkers.enabled", false);
   }
 
-  ServiceWorkerGlobalScope* scope = nullptr;
-  nsresult rv = UNWRAP_OBJECT(ServiceWorkerGlobalScope, aObj, scope);
-  return NS_SUCCEEDED(rv);
+  return IS_INSTANCE_OF(ServiceWorkerGlobalScope, aObj);
 }
 
 ServiceWorker::ServiceWorker(nsPIDOMWindowInner* aWindow,
                              ServiceWorkerInfo* aInfo)
   : DOMEventTargetHelper(aWindow),
     mInfo(aInfo)
 {
   AssertIsOnMainThread();
--- a/dom/workers/ServiceWorkerScriptCache.cpp
+++ b/dom/workers/ServiceWorkerScriptCache.cpp
@@ -468,17 +468,17 @@ private:
       JS::Rooted<JS::Value> val(aCx);
       if (NS_WARN_IF(!JS_GetElement(aCx, obj, i, &val)) ||
           NS_WARN_IF(!val.isObject())) {
         return;
       }
 
       Request* request;
       JS::Rooted<JSObject*> requestObj(aCx, &val.toObject());
-      if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Request, requestObj, request)))) {
+      if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Request, &requestObj, request)))) {
         return;
       };
 
       nsString URL;
       request->GetUrl(URL);
 
       rv = FetchScript(URL, mURL == URL /* aIsMainScript */, mOldCache);
       if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -505,17 +505,17 @@ private:
     }
 
     JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
     if (NS_WARN_IF(!obj)) {
       return;
     }
 
     Cache* cache = nullptr;
-    rv = UNWRAP_OBJECT(Cache, obj, cache);
+    rv = UNWRAP_OBJECT(Cache, &obj, cache);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return;
     }
 
     // Just to be safe.
     RefPtr<Cache> kungfuDeathGrip = cache;
 
     MOZ_ASSERT(mPendingCount == 0);
@@ -1119,17 +1119,17 @@ CompareCache::ManageValueResult(JSContex
 
   JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
   if (NS_WARN_IF(!obj)) {
     Finish(NS_ERROR_FAILURE, false);
     return;
   }
 
   Response* response = nullptr;
-  nsresult rv = UNWRAP_OBJECT(Response, obj, response);
+  nsresult rv = UNWRAP_OBJECT(Response, &obj, response);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     Finish(rv, false);
     return;
   }
 
   MOZ_ASSERT(response->Ok());
 
   nsCOMPtr<nsIInputStream> inputStream;
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -1045,21 +1045,21 @@ public:
         JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
         NS_ASSERTION(global, "This should never be null!");
 
         nsEventStatus status = nsEventStatus_eIgnore;
         nsIScriptGlobalObject* sgo;
 
         if (aWorkerPrivate) {
           WorkerGlobalScope* globalScope = nullptr;
-          UNWRAP_OBJECT(WorkerGlobalScope, global, globalScope);
+          UNWRAP_OBJECT(WorkerGlobalScope, &global, globalScope);
 
           if (!globalScope) {
             WorkerDebuggerGlobalScope* globalScope = nullptr;
-            UNWRAP_OBJECT(WorkerDebuggerGlobalScope, global, globalScope);
+            UNWRAP_OBJECT(WorkerDebuggerGlobalScope, &global, globalScope);
 
             MOZ_ASSERT_IF(globalScope, globalScope->GetWrapperPreserveColor() == global);
             if (globalScope || IsDebuggerSandbox(global)) {
               aWorkerPrivate->ReportErrorToDebugger(aReport.mFilename, aReport.mLineNumber,
                                                     aReport.mMessage);
               return;
             }
 
@@ -7107,18 +7107,19 @@ BEGIN_WORKERS_NAMESPACE
 
 WorkerCrossThreadDispatcher*
 GetWorkerCrossThreadDispatcher(JSContext* aCx, const JS::Value& aWorker)
 {
   if (!aWorker.isObject()) {
     return nullptr;
   }
 
+  JS::Rooted<JSObject*> obj(aCx, &aWorker.toObject());
   WorkerPrivate* w = nullptr;
-  UNWRAP_OBJECT(Worker, &aWorker.toObject(), w);
+  UNWRAP_OBJECT(Worker, &obj, w);
   MOZ_ASSERT(w);
   return w->GetCrossThreadDispatcher();
 }
 
 // Force instantiation.
 template class WorkerPrivateParent<WorkerPrivate>;
 
 END_WORKERS_NAMESPACE
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -1099,27 +1099,23 @@ WorkerDebuggerGlobalScope::AbstractMainT
   MOZ_CRASH("AbstractMainThreadFor not supported for workers.");
 }
 
 BEGIN_WORKERS_NAMESPACE
 
 bool
 IsWorkerGlobal(JSObject* object)
 {
-  nsIGlobalObject* globalObject = nullptr;
-  return NS_SUCCEEDED(UNWRAP_OBJECT(WorkerGlobalScope, object,
-                                    globalObject)) && !!globalObject;
+  return IS_INSTANCE_OF(WorkerGlobalScope, object);
 }
 
 bool
 IsDebuggerGlobal(JSObject* object)
 {
-  nsIGlobalObject* globalObject = nullptr;
-  return NS_SUCCEEDED(UNWRAP_OBJECT(WorkerDebuggerGlobalScope, object,
-                                    globalObject)) && !!globalObject;
+  return IS_INSTANCE_OF(WorkerDebuggerGlobalScope, object);
 }
 
 bool
 IsDebuggerSandbox(JSObject* object)
 {
   return SimpleGlobalObject::SimpleGlobalType(object) ==
     SimpleGlobalObject::GlobalType::WorkerDebuggerSandbox;
 }
--- a/dom/xbl/nsXBLProtoImplField.cpp
+++ b/dom/xbl/nsXBLProtoImplField.cpp
@@ -150,18 +150,17 @@ InstallXBLField(JSContext* cx,
   //
   // FieldAccessorGuard already determined whether |thisObj| was acceptable as
   // |this| in terms of not throwing a TypeError.  Assert this for good measure.
   MOZ_ASSERT(ValueHasISupportsPrivate(cx, JS::ObjectValue(*thisObj)));
 
   // But there are some cases where we must accept |thisObj| but not install a
   // property on it, or otherwise touch it.  Hence this split of |this|-vetting
   // duties.
-  nsISupports* native =
-    nsContentUtils::XPConnect()->GetNativeOfWrapper(cx, thisObj);
+  nsCOMPtr<nsISupports> native = xpc::UnwrapReflectorToISupports(thisObj);
   if (!native) {
     // Looks like whatever |thisObj| is it's not our nsIContent.  It might well
     // be the proto our binding installed, however, where the private is the
     // nsXBLDocumentInfo, so just baul out quietly.  Do NOT throw an exception
     // here.
     //
     // We could make this stricter by checking the class maybe, but whatever.
     return true;
@@ -410,18 +409,20 @@ nsXBLProtoImplField::InstallField(JS::Ha
   if (!jsapi.Init(globalObject)) {
     return NS_ERROR_UNEXPECTED;
   }
   MOZ_ASSERT(!::JS_IsExceptionPending(jsapi.cx()),
              "Shouldn't get here when an exception is pending!");
 
   JSAddonId* addonId = MapURIToAddonID(aBindingDocURI);
 
+  // Note: the UNWRAP_OBJECT may mutate boundNode; don't use it after that call.
+  JS::Rooted<JSObject*> boundNode(jsapi.cx(), aBoundNode);
   Element* boundElement = nullptr;
-  rv = UNWRAP_OBJECT(Element, aBoundNode, boundElement);
+  rv = UNWRAP_OBJECT(Element, &boundNode, boundElement);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // First, enter the xbl scope, build the element's scope chain, and use
   // that as the scope chain for the evaluation.
   JS::Rooted<JSObject*> scopeObject(jsapi.cx(),
     xpc::GetScopeForXBLExecution(jsapi.cx(), aBoundNode, addonId));
--- a/dom/xslt/xpath/XPathExpression.cpp
+++ b/dom/xslt/xpath/XPathExpression.cpp
@@ -70,17 +70,17 @@ already_AddRefed<XPathResult>
 XPathExpression::EvaluateWithContext(JSContext* aCx,
                                      nsINode& aContextNode,
                                      uint32_t aContextPosition,
                                      uint32_t aContextSize,
                                      uint16_t aType,
                                      JS::Handle<JSObject*> aInResult,
                                      ErrorResult& aRv)
 {
-    XPathResult* inResult = nullptr;
+    RefPtr<XPathResult> inResult;
     if (aInResult) {
         nsresult rv = UNWRAP_OBJECT(XPathResult, aInResult, inResult);
         if (NS_FAILED(rv) && rv != NS_ERROR_XPC_BAD_CONVERT_JS) {
             aRv.Throw(rv);
             return nullptr;
         }
     }
 
--- a/gfx/2d/Factory.cpp
+++ b/gfx/2d/Factory.cpp
@@ -210,27 +210,16 @@ DrawEventRecorder *Factory::mRecorder;
 mozilla::gfx::Config* Factory::sConfig = nullptr;
 
 void
 Factory::Init(const Config& aConfig)
 {
   MOZ_ASSERT(!sConfig);
   sConfig = new Config(aConfig);
 
-  // Make sure we don't completely break rendering because of a typo in the
-  // pref or whatnot.
-  const int32_t kMinAllocPref = 10000000;
-  const int32_t kMinSizePref = 2048;
-  if (sConfig->mMaxAllocSize < kMinAllocPref) {
-    sConfig->mMaxAllocSize = kMinAllocPref;
-  }
-  if (sConfig->mMaxTextureSize < kMinSizePref) {
-    sConfig->mMaxTextureSize = kMinSizePref;
-  }
-
 #ifdef MOZ_ENABLE_FREETYPE
   mFTLock = new Mutex("Factory::mFTLock");
 #endif
 }
 
 void
 Factory::ShutDown()
 {
--- a/gfx/config/gfxConfig.cpp
+++ b/gfx/config/gfxConfig.cpp
@@ -1,15 +1,17 @@
 /* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* vim: set sts=2 ts=8 sw=2 tw=99 et: */
 /* 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 "gfxConfig.h"
 #include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/gfx/GPUParent.h"
 #include "mozilla/gfx/GraphicsMessages.h"
 #include "plstr.h"
 
 namespace mozilla {
 namespace gfx {
 
 static UniquePtr<gfxConfig> sConfig;
 
@@ -138,16 +140,23 @@ gfxConfig::Reenable(Feature aFeature, Fa
   MOZ_ASSERT(IsFeatureStatusFailure(state.GetValue()));
 
   const char* message = state.GetRuntimeMessage();
   EnableFallback(aFallback, message);
   state.SetRuntime(FeatureStatus::Available, nullptr);
 }
 
 /* static */ void
+gfxConfig::Reset(Feature aFeature)
+{
+  FeatureState& state = sConfig->GetState(aFeature);
+  state.Reset();
+}
+
+/* static */ void
 gfxConfig::Inherit(Feature aFeature, FeatureStatus aStatus)
 {
   FeatureState& state = sConfig->GetState(aFeature);
 
   state.Reset();
 
   switch (aStatus) {
   case FeatureStatus::Unused:
@@ -173,17 +182,33 @@ gfxConfig::Inherit(Feature aFeature, Fea
 gfxConfig::UseFallback(Fallback aFallback)
 {
   return sConfig->UseFallbackImpl(aFallback);
 }
 
 /* static */ void
 gfxConfig::EnableFallback(Fallback aFallback, const char* aMessage)
 {
-  // Ignore aMessage for now.
+  if (!NS_IsMainThread()) {
+    nsCString message(aMessage);
+    NS_DispatchToMainThread(
+      NS_NewRunnableFunction("gfxConfig::EnableFallback",
+                             [=]() -> void {
+
+        gfxConfig::EnableFallback(aFallback, message.get());
+      }));
+    return;
+  }
+
+  if (XRE_IsGPUProcess()) {
+    nsCString message(aMessage);
+    Unused << GPUParent::GetSingleton()->SendUsedFallback(aFallback, message);
+    return;
+  }
+
   sConfig->EnableFallbackImpl(aFallback, aMessage);
 }
 
 bool
 gfxConfig::UseFallbackImpl(Fallback aFallback) const
 {
   return !!(mFallbackBits & (uint64_t(1) << uint64_t(aFallback)));
 }
--- a/gfx/config/gfxConfig.h
+++ b/gfx/config/gfxConfig.h
@@ -57,16 +57,19 @@ public:
   //
   //  1. If a runtime failure was set, return it.
   //  2. If the user force-enabled the feature, return ForceEnabled.
   //  3. If an environment status was set, return it.
   //  4. If a user status was set, return it.
   //  5. Return the default status.
   static FeatureStatus GetValue(Feature aFeature);
 
+  // Reset the entire state of a feature.
+  static void Reset(Feature aFeature);
+
   // Initialize the base value of a parameter. The return value is aEnable.
   static bool SetDefault(Feature aFeature,
                          bool aEnable,
                          FeatureStatus aDisableStatus,
                          const char* aDisableMessage);
   static void DisableByDefault(Feature aFeature,
                                FeatureStatus aDisableStatus,
                                const char* aDisableMessage,
@@ -159,17 +162,18 @@ public:
   // of a parameter.
   static void UserEnable(Feature aFeature, const char* aMessage);
   static void UserForceEnable(Feature aFeature, const char* aMessage);
   static void UserDisable(Feature aFeature, const char* aMessage, const nsACString& aFailureId = EmptyCString());
 
   // Query whether a fallback has been toggled.
   static bool UseFallback(Fallback aFallback);
 
-  // Enable a fallback.
+  // Add a log entry denoting that a given fallback had to be used. This can
+  // be called from any thread in the UI or GPU process.
   static void EnableFallback(Fallback aFallback, const char* aMessage);
 
   // Run a callback for each initialized FeatureState.
   typedef std::function<void(const char* aName,
                              const char* aDescription,
                              FeatureState& aFeature)> FeatureIterCallback;
   static void ForEachFeature(const FeatureIterCallback& aCallback);
 
--- a/gfx/config/gfxFallback.h
+++ b/gfx/config/gfxFallback.h
@@ -9,17 +9,17 @@
 #include <stdint.h>
 #include "gfxTelemetry.h"
 
 namespace mozilla {
 namespace gfx {
 
 #define GFX_FALLBACK_MAP(_)                                                       \
   /* Name */                                                                      \
-  _(PLACEHOLDER_DO_NOT_USE)                                                       \
+  _(NO_CONSTANT_BUFFER_OFFSETTING)                                                \
   /* Add new entries above this comment */
 
 enum class Fallback : uint32_t {
 #define MAKE_ENUM(name) name,
   GFX_FALLBACK_MAP(MAKE_ENUM)
 #undef MAKE_ENUM
   NumValues
 };
--- a/gfx/doc/AdvancedLayers.md
+++ b/gfx/doc/AdvancedLayers.md
@@ -90,39 +90,34 @@ Advanced Layers currently has five layer
 
  - Textured (PaintedLayer, ImageLayer, CanvasLayer)
  - ComponentAlpha (PaintedLayer with component-alpha)
  - YCbCr (ImageLayer with YCbCr video)
  - Color (ColorLayers)
  - Blend (ContainerLayers with mix-blend modes)
 
 There are also three special shader pipelines:
+
  - MaskCombiner, which is used to combine mask layers into a single texture.
  - Clear, which is used for fast region-based clears when not directly supported by the GPU.
  - Diagnostic, which is used to display the diagnostic overlay texture.
 
-The basic layer shaders follow a unified structure. Each pipeline has a vertex and pixel shader.
-The vertex shader takes a layers ID, a z-buffer depth, a vertex (in a triangle list), and any
-ancillary data needed for the pixel shader. Often this ancillary data is just an index into
-a constant buffer (more on this below).
-
-The vertex shader applies transforms and sends data down to the pixel shader, which performs
-clipping and masking.
+The layer shaders follow a unified structure. Each pipeline has a vertex and pixel shader.
+The vertex shader takes a layers ID, a z-buffer depth, a unit position in either a unit
+square or unit triangle, and either rectangular or triangular geometry. Shaders can also
+have ancillary data needed like texture coordinates or colors.
 
 Most of the time, layers have simple rectangular clips with simple rectilinear transforms, and
-pixel shaders do not need to perform masking or clipping. We take advantage of this for common
-layer types, and use a second set of vertex and pixel shaders. These shaders have a unified
-input layout: a draw rect, a layers ID, and a z-buffer depth. The pipeline uses a unit quad
-as input. Ancillary data is stored in a constant buffer, which is indexed by the instance ID.
-This path performs clipping in the vertex shader, and the pixel shader does not support masks.
+pixel shaders do not need to perform masking or clipping. For these layers we use a fast-path
+pipeline, using unit-quad shaders that are able to clip geometry so the pixel shader does not
+have to. This type of pipeline does not support complex masks.
 
-Most shader types use ancillary data (such as texture coordinates, or a color value). This is
-stored in a constant buffer, which is bound to the vertex shader. Unit quad shaders use
-instancing to access the buffer. Full-fledged, triangle list shaders store the index in each
-vertex.
+If a layer has a complex mask, a rotation or 3d transform, or a complex operation like blending,
+then we use shaders capable of handling arbitrary geometry. Their input is a unit triangle,
+and these shaders are generally more expensive.
 
 All of the shader-specific data is modelled in ShaderDefinitionsMLGPU.h.
 
 CPU Occlusion Culling
 -------------------------------------
 
 By default, Advanced Layers performs occlusion culling on the CPU. Since layers are visited
 front-to-back, this is simply a matter of accumulating the visible region of opaque layers, and
--- a/gfx/ipc/GPUChild.cpp
+++ b/gfx/ipc/GPUChild.cpp
@@ -275,16 +275,23 @@ GPUChild::ActorDestroy(ActorDestroyReaso
 
 mozilla::ipc::IPCResult
 GPUChild::RecvUpdateFeature(const Feature& aFeature, const FeatureFailure& aChange)
 {
   gfxConfig::SetFailed(aFeature, aChange.status(), aChange.message().get(), aChange.failureId());
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+GPUChild::RecvUsedFallback(const Fallback& aFallback, const nsCString& aMessage)
+{
+  gfxConfig::EnableFallback(aFallback, aMessage.get());
+  return IPC_OK();
+}
+
 class DeferredDeleteGPUChild : public Runnable
 {
 public:
   explicit DeferredDeleteGPUChild(UniquePtr<GPUChild>&& aChild)
     : Runnable("gfx::DeferredDeleteGPUChild")
     , mChild(Move(aChild))
   {
   }
--- a/gfx/ipc/GPUChild.h
+++ b/gfx/ipc/GPUChild.h
@@ -54,16 +54,17 @@ public:
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
   mozilla::ipc::IPCResult RecvGraphicsError(const nsCString& aError) override;
   mozilla::ipc::IPCResult RecvNotifyUiObservers(const nsCString& aTopic) override;
   mozilla::ipc::IPCResult RecvNotifyDeviceReset(const GPUDeviceData& aData) override;
   mozilla::ipc::IPCResult RecvAddMemoryReport(const MemoryReport& aReport) override;
   mozilla::ipc::IPCResult RecvFinishMemoryReport(const uint32_t& aGeneration) override;
   mozilla::ipc::IPCResult RecvUpdateFeature(const Feature& aFeature, const FeatureFailure& aChange) override;
+  mozilla::ipc::IPCResult RecvUsedFallback(const Fallback& aFallback, const nsCString& aMessage) override;
 
   bool SendRequestMemoryReport(const uint32_t& aGeneration,
                                const bool& aAnonymize,
                                const bool& aMinimizeMemoryUsage,
                                const MaybeFileDesc& aDMDFile);
 
   static void Destroy(UniquePtr<GPUChild>&& aChild);
 
--- a/gfx/ipc/GPUProcessManager.cpp
+++ b/gfx/ipc/GPUProcessManager.cpp
@@ -392,18 +392,21 @@ ShouldLimitDeviceResets(uint32_t count, 
   } else if (hasCountLimit) {
     return triggeredCount;
   }
 
   return false;
 }
 
 void
-GPUProcessManager::TriggerDeviceResetForTesting()
+GPUProcessManager::SimulateDeviceReset()
 {
+  // Make sure we rebuild environment and configuration for accelerated features.
+  gfxPlatform::GetPlatform()->CompositorUpdated();
+
   if (mProcess) {
     OnRemoteProcessDeviceReset(mProcess);
   } else {
     OnInProcessDeviceReset();
   }
 }
 
 void
@@ -423,16 +426,27 @@ GPUProcessManager::OnRemoteProcessDevice
   auto newTime = TimeStamp::Now();
   auto delta = (int32_t)(newTime - mDeviceResetLastTime).ToMilliseconds();
   mDeviceResetLastTime = newTime;
 
   if (ShouldLimitDeviceResets(mDeviceResetCount, delta)) {
     DestroyProcess();
     DisableGPUProcess("GPU processed experienced too many device resets");
 
+    // Reaches the limited TDR attempts, fallback to software solution.
+    gfxConfig::SetFailed(Feature::HW_COMPOSITING,
+      FeatureStatus::Blocked,
+      "Too many attemps of D3D11 creation, fallback to software solution.");
+    gfxConfig::SetFailed(Feature::D3D11_COMPOSITING,
+      FeatureStatus::Blocked,
+      "Too many attemps of D3D11 creation, fallback to software solution.");
+    gfxConfig::SetFailed(Feature::DIRECT2D,
+      FeatureStatus::Blocked,
+      "Too many attemps of D3D11 creation, fallback to software solution.");
+
     HandleProcessLost();
     return;
   }
 
   RebuildRemoteSessions();
   NotifyListenersOnCompositeDeviceReset();
 }
 
--- a/gfx/ipc/GPUProcessManager.h
+++ b/gfx/ipc/GPUProcessManager.h
@@ -139,17 +139,17 @@ public:
   bool AllocateAndConnectLayerTreeId(
     PCompositorBridgeChild* aCompositorBridge,
     base::ProcessId aOtherPid,
     uint64_t* aOutLayersId,
     CompositorOptions* aOutCompositorOptions);
 
   void OnProcessLaunchComplete(GPUProcessHost* aHost) override;
   void OnProcessUnexpectedShutdown(GPUProcessHost* aHost) override;
-  void TriggerDeviceResetForTesting();
+  void SimulateDeviceReset();
   void OnInProcessDeviceReset();
   void OnRemoteProcessDeviceReset(GPUProcessHost* aHost) override;
   void NotifyListenersOnCompositeDeviceReset();
 
   // Notify the GPUProcessManager that a top-level PGPU protocol has been
   // terminated. This may be called from any thread.
   void NotifyRemoteActorDestroyed(const uint64_t& aProcessToken);
 
--- a/gfx/ipc/GfxMessageUtils.h
+++ b/gfx/ipc/GfxMessageUtils.h
@@ -9,16 +9,17 @@
 
 #include "FilterSupport.h"
 #include "FrameMetrics.h"
 #include "ImageTypes.h"
 #include "RegionBuilder.h"
 #include "base/process_util.h"
 #include "chrome/common/ipc_message_utils.h"
 #include "gfxFeature.h"
+#include "gfxFallback.h"
 #include "gfxPoint.h"
 #include "gfxRect.h"
 #include "gfxTelemetry.h"
 #include "gfxTypes.h"
 #include "ipc/IPCMessageUtils.h"
 #include "mozilla/gfx/Matrix.h"
 #include "nsRect.h"
 #include "nsRegion.h"
@@ -224,16 +225,24 @@ template <>
 struct ParamTraits<mozilla::gfx::Feature>
   : public ContiguousEnumSerializer<
              mozilla::gfx::Feature,
              mozilla::gfx::Feature::HW_COMPOSITING,
              mozilla::gfx::Feature::NumValues>
 {};
 
 template <>
+struct ParamTraits<mozilla::gfx::Fallback>
+  : public ContiguousEnumSerializer<
+             mozilla::gfx::Fallback,
+             mozilla::gfx::Fallback::NO_CONSTANT_BUFFER_OFFSETTING,
+             mozilla::gfx::Fallback::NumValues>
+{};
+
+template <>
 struct ParamTraits<mozilla::gfx::FeatureStatus>
   : public ContiguousEnumSerializer<
              mozilla::gfx::FeatureStatus,
              mozilla::gfx::FeatureStatus::Unused,
              mozilla::gfx::FeatureStatus::LAST>
 {};
 
 template <>
--- a/gfx/ipc/PGPU.ipdl
+++ b/gfx/ipc/PGPU.ipdl
@@ -17,16 +17,17 @@ using base::ProcessId from "base/process
 using mozilla::dom::NativeThreadId from "mozilla/dom/TabMessageUtils.h";
 using mozilla::Telemetry::Accumulation from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::KeyedAccumulation from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::ScalarAction from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::KeyedScalarAction from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::ChildEventData from "mozilla/TelemetryComms.h";
 using mozilla::Telemetry::DiscardedData from "mozilla/TelemetryComms.h";
 using mozilla::gfx::Feature from "gfxFeature.h";
+using mozilla::gfx::Fallback from "gfxFallback.h";
 
 namespace mozilla {
 namespace gfx {
 
 union GfxPrefValue {
   bool;
   int32_t;
   uint32_t;
@@ -115,12 +116,15 @@ child:
   async NotifyDeviceReset(GPUDeviceData status);
 
   async AddMemoryReport(MemoryReport aReport);
   async FinishMemoryReport(uint32_t aGeneration);
 
   // Update the UI process after a feature's status has changed. This is used
   // outside of the normal startup flow.
   async UpdateFeature(Feature aFeature, FeatureFailure aChange);
+
+  // Notify about:support/Telemetry that a fallback occurred.
+  async UsedFallback(Fallback aFallback, nsCString message);
 };
 
 } // namespace gfx
 } // namespace mozilla
--- a/gfx/layers/composite/LayerManagerComposite.cpp
+++ b/gfx/layers/composite/LayerManagerComposite.cpp
@@ -598,16 +598,21 @@ LayerManagerComposite::DrawPaintTimes(Co
 
 static uint16_t sFrameCount = 0;
 void
 LayerManagerComposite::RenderDebugOverlay(const IntRect& aBounds)
 {
   bool drawFps = gfxPrefs::LayersDrawFPS();
   bool drawFrameColorBars = gfxPrefs::CompositorDrawColorBars();
 
+  // Don't draw diagnostic overlays if we want to snapshot the output.
+  if (