Merge m-c to autoland, a=merge
authorWes Kocher <wkocher@mozilla.com>
Mon, 10 Jul 2017 18:51:05 -0700
changeset 368168 33f8d1b03767e0f63c2420a9b510372ff98ffe8b
parent 368167 b6cb72de33af2bd79a307fe11145f035bcab2366 (current diff)
parent 368131 0e41d07a703f19224f60b01577b2cbb5708046c9 (diff)
child 368169 8d529f0b6575b7f95111597e060dc3a0538d388d
push id32158
push usercbook@mozilla.com
push dateTue, 11 Jul 2017 10:48:59 +0000
treeherdermozilla-central@5e2692f8a367 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone56.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to autoland, a=merge
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
taskcluster/taskgraph/transforms/job/mozharness_test.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
--- 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/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",
--- 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/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/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/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;
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -6017,17 +6017,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/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/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/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/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/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/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/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;
         }
     }
 
new file mode 100644
--- /dev/null
+++ b/intl/icu-patches/bug-1373763-convertToPosix-stack-value-out-of-scope.diff
@@ -0,0 +1,34 @@
+Move the stack allocated buffer to the top-level, so its value can be accessed after the if-statement.
+
+https://ssl.icu-project.org/trac/ticket/13263
+
+diff --git a/intl/icu/source/common/locmap.cpp b/intl/icu/source/common/locmap.cpp
+--- a/intl/icu/source/common/locmap.cpp
++++ b/intl/icu/source/common/locmap.cpp
+@@ -1028,24 +1028,25 @@ U_CAPI int32_t
+ uprv_convertToPosix(uint32_t hostid, char *posixID, int32_t posixIDCapacity, UErrorCode* status)
+ {
+     uint16_t langID;
+     uint32_t localeIndex;
+     UBool bLookup = TRUE;
+     const char *pPosixID = NULL;
+ 
+ #ifdef USE_WINDOWS_LCID_MAPPING_API
++    char locName[LOCALE_NAME_MAX_LENGTH] = {};      // ICU name can't be longer than Windows name
++
+     // Note: Windows primary lang ID 0x92 in LCID is used for Central Kurdish and
+     // GetLocaleInfo() maps such LCID to "ku". However, CLDR uses "ku" for
+     // Northern Kurdish and "ckb" for Central Kurdish. For this reason, we cannot
+     // use the Windows API to resolve locale ID for this specific case.
+     if ((hostid & 0x3FF) != 0x92) {
+         int32_t tmpLen = 0;
+         UChar windowsLocaleName[LOCALE_NAME_MAX_LENGTH];  // ULOC_FULLNAME_CAPACITY > LOCALE_NAME_MAX_LENGTH
+-        char locName[LOCALE_NAME_MAX_LENGTH];             // ICU name can't be longer than Windows name
+ 
+         // Note: LOCALE_ALLOW_NEUTRAL_NAMES was enabled in Windows7+, prior versions did not handle neutral (no-region) locale names.
+         tmpLen = LCIDToLocaleName(hostid, (PWSTR)windowsLocaleName, UPRV_LENGTHOF(windowsLocaleName), LOCALE_ALLOW_NEUTRAL_NAMES);
+         if (tmpLen > 1) {
+             int32_t i = 0;
+             // Only need to look up in table if have _, eg for de-de_phoneb type alternate sort.
+             bLookup = FALSE;
+             for (i = 0; i < UPRV_LENGTHOF(locName); i++)
--- a/intl/icu/source/common/locmap.cpp
+++ b/intl/icu/source/common/locmap.cpp
@@ -1028,24 +1028,25 @@ U_CAPI int32_t
 uprv_convertToPosix(uint32_t hostid, char *posixID, int32_t posixIDCapacity, UErrorCode* status)
 {
     uint16_t langID;
     uint32_t localeIndex;
     UBool bLookup = TRUE;
     const char *pPosixID = NULL;
 
 #ifdef USE_WINDOWS_LCID_MAPPING_API
+    char locName[LOCALE_NAME_MAX_LENGTH] = {};      // ICU name can't be longer than Windows name
+
     // Note: Windows primary lang ID 0x92 in LCID is used for Central Kurdish and
     // GetLocaleInfo() maps such LCID to "ku". However, CLDR uses "ku" for
     // Northern Kurdish and "ckb" for Central Kurdish. For this reason, we cannot
     // use the Windows API to resolve locale ID for this specific case.
     if ((hostid & 0x3FF) != 0x92) {
         int32_t tmpLen = 0;
         UChar windowsLocaleName[LOCALE_NAME_MAX_LENGTH];  // ULOC_FULLNAME_CAPACITY > LOCALE_NAME_MAX_LENGTH
-        char locName[LOCALE_NAME_MAX_LENGTH];             // ICU name can't be longer than Windows name
 
         // Note: LOCALE_ALLOW_NEUTRAL_NAMES was enabled in Windows7+, prior versions did not handle neutral (no-region) locale names.
         tmpLen = LCIDToLocaleName(hostid, (PWSTR)windowsLocaleName, UPRV_LENGTHOF(windowsLocaleName), LOCALE_ALLOW_NEUTRAL_NAMES);
         if (tmpLen > 1) {
             int32_t i = 0;
             // Only need to look up in table if have _, eg for de-de_phoneb type alternate sort.
             bLookup = FALSE;
             for (i = 0; i < UPRV_LENGTHOF(locName); i++)
--- a/intl/update-icu.sh
+++ b/intl/update-icu.sh
@@ -66,16 +66,17 @@ find ${icu_dir}/source/data/zone \
 svn info $1 | grep -v '^Revision: [[:digit:]]\+$' > ${icu_dir}/SVN-INFO
 
 for patch in \
  bug-915735 \
  suppress-warnings.diff \
  bug-1172609-timezone-recreateDefault.diff \
  bug-1198952-workaround-make-3.82-bug.diff \
  u_setMemoryFunctions-callconvention-anachronism-msvc.diff \
+ bug-1373763-convertToPosix-stack-value-out-of-scope.diff \
 ; do
   echo "Applying local patch $patch"
   patch -d ${icu_dir}/../../ -p1 --no-backup-if-mismatch < ${icu_dir}/../icu-patches/$patch
 done
 
 topsrcdir=`dirname $0`/../
 python ${topsrcdir}/js/src/tests/ecma_6/String/make-normalize-generateddata-input.py $topsrcdir
 
--- a/js/public/GCHashTable.h
+++ b/js/public/GCHashTable.h
@@ -2,16 +2,18 @@
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef GCHashTable_h
 #define GCHashTable_h
 
+#include "mozilla/Maybe.h"
+
 #include "js/GCPolicyAPI.h"
 #include "js/HashTable.h"
 #include "js/RootingAPI.h"
 #include "js/SweepingAPI.h"
 #include "js/TracingAPI.h"
 
 namespace JS {
 
@@ -279,21 +281,21 @@ class GCHashSet : public js::HashSet<T, 
 } // namespace JS
 
 namespace js {
 
 template <typename Wrapper, typename... Args>
 class WrappedPtrOperations<JS::GCHashSet<Args...>, Wrapper>
 {
     using Set = JS::GCHashSet<Args...>;
-    using Lookup = typename Set::Lookup;
 
     const Set& set() const { return static_cast<const Wrapper*>(this)->get(); }
 
   public:
+    using Lookup = typename Set::Lookup;
     using AddPtr = typename Set::AddPtr;
     using Entry = typename Set::Entry;
     using Ptr = typename Set::Ptr;
     using Range = typename Set::Range;
 
     bool initialized() const                   { return set().initialized(); }
     Ptr lookup(const Lookup& l) const          { return set().lookup(l); }
     AddPtr lookupForAdd(const Lookup& l) const { return set().lookupForAdd(l); }
@@ -355,9 +357,448 @@ class MutableWrappedPtrOperations<JS::GC
     template<typename TInput>
     bool putNew(const Lookup& l, TInput&& t) {
         return set().putNew(l, mozilla::Forward<TInput>(t));
     }
 };
 
 } /* namespace js */
 
+namespace JS {
+
+// Specialize WeakCache for GCHashMap to provide a barriered map that does not
+// need to be swept immediately.
+template <typename Key, typename Value,
+          typename HashPolicy, typename AllocPolicy, typename MapSweepPolicy>
+class WeakCache<GCHashMap<Key, Value, HashPolicy, AllocPolicy, MapSweepPolicy>>
+  : protected detail::WeakCacheBase
+{
+    using Map = GCHashMap<Key, Value, HashPolicy, AllocPolicy, MapSweepPolicy>;
+    using Self = WeakCache<Map>;
+
+    Map map;
+    bool needsBarrier;
+
+  public:
+    template <typename... Args>
+    explicit WeakCache(Zone* zone, Args&&... args)
+      : WeakCacheBase(zone), map(mozilla::Forward<Args>(args)...), needsBarrier(false)
+    {}
+    template <typename... Args>
+    explicit WeakCache(JSRuntime* rt, Args&&... args)
+      : WeakCacheBase(rt), map(mozilla::Forward<Args>(args)...), needsBarrier(false)
+    {}
+    ~WeakCache() {
+        MOZ_ASSERT(!needsBarrier);
+    }
+
+    bool needsSweep() override {
+        return map.needsSweep();
+    }
+
+    size_t sweep() override {
+        if (!this->initialized())
+            return 0;
+
+        size_t steps = map.count();
+        map.sweep();
+        return steps;
+    }
+
+    bool setNeedsIncrementalBarrier(bool needs) override {
+        MOZ_ASSERT(needsBarrier != needs);
+        needsBarrier = needs;
+        return true;
+    }
+
+    bool needsIncrementalBarrier() const override {
+        return needsBarrier;
+    }
+
+  private:
+    using Entry = typename Map::Entry;
+
+    static bool entryNeedsSweep(const Entry& prior) {
+        Key key(prior.key());
+        Value value(prior.value());
+        bool result = MapSweepPolicy::needsSweep(&key, &value);
+        MOZ_ASSERT(prior.key() == key); // We shouldn't update here.
+        MOZ_ASSERT(prior.value() == value); // We shouldn't update here.
+        return result;
+    }
+
+  public:
+    using Lookup = typename Map::Lookup;
+    using Ptr = typename Map::Ptr;
+    using AddPtr = typename Map::AddPtr;
+
+    struct Range
+    {
+        explicit Range(const typename Map::Range& r)
+          : range(r)
+        {
+            settle();
+        }
+        Range() {}
+
+        bool empty() const { return range.empty(); }
+        const Entry& front() const { return range.front(); }
+
+        void popFront() {
+            range.popFront();
+            settle();
+        }
+
+      private:
+        typename Map::Range range;
+
+        void settle() {
+            while (!empty() && entryNeedsSweep(front()))
+                popFront();
+        }
+    };
+
+    struct Enum : public Map::Enum
+    {
+        explicit Enum(Self& cache)
+          : Map::Enum(cache.map)
+        {
+            // This operation is not allowed while barriers are in place as we
+            // may also need to enumerate the set for sweeping.
+            MOZ_ASSERT(!cache.needsBarrier);
+        }
+    };
+
+    bool initialized() const {
+        return map.initialized();
+    }
+
+    Ptr lookup(const Lookup& l) const {
+        Ptr ptr = map.lookup(l);
+        if (needsBarrier && ptr && entryNeedsSweep(*ptr)) {
+            const_cast<Map&>(map).remove(ptr);
+            return Ptr();
+        }
+        return ptr;
+    }
+
+    AddPtr lookupForAdd(const Lookup& l) const {
+        AddPtr ptr = map.lookupForAdd(l);
+        if (needsBarrier && ptr && entryNeedsSweep(*ptr)) {
+            const_cast<Map&>(map).remove(ptr);
+            return map.lookupForAdd(l);
+        }
+        return ptr;
+    }
+
+    Range all() const {
+        return Range(map.all());
+    }
+
+    bool empty() const {
+        // This operation is not currently allowed while barriers are in place
+        // as it would require iterating the map and the caller expects a
+        // constant time operation.
+        MOZ_ASSERT(!needsBarrier);
+        return map.empty();
+    }
+
+    uint32_t count() const {
+        // This operation is not currently allowed while barriers are in place
+        // as it would require iterating the set and the caller expects a
+        // constant time operation.
+        MOZ_ASSERT(!needsBarrier);
+        return map.count();
+    }
+
+    size_t capacity() const {
+        return map.capacity();
+    }
+
+    bool has(const Lookup& l) const {
+        return lookup(l).found();
+    }
+
+    size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+        return map.sizeOfExcludingThis(mallocSizeOf);
+    }
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+        return mallocSizeOf(this) + map.sizeOfExcludingThis(mallocSizeOf);
+    }
+
+    bool init(uint32_t len = 16) {
+        MOZ_ASSERT(!needsBarrier);
+        return map.init(len);
+    }
+
+    void clear() {
+        // This operation is not currently allowed while barriers are in place
+        // since it doesn't make sense to clear a cache while it is being swept.
+        MOZ_ASSERT(!needsBarrier);
+        map.clear();
+    }
+
+    void finish() {
+        // This operation is not currently allowed while barriers are in place
+        // since it doesn't make sense to destroy a cache while it is being swept.
+        MOZ_ASSERT(!needsBarrier);
+        map.finish();
+    }
+
+    void remove(Ptr p) {
+        // This currently supports removing entries during incremental
+        // sweeping. If we allow these tables to be swept incrementally this may
+        // no longer be possible.
+        map.remove(p);
+    }
+
+    void remove(const Lookup& l) {
+        Ptr p = lookup(l);
+        if (p)
+            remove(p);
+    }
+
+    template<typename KeyInput>
+    bool add(AddPtr& p, KeyInput&& k) {
+        using mozilla::Forward;
+        return map.add(p, Forward<KeyInput>(k));
+    }
+
+    template<typename KeyInput, typename ValueInput>
+    bool add(AddPtr& p, KeyInput&& k, ValueInput&& v) {
+        using mozilla::Forward;
+        return map.add(p, Forward<KeyInput>(k), Forward<ValueInput>(v));
+    }
+
+    template<typename KeyInput, typename ValueInput>
+    bool relookupOrAdd(AddPtr& p, KeyInput&& k, ValueInput&& v) {
+        using mozilla::Forward;
+        return map.relookupOrAdd(p, Forward<KeyInput>(k), Forward<ValueInput>(v));
+    }
+
+    template<typename KeyInput, typename ValueInput>
+    bool put(KeyInput&& k, ValueInput&& v) {
+        using mozilla::Forward;
+        return map.put(Forward<KeyInput>(k), Forward<ValueInput>(v));
+    }
+
+    template<typename KeyInput, typename ValueInput>
+    bool putNew(KeyInput&& k, ValueInput&& v) {
+        using mozilla::Forward;
+        return map.putNew(Forward<KeyInput>(k), Forward<ValueInput>(v));
+    }
+};
+
+// Specialize WeakCache for GCHashSet to provide a barriered set that does not
+// need to be swept immediately.
+template <typename T, typename HashPolicy, typename AllocPolicy>
+class WeakCache<GCHashSet<T, HashPolicy, AllocPolicy>>
+    : protected detail::WeakCacheBase
+{
+    using Set = GCHashSet<T, HashPolicy, AllocPolicy>;
+    using Self = WeakCache<Set>;
+
+    Set set;
+    bool needsBarrier;
+
+  public:
+    using Entry = typename Set::Entry;
+
+    template <typename... Args>
+    explicit WeakCache(Zone* zone, Args&&... args)
+      : WeakCacheBase(zone), set(mozilla::Forward<Args>(args)...), needsBarrier(false)
+    {}
+    template <typename... Args>
+    explicit WeakCache(JSRuntime* rt, Args&&... args)
+      : WeakCacheBase(rt), set(mozilla::Forward<Args>(args)...), needsBarrier(false)
+    {}
+
+    size_t sweep() override {
+        if (!this->initialized())
+            return 0;
+
+        size_t steps = set.count();
+        set.sweep();
+        return steps;
+    }
+
+    bool needsSweep() override {
+        return set.needsSweep();
+    }
+
+    bool setNeedsIncrementalBarrier(bool needs) override {
+        MOZ_ASSERT(needsBarrier != needs);
+        needsBarrier = needs;
+        return true;
+    }
+
+    bool needsIncrementalBarrier() const override {
+        return needsBarrier;
+    }
+
+  private:
+   static bool entryNeedsSweep(const Entry& prior) {
+        Entry entry(prior);
+        bool result = GCPolicy<T>::needsSweep(&entry);
+        MOZ_ASSERT(prior == entry); // We shouldn't update here.
+        return result;
+    }
+
+  public:
+    using Lookup = typename Set::Lookup;
+    using Ptr = typename Set::Ptr;
+    using AddPtr = typename Set::AddPtr;
+
+    struct Range
+    {
+        explicit Range(const typename Set::Range& r)
+          : range(r)
+        {
+            settle();
+        }
+        Range() {}
+
+        bool empty() const { return range.empty(); }
+        const Entry& front() const { return range.front(); }
+
+        void popFront() {
+            range.popFront();
+            settle();
+        }
+
+      private:
+        typename Set::Range range;
+
+        void settle() {
+            while (!empty() && entryNeedsSweep(front()))
+                popFront();
+        }
+    };
+
+    struct Enum : public Set::Enum
+    {
+        explicit Enum(Self& cache)
+          : Set::Enum(cache.set)
+        {
+            // This operation is not allowed while barriers are in place as we
+            // may also need to enumerate the set for sweeping.
+            MOZ_ASSERT(!cache.needsBarrier);
+        }
+    };
+
+    bool initialized() const {
+        return set.initialized();
+    }
+
+    Ptr lookup(const Lookup& l) const {
+        Ptr ptr = set.lookup(l);
+        if (needsBarrier && ptr && entryNeedsSweep(*ptr)) {
+            const_cast<Set&>(set).remove(ptr);
+            return Ptr();
+        }
+        return ptr;
+    }
+
+    AddPtr lookupForAdd(const Lookup& l) const {
+        AddPtr ptr = set.lookupForAdd(l);
+        if (needsBarrier && ptr && entryNeedsSweep(*ptr)) {
+            const_cast<Set&>(set).remove(ptr);
+            return set.lookupForAdd(l);
+        }
+        return ptr;
+    }
+
+    Range all() const {
+        return Range(set.all());
+    }
+
+    bool empty() const {
+        // This operation is not currently allowed while barriers are in place
+        // as it would require iterating the set and the caller expects a
+        // constant time operation.
+        MOZ_ASSERT(!needsBarrier);
+        return set.empty();
+    }
+
+    uint32_t count() const {
+        // This operation is not currently allowed while barriers are in place
+        // as it would require iterating the set and the caller expects a
+        // constant time operation.
+        MOZ_ASSERT(!needsBarrier);
+        return set.count();
+    }
+
+    size_t capacity() const {
+        return set.capacity();
+    }
+
+    bool has(const Lookup& l) const {
+        return lookup(l).found();
+    }
+
+    size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+        return set.sizeOfExcludingThis(mallocSizeOf);
+    }
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+        return mallocSizeOf(this) + set.sizeOfExcludingThis(mallocSizeOf);
+    }
+
+    bool init(uint32_t len = 16) {
+        MOZ_ASSERT(!needsBarrier);
+        return set.init(len);
+    }
+
+    void clear() {
+        // This operation is not currently allowed while barriers are in place
+        // since it doesn't make sense to clear a cache while it is being swept.
+        MOZ_ASSERT(!needsBarrier);
+        set.clear();
+    }
+
+    void finish() {
+        // This operation is not currently allowed while barriers are in place
+        // since it doesn't make sense to destroy a cache while it is being swept.
+        MOZ_ASSERT(!needsBarrier);
+        set.finish();
+    }
+
+    void remove(Ptr p) {
+        // This currently supports removing entries during incremental
+        // sweeping. If we allow these tables to be swept incrementally this may
+        // no longer be possible.
+        set.remove(p);
+    }
+
+    void remove(const Lookup& l) {
+        Ptr p = lookup(l);
+        if (p)
+            remove(p);
+    }
+
+    template<typename TInput>
+    bool add(AddPtr& p, TInput&& t) {
+        return set.add(p, mozilla::Forward<TInput>(t));
+    }
+
+    template<typename TInput>
+    bool relookupOrAdd(AddPtr& p, const Lookup& l, TInput&& t) {
+        return set.relookupOrAdd(p, l, mozilla::Forward<TInput>(t));
+    }
+
+    template<typename TInput>
+    bool put(TInput&& t) {
+        return set.put(mozilla::Forward<TInput>(t));
+    }
+
+    template<typename TInput>
+    bool putNew(TInput&& t) {
+        return set.putNew(mozilla::Forward<TInput>(t));
+    }
+
+    template<typename TInput>
+    bool putNew(const Lookup& l, TInput&& t) {
+        return set.putNew(l, mozilla::Forward<TInput>(t));
+    }
+};
+
+} // namespace JS
+
 #endif /* GCHashTable_h */
--- a/js/public/GCPolicyAPI.h
+++ b/js/public/GCPolicyAPI.h
@@ -135,17 +135,17 @@ template <> struct GCPolicy<JSString*> :
 
 template <typename T>
 struct GCPolicy<JS::Heap<T>>
 {
     static void trace(JSTracer* trc, JS::Heap<T>* thingp, const char* name) {
         TraceEdge(trc, thingp, name);
     }
     static bool needsSweep(JS::Heap<T>* thingp) {
-        return js::gc::EdgeNeedsSweep(thingp);
+        return *thingp && js::gc::EdgeNeedsSweep(thingp);
     }
 };
 
 // GCPolicy<UniquePtr<T>> forwards the contained pointer to GCPolicy<T>.
 template <typename T, typename D>
 struct GCPolicy<mozilla::UniquePtr<T, D>>
 {
     static mozilla::UniquePtr<T,D> initial() { return mozilla::UniquePtr<T,D>(); }
--- a/js/public/SweepingAPI.h
+++ b/js/public/SweepingAPI.h
@@ -32,18 +32,27 @@ class WeakCacheBase : public mozilla::Li
         shadow::RegisterWeakCache(zone, this);
     }
     explicit WeakCacheBase(JSRuntime* rt) {
         shadow::RegisterWeakCache(rt, this);
     }
     WeakCacheBase(WeakCacheBase&& other) = default;
     virtual ~WeakCacheBase() {}
 
-    virtual void sweep() = 0;
+    virtual size_t sweep() = 0;
     virtual bool needsSweep() = 0;
+
+    virtual bool setNeedsIncrementalBarrier(bool needs) {
+        // Derived classes do not support incremental barriers by default.
+        return false;
+    }
+    virtual bool needsIncrementalBarrier() const {
+        // Derived classes do not support incremental barriers by default.
+        return false;
+    }
 };
 } // namespace detail
 
 // A WeakCache stores the given Sweepable container and links itself into a
 // list of such caches that are swept during each GC. A WeakCache can be
 // specific to a zone, or across a whole runtime, depending on which
 // constructor is used.
 template <typename T>
@@ -62,18 +71,19 @@ class WeakCache : protected detail::Weak
     template <typename... Args>
     explicit WeakCache(JSRuntime* rt, Args&&... args)
       : WeakCacheBase(rt), cache(mozilla::Forward<Args>(args)...)
     {}
 
     const T& get() const { return cache; }
     T& get() { return cache; }
 
-    void sweep() override {
+    size_t sweep() override {
         GCPolicy<T>::sweep(&cache);
+        return 0;
     }
 
     bool needsSweep() override {
         return cache.needsSweep();
     }
 };
 
 } // namespace JS
--- a/js/src/builtin/DataViewObject.cpp
+++ b/js/src/builtin/DataViewObject.cpp
@@ -207,18 +207,17 @@ DataViewObject::constructSameCompartment
     MOZ_ASSERT(args.isConstructing());
     assertSameCompartment(cx, bufobj);
 
     uint32_t byteOffset, byteLength;
     if (!getAndCheckConstructorArgs(cx, bufobj, args, &byteOffset, &byteLength))
         return false;
 
     RootedObject proto(cx);
-    RootedObject newTarget(cx, &args.newTarget().toObject());
-    if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
+    if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
         return false;
 
     Rooted<ArrayBufferObjectMaybeShared*> buffer(cx, &AsArrayBufferMaybeShared(bufobj));
     JSObject* obj = DataViewObject::create(cx, byteOffset, byteLength, buffer, proto);
     if (!obj)
         return false;
     args.rval().setObject(*obj);
     return true;
@@ -252,18 +251,17 @@ DataViewObject::constructWrapped(JSConte
     // NB: This entails the IsArrayBuffer check
     uint32_t byteOffset, byteLength;
     if (!getAndCheckConstructorArgs(cx, unwrapped, args, &byteOffset, &byteLength))
         return false;
 
     // Make sure to get the [[Prototype]] for the created view from this
     // compartment.
     RootedObject proto(cx);
-    RootedObject newTarget(cx, &args.newTarget().toObject());
-    if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
+    if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
         return false;
 
     Rooted<GlobalObject*> global(cx, cx->compartment()->maybeGlobal());
     if (!proto) {
         proto = GlobalObject::getOrCreateDataViewPrototype(cx, global);
         if (!proto)
             return false;
     }
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -984,17 +984,17 @@ static const JSPropertySpec collator_pro
  */
 static bool
 Collator(JSContext* cx, const CallArgs& args)
 {
     // Step 1 (Handled by OrdinaryCreateFromConstructor fallback code).
 
     // Steps 2-5 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
     RootedObject proto(cx);
-    if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto))
+    if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
         return false;
 
     if (!proto) {
         proto = GlobalObject::getOrCreateCollatorPrototype(cx, cx->global());
         if (!proto)
             return false;
     }
 
@@ -1552,17 +1552,17 @@ static const JSPropertySpec numberFormat
  */
 static bool
 NumberFormat(JSContext* cx, const CallArgs& args, bool construct)
 {
     // Step 1 (Handled by OrdinaryCreateFromConstructor fallback code).
 
     // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
     RootedObject proto(cx);
-    if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto))
+    if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
         return false;
 
     if (!proto) {
         proto = GlobalObject::getOrCreateNumberFormatPrototype(cx, cx->global());
         if (!proto)
             return false;
     }
 
@@ -2427,17 +2427,17 @@ static const JSPropertySpec dateTimeForm
  */
 static bool
 DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct, DateTimeFormatOptions dtfOptions)
 {
     // Step 1 (Handled by OrdinaryCreateFromConstructor fallback code).
 
     // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
     RootedObject proto(cx);
-    if (args.isConstructing() && !GetPrototypeFromCallableConstructor(cx, args, &proto))
+    if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
         return false;
 
     if (!proto) {
         proto = GlobalObject::getOrCreateDateTimeFormatPrototype(cx, cx->global());
         if (!proto)
             return false;
     }
 
@@ -3535,17 +3535,17 @@ PluralRules(JSContext* cx, unsigned argc
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1.
     if (!ThrowIfNotConstructing(cx, args, "Intl.PluralRules"))
         return false;
 
     // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
     RootedObject proto(cx);
-    if (!GetPrototypeFromCallableConstructor(cx, args, &proto))
+    if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
         return false;
 
     if (!proto) {
         proto = GlobalObject::getOrCreatePluralRulesPrototype(cx, cx->global());
         if (!proto)
             return false;
     }
 
--- a/js/src/builtin/MapObject.cpp
+++ b/js/src/builtin/MapObject.cpp
@@ -559,18 +559,17 @@ bool
 MapObject::construct(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (!ThrowIfNotConstructing(cx, args, "Map"))
         return false;
 
     RootedObject proto(cx);
-    RootedObject newTarget(cx, &args.newTarget().toObject());
-    if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
+    if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
         return false;
 
     Rooted<MapObject*> obj(cx, MapObject::create(cx, proto));
     if (!obj)
         return false;
 
     if (!args.get(0).isNullOrUndefined()) {
         FixedInvokeArgs<1> args2(cx);
@@ -1163,18 +1162,17 @@ bool
 SetObject::construct(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (!ThrowIfNotConstructing(cx, args, "Set"))
         return false;
 
     RootedObject proto(cx);
-    RootedObject newTarget(cx, &args.newTarget().toObject());
-    if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
+    if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
         return false;
 
     Rooted<SetObject*> obj(cx, SetObject::create(cx, proto));
     if (!obj)
         return false;
 
     if (!args.get(0).isNullOrUndefined()) {
         RootedValue iterable(cx, args[0]);
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -1286,17 +1286,16 @@ PromiseConstructor(JSContext* cx, unsign
     // Step 2.
     RootedValue executorVal(cx, args.get(0));
     if (!IsCallable(executorVal))
         return ReportIsNotFunction(cx, executorVal);
     RootedObject executor(cx, &executorVal.toObject());
 
     // Steps 3-10.
     RootedObject newTarget(cx, &args.newTarget().toObject());
-    RootedObject originalNewTarget(cx, newTarget);
     bool needsWrapping = false;
 
     // If the constructor is called via an Xray wrapper, then the newTarget
     // hasn't been unwrapped. We want that because, while the actual instance
     // should be created in the target compartment, the constructor's code
     // should run in the wrapper's compartment.
     //
     // This is so that the resolve and reject callbacks get created in the
@@ -1317,38 +1316,45 @@ PromiseConstructor(JSContext* cx, unsign
     // Promise from privileged code; as a return value of a JS-implemented
     // API, say. If the resolution functions were unprivileged, then resolving
     // with a privileged Promise would cause `resolve` to attempt accessing
     // .then on the passed Promise, which would throw an exception, so we'd
     // just end up with a rejected Promise. Really, we want to chain the two
     // Promises, with the unprivileged one resolved with the resolution of the
     // privileged one.
     if (IsWrapper(newTarget)) {
-        newTarget = CheckedUnwrap(newTarget);
-        MOZ_ASSERT(newTarget);
-        MOZ_ASSERT(newTarget != originalNewTarget);
+        JSObject* unwrappedNewTarget = CheckedUnwrap(newTarget);
+        MOZ_ASSERT(unwrappedNewTarget);
+        MOZ_ASSERT(unwrappedNewTarget != newTarget);
+
+        newTarget = unwrappedNewTarget;
         {
             AutoCompartment ac(cx, newTarget);
             RootedObject promiseCtor(cx);
             if (!GetBuiltinConstructor(cx, JSProto_Promise, &promiseCtor))
                 return false;
 
             // Promise subclasses don't get the special Xray treatment, so
             // we only need to do the complex wrapping and unwrapping scheme
             // described above for instances of Promise itself.
             if (newTarget == promiseCtor)
                 needsWrapping = true;
         }
     }
 
     RootedObject proto(cx);
-    if (!GetPrototypeFromConstructor(cx, needsWrapping ? newTarget : originalNewTarget, &proto))
-        return false;
-    if (needsWrapping && !cx->compartment()->wrap(cx, &proto))
-        return false;
+    if (needsWrapping) {
+        if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
+            return false;
+        if (!cx->compartment()->wrap(cx, &proto))
+            return false;
+    } else {
+        if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
+            return false;
+    }
     Rooted<PromiseObject*> promise(cx, PromiseObject::create(cx, executor, proto, needsWrapping));
     if (!promise)
         return false;
 
     // Step 11.
     args.rval().setObject(*promise);
     if (needsWrapping)
         return cx->compartment()->wrap(cx, args.rval());
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -394,17 +394,17 @@ js::regexp_construct(JSContext* cx, unsi
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Steps 1.
     bool patternIsRegExp;
     if (!IsRegExp(cx, args.get(0), &patternIsRegExp))
         return false;
 
     // We can delay step 3 and step 4a until later, during
-    // GetPrototypeFromCallableConstructor calls. Accessing the new.target
+    // GetPrototypeFromBuiltinConstructor calls. Accessing the new.target
     // and the callee from the stack is unobservable.
     if (!args.isConstructing()) {
         // Step 3.b.
         if (patternIsRegExp && !args.hasDefined(1)) {
             RootedObject patternObj(cx, &args[0].toObject());
 
             // Step 3.b.i.
             RootedValue patternConstructor(cx);
@@ -442,17 +442,17 @@ js::regexp_construct(JSContext* cx, unsi
 
             // Step 4.b.
             // Get original flags in all cases, to compare with passed flags.
             flags = g->getFlags();
         }
 
         // Step 7.
         RootedObject proto(cx);
-        if (!GetPrototypeFromCallableConstructor(cx, args, &proto))
+        if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
             return false;
 
         Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx, GenericObject, proto));
         if (!regexp)
             return false;
 
         // Step 8.
         if (args.hasDefined(1)) {
@@ -501,17 +501,17 @@ js::regexp_construct(JSContext* cx, unsi
     } else {
         // Steps 6.a-b.
         P = patternValue;
         F = args.get(1);
     }
 
     // Step 7.
     RootedObject proto(cx);
-    if (!GetPrototypeFromCallableConstructor(cx, args, &proto))
+    if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
         return false;
 
     Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx, GenericObject, proto));
     if (!regexp)
         return false;
 
     // Step 8.
     if (!RegExpInitializeIgnoringLastIndex(cx, regexp, P, F))
--- a/js/src/builtin/WeakMapObject.cpp
+++ b/js/src/builtin/WeakMapObject.cpp
@@ -286,18 +286,21 @@ static bool
 WeakMap_construct(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // ES6 draft rev 31 (15 Jan 2015) 23.3.1.1 step 1.
     if (!ThrowIfNotConstructing(cx, args, "WeakMap"))
         return false;
 
-    RootedObject newTarget(cx, &args.newTarget().toObject());
-    RootedObject obj(cx, CreateThis(cx, &WeakMapObject::class_, newTarget));
+    RootedObject proto(cx);
+    if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
+        return false;
+
+    RootedObject obj(cx, NewObjectWithClassProto<WeakMapObject>(cx, proto));
     if (!obj)
         return false;
 
     // Steps 5-6, 11.
     if (!args.get(0).isNullOrUndefined()) {
         FixedInvokeArgs<1> args2(cx);
         args2[0].set(args[0]);
 
--- a/js/src/builtin/WeakSetObject.cpp
+++ b/js/src/builtin/WeakSetObject.cpp
@@ -88,18 +88,17 @@ WeakSetObject::construct(JSContext* cx, 
 {
     // Based on our "Set" implementation instead of the more general ES6 steps.
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (!ThrowIfNotConstructing(cx, args, "WeakSet"))
         return false;
 
     RootedObject proto(cx);
-    RootedObject newTarget(cx, &args.newTarget().toObject());
-    if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
+    if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
         return false;
 
     Rooted<WeakSetObject*> obj(cx, WeakSetObject::create(cx, proto));
     if (!obj)
         return false;
 
     if (!args.get(0).isNullOrUndefined()) {
         RootedValue iterable(cx, args[0]);
--- a/js/src/gc/Barrier.cpp
+++ b/js/src/gc/Barrier.cpp
@@ -137,32 +137,33 @@ MovableCellHasher<T>::hasHash(const Look
 template <typename T>
 /* static */ bool
 MovableCellHasher<T>::ensureHash(const Lookup& l)
 {
     if (!l)
         return true;
 
     uint64_t unusedId;
-    return l->zoneFromAnyThread()->getUniqueId(l, &unusedId);
+    return l->zoneFromAnyThread()->getOrCreateUniqueId(l, &unusedId);
 }
 
 template <typename T>
 /* static */ HashNumber
 MovableCellHasher<T>::hash(const Lookup& l)
 {
     if (!l)
         return 0;
 
     // We have to access the zone from-any-thread here: a worker thread may be
     // cloning a self-hosted object from the main runtime's self- hosting zone
     // into another runtime. The zone's uid lock will protect against multiple
     // workers doing this simultaneously.
     MOZ_ASSERT(CurrentThreadCanAccessZone(l->zoneFromAnyThread()) ||
-               l->zoneFromAnyThread()->isSelfHostingZone());
+               l->zoneFromAnyThread()->isSelfHostingZone() ||
+               CurrentThreadIsPerformingGC());
 
     return l->zoneFromAnyThread()->getHashCodeInfallible(l);
 }
 
 template <typename T>
 /* static */ bool
 MovableCellHasher<T>::match(const Key& k, const Lookup& l)
 {
@@ -175,21 +176,35 @@ MovableCellHasher<T>::match(const Key& k
     MOZ_ASSERT(k);
     MOZ_ASSERT(l);
     MOZ_ASSERT(CurrentThreadCanAccessZone(l->zoneFromAnyThread()) ||
                l->zoneFromAnyThread()->isSelfHostingZone());
 
     Zone* zone = k->zoneFromAnyThread();
     if (zone != l->zoneFromAnyThread())
         return false;
-    MOZ_ASSERT(zone->hasUniqueId(k));
-    MOZ_ASSERT(zone->hasUniqueId(l));
 
-    // Since both already have a uid (from hash), the get is infallible.
-    return zone->getUniqueIdInfallible(k) == zone->getUniqueIdInfallible(l);
+#ifdef DEBUG
+    // Incremental table sweeping means that existing table entries may no
+    // longer have unique IDs. We fail the match in that case and the entry is
+    // removed from the table later on.
+    if (!zone->hasUniqueId(k)) {
+        Key key = k;
+        MOZ_ASSERT(IsAboutToBeFinalizedUnbarriered(&key));
+    }
+    MOZ_ASSERT(zone->hasUniqueId(l));
+#endif
+
+    uint64_t keyId;
+    if (!zone->maybeGetUniqueId(k, &keyId)) {
+        // Key is dead and cannot match lookup which must be live.
+        return false;
+    }
+
+    return keyId == zone->getUniqueIdInfallible(l);
 }
 
 #ifdef JS_BROKEN_GCC_ATTRIBUTE_WARNING
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wattributes"
 #endif // JS_BROKEN_GCC_ATTRIBUTE_WARNING
 
 template struct JS_PUBLIC_API(MovableCellHasher<JSObject*>);
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -33,16 +33,17 @@ namespace gc {
 typedef Vector<ZoneGroup*, 4, SystemAllocPolicy> ZoneGroupVector;
 using BlackGrayEdgeVector = Vector<TenuredCell*, 0, SystemAllocPolicy>;
 
 class AutoMaybeStartBackgroundAllocation;
 class AutoRunParallelTask;
 class AutoTraceSession;
 class MarkingValidator;
 struct MovingTracer;
+class WeakCacheSweepIterator;
 
 enum IncrementalProgress
 {
     NotFinished = 0,
     Finished
 };
 
 enum SweepActionList
@@ -918,16 +919,24 @@ class GCRuntime
     static JSObject* tryNewTenuredObject(JSContext* cx, AllocKind kind, size_t thingSize,
                                          size_t nDynamicSlots);
     template <typename T, AllowGC allowGC>
     static T* tryNewTenuredThing(JSContext* cx, AllocKind kind, size_t thingSize);
     static TenuredCell* refillFreeListInGC(Zone* zone, AllocKind thingKind);
 
     void bufferGrayRoots();
 
+    /*
+     * Concurrent sweep infrastructure.
+     */
+    void startTask(GCParallelTask& task, gcstats::PhaseKind phase, AutoLockHelperThreadState& locked);
+    void joinTask(GCParallelTask& task, gcstats::PhaseKind phase, AutoLockHelperThreadState& locked);
+
+  private:
+
   private:
     enum IncrementalResult
     {
         Reset = 0,
         Ok
     };
 
     // For ArenaLists::allocateFromArena()
@@ -1020,16 +1029,18 @@ class GCRuntime
                                             AutoLockForExclusiveAccess& lock);
     static IncrementalProgress sweepTypeInformation(GCRuntime* gc, FreeOp* fop, Zone* zone,
                                                     SliceBudget& budget, AllocKind kind);
     static IncrementalProgress mergeSweptObjectArenas(GCRuntime* gc, FreeOp* fop, Zone* zone,
                                                       SliceBudget& budget, AllocKind kind);
     static IncrementalProgress sweepAtomsTable(GCRuntime* gc, SliceBudget& budget);
     void startSweepingAtomsTable();
     IncrementalProgress sweepAtomsTable(SliceBudget& budget);
+    static IncrementalProgress sweepWeakCaches(GCRuntime* gc, SliceBudget& budget);
+    IncrementalProgress sweepWeakCaches(SliceBudget& budget);
     static IncrementalProgress finalizeAllocKind(GCRuntime* gc, FreeOp* fop, Zone* zone,
                                                  SliceBudget& budget, AllocKind kind);
     static IncrementalProgress sweepShapeTree(GCRuntime* gc, FreeOp* fop, Zone* zone,
                                               SliceBudget& budget, AllocKind kind);
     void endSweepPhase(bool lastGC, AutoLockForExclusiveAccess& lock);
     bool allCCVisibleZonesWereCollected() const;
     void sweepZones(FreeOp* fop, ZoneGroup* group, bool lastGC);
     void sweepZoneGroups(FreeOp* fop, bool destroyingRuntime);
@@ -1242,27 +1253,23 @@ class GCRuntime
     /*
      * Incremental sweep state.
      */
 
     ActiveThreadData<JS::Zone*> sweepGroups;
     ActiveThreadOrGCTaskData<JS::Zone*> currentSweepGroup;
     ActiveThreadData<SweepActionList> sweepActionList;
     ActiveThreadData<size_t> sweepPhaseIndex;
-    ActiveThreadData<JS::Zone*> sweepZone;
+    ActiveThreadOrGCTaskData<JS::Zone*> sweepZone;
     ActiveThreadData<size_t> sweepActionIndex;
     ActiveThreadData<mozilla::Maybe<AtomSet::Enum>> maybeAtomsToSweep;
+    ActiveThreadOrGCTaskData<JS::detail::WeakCacheBase*> sweepCache;
     ActiveThreadData<bool> abortSweepAfterCurrentGroup;
 
-    /*
-     * Concurrent sweep infrastructure.
-     */
-    void startTask(GCParallelTask& task, gcstats::PhaseKind phase, AutoLockHelperThreadState& locked);
-    void joinTask(GCParallelTask& task, gcstats::PhaseKind phase, AutoLockHelperThreadState& locked);
-    friend class AutoRunParallelTask;
+    friend class WeakCacheSweepIterator;
 
     /*
      * List head of arenas allocated during the sweep phase.
      */
     ActiveThreadData<Arena*> arenasAllocatedDuringSweep;
 
     /*
      * Incremental compacting state.
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -344,17 +344,17 @@ struct Zone : public JS::shadow::Zone,
     using WeakEdges = js::Vector<js::gc::TenuredCell**, 0, js::SystemAllocPolicy>;
   private:
     js::ZoneGroupOrGCTaskData<WeakEdges> gcWeakRefs_;
   public:
     WeakEdges& gcWeakRefs() { return gcWeakRefs_.ref(); }
 
   private:
     // List of non-ephemeron weak containers to sweep during beginSweepingSweepGroup.
-    js::ZoneGroupData<mozilla::LinkedList<detail::WeakCacheBase>> weakCaches_;
+    js::ZoneGroupOrGCTaskData<mozilla::LinkedList<detail::WeakCacheBase>> weakCaches_;
   public:
     mozilla::LinkedList<detail::WeakCacheBase>& weakCaches() { return weakCaches_.ref(); }
     void registerWeakCache(detail::WeakCacheBase* cachep) {
         weakCaches().insertBack(cachep);
     }
 
   private:
     /*
@@ -502,35 +502,50 @@ struct Zone : public JS::shadow::Zone,
 
     static js::HashNumber UniqueIdToHash(uint64_t uid) {
         return js::HashNumber(uid >> 32) ^ js::HashNumber(uid & 0xFFFFFFFF);
     }
 
     // Creates a HashNumber based on getUniqueId. Returns false on OOM.
     MOZ_MUST_USE bool getHashCode(js::gc::Cell* cell, js::HashNumber* hashp) {
         uint64_t uid;
-        if (!getUniqueId(cell, &uid))
+        if (!getOrCreateUniqueId(cell, &uid))
             return false;
         *hashp = UniqueIdToHash(uid);
         return true;
     }
 
+    // Gets an existing UID in |uidp| if one exists.
+    MOZ_MUST_USE bool maybeGetUniqueId(js::gc::Cell* cell, uint64_t* uidp) {
+        MOZ_ASSERT(uidp);
+        MOZ_ASSERT(js::CurrentThreadCanAccessZone(this));
+
+        // Get an existing uid, if one has been set.
+        auto p = uniqueIds().lookup(cell);
+        if (p)
+            *uidp = p->value();
+
+        return p.found();
+    }
+
     // Puts an existing UID in |uidp|, or creates a new UID for this Cell and
     // puts that into |uidp|. Returns false on OOM.
-    MOZ_MUST_USE bool getUniqueId(js::gc::Cell* cell, uint64_t* uidp) {
+    MOZ_MUST_USE bool getOrCreateUniqueId(js::gc::Cell* cell, uint64_t* uidp) {
         MOZ_ASSERT(uidp);
-        MOZ_ASSERT(js::CurrentThreadCanAccessZone(this));
+        MOZ_ASSERT(js::CurrentThreadCanAccessZone(this) || js::CurrentThreadIsPerformingGC());
 
         // Get an existing uid, if one has been set.
         auto p = uniqueIds().lookupForAdd(cell);
         if (p) {
             *uidp = p->value();
             return true;
         }
 
+        MOZ_ASSERT(js::CurrentThreadCanAccessZone(this));
+
         // Set a new uid on the cell.
         *uidp = js::gc::NextCellUniqueId(runtimeFromAnyThread());
         if (!uniqueIds().add(p, cell, *uidp))
             return false;
 
         // If the cell was in the nursery, hopefully unlikely, then we need to
         // tell the nursery about it so that it can sweep the uid if the thing
         // does not get tenured.
@@ -544,24 +559,24 @@ struct Zone : public JS::shadow::Zone,
 
     js::HashNumber getHashCodeInfallible(js::gc::Cell* cell) {
         return UniqueIdToHash(getUniqueIdInfallible(cell));
     }
 
     uint64_t getUniqueIdInfallible(js::gc::Cell* cell) {
         uint64_t uid;
         js::AutoEnterOOMUnsafeRegion oomUnsafe;
-        if (!getUniqueId(cell, &uid))
+        if (!getOrCreateUniqueId(cell, &uid))
             oomUnsafe.crash("failed to allocate uid");
         return uid;
     }
 
     // Return true if this cell has a UID associated with it.
     MOZ_MUST_USE bool hasUniqueId(js::gc::Cell* cell) {
-        MOZ_ASSERT(js::CurrentThreadCanAccessZone(this));
+        MOZ_ASSERT(js::CurrentThreadCanAccessZone(this) || js::CurrentThreadIsPerformingGC());
         return uniqueIds().has(cell);
     }
 
     // Transfer an id from another cell. This must only be called on behalf of a
     // moving GC. This method is infallible.
     void transferUniqueId(js::gc::Cell* tgt, js::gc::Cell* src) {
         MOZ_ASSERT(src != tgt);
         MOZ_ASSERT(!IsInsideNursery(tgt));
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1370905.js
@@ -0,0 +1,20 @@
+// |jit-test| allow-oom
+
+if (!('oomTest' in this))
+    quit();
+
+var source = `
+    var global = newGlobal();
+    global.eval('function f() { debugger; }');
+    var debug = new Debugger(global);
+    var foo;
+    debug.onDebuggerStatement = function(frame) {
+        foo = frame.arguments[0];
+        return null;
+    };
+    global.eval('f(0)');
+`;
+function test() {
+    oomTest(new Function(source), false);
+}
+test();
--- a/js/src/jsapi-tests/testGCUniqueId.cpp
+++ b/js/src/jsapi-tests/testGCUniqueId.cpp
@@ -37,33 +37,33 @@ BEGIN_TEST(testGCUID)
     uintptr_t nurseryAddr = uintptr_t(obj.get());
     CHECK(obj);
     CHECK(js::gc::IsInsideNursery(obj));
 
     // Do not start with an ID.
     CHECK(!obj->zone()->hasUniqueId(obj));
 
     // Ensure we can get a new UID.
-    CHECK(obj->zone()->getUniqueId(obj, &uid));
+    CHECK(obj->zone()->getOrCreateUniqueId(obj, &uid));
     CHECK(uid > js::gc::LargestTaggedNullCellPointer);
 
     // We should now have an id.
     CHECK(obj->zone()->hasUniqueId(obj));
 
     // Calling again should get us the same thing.
-    CHECK(obj->zone()->getUniqueId(obj, &tmp));
+    CHECK(obj->zone()->getOrCreateUniqueId(obj, &tmp));
     CHECK(uid == tmp);
 
     // Tenure the thing and check that the UID moved with it.
     MinimizeHeap(cx);
     uintptr_t tenuredAddr = uintptr_t(obj.get());
     CHECK(tenuredAddr != nurseryAddr);
     CHECK(!js::gc::IsInsideNursery(obj));
     CHECK(obj->zone()->hasUniqueId(obj));
-    CHECK(obj->zone()->getUniqueId(obj, &tmp));
+    CHECK(obj->zone()->getOrCreateUniqueId(obj, &tmp));
     CHECK(uid == tmp);
 
     // Allocate a new nursery thing in the same location and check that we
     // removed the prior uid that was attached to the location.
     obj = JS_NewPlainObject(cx);
     CHECK(obj);
     CHECK(uintptr_t(obj.get()) == nurseryAddr);
     CHECK(!obj->zone()->hasUniqueId(obj));
@@ -71,17 +71,17 @@ BEGIN_TEST(testGCUID)
     // Try to get another tenured object in the same location and check that
     // the uid was removed correctly.
     obj = nullptr;
     MinimizeHeap(cx);
     obj = JS_NewPlainObject(cx);
     MinimizeHeap(cx);
     CHECK(uintptr_t(obj.get()) == tenuredAddr);
     CHECK(!obj->zone()->hasUniqueId(obj));
-    CHECK(obj->zone()->getUniqueId(obj, &tmp));
+    CHECK(obj->zone()->getOrCreateUniqueId(obj, &tmp));
     CHECK(uid != tmp);
     uid = tmp;
 
     // Allocate a few arenas worth of objects to ensure we get some compaction.
     const static size_t N = 2049;
     using ObjectVector = JS::GCVector<JSObject*>;
     JS::Rooted<ObjectVector> vec(cx, ObjectVector(cx));
     for (size_t i = 0; i < N; ++i) {
@@ -101,23 +101,23 @@ BEGIN_TEST(testGCUID)
     }
     vec.clear();
     MinimizeHeap(cx);
 
     // Grab the last object in the vector as our object of interest.
     obj = vec2.back();
     CHECK(obj);
     tenuredAddr = uintptr_t(obj.get());
-    CHECK(obj->zone()->getUniqueId(obj, &uid));
+    CHECK(obj->zone()->getOrCreateUniqueId(obj, &uid));
 
     // Force a compaction to move the object and check that the uid moved to
     // the new tenured heap location.
     JS::PrepareForFullGC(cx);
     JS::GCForReason(cx, GC_SHRINK, JS::gcreason::API);
     MinimizeHeap(cx);
     CHECK(uintptr_t(obj.get()) != tenuredAddr);
     CHECK(obj->zone()->hasUniqueId(obj));
-    CHECK(obj->zone()->getUniqueId(obj, &tmp));
+    CHECK(obj->zone()->getOrCreateUniqueId(obj, &tmp));
     CHECK(uid == tmp);
 
     return true;
 }
 END_TEST(testGCUID)
--- a/js/src/jsapi-tests/testGCWeakCache.cpp
+++ b/js/src/jsapi-tests/testGCWeakCache.cpp
@@ -1,39 +1,42 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
-* vim: set ts=8 sts=4 et sw=4 tw=99:
-*/
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gc/Policy.h"
+#include "gc/Zone.h"
 #include "js/GCHashTable.h"
 #include "js/RootingAPI.h"
 #include "js/SweepingAPI.h"
 
 #include "jsapi-tests/tests.h"
 
+using namespace js;
+
 // Exercise WeakCache<GCHashSet>.
 BEGIN_TEST(testWeakCacheSet)
 {
     // Create two objects tenured and two in the nursery. If zeal is on,
     // this may fail and we'll get more tenured objects. That's fine:
     // the test will continue to work, it will just not test as much.
     JS::RootedObject tenured1(cx, JS_NewPlainObject(cx));
     JS::RootedObject tenured2(cx, JS_NewPlainObject(cx));
     JS_GC(cx);
     JS::RootedObject nursery1(cx, JS_NewPlainObject(cx));
     JS::RootedObject nursery2(cx, JS_NewPlainObject(cx));
 
-    using ObjectSet = js::GCHashSet<JS::Heap<JSObject*>,
-                                    js::MovableCellHasher<JS::Heap<JSObject*>>,
-                                    js::SystemAllocPolicy>;
+    using ObjectSet = GCHashSet<JS::Heap<JSObject*>,
+                                MovableCellHasher<JS::Heap<JSObject*>>,
+                                SystemAllocPolicy>;
     using Cache = JS::WeakCache<ObjectSet>;
-    auto cache = Cache(JS::GetObjectZone(tenured1), ObjectSet());
+    Cache cache(JS::GetObjectZone(tenured1));
     CHECK(cache.init());
 
     cache.put(tenured1);
     cache.put(tenured2);
     cache.put(nursery1);
     cache.put(nursery2);
 
     // Verify relocation and that we don't sweep too aggressively.
@@ -64,17 +67,17 @@ BEGIN_TEST(testWeakCacheMap)
     JS::RootedObject tenured2(cx, JS_NewPlainObject(cx));
     JS_GC(cx);
     JS::RootedObject nursery1(cx, JS_NewPlainObject(cx));
     JS::RootedObject nursery2(cx, JS_NewPlainObject(cx));
 
     using ObjectMap = js::GCHashMap<JS::Heap<JSObject*>, uint32_t,
                                     js::MovableCellHasher<JS::Heap<JSObject*>>>;
     using Cache = JS::WeakCache<ObjectMap>;
-    auto cache = Cache(JS::GetObjectZone(tenured1), ObjectMap(cx));
+    Cache cache(JS::GetObjectZone(tenured1), cx);
     CHECK(cache.init());
 
     cache.put(tenured1, 1);
     cache.put(tenured2, 2);
     cache.put(nursery1, 3);
     cache.put(nursery2, 4);
 
     JS_GC(cx);
@@ -100,19 +103,18 @@ BEGIN_TEST(testWeakCacheGCVector)
     // this may fail and we'll get more tenured objects. That's fine:
     // the test will continue to work, it will just not test as much.
     JS::RootedObject tenured1(cx, JS_NewPlainObject(cx));
     JS::RootedObject tenured2(cx, JS_NewPlainObject(cx));
     JS_GC(cx);
     JS::RootedObject nursery1(cx, JS_NewPlainObject(cx));
     JS::RootedObject nursery2(cx, JS_NewPlainObject(cx));
 
-    using ObjectVector = js::GCVector<JS::Heap<JSObject*>>;
-    using Cache = JS::WeakCache<ObjectVector>;
-    auto cache = Cache(JS::GetObjectZone(tenured1), ObjectVector(cx));
+    using ObjectVector = JS::WeakCache<GCVector<JS::Heap<JSObject*>>>;
+    ObjectVector cache(JS::GetObjectZone(tenured1), cx);
 
     CHECK(cache.append(tenured1));
     CHECK(cache.append(tenured2));
     CHECK(cache.append(nursery1));
     CHECK(cache.append(nursery2));
 
     JS_GC(cx);
     CHECK(cache.get().length() == 4);
@@ -125,8 +127,592 @@ BEGIN_TEST(testWeakCacheGCVector)
     JS_GC(cx);
     CHECK(cache.get().length() == 2);
     CHECK(cache.get()[0] == tenured1);
     CHECK(cache.get()[1] == nursery1);
 
     return true;
 }
 END_TEST(testWeakCacheGCVector)
+
+#ifdef JS_GC_ZEAL
+
+// A simple structure that embeds an object pointer. We cripple the hash
+// implementation so that we can test hash table collisions.
+struct ObjectEntry
+{
+    JS::Heap<JSObject*> obj;
+    explicit ObjectEntry(JSObject* o) : obj(o) {}
+    bool operator==(const ObjectEntry& other) const {
+        return obj == other.obj;
+    }
+    bool needsSweep() {
+        return JS::GCPolicy<JS::Heap<JSObject*>>::needsSweep(&obj);
+    }
+};
+
+namespace js {
+template <>
+struct MovableCellHasher<ObjectEntry>
+{
+    using Key = ObjectEntry;
+    using Lookup = JSObject*;
+
+    static bool hasHash(const Lookup& l) {
+        return MovableCellHasher<JSObject*>::hasHash(l);
+    }
+    static bool ensureHash(const Lookup& l) {
+        return MovableCellHasher<JSObject*>::ensureHash(l);
+    }
+    static HashNumber hash(const Lookup& l) {
+        // Reduce hash code to single bit to generate hash collisions.
+        return MovableCellHasher<JS::Heap<JSObject*>>::hash(l) & 0x1;
+    }
+    static bool match(const Key& k, const Lookup& l) {
+        return MovableCellHasher<JS::Heap<JSObject*>>::match(k.obj, l);
+    }
+};
+} // namespace js
+
+// A structure that contains a pointer to a JSObject but is keyed based on an
+// integer. This lets us test replacing dying entries in a set.
+struct NumberAndObjectEntry
+{
+    uint32_t number;
+    JS::Heap<JSObject*> obj;
+
+    NumberAndObjectEntry(uint32_t n, JSObject* o) : number(n), obj(o) {}
+    bool operator==(const NumberAndObjectEntry& other) const {
+        return number == other.number && obj == other.obj;
+    }
+    bool needsSweep() {
+        return JS::GCPolicy<JS::Heap<JSObject*>>::needsSweep(&obj);
+    }
+};
+
+struct NumberAndObjectLookup
+{
+    uint32_t number;
+    JS::Heap<JSObject*> obj;
+
+    NumberAndObjectLookup(uint32_t n, JSObject* o) : number(n), obj(o) {}
+    MOZ_IMPLICIT NumberAndObjectLookup(const NumberAndObjectEntry& entry)
+      : number(entry.number), obj(entry.obj)
+    {}
+};
+
+namespace js {
+template <>
+struct MovableCellHasher<NumberAndObjectEntry>
+{
+    using Key = NumberAndObjectEntry;
+    using Lookup = NumberAndObjectLookup;
+
+    static bool hasHash(const Lookup& l) {
+        return MovableCellHasher<JSObject*>::hasHash(l.obj);
+    }
+    static bool ensureHash(const Lookup& l) {
+        return MovableCellHasher<JSObject*>::ensureHash(l.obj);
+    }
+    static HashNumber hash(const Lookup& l) {
+        // Reduce hash code to single bit to generate hash collisions.
+        return MovableCellHasher<JS::Heap<JSObject*>>::hash(l.obj) ^ l.number;
+    }
+    static bool match(const Key& k, const Lookup& l) {
+        return k.number == l.number && MovableCellHasher<JS::Heap<JSObject*>>::match(k.obj, l.obj);
+    }
+};
+} // namespace js
+
+BEGIN_TEST(testIncrementalWeakCacheSweeping)
+{
+    AutoLeaveZeal nozeal(cx);
+
+    JS_SetGCParameter(cx, JSGC_MODE, JSGC_MODE_INCREMENTAL);
+    JS_SetGCZeal(cx, 17, 1000000);
+
+    CHECK(TestSet());
+    CHECK(TestMap());
+    CHECK(TestReplaceDyingInSet());
+    CHECK(TestReplaceDyingInMap());
+    CHECK(TestUniqueIDLookups());
+
+    JS_SetGCZeal(cx, 0, 0);
+    JS_SetGCParameter(cx, JSGC_MODE, JSGC_MODE_GLOBAL);
+
+    return true;
+}
+
+template <typename Cache>
+bool
+GCUntilCacheSweep(JSContext *cx, const Cache& cache)
+{
+    CHECK(!IsIncrementalGCInProgress(cx));
+
+    JS::Zone* zone = JS::GetObjectZone(global);
+    JS::PrepareZoneForGC(zone);
+    SliceBudget budget(WorkBudget(1));
+    cx->runtime()->gc.startDebugGC(GC_NORMAL, budget);
+
+    CHECK(IsIncrementalGCInProgress(cx));
+    CHECK(zone->isGCSweeping());
+    CHECK(cache.needsIncrementalBarrier());
+
+    return true;
+}
+
+template <typename Cache>
+bool
+SweepCacheAndFinishGC(JSContext* cx, const Cache& cache)
+{
+    CHECK(IsIncrementalGCInProgress(cx));
+
+    PrepareForIncrementalGC(cx);
+    IncrementalGCSlice(cx, JS::gcreason::API);
+
+    JS::Zone* zone = JS::GetObjectZone(global);
+    CHECK(!IsIncrementalGCInProgress(cx));
+    CHECK(!zone->isCollecting());
+    CHECK(!cache.needsIncrementalBarrier());
+
+    return true;
+}
+
+bool
+TestSet()
+{
+    using ObjectSet = GCHashSet<JS::Heap<JSObject*>,
+                                MovableCellHasher<JS::Heap<JSObject*>>,
+                                TempAllocPolicy>;
+    using Cache = JS::WeakCache<ObjectSet>;
+    Cache cache(JS::GetObjectZone(global), cx);
+    CHECK(cache.init());
+    CHECK(cache.initialized());
+
+    // Sweep empty cache.
+
+    CHECK(cache.empty());
+    JS_GC(cx);
+    CHECK(cache.empty());
+
+    // Add an entry while sweeping.
+
+    JS::RootedObject obj1(cx, JS_NewPlainObject(cx));
+    JS::RootedObject obj2(cx, JS_NewPlainObject(cx));
+    JS::RootedObject obj3(cx, JS_NewPlainObject(cx));
+    JS::RootedObject obj4(cx, JS_NewPlainObject(cx));
+    CHECK(obj1);
+    CHECK(obj2);
+    CHECK(obj3);
+    CHECK(obj4);
+
+    CHECK(!cache.has(obj1));
+    CHECK(cache.put(obj1));
+    CHECK(cache.count() == 1);
+    CHECK(cache.has(obj1));
+    CHECK(*cache.lookup(obj1) == obj1);
+
+    CHECK(GCUntilCacheSweep(cx, cache));
+
+    CHECK(!cache.has(obj2));
+    CHECK(cache.put(obj2));
+    CHECK(cache.has(obj2));
+    CHECK(*cache.lookup(obj2) == obj2);
+
+    CHECK(SweepCacheAndFinishGC(cx, cache));
+
+    CHECK(cache.count() == 2);
+    CHECK(cache.has(obj1));
+    CHECK(cache.has(obj2));
+
+    // Test dying entries are not found while sweeping.
+
+    CHECK(cache.put(obj3));
+    CHECK(cache.put(obj4));
+    void* old3 = obj3;
+    void* old4 = obj4;
+    obj3 = obj4 = nullptr;
+
+    CHECK(GCUntilCacheSweep(cx, cache));
+
+    CHECK(cache.has(obj1));
+    CHECK(cache.has(obj2));
+    CHECK(!cache.has(static_cast<JSObject*>(old3)));
+    CHECK(!cache.has(static_cast<JSObject*>(old4)));
+
+    size_t count = 0;
+    for (auto r = cache.all(); !r.empty(); r.popFront()) {
+        CHECK(r.front() == obj1 || r.front() == obj2);
+        count++;
+    }
+    CHECK(count == 2);
+
+    CHECK(SweepCacheAndFinishGC(cx, cache));
+
+    CHECK(cache.count() == 2);
+
+    // Test lookupForAdd while sweeping.
+
+    obj3 = JS_NewPlainObject(cx);
+    obj4 = JS_NewPlainObject(cx);
+    CHECK(obj3);
+    CHECK(obj4);
+
+    CHECK(cache.lookupForAdd(obj1));
+    CHECK(*cache.lookupForAdd(obj1) == obj1);
+
+    auto addp = cache.lookupForAdd(obj3);
+    CHECK(!addp);
+    CHECK(cache.add(addp, obj3));
+    CHECK(cache.has(obj3));
+
+    CHECK(GCUntilCacheSweep(cx, cache));
+
+    addp = cache.lookupForAdd(obj4);
+    CHECK(!addp);
+    CHECK(cache.add(addp, obj4));
+    CHECK(cache.has(obj4));
+
+    CHECK(SweepCacheAndFinishGC(cx, cache));
+
+    CHECK(cache.count() == 4);
+    CHECK(cache.has(obj3));
+    CHECK(cache.has(obj4));
+
+    // Test remove while sweeping.
+
+    cache.remove(obj3);
+
+    CHECK(GCUntilCacheSweep(cx, cache));
+
+    cache.remove(obj4);
+
+    CHECK(SweepCacheAndFinishGC(cx, cache));
+
+    CHECK(cache.count() == 2);
+    CHECK(!cache.has(obj3));
+    CHECK(!cache.has(obj4));
+
+    // Test putNew while sweeping.
+
+    CHECK(GCUntilCacheSweep(cx, cache));
+
+    CHECK(cache.putNew(obj3));
+    CHECK(cache.putNew(obj4, obj4));
+
+    CHECK(SweepCacheAndFinishGC(cx, cache));
+
+    CHECK(cache.count() == 4);
+    CHECK(cache.has(obj3));
+    CHECK(cache.has(obj4));
+
+    cache.clear();
+    cache.finish();
+
+    return true;
+}
+
+bool
+TestMap()
+{
+    using ObjectMap = GCHashMap<JS::Heap<JSObject*>, uint32_t,
+                                MovableCellHasher<JS::Heap<JSObject*>>,
+                                TempAllocPolicy>;
+    using Cache = JS::WeakCache<ObjectMap>;
+    Cache cache(JS::GetObjectZone(global), cx);
+    CHECK(cache.init());
+    CHECK(cache.initialized());
+
+    // Sweep empty cache.
+
+    CHECK(cache.empty());
+    JS_GC(cx);
+    CHECK(cache.empty());
+
+    // Add an entry while sweeping.
+
+    JS::RootedObject obj1(cx, JS_NewPlainObject(cx));
+    JS::RootedObject obj2(cx, JS_NewPlainObject(cx));
+    JS::RootedObject obj3(cx, JS_NewPlainObject(cx));
+    JS::RootedObject obj4(cx, JS_NewPlainObject(cx));
+    CHECK(obj1);
+    CHECK(obj2);
+    CHECK(obj3);
+    CHECK(obj4);
+
+    CHECK(!cache.has(obj1));
+    CHECK(cache.put(obj1, 1));
+    CHECK(cache.count() == 1);
+    CHECK(cache.has(obj1));
+    CHECK(cache.lookup(obj1)->key() == obj1);
+
+    CHECK(GCUntilCacheSweep(cx, cache));
+    CHECK(cache.needsIncrementalBarrier());
+
+    CHECK(!cache.has(obj2));
+    CHECK(cache.put(obj2, 2));
+    CHECK(cache.has(obj2));
+    CHECK(cache.lookup(obj2)->key() == obj2);
+
+    CHECK(SweepCacheAndFinishGC(cx, cache));
+    CHECK(!cache.needsIncrementalBarrier());
+
+    CHECK(cache.count() == 2);
+    CHECK(cache.has(obj1));
+    CHECK(cache.has(obj2));
+
+    // Test iteration.
+
+    CHECK(cache.put(obj3, 3));
+    CHECK(cache.put(obj4, 4));
+    void* old3 = obj3;
+    void* old4 = obj4;
+    obj3 = obj4 = nullptr;
+
+    CHECK(GCUntilCacheSweep(cx, cache));
+
+    CHECK(cache.has(obj1));
+    CHECK(cache.has(obj2));
+    CHECK(!cache.has(static_cast<JSObject*>(old3)));
+    CHECK(!cache.has(static_cast<JSObject*>(old4)));
+
+    size_t count = 0;
+    for (auto r = cache.all(); !r.empty(); r.popFront()) {
+        CHECK(r.front().key() == obj1 || r.front().key() == obj2);
+        count++;
+    }
+    CHECK(count == 2);
+
+    CHECK(SweepCacheAndFinishGC(cx, cache));
+
+    CHECK(cache.count() == 2);
+
+    // Test lookupForAdd while sweeping.
+
+    obj3 = JS_NewPlainObject(cx);
+    obj4 = JS_NewPlainObject(cx);
+    CHECK(obj3);
+    CHECK(obj4);
+
+    CHECK(cache.lookupForAdd(obj1));
+    CHECK(cache.lookupForAdd(obj1)->key() == obj1);
+
+    auto addp = cache.lookupForAdd(obj3);
+    CHECK(!addp);
+    CHECK(cache.add(addp, obj3, 3));
+    CHECK(cache.has(obj3));
+
+    CHECK(GCUntilCacheSweep(cx, cache));
+
+    addp = cache.lookupForAdd(obj4);
+    CHECK(!addp);
+    CHECK(cache.add(addp, obj4, 4));
+    CHECK(cache.has(obj4));
+
+    CHECK(SweepCacheAndFinishGC(cx, cache));
+
+    CHECK(cache.count() == 4);
+    CHECK(cache.has(obj3));
+    CHECK(cache.has(obj4));
+
+    // Test remove while sweeping.
+
+    cache.remove(obj3);
+
+    CHECK(GCUntilCacheSweep(cx, cache));
+
+    cache.remove(obj4);
+
+    CHECK(SweepCacheAndFinishGC(cx, cache));
+
+    CHECK(cache.count() == 2);
+    CHECK(!cache.has(obj3));
+    CHECK(!cache.has(obj4));
+
+    // Test putNew while sweeping.
+
+    CHECK(GCUntilCacheSweep(cx, cache));
+
+    CHECK(cache.putNew(obj3, 3));
+    CHECK(cache.putNew(obj4, 4));
+
+    CHECK(SweepCacheAndFinishGC(cx, cache));
+
+    CHECK(cache.count() == 4);
+    CHECK(cache.has(obj3));
+    CHECK(cache.has(obj4));
+
+    cache.clear();
+    cache.finish();
+
+    return true;
+}
+
+bool
+TestReplaceDyingInSet()
+{
+    // Test replacing dying entries with ones that have the same key using the
+    // various APIs.
+
+    using Cache = JS::WeakCache<GCHashSet<NumberAndObjectEntry,
+                                          MovableCellHasher<NumberAndObjectEntry>,
+                                          TempAllocPolicy>>;
+    Cache cache(JS::GetObjectZone(global), cx);
+    CHECK(cache.init());
+
+    RootedObject value1(cx, JS_NewPlainObject(cx));
+    RootedObject value2(cx, JS_NewPlainObject(cx));
+    CHECK(value1);
+    CHECK(value2);
+
+    CHECK(cache.put(NumberAndObjectEntry(1, value1)));
+    CHECK(cache.put(NumberAndObjectEntry(2, value2)));
+    CHECK(cache.put(NumberAndObjectEntry(3, value2)));
+    CHECK(cache.put(NumberAndObjectEntry(4, value2)));
+    CHECK(cache.put(NumberAndObjectEntry(5, value2)));
+    CHECK(cache.put(NumberAndObjectEntry(6, value2)));
+    CHECK(cache.put(NumberAndObjectEntry(7, value2)));
+
+    value2 = nullptr;
+    CHECK(GCUntilCacheSweep(cx, cache));
+
+    CHECK(!cache.has(NumberAndObjectLookup(2, value1)));
+    CHECK(!cache.has(NumberAndObjectLookup(3, value1)));
+    CHECK(!cache.has(NumberAndObjectLookup(4, value1)));
+    CHECK(!cache.has(NumberAndObjectLookup(5, value1)));
+    CHECK(!cache.has(NumberAndObjectLookup(6, value1)));
+
+    auto ptr = cache.lookupForAdd(NumberAndObjectLookup(2, value1));
+    CHECK(!ptr);
+    CHECK(cache.add(ptr, NumberAndObjectEntry(2, value1)));
+
+    auto ptr2 = cache.lookupForAdd(NumberAndObjectLookup(3, value1));
+    CHECK(!ptr2);
+    CHECK(cache.relookupOrAdd(ptr2,
+                              NumberAndObjectLookup(3, value1),
+                              NumberAndObjectEntry(3, value1)));
+
+    CHECK(cache.put(NumberAndObjectEntry(4, value1)));
+    CHECK(cache.putNew(NumberAndObjectEntry(5, value1)));
+
+    CHECK(cache.putNew(NumberAndObjectLookup(6, value1),
+                       NumberAndObjectEntry(6, value1)));
+
+    CHECK(SweepCacheAndFinishGC(cx, cache));
+
+    CHECK(cache.count() == 6);
+    CHECK(cache.has(NumberAndObjectLookup(1, value1)));
+    CHECK(cache.has(NumberAndObjectLookup(2, value1)));
+    CHECK(cache.has(NumberAndObjectLookup(3, value1)));
+    CHECK(cache.has(NumberAndObjectLookup(4, value1)));
+    CHECK(cache.has(NumberAndObjectLookup(5, value1)));
+    CHECK(cache.has(NumberAndObjectLookup(6, value1)));
+
+    return true;
+}
+
+bool
+TestReplaceDyingInMap()
+{
+    // Test replacing dying entries with ones that have the same key using the
+    // various APIs.
+
+    using Cache = JS::WeakCache<GCHashMap<uint32_t,
+                                          JS::Heap<JSObject*>,
+                                          DefaultHasher<uint32_t>,
+                                          TempAllocPolicy>>;
+    Cache cache(JS::GetObjectZone(global), cx);
+    CHECK(cache.init());
+
+    RootedObject value1(cx, JS_NewPlainObject(cx));
+    RootedObject value2(cx, JS_NewPlainObject(cx));
+    CHECK(value1);
+    CHECK(value2);
+
+    CHECK(cache.put(1, value1));
+    CHECK(cache.put(2, value2));
+    CHECK(cache.put(3, value2));
+    CHECK(cache.put(4, value2));
+    CHECK(cache.put(5, value2));
+    CHECK(cache.put(6, value2));
+
+    value2 = nullptr;
+    CHECK(GCUntilCacheSweep(cx, cache));
+
+    CHECK(!cache.has(2));
+    CHECK(!cache.has(3));
+    CHECK(!cache.has(4));
+    CHECK(!cache.has(5));
+    CHECK(!cache.has(6));
+
+    auto ptr = cache.lookupForAdd(2);
+    CHECK(!ptr);
+    CHECK(cache.add(ptr, 2, value1));
+
+    auto ptr2 = cache.lookupForAdd(3);
+    CHECK(!ptr2);
+    CHECK(cache.add(ptr2, 3));
+
+    auto ptr3 = cache.lookupForAdd(4);
+    CHECK(!ptr3);
+    CHECK(cache.relookupOrAdd(ptr3, 4, value1));
+
+    CHECK(cache.put(5, value1));
+    CHECK(cache.putNew(6, value1));
+
+    CHECK(SweepCacheAndFinishGC(cx, cache));
+
+    CHECK(cache.count() == 6);
+    CHECK(cache.lookup(1)->value() == value1);
+    CHECK(cache.lookup(2)->value() == value1);
+    CHECK(cache.lookup(3)->value() == nullptr);
+    CHECK(cache.lookup(4)->value() == value1);
+    CHECK(cache.lookup(5)->value() == value1);
+    CHECK(cache.lookup(6)->value() == value1);
+
+    return true;
+}
+
+bool
+TestUniqueIDLookups()
+{
+    // Test hash table lookups during incremental sweeping where the hash is
+    // generated based on a unique ID. The problem is that the unique ID table
+    // will have already been swept by this point so looking up a dead pointer
+    // in the table will fail. This lookup happens if we try to match a live key
+    // against a dead table entry with the same hash code.
+
+    const size_t DeadFactor = 3;
+    const size_t ObjectCount = 100;
+
+    using Cache = JS::WeakCache<GCHashSet<ObjectEntry,
+                                          MovableCellHasher<ObjectEntry>,
+                                          TempAllocPolicy>>;
+    Cache cache(JS::GetObjectZone(global), cx);
+    CHECK(cache.init());
+
+    Rooted<GCVector<JSObject*, 0, SystemAllocPolicy>> liveObjects(cx);
+
+    for (size_t j = 0; j < ObjectCount; j++) {
+        JSObject* obj = JS_NewPlainObject(cx);
+        CHECK(obj);
+        CHECK(cache.put(obj));
+        if (j % DeadFactor == 0)
+            CHECK(liveObjects.get().append(obj));
+    }
+
+    CHECK(cache.count() == ObjectCount);
+
+    CHECK(GCUntilCacheSweep(cx, cache));
+
+    for (size_t j = 0; j < liveObjects.length(); j++)
+        CHECK(cache.has(liveObjects[j]));
+
+    CHECK(SweepCacheAndFinishGC(cx, cache));
+
+    CHECK(cache.count() == liveObjects.length());
+
+    return true;
+}
+
+END_TEST(testIncrementalWeakCacheSweeping)
+
+#endif // defined JS_GC_ZEAL
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -3510,17 +3510,17 @@ const JSPropertySpec array_static_props[
     JS_PS_END
 };
 
 static inline bool
 ArrayConstructorImpl(JSContext* cx, CallArgs& args, bool isConstructor)
 {
     RootedObject proto(cx);
     if (isConstructor) {
-        if (!GetPrototypeFromCallableConstructor(cx, args, &proto))
+        if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
             return false;
     } else {
         // We're emulating |new Array(n)| with |std_Array(n)| in self-hosted JS,
         // and the proto should be %ArrayPrototype% regardless of the callee.
         proto = GlobalObject::getOrCreateArrayPrototype(cx, cx->global());
         if (!proto)
             return false;
     }
--- a/js/src/jsbool.cpp
+++ b/js/src/jsbool.cpp
@@ -112,20 +112,18 @@ static const JSFunctionSpec boolean_meth
 static bool
 Boolean(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     bool b = args.length() != 0 ? JS::ToBoolean(args[0]) : false;
 
     if (args.isConstructing()) {
-        RootedObject newTarget (cx, &args.newTarget().toObject());
         RootedObject proto(cx);
-
-        if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
+        if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
             return false;
 
         JSObject* obj = BooleanObject::create(cx, b, proto);
         if (!obj)
             return false;
         args.rval().setObject(*obj);
     } else {
         args.rval().setBoolean(b);
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -3090,18 +3090,17 @@ static const JSFunctionSpec date_methods
 };
 
 static bool
 NewDateObject(JSContext* cx, const CallArgs& args, ClippedTime t)
 {
     MOZ_ASSERT(args.isConstructing());
 
     RootedObject proto(cx);
-    RootedObject newTarget(cx, &args.newTarget().toObject());
-    if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
+    if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
         return false;
 
     JSObject* obj = NewDateObjectMsec(cx, t, proto);
     if (!obj)
         return false;
 
     args.rval().setObject(*obj);
     return true;
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -437,17 +437,17 @@ ExceptionStackOrNull(HandleObject objArg
 
 bool
 Error(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // ES6 19.5.1.1 mandates the .prototype lookup happens before the toString
     RootedObject proto(cx);
-    if (!GetPrototypeFromCallableConstructor(cx, args, &proto))
+    if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
         return false;
 
     /* Compute the error message, if any. */
     RootedString message(cx, nullptr);
     if (args.hasDefined(0)) {
         message = ToString<CanGC>(cx, args[0]);
         if (!message)
             return false;
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -1879,17 +1879,17 @@ FunctionConstructor(JSContext* cx, const
      * Thus 'var x = 42; f = new Function("return x"); print(f())' prints 42,
      * and so would a call to f from another top-level's script or function.
      */
     RootedAtom anonymousAtom(cx, cx->names().anonymous);
 
     // Step 24.
     RootedObject proto(cx);
     if (!isAsync) {
-        if (!GetPrototypeFromCallableConstructor(cx, args, &proto))
+        if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
             return false;
     }
 
     // Step 4.d, use %Generator% as the fallback prototype.
     // Also use %Generator% for the unwrapped function of async functions.
     if (!proto && (isStarGenerator || isAsync)) {
         proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, global);
         if (!proto)
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -190,16 +190,17 @@
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/MacroForEach.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Move.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/SizePrintfMacros.h"
 #include "mozilla/TimeStamp.h"
+#include "mozilla/Unused.h"
 
 #include <ctype.h>
 #include <string.h>
 #ifndef XP_WIN
 # include <sys/mman.h>
 # include <unistd.h>
 #endif
 
@@ -5002,28 +5003,28 @@ GCRuntime::endMarkingSweepGroup()
         MOZ_ASSERT(zone->isGCMarkingGray());
         zone->setGCState(Zone::Mark);
     }
     MOZ_ASSERT(marker.isDrained());
     marker.setMarkColorBlack();
 }
 
 // Causes the given WeakCache to be swept when run.
-class SweepWeakCacheTask : public GCParallelTask
+class ImmediateSweepWeakCacheTask : public GCParallelTask
 {
     JS::detail::WeakCacheBase& cache;
 
-    SweepWeakCacheTask(const SweepWeakCacheTask&) = delete;
+    ImmediateSweepWeakCacheTask(const ImmediateSweepWeakCacheTask&) = delete;
 
   public:
-    SweepWeakCacheTask(JSRuntime* rt, JS::detail::WeakCacheBase& wc)
+    ImmediateSweepWeakCacheTask(JSRuntime* rt, JS::detail::WeakCacheBase& wc)
       : GCParallelTask(rt), cache(wc)
     {}
 
-    SweepWeakCacheTask(SweepWeakCacheTask&& other)
+    ImmediateSweepWeakCacheTask(ImmediateSweepWeakCacheTask&& other)
       : GCParallelTask(mozilla::Move(other)), cache(other.cache)
     {}
 
     void run() override {
         cache.sweep();
     }
 };
 
@@ -5217,60 +5218,83 @@ GCRuntime::sweepJitDataOnMainThread(Free
     {
         gcstats::AutoPhase ap1(stats(), gcstats::PhaseKind::SWEEP_TYPES);
         gcstats::AutoPhase ap2(stats(), gcstats::PhaseKind::SWEEP_TYPES_BEGIN);
         for (GCSweepGroupIter zone(rt); !zone.done(); zone.next())
             zone->beginSweepTypes(fop, releaseObservedTypes && !zone->isPreservingCode());
     }
 }
 
-using WeakCacheTaskVector = mozilla::Vector<SweepWeakCacheTask, 0, SystemAllocPolicy>;
-
+using WeakCacheTaskVector = mozilla::Vector<ImmediateSweepWeakCacheTask, 0, SystemAllocPolicy>;
+
+enum WeakCacheLocation
+{
+    RuntimeWeakCache,
+    ZoneWeakCache
+};
+
+// Call a functor for all weak caches that need to be swept in the current
+// sweep group.
 template <typename Functor>
 static inline bool
 IterateWeakCaches(JSRuntime* rt, Functor f)
 {
     for (GCSweepGroupIter zone(rt); !zone.done(); zone.next()) {
         for (JS::detail::WeakCacheBase* cache : zone->weakCaches()) {
-            if (!f(cache))
+            if (!f(cache, ZoneWeakCache))
                 return false;
         }
     }
 
     for (JS::detail::WeakCacheBase* cache : rt->weakCaches()) {
-        if (!f(cache))
+        if (!f(cache, RuntimeWeakCache))
             return false;
     }
 
     return true;
 }
 
-static WeakCacheTaskVector
-PrepareWeakCacheTasks(JSRuntime* rt)
-{
-    // Build a vector of sweep tasks to run on a helper thread.
-    WeakCacheTaskVector tasks;
-    bool ok = IterateWeakCaches(rt, [&] (JS::detail::WeakCacheBase* cache) {
+static bool
+PrepareWeakCacheTasks(JSRuntime* rt, WeakCacheTaskVector* immediateTasks)
+{
+    // Start incremental sweeping for caches that support it or add to a vector
+    // of sweep tasks to run on a helper thread.
+
+    MOZ_ASSERT(immediateTasks->empty());
+
+    bool ok = IterateWeakCaches(rt, [&] (JS::detail::WeakCacheBase* cache,
+                                         WeakCacheLocation location)
+    {
         if (!cache->needsSweep())
             return true;
 
-        return tasks.emplaceBack(rt, *cache);
+        // Caches that support incremental sweeping will be swept later.
+        if (location == ZoneWeakCache && cache->setNeedsIncrementalBarrier(true))
+            return true;
+
+        return immediateTasks->emplaceBack(rt, *cache);
     });
 
-    // If we ran out of memory, do all the work now and ensure we return an
-    // empty list.
-    if (!ok) {
-        IterateWeakCaches(rt, [&] (JS::detail::WeakCacheBase* cache) {
-            SweepWeakCacheTask(rt, *cache).runFromActiveCooperatingThread(rt);
-            return true;
-        });
-        tasks.clear();
-    }
-
-    return tasks;
+    if (!ok)
+        immediateTasks->clearAndFree();
+
+    return ok;
+}
+
+static void
+SweepWeakCachesOnMainThread(JSRuntime* rt)
+{
+    // If we ran out of memory, do all the work on the main thread.
+    gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PhaseKind::SWEEP_WEAK_CACHES);
+    IterateWeakCaches(rt, [&] (JS::detail::WeakCacheBase* cache, WeakCacheLocation location) {
+        if (cache->needsIncrementalBarrier())
+            cache->setNeedsIncrementalBarrier(false);
+        cache->sweep();
+        return true;
+    });
 }
 
 void
 GCRuntime::beginSweepingSweepGroup()
 {
     /*
      * Begin sweeping the group of zones in currentSweepGroup, performing
      * actions that must be done before yielding to caller.
@@ -5332,17 +5356,20 @@ GCRuntime::beginSweepingSweepGroup()
         AutoRunParallelTask sweepCCWrappers(rt, SweepCCWrappers, PhaseKind::SWEEP_CC_WRAPPER, lock);
         AutoRunParallelTask sweepObjectGroups(rt, SweepObjectGroups, PhaseKind::SWEEP_TYPE_OBJECT, lock);
         AutoRunParallelTask sweepRegExps(rt, SweepRegExps, PhaseKind::SWEEP_REGEXP, lock);
         AutoRunParallelTask sweepMisc(rt, SweepMisc, PhaseKind::SWEEP_MISC, lock);
         AutoRunParallelTask sweepCompTasks(rt, SweepCompressionTasks, PhaseKind::SWEEP_COMPRESSION, lock);
         AutoRunParallelTask sweepWeakMaps(rt, SweepWeakMaps, PhaseKind::SWEEP_WEAKMAPS, lock);
         AutoRunParallelTask sweepUniqueIds(rt, SweepUniqueIds, PhaseKind::SWEEP_UNIQUEIDS, lock);
 
-        WeakCacheTaskVector sweepCacheTasks = PrepareWeakCacheTasks(rt);
+        WeakCacheTaskVector sweepCacheTasks;
+        if (!PrepareWeakCacheTasks(rt, &sweepCacheTasks))
+            SweepWeakCachesOnMainThread(rt);
+
         for (auto& task : sweepCacheTasks)
             startTask(task, PhaseKind::SWEEP_WEAK_CACHES, lock);
 
         {
             AutoUnlockHelperThreadState unlock(lock);
             sweepJitDataOnMainThread(&fop);
         }
 
@@ -5366,17 +5393,18 @@ GCRuntime::beginSweepingSweepGroup()
             zone->arenas.queueForBackgroundSweep(&fop, BackgroundFinalizePhases[i]);
 
         zone->arenas.queueForegroundThingsForSweep(&fop);
     }
 
     sweepActionList = PerSweepGroupActionList;
     sweepActionIndex = 0;
     sweepPhaseIndex = 0;
-    sweepZone = currentSweepGroup;
+    sweepZone = nullptr;
+    sweepCache = nullptr;
 }
 
 void
 GCRuntime::endSweepingSweepGroup()
 {
     {
         gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::FINALIZE_END);
         FreeOp fop(rt);
@@ -5635,16 +5663,145 @@ GCRuntime::sweepAtomsTable(SliceBudget& 
             oomUnsafe.crash("Adding atom from secondary table after sweep");
     }
     rt->destroyAtomsAddedWhileSweepingTable();
 
     maybeAtoms.reset();
     return Finished;
 }
 
+class js::gc::WeakCacheSweepIterator
+{
+    JS::Zone*& sweepZone;
+    JS::detail::WeakCacheBase*& sweepCache;
+
+  public:
+    explicit WeakCacheSweepIterator(GCRuntime* gc)
+      : sweepZone(gc->sweepZone.ref()), sweepCache(gc->sweepCache.ref())
+    {
+        // Initialize state when we start sweeping a sweep group.
+        if (!sweepZone) {
+            sweepZone = gc->currentSweepGroup;
+            MOZ_ASSERT(!sweepCache);
+            sweepCache = sweepZone->weakCaches().getFirst();
+            settle();
+        }
+
+        checkState();
+    }
+
+    bool empty(AutoLockHelperThreadState& lock) {
+        return !sweepZone;
+    }
+
+    JS::detail::WeakCacheBase* next(AutoLockHelperThreadState& lock) {
+        if (empty(lock))
+            return nullptr;
+
+        JS::detail::WeakCacheBase* result = sweepCache;
+        sweepCache = sweepCache->getNext();
+        settle();
+        checkState();
+        return result;
+    }
+
+    void settle() {
+        while (sweepZone) {
+            while (sweepCache && !sweepCache->needsIncrementalBarrier())
+                sweepCache = sweepCache->getNext();
+
+            if (sweepCache)
+                break;
+
+            sweepZone = sweepZone->nextNodeInGroup();
+            if (sweepZone)
+                sweepCache = sweepZone->weakCaches().getFirst();
+        }
+    }
+
+  private:
+    void checkState() {
+        MOZ_ASSERT((!sweepZone && !sweepCache) ||
+                   (sweepCache && sweepCache->needsIncrementalBarrier()));
+    }
+};
+
+class IncrementalSweepWeakCacheTask : public GCParallelTask
+{
+    WeakCacheSweepIterator& work_;
+    SliceBudget& budget_;
+    AutoLockHelperThreadState& lock_;
+    JS::detail::WeakCacheBase* cache_;
+
+  public:
+    IncrementalSweepWeakCacheTask(JSRuntime* rt, WeakCacheSweepIterator& work, SliceBudget& budget,
+                                  AutoLockHelperThreadState& lock)
+      : GCParallelTask(rt), work_(work), budget_(budget), lock_(lock),
+        cache_(work.next(lock))
+    {
+        MOZ_ASSERT(cache_);
+        runtime()->gc.startTask(*this, gcstats::PhaseKind::SWEEP_WEAK_CACHES, lock_);
+    }
+
+    ~IncrementalSweepWeakCacheTask() {
+        runtime()->gc.joinTask(*this, gcstats::PhaseKind::SWEEP_WEAK_CACHES, lock_);
+    }
+
+  private:
+    void run() override {
+        do {
+            MOZ_ASSERT(cache_->needsIncrementalBarrier());
+            size_t steps = cache_->sweep();
+            cache_->setNeedsIncrementalBarrier(false);
+
+            AutoLockHelperThreadState lock;
+            budget_.step(steps);
+            if (budget_.isOverBudget())
+                break;
+
+            cache_ = work_.next(lock);
+        } while(cache_);
+    }
+};
+
+/* static */ IncrementalProgress
+GCRuntime::sweepWeakCaches(GCRuntime* gc, SliceBudget& budget)
+{
+    return gc->sweepWeakCaches(budget);
+}
+
+static const size_t MaxWeakCacheSweepTasks = 8;
+
+static size_t
+WeakCacheSweepTaskCount()
+{
+    size_t targetTaskCount = HelperThreadState().cpuCount;
+    return Min(targetTaskCount, MaxWeakCacheSweepTasks);
+}
+
+IncrementalProgress
+GCRuntime::sweepWeakCaches(SliceBudget& budget)
+{
+    WeakCacheSweepIterator work(this);
+
+    {
+        AutoLockHelperThreadState lock;
+        gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::SWEEP_COMPARTMENTS);
+
+        Maybe<IncrementalSweepWeakCacheTask> tasks[MaxWeakCacheSweepTasks];
+        for (size_t i = 0; !work.empty(lock) && i < WeakCacheSweepTaskCount(); i++)
+            tasks[i].emplace(rt, work, budget, lock);
+
+        // Tasks run until budget or work is exhausted.
+    }
+
+    AutoLockHelperThreadState lock;
+    return work.empty(lock) ? Finished : NotFinished;
+}
+
 /* static */ IncrementalProgress
 GCRuntime::finalizeAllocKind(GCRuntime* gc, FreeOp* fop, Zone* zone, SliceBudget& budget,
                              AllocKind kind)
 {
     // Set the number of things per arena for this AllocKind.
     size_t thingsPerArena = Arena::thingsPerArena(kind);
     auto& sweepList = gc->incrementalSweepList.ref();
     sweepList.setThingsPerArena(thingsPerArena);
@@ -5701,16 +5858,17 @@ AddPerZoneSweepAction(bool* ok, PerZoneS
 }
 
 /* static */ bool
 GCRuntime::initializeSweepActions()
 {
     bool ok = true;
 
     AddPerSweepGroupSweepAction(&ok, GCRuntime::sweepAtomsTable);
+    AddPerSweepGroupSweepAction(&ok, GCRuntime::sweepWeakCaches);
 
     AddPerZoneSweepPhase(&ok);
     for (auto kind : ForegroundObjectFinalizePhase.kinds)
         AddPerZoneSweepAction(&ok, GCRuntime::finalizeAllocKind, kind);
 
     AddPerZoneSweepPhase(&ok);
     AddPerZoneSweepAction(&ok, GCRuntime::sweepTypeInformation);
     AddPerZoneSweepAction(&ok, GCRuntime::mergeSweptObjectArenas);
@@ -5759,25 +5917,27 @@ GCRuntime::performSweepActions(SliceBudg
                 }
                 sweepActionIndex = 0;
                 break;
               }
 
               case PerZoneActionList:
                 for (; sweepPhaseIndex < PerZoneSweepPhases.length(); sweepPhaseIndex++) {
                     const auto& actions = PerZoneSweepPhases[sweepPhaseIndex];
+                    if (!sweepZone)
+                        sweepZone = currentSweepGroup;
                     for (; sweepZone; sweepZone = sweepZone->nextNodeInGroup()) {
                         for (; sweepActionIndex < actions.length(); sweepActionIndex++) {
                             const auto& action = actions[sweepActionIndex];
                             if (action.func(this, &fop, sweepZone, budget, action.kind) == NotFinished)
                                 return NotFinished;
                         }
                         sweepActionIndex = 0;
                     }
-                    sweepZone = currentSweepGroup;
+                    sweepZone = nullptr;
                 }
                 sweepPhaseIndex = 0;
                 break;
 
               default:
                 MOZ_CRASH("Unexpected sweepActionList value");
             }
         }
--- a/js/src/jsnum.cpp
+++ b/js/src/jsnum.cpp
@@ -491,35 +491,35 @@ const Class NumberObject::class_ = {
     JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_HAS_CACHED_PROTO(JSProto_Number)
 };
 
 static bool
 Number(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
-    /* Sample JS_CALLEE before clobbering. */
-    bool isConstructing = args.isConstructing();
-
     if (args.length() > 0) {
         if (!ToNumber(cx, args[0]))
             return false;
-        args.rval().set(args[0]);
-    } else {
-        args.rval().setInt32(0);
     }
 
-    if (!isConstructing)
+    if (!args.isConstructing()) {
+        if (args.length() > 0)
+            args.rval().set(args[0]);
+        else
+            args.rval().setInt32(0);
         return true;
+    }
 
-    RootedObject newTarget(cx, &args.newTarget().toObject());
     RootedObject proto(cx);
-    if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
+    if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
         return false;
-    JSObject* obj = NumberObject::create(cx, args.rval().toNumber(), proto);
+
+    double d = args.length() > 0 ? args[0].toNumber() : 0;
+    JSObject* obj = NumberObject::create(cx, d, proto);
     if (!obj)
         return false;
     args.rval().setObject(*obj);
     return true;
 }
 
 MOZ_ALWAYS_INLINE bool
 IsNumber(HandleValue v)
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -997,27 +997,16 @@ js::GetPrototypeFromConstructor(JSContex
 {
     RootedValue protov(cx);
     if (!GetProperty(cx, newTarget, newTarget, cx->names().prototype, &protov))
         return false;
     proto.set(protov.isObject() ? &protov.toObject() : nullptr);
     return true;
 }
 
-bool
-js::GetPrototypeFromCallableConstructor(JSContext* cx, const CallArgs& args, MutableHandleObject proto)
-{
-    RootedObject newTarget(cx);
-    if (args.isConstructing())
-        newTarget = &args.newTarget().toObject();
-    else
-        newTarget = &args.callee();
-    return GetPrototypeFromConstructor(cx, newTarget, proto);
-}
-
 JSObject*
 js::CreateThisForFunction(JSContext* cx, HandleObject callee, HandleObject newTarget,
                           NewObjectKind newKind)
 {
     RootedObject proto(cx);
     if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
         return nullptr;
 
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -1147,18 +1147,31 @@ GetInitialHeap(NewObjectKind newKind, co
 bool
 NewObjectWithTaggedProtoIsCachable(JSContext* cx, Handle<TaggedProto> proto,
                                    NewObjectKind newKind, const Class* clasp);
 
 // ES6 9.1.15 GetPrototypeFromConstructor.
 extern bool
 GetPrototypeFromConstructor(JSContext* cx, js::HandleObject newTarget, js::MutableHandleObject proto);
 
-extern bool
-GetPrototypeFromCallableConstructor(JSContext* cx, const CallArgs& args, js::MutableHandleObject proto);
+MOZ_ALWAYS_INLINE bool
+GetPrototypeFromBuiltinConstructor(JSContext* cx, const CallArgs& args, js::MutableHandleObject proto)
+{
+    // When proto is set to nullptr, the caller is expected to select the
+    // correct default built-in prototype for this constructor.
+    if (!args.isConstructing() || &args.newTarget().toObject() == &args.callee()) {
+        proto.set(nullptr);
+        return true;
+    }
+
+    // We're calling this constructor from a derived class, retrieve the
+    // actual prototype from newTarget.
+    RootedObject newTarget(cx, &args.newTarget().toObject());
+    return GetPrototypeFromConstructor(cx, newTarget, proto);
+}
 
 // Specialized call for constructing |this| with a known function callee,
 // and a known prototype.
 extern JSObject*
 CreateThisForFunctionWithProto(JSContext* cx, js::HandleObject callee, HandleObject newTarget,
                                HandleObject proto, NewObjectKind newKind = GenericObject);
 
 // Specialized call for constructing |this| with a known function callee.
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -3204,18 +3204,17 @@ js::StringConstructor(JSContext* cx, uns
         if (!str)
             return false;
     } else {
         str = cx->runtime()->emptyString;
     }
 
     if (args.isConstructing()) {
         RootedObject proto(cx);
-        RootedObject newTarget(cx, &args.newTarget().toObject());
-        if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
+        if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
             return false;
 
         StringObject* strobj = StringObject::create(cx, str, proto);
         if (!strobj)
             return false;
         args.rval().setObject(*strobj);
         return true;
     }
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -280,18 +280,17 @@ ArrayBufferObject::class_constructor(JSC
     // Step 2.
     uint64_t byteLength;
     if (!ToIndex(cx, args.get(0), &byteLength))
         return false;
 
     // Step 3 (Inlined 24.1.1.1 AllocateArrayBuffer).
     // 24.1.1.1, step 1 (Inlined 9.1.14 OrdinaryCreateFromConstructor).
     RootedObject proto(cx);
-    RootedObject newTarget(cx, &args.newTarget().toObject());
-    if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
+    if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
         return false;
 
     // 24.1.1.1, step 3 (Inlined 6.2.6.1 CreateByteDataBlock, step 2).
     // Refuse to allocate too large buffers, currently limited to ~2 GiB.
     if (byteLength > INT32_MAX) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
         return false;
     }
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -8101,16 +8101,18 @@ DebuggerFrame::getArguments(JSContext *c
 
     RootedDebuggerArguments arguments(cx);
     if (referent.hasArgs()) {
         Rooted<GlobalObject*> global(cx, &frame->global());
         RootedObject proto(cx, GlobalObject::getOrCreateArrayPrototype(cx, global));
         if (!proto)
             return false;
         arguments = DebuggerArguments::create(cx, proto, frame);
+        if (!arguments)
+            return false;
     } else {
         arguments = nullptr;
     }
 
     result.set(arguments);
     frame->setReservedSlot(JSSLOT_DEBUGFRAME_ARGUMENTS, ObjectOrNullValue(result));
     return true;
 }
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -1052,26 +1052,22 @@ size_t
 GlobalHelperThreadState::maxGCParallelThreads() const
 {
     if (IsHelperThreadSimulatingOOM(js::oom::THREAD_TYPE_GCPARALLEL))
         return 1;
     return threadCount;
 }
 
 bool
-GlobalHelperThreadState::canStartWasmCompile(const AutoLockHelperThreadState& lock,
-                                             bool assumeThreadAvailable)
+GlobalHelperThreadState::canStartWasmCompile(const AutoLockHelperThreadState& lock)
 {
     // Don't execute an wasm job if an earlier one failed.
     if (wasmWorklist(lock).empty() || numWasmFailedJobs)
         return false;
 
-    if (assumeThreadAvailable)
-        return true;
-
     // Honor the maximum allowed threads to compile wasm jobs at once,
     // to avoid oversaturating the machine.
     if (!checkTaskThreadLimit<wasm::CompileTask*>(maxWasmCompilationThreads()))
         return false;
 
     return true;
 }
 
@@ -1663,19 +1659,19 @@ HelperThread::ThreadMain(void* arg)
 {
     ThisThread::SetName("JS Helper");
 
     static_cast<HelperThread*>(arg)->threadLoop();
     Mutex::ShutDown();
 }
 
 void
-HelperThread::handleWasmWorkload(AutoLockHelperThreadState& locked, bool assumeThreadAvailable)
+HelperThread::handleWasmWorkload(AutoLockHelperThreadState& locked)
 {
-    MOZ_ASSERT(HelperThreadState().canStartWasmCompile(locked, assumeThreadAvailable));
+    MOZ_ASSERT(HelperThreadState().canStartWasmCompile(locked));
     MOZ_ASSERT(idle());
 
     currentTask.emplace(HelperThreadState().wasmWorklist(locked).popCopy());
     bool success = false;
     UniqueChars error;
 
     wasm::CompileTask* task = wasmTask();
     {
@@ -1693,43 +1689,16 @@ HelperThread::handleWasmWorkload(AutoLoc
         HelperThreadState().setWasmError(locked, Move(error));
     }
 
     // Notify the active thread in case it's waiting.
     HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
     currentTask.reset();
 }
 
-bool
-HelperThread::handleWasmIdleWorkload(AutoLockHelperThreadState& locked)
-{
-    // Perform wasm compilation work on a HelperThread that is running
-    // ModuleGenerator instead of blocking while other compilation threads
-    // finish.  This removes a source of deadlocks, as putting all threads to
-    // work guarantees forward progress for compilation.
-
-    // The current thread has already been accounted for, so don't guard on
-    // thread subscription when checking whether we can do work.
-
-    if (HelperThreadState().canStartWasmCompile(locked, /*assumeThreadAvailable=*/ true)) {
-        HelperTaskUnion oldTask = currentTask.value();
-        currentTask.reset();
-        js::oom::ThreadType oldType = (js::oom::ThreadType)js::oom::GetThreadType();
-
-        js::oom::SetThreadType(js::oom::THREAD_TYPE_WASM);
-        handleWasmWorkload(locked, /*assumeThreadAvailable=*/ true);
-
-        js::oom::SetThreadType(oldType);
-        currentTask.emplace(oldTask);
-        return true;
-    }
-
-    return false;
-}
-
 void
 HelperThread::handlePromiseTaskWorkload(AutoLockHelperThreadState& locked)
 {
     MOZ_ASSERT(HelperThreadState().canStartPromiseTask(locked));
     MOZ_ASSERT(idle());
 
     PromiseTask* task = HelperThreadState().promiseTasks(locked).popCopy();
     currentTask.emplace(task);
--- a/js/src/vm/HelperThreads.h
+++ b/js/src/vm/HelperThreads.h
@@ -219,18 +219,17 @@ class GlobalHelperThreadState
     GCHelperStateVector& gcHelperWorklist(const AutoLockHelperThreadState&) {
         return gcHelperWorklist_;
     }
 
     GCParallelTaskVector& gcParallelWorklist(const AutoLockHelperThreadState&) {
         return gcParallelWorklist_;
     }
 
-    bool canStartWasmCompile(const AutoLockHelperThreadState& lock,
-                             bool assumeThreadAvailable = false);
+    bool canStartWasmCompile(const AutoLockHelperThreadState& lock);
     bool canStartPromiseTask(const AutoLockHelperThreadState& lock);
     bool canStartIonCompile(const AutoLockHelperThreadState& lock);
     bool canStartIonFreeTask(const AutoLockHelperThreadState& lock);
     bool canStartParseTask(const AutoLockHelperThreadState& lock);
     bool canStartCompressionTask(const AutoLockHelperThreadState& lock);
     bool canStartGCHelperTask(const AutoLockHelperThreadState& lock);
     bool canStartGCParallelTask(const AutoLockHelperThreadState& lock);
 
@@ -383,23 +382,16 @@ struct HelperThread
         return maybeCurrentTaskAs<jit::IonBuilder*>();
     }
 
     /* Any wasm data currently being optimized on this thread. */
     wasm::CompileTask* wasmTask() {
         return maybeCurrentTaskAs<wasm::CompileTask*>();
     }
 
-    /*
-     * Perform wasm compilation work on behalf of a thread that is running a
-     * wasm ModuleGenerator and would otherwise block waiting for other
-     * compilation threads. Return true if work was performed, otherwise false.
-     */
-    bool handleWasmIdleWorkload(AutoLockHelperThreadState& locked);
-
     /* Any source being parsed/emitted on this thread. */
     ParseTask* parseTask() {
         return maybeCurrentTaskAs<ParseTask*>();
     }
 
     /* Any source being compressed on this thread. */
     SourceCompressionTask* compressionTask() {
         return maybeCurrentTaskAs<SourceCompressionTask*>();
@@ -424,17 +416,17 @@ struct HelperThread
     template <typename T>
     T maybeCurrentTaskAs() {
         if (currentTask.isSome() && currentTask->is<T>())
             return currentTask->as<T>();
 
         return nullptr;
     }
 
-    void handleWasmWorkload(AutoLockHelperThreadState& locked, bool assumeThreadAvailable = false);
+    void handleWasmWorkload(AutoLockHelperThreadState& locked);
     void handlePromiseTaskWorkload(AutoLockHelperThreadState& locked);
     void handleIonWorkload(AutoLockHelperThreadState& locked);
     void handleIonFreeWorkload(AutoLockHelperThreadState& locked);
     void handleParseWorkload(AutoLockHelperThreadState& locked);
     void handleCompressionWorkload(AutoLockHelperThreadState& locked);
     void handleGCHelperWorkload(AutoLockHelperThreadState& locked);
     void handleGCParallelWorkload(AutoLockHelperThreadState& locked);
 };
--- a/js/src/vm/ObjectGroup.cpp
+++ b/js/src/vm/ObjectGroup.cpp
@@ -379,68 +379,63 @@ struct ObjectGroupCompartment::NewEntry
         const Class* clasp;
         TaggedProto proto;
         JSObject* associated;
 
         Lookup(const Class* clasp, TaggedProto proto, JSObject* associated)
           : clasp(clasp), proto(proto), associated(associated)
         {}
 
-        bool hasAssocId() const {
-            return !associated || associated->zone()->hasUniqueId(associated);
-        }
-
-        bool ensureAssocId() const {
-            uint64_t unusedId;
-            return !associated ||
-                   associated->zoneFromAnyThread()->getUniqueId(associated, &unusedId);
-        }
-
-        uint64_t getAssocId() const {
-            return associated ? associated->zone()->getUniqueIdInfallible(associated) : 0;
+        explicit Lookup(const NewEntry& entry)
+          : clasp(entry.group.unbarrieredGet()->clasp()),
+            proto(entry.group.unbarrieredGet()->proto()),
+            associated(entry.associated)
+        {
+            if (associated && associated->is<JSFunction>())
+                clasp = nullptr;
         }
     };
 
     static bool hasHash(const Lookup& l) {
-        return l.proto.hasUniqueId() && l.hasAssocId();
+        return MovableCellHasher<TaggedProto>::hasHash(l.proto) &&
+               MovableCellHasher<JSObject*>::hasHash(l.associated);
     }
 
     static bool ensureHash(const Lookup& l) {
-        return l.proto.ensureUniqueId() && l.ensureAssocId();
+        return MovableCellHasher<TaggedProto>::ensureHash(l.proto) &&
+               MovableCellHasher<JSObject*>::ensureHash(l.associated);
     }
 
     static inline HashNumber hash(const Lookup& lookup) {
-        MOZ_ASSERT(lookup.proto.hasUniqueId());
-        MOZ_ASSERT(lookup.hasAssocId());
         HashNumber hash = uintptr_t(lookup.clasp);
-        hash = mozilla::RotateLeft(hash, 4) ^ Zone::UniqueIdToHash(lookup.proto.uniqueId());
-        hash = mozilla::RotateLeft(hash, 4) ^ Zone::UniqueIdToHash(lookup.getAssocId());
+        hash = mozilla::RotateLeft(hash, 4) ^ MovableCellHasher<TaggedProto>::hash(lookup.proto);
+        hash = mozilla::RotateLeft(hash, 4) ^ MovableCellHasher<JSObject*>::hash(lookup.associated);
         return hash;
     }
 
     static inline bool match(const ObjectGroupCompartment::NewEntry& key, const Lookup& lookup) {
-        TaggedProto proto = key.group.unbarrieredGet()->proto();
-        JSObject* assoc = key.associated;
-        MOZ_ASSERT(proto.hasUniqueId());
-        MOZ_ASSERT_IF(assoc, assoc->zone()->hasUniqueId(assoc));
-        MOZ_ASSERT(lookup.proto.hasUniqueId());
-        MOZ_ASSERT(lookup.hasAssocId());
-
         if (lookup.clasp && key.group.unbarrieredGet()->clasp() != lookup.clasp)
             return false;
-        if (proto.uniqueId() != lookup.proto.uniqueId())
+
+        TaggedProto proto = key.group.unbarrieredGet()->proto();
+        if (!MovableCellHasher<TaggedProto>::match(proto, lookup.proto))
             return false;
-        return !assoc || assoc->zone()->getUniqueIdInfallible(assoc) == lookup.getAssocId();
+
+        return MovableCellHasher<JSObject*>::match(key.associated, lookup.associated);
     }
 
     static void rekey(NewEntry& k, const NewEntry& newKey) { k = newKey; }
 
     bool needsSweep() {
-        return (IsAboutToBeFinalized(&group) ||
-                (associated && IsAboutToBeFinalizedUnbarriered(&associated)));
+        return IsAboutToBeFinalized(&group) ||
+               (associated && IsAboutToBeFinalizedUnbarriered(&associated));
+    }
+
+    bool operator==(const NewEntry& other) const {
+        return group == other.group && associated == other.associated;
     }
 };
 
 namespace js {
 template <>
 struct FallibleHashMethods<ObjectGroupCompartment::NewEntry>
 {
     template <typename Lookup> static bool hasHash(Lookup&& l) {
@@ -1448,16 +1443,23 @@ struct ObjectGroupCompartment::Allocatio
         TraceRoot(trc, &script, "AllocationSiteKey script");
         TraceNullableRoot(trc, &proto, "AllocationSiteKey proto");
     }
 
     bool needsSweep() {
         return IsAboutToBeFinalizedUnbarriered(script.unsafeGet()) ||
             (proto && IsAboutToBeFinalizedUnbarriered(proto.unsafeGet()));
     }
+
+    bool operator==(const AllocationSiteKey& other) const {
+        return script == other.script &&
+               offset == other.offset &&
+               kind == other.kind &&
+               proto == other.proto;
+    }
 };
 
 class ObjectGroupCompartment::AllocationSiteTable
   : public JS::WeakCache<js::GCHashMap<AllocationSiteKey, ReadBarrieredObjectGroup,
                                        AllocationSiteKey, SystemAllocPolicy>>
 {
     using Table = js::GCHashMap<AllocationSiteKey, ReadBarrieredObjectGroup,
                                 AllocationSiteKey, SystemAllocPolicy>;
@@ -1552,17 +1554,17 @@ ObjectGroup::allocationSiteGroup(JSConte
 void
 ObjectGroupCompartment::replaceAllocationSiteGroup(JSScript* script, jsbytecode* pc,
                                                    JSProtoKey kind, ObjectGroup* group)
 {
     AllocationSiteKey key(script, script->pcToOffset(pc), kind, group->proto().toObjectOrNull());
 
     AllocationSiteTable::Ptr p = allocationSiteTable->lookup(key);
     MOZ_RELEASE_ASSERT(p);
-    allocationSiteTable->get().remove(p);
+    allocationSiteTable->remove(p);
     {
         AutoEnterOOMUnsafeRegion oomUnsafe;
         if (!allocationSiteTable->putNew(key, group))
             oomUnsafe.crash("Inconsistent object table");
     }
 }
 
 /* static */ ObjectGroup*
@@ -1699,29 +1701,29 @@ ObjectGroupCompartment::~ObjectGroupComp
 
 void
 ObjectGroupCompartment::removeDefaultNewGroup(const Class* clasp, TaggedProto proto,
                                               JSObject* associated)
 {
     auto p = defaultNewTable->lookup(NewEntry::Lookup(clasp, proto, associated));
     MOZ_RELEASE_ASSERT(p);
 
-    defaultNewTable->get().remove(p);
+    defaultNewTable->remove(p);
     defaultNewGroupCache.purge();
 }
 
 void
 ObjectGroupCompartment::replaceDefaultNewGroup(const Class* clasp, TaggedProto proto,
                                                JSObject* associated, ObjectGroup* group)
 {
     NewEntry::Lookup lookup(clasp, proto, associated);
 
     auto p = defaultNewTable->lookup(lookup);
     MOZ_RELEASE_ASSERT(p);
-    defaultNewTable->get().remove(p);
+    defaultNewTable->remove(p);
     defaultNewGroupCache.purge();
     {
         AutoEnterOOMUnsafeRegion oomUnsafe;
         if (!defaultNewTable->putNew(lookup, NewEntry(group, associated)))
             oomUnsafe.crash("Inconsistent object table");
     }
 }
 
@@ -1911,19 +1913,14 @@ ObjectGroupCompartment::checkNewTableAft
     for (auto r = table->all(); !r.empty(); r.popFront()) {
         NewEntry entry = r.front();
         CheckGCThingAfterMovingGC(entry.group.unbarrieredGet());
         TaggedProto proto = entry.group.unbarrieredGet()->proto();
         if (proto.isObject())
             CheckGCThingAfterMovingGC(proto.toObject());
         CheckGCThingAfterMovingGC(entry.associated);
 
-        const Class* clasp = entry.group.unbarrieredGet()->clasp();
-        if (entry.associated && entry.associated->is<JSFunction>())
-            clasp = nullptr;
-
-        NewEntry::Lookup lookup(clasp, proto, entry.associated);
-        auto ptr = table->lookup(lookup);
+        auto ptr = table->lookup(NewEntry::Lookup(entry));
         MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &r.front());
     }
 }
 
 #endif // JSGC_HASH_TABLE_CHECKS
--- a/js/src/vm/Shape.cpp
+++ b/js/src/vm/Shape.cpp
@@ -1115,31 +1115,16 @@ Shape::setObjectFlags(JSContext* cx, Bas
 
     StackBaseShape base(last);
     base.flags |= flags;
 
     RootedShape lastRoot(cx, last);
     return replaceLastProperty(cx, base, proto, lastRoot);
 }
 
-/* static */ inline HashNumber
-StackBaseShape::hash(const Lookup& lookup)
-{
-    HashNumber hash = lookup.flags;
-    hash = RotateLeft(hash, 4) ^ (uintptr_t(lookup.clasp) >> 3);
-    return hash;
-}
-
-/* static */ inline bool
-StackBaseShape::match(const ReadBarriered<UnownedBaseShape*>& key, const Lookup& lookup)
-{
-    return key.unbarrieredGet()->flags == lookup.flags &&
-           key.unbarrieredGet()->clasp_ == lookup.clasp;
-}
-
 inline
 BaseShape::BaseShape(const StackBaseShape& base)
   : clasp_(base.clasp),
     flags(base.flags),
     slotSpan_(0),
     unowned_(nullptr),
     table_(nullptr)
 {
@@ -1290,33 +1275,16 @@ InitialShapeEntry::InitialShapeEntry() :
 }
 
 inline
 InitialShapeEntry::InitialShapeEntry(Shape* shape, const Lookup::ShapeProto& proto)
   : shape(shape), proto(proto)
 {
 }
 
-/* static */ inline HashNumber
-InitialShapeEntry::hash(const Lookup& lookup)
-{
-    return (RotateLeft(uintptr_t(lookup.clasp) >> 3, 4) ^ lookup.proto.hashCode()) +
-           lookup.nfixed;
-}
-
-/* static */ inline bool
-InitialShapeEntry::match(const InitialShapeEntry& key, const Lookup& lookup)
-{
-    const Shape* shape = key.shape.unbarrieredGet();
-    return lookup.clasp == shape->getObjectClass()
-        && lookup.nfixed == shape->numFixedSlots()
-        && lookup.baseFlags == shape->getObjectFlags()
-        && lookup.proto.match(key.proto);
-}
-
 #ifdef JSGC_HASH_TABLE_CHECKS
 
 void
 Zone::checkInitialShapesTableAfterMovingGC()
 {
     if (!initialShapes().initialized())
         return;
 
--- a/js/src/vm/Shape.h
+++ b/js/src/vm/Shape.h
@@ -654,18 +654,25 @@ struct StackBaseShape : public DefaultHa
 
         explicit Lookup(const ReadBarriered<UnownedBaseShape*>& base)
           : flags(base.unbarrieredGet()->getObjectFlags()), clasp(base.unbarrieredGet()->clasp())
         {
             MOZ_ASSERT(!base.unbarrieredGet()->isOwned());
         }
     };
 
-    static inline HashNumber hash(const Lookup& lookup);
-    static inline bool match(const ReadBarriered<UnownedBaseShape*>& key, const Lookup& lookup);
+    static HashNumber hash(const Lookup& lookup) {
+        HashNumber hash = lookup.flags;
+        hash = mozilla::RotateLeft(hash, 4) ^ (uintptr_t(lookup.clasp) >> 3);
+        return hash;
+    }
+    static inline bool match(const ReadBarriered<UnownedBaseShape*>& key, const Lookup& lookup) {
+        return key.unbarrieredGet()->flags == lookup.flags &&
+               key.unbarrieredGet()->clasp_ == lookup.clasp;
+    }
 };
 
 static MOZ_ALWAYS_INLINE js::HashNumber
 HashId(jsid id)
 {
     // HashGeneric alone would work, but bits of atom and symbol addresses
     // could then be recovered from the hash code. See bug 1330769.
     if (MOZ_LIKELY(JSID_IS_ATOM(id)))
@@ -1299,34 +1306,50 @@ class InitialShapeProto
       : key_(JSProto_LIMIT), proto_(proto)
     {}
     explicit InitialShapeProto(JSProtoKey key)
       : key_(key), proto_(nullptr)
     {
         MOZ_ASSERT(key < JSProto_LIMIT);
     }
 
-    HashNumber hashCode() const {
-        return proto_.hashCode() ^ HashNumber(key_);
-    }
-    template <typename T>
-    bool match(const InitialShapeProto<T>& other) const {
-        return key_ == other.key_ &&
-               proto_.uniqueId() == other.proto_.unbarrieredGet().uniqueId();
-    }
-
     JSProtoKey key() const {
         return key_;
     }
     const PtrType& proto() const {
         return proto_;
     }
     void setProto(TaggedProto proto) {
         proto_ = proto;
     }
+
+    bool operator==(const InitialShapeProto& other) const {
+        return key_ == other.key_ && proto_ == other.proto_;
+    }
+};
+
+template <>
+struct MovableCellHasher<InitialShapeProto<ReadBarriered<TaggedProto>>>
+{
+    using Key = InitialShapeProto<ReadBarriered<TaggedProto>>;
+    using Lookup = InitialShapeProto<TaggedProto>;
+
+    static bool hasHash(const Lookup& l) {
+        return MovableCellHasher<TaggedProto>::hasHash(l.proto());
+    }
+    static bool ensureHash(const Lookup& l) {
+        return MovableCellHasher<TaggedProto>::ensureHash(l.proto());
+    }
+    static HashNumber hash(const Lookup& l) {
+        return HashNumber(l.key()) ^ MovableCellHasher<TaggedProto>::hash(l.proto());
+    }
+    static bool match(const Key& k, const Lookup& l) {
+        return k.key() == l.key() &&
+               MovableCellHasher<TaggedProto>::match(k.proto().unbarrieredGet(), l.proto());
+    }
 };
 
 /*
  * Entries for the per-zone initialShapes set indexing initial shapes for
  * objects in the zone and the associated types.
  */
 struct InitialShapeEntry
 {
@@ -1365,27 +1388,43 @@ struct InitialShapeEntry
             nfixed = shape->numFixedSlots();
             baseFlags = shape->getObjectFlags();
         }
     };
 
     inline InitialShapeEntry();
     inline InitialShapeEntry(Shape* shape, const Lookup::ShapeProto& proto);
 
-    static inline HashNumber hash(const Lookup& lookup);
-    static inline bool match(const InitialShapeEntry& key, const Lookup& lookup);
-    static void rekey(InitialShapeEntry& k, const InitialShapeEntry& newKey) { k = newKey; }