Merge mozilla-central to mozilla-inbound. a=merge CLOSED TREE
authorBogdan Tara <btara@mozilla.com>
Tue, 12 Mar 2019 18:12:53 +0200
changeset 521678 a29abc7e2ab9fd8f9a3d8c801bb45c67c8d626e8
parent 521677 022205c8500c472569f37b8e13a1fba57900235f (current diff)
parent 521559 aecb76a0cd77184509d1d3deeea7fafd9a43262f (diff)
child 521679 47fef0eb16e990e80375352a937c07160a27ef28
push id10867
push userdvarga@mozilla.com
push dateThu, 14 Mar 2019 15:20:45 +0000
treeherdermozilla-beta@abad13547875 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone67.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 mozilla-central to mozilla-inbound. a=merge CLOSED TREE
testing/web-platform/meta/webrtc/RTCPeerConnection-setRemoteDescription.html.ini
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -678,16 +678,17 @@ HistoryMenu.prototype = {
 
     this.toggleHiddenTabs();
     this.toggleRecentlyClosedTabs();
     this.toggleRecentlyClosedWindows();
     this.toggleTabsFromOtherComputers();
   },
 
   _onCommand: function HM__onCommand(aEvent) {
+    aEvent = getRootEvent(aEvent);
     let placesNode = aEvent.target._placesNode;
     if (placesNode) {
       if (!PrivateBrowsingUtils.isWindowPrivate(window))
         PlacesUIUtils.markPageAsTyped(placesNode.uri);
       openUILink(placesNode.uri, aEvent, {
         ignoreAlt: true,
         triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
       });
--- a/browser/components/preferences/in-content/tests/browser_homepages_use_bookmark.js
+++ b/browser/components/preferences/in-content/tests/browser_homepages_use_bookmark.js
@@ -1,14 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-const TEST_URL = "http://example.com/";
+const TEST_URL1 = "http://example.com/1";
+const TEST_URL2 = "http://example.com/2";
 
 add_task(async function setup() {
   let oldHomepagePref = Services.prefs.getCharPref("browser.startup.homepage");
 
   await openPreferencesViaOpenPreferencesAPI("paneHome", {leaveOpen: true});
 
   Assert.equal(gBrowser.currentURI.spec, "about:preferences#home",
                "#home should be in the URI for about:preferences");
@@ -19,29 +20,50 @@ add_task(async function setup() {
     await PlacesUtils.bookmarks.eraseEverything();
   });
 });
 
 add_task(async function testSetHomepageFromBookmark() {
   let bm = await PlacesUtils.bookmarks.insert({
     parentGuid: PlacesUtils.bookmarks.menuGuid,
     title: "TestHomepage",
-    url: TEST_URL,
+    url: TEST_URL1,
   });
 
   let doc = gBrowser.contentDocument;
-
   // Select the custom URLs option.
   doc.getElementById("homeMode").value = 2;
 
   let promiseSubDialogLoaded = promiseLoadSubDialog("chrome://browser/content/preferences/selectBookmark.xul");
-
   doc.getElementById("useBookmarkBtn").click();
 
   let dialog = await promiseSubDialogLoaded;
-
   dialog.document.getElementById("bookmarks").selectItems([bm.guid]);
-
   dialog.document.documentElement.getButton("accept").click();
 
-  Assert.equal(Services.prefs.getCharPref("browser.startup.homepage"), TEST_URL,
+  Assert.equal(Services.prefs.getCharPref("browser.startup.homepage"), TEST_URL1,
                "Should have set the homepage to the same as the bookmark.");
 });
+
+add_task(async function testSetHomepageFromTopLevelFolder() {
+  // Insert a second item into the menu folder
+  await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.menuGuid,
+    title: "TestHomepage",
+    url: TEST_URL2,
+  });
+
+  let doc = gBrowser.contentDocument;
+  // Select the custom URLs option.
+  doc.getElementById("homeMode").value = 2;
+
+  let promiseSubDialogLoaded = promiseLoadSubDialog("chrome://browser/content/preferences/selectBookmark.xul");
+  doc.getElementById("useBookmarkBtn").click();
+
+  let dialog = await promiseSubDialogLoaded;
+  dialog.document.getElementById("bookmarks")
+        .selectItems([PlacesUtils.bookmarks.menuGuid]);
+  dialog.document.documentElement.getButton("accept").click();
+
+  Assert.equal(Services.prefs.getCharPref("browser.startup.homepage"),
+               `${TEST_URL1}|${TEST_URL2}`,
+               "Should have set the homepage to the same as the bookmark.");
+});
--- a/browser/components/preferences/selectBookmark.js
+++ b/browser/components/preferences/selectBookmark.js
@@ -73,17 +73,18 @@ var SelectBookmarkDialog = {
   accept: function SBD_accept() {
     var bookmarks = document.getElementById("bookmarks");
     if (!bookmarks.hasSelection)
       throw new Error("Should not be able to accept dialog if there is no selected URL!");
     var urls = [];
     var names = [];
     var selectedNode = bookmarks.selectedNode;
     if (PlacesUtils.nodeIsFolder(selectedNode)) {
-      var contents = PlacesUtils.getFolderContents(selectedNode.bookmarkGuid).root;
+      let concreteGuid = PlacesUtils.getConcreteItemGuid(selectedNode);
+      var contents = PlacesUtils.getFolderContents(concreteGuid).root;
       var cc = contents.childCount;
       for (var i = 0; i < cc; ++i) {
         var node = contents.getChild(i);
         if (PlacesUtils.nodeIsURI(node)) {
           urls.push(node.uri);
           names.push(node.title);
         }
       }
--- a/devtools/client/inspector/markup/test/browser_markup_events_chrome_blocked.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_chrome_blocked.js
@@ -1,26 +1,52 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 /* import-globals-from helper_events_test_runner.js */
+/* global sendAsyncMessage */
 
 "use strict";
 
-// Test that markup view event bubbles are hidden for <video> tags in the
-// content process when devtools.chrome.enabled=false.
-// <video> tags have 22 chrome listeners.
+// Test that markup view chrome event bubbles are hidden when
+// devtools.chrome.enabled = false.
 
 const TEST_URL = URL_ROOT + "doc_markup_events_chrome_listeners.html";
+const FRAMESCRIPT_URL = `data:,(${frameScript.toString()})()`;
 
 loadHelperScript("helper_events_test_runner.js");
 
 const TEST_DATA = [
   {
-    selector: "video",
-    expected: [ ],
+    selector: "div",
+    expected: [],
   },
 ];
 
 add_task(async function() {
+  waitForExplicitFinish();
   await pushPref("devtools.chrome.enabled", false);
-  await runEventPopupTests(TEST_URL, TEST_DATA);
+
+  const {tab, inspector, testActor} = await openInspectorForURL(TEST_URL);
+  const browser = tab.linkedBrowser;
+  const mm = browser.messageManager;
+
+  const badgeEventAdded = inspector.markup.once("badge-added-event");
+
+  info("Loading frame script");
+  mm.loadFrameScript(`${FRAMESCRIPT_URL}`, false);
+
+  // We need to check that the "badge-added-event" event is not triggered so we
+  // need to wait for 5 seconds here.
+  const result = await awaitWithTimeout(badgeEventAdded, 3000);
+  is(result, "timeout", "Ensure that no event badges were added");
+
+  for (const test of TEST_DATA) {
+    await checkEventsForNode(test, inspector, testActor);
+  }
 });
+
+function frameScript() {
+  const div = content.document.querySelector("div");
+  div.addEventListener("click", () => {
+   /* Do nothing */
+  });
+}
--- a/devtools/client/inspector/markup/test/browser_markup_events_chrome_not_blocked.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_chrome_not_blocked.js
@@ -1,74 +1,57 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 /* import-globals-from helper_events_test_runner.js */
+/* global sendAsyncMessage */
 
 "use strict";
 
-// Test that markup view event bubbles are shown for <video> tags in the
-// content process when devtools.chrome.enabled=true.
+// Test that markup view chrome event bubbles are shown when
+// devtools.chrome.enabled = true.
 
 const TEST_URL = URL_ROOT + "doc_markup_events_chrome_listeners.html";
+const FRAMESCRIPT_URL = `data:,(${frameScript.toString()})()`;
 
 loadHelperScript("helper_events_test_runner.js");
 
 const TEST_DATA = [
   {
-    selector: "video",
+    selector: "div",
     expected: [
-      createEvent("canplay"),
-      createEvent("canplaythrough"),
-      createEvent("emptied"),
-      createEvent("ended"),
-      createEvent("error"),
-      createEvent("keypress"),
-      createEvent("loadeddata"),
-      createEvent("loadedmetadata"),
-      createEvent("loadstart"),
-      createEvent("mozvideoonlyseekbegin"),
-      createEvent("mozvideoonlyseekcompleted"),
-      createEvent("pause"),
-      createEvent("play"),
-      createEvent("playing"),
-      createEvent("progress"),
-      createEvent("seeked"),
-      createEvent("seeking"),
-      createEvent("stalled"),
-      createEvent("suspend"),
-      createEvent("timeupdate"),
-      createEvent("volumechange"),
-      createEvent("waiting"),
+      {
+        type: "click",
+        filename: `${FRAMESCRIPT_URL}:1`,
+        attributes: [
+          "Bubbling",
+          "DOM2",
+        ],
+        handler: `() => { /* Do nothing */ }`,
+      },
     ],
   },
 ];
 
-function createEvent(type) {
-  return {
-    type: type,
-    filename: "chrome://global/content/elements/videocontrols.js:437",
-    attributes: [
-      "Capturing",
-      "DOM2",
-    ],
-    handler: `
-      ${type === "play" ? "function" : "handleEvent"}(aEvent) {
-        if (!aEvent.isTrusted) {
-          this.log("Drop untrusted event ----> " + aEvent.type);
-          return;
-        }
+add_task(async function() {
+  waitForExplicitFinish();
+  await pushPref("devtools.chrome.enabled", true);
+
+  const {tab, inspector, testActor} = await openInspectorForURL(TEST_URL);
+  const browser = tab.linkedBrowser;
+  const mm = browser.messageManager;
 
-        this.log("Got event ----> " + aEvent.type);
+  const eventBadgeAdded = inspector.markup.once("badge-added-event");
+  info("Loading frame script");
+  mm.loadFrameScript(`${FRAMESCRIPT_URL}`, false);
+  await eventBadgeAdded;
 
-        if (this.videoEvents.includes(aEvent.type)) {
-          this.handleVideoEvent(aEvent);
-        } else {
-          this.handleControlEvent(aEvent);
-        }
-      }`,
-  };
+  for (const test of TEST_DATA) {
+    await checkEventsForNode(test, inspector, testActor);
+  }
+});
+
+function frameScript() {
+  const div = content.document.querySelector("div");
+  div.addEventListener("click", () => {
+   /* Do nothing */
+  });
 }
-
-add_task(async function() {
-  await pushPref("devtools.chrome.enabled", true);
-  await runEventPopupTests(TEST_URL, TEST_DATA);
-});
--- a/devtools/client/inspector/markup/test/doc_markup_events_chrome_listeners.html
+++ b/devtools/client/inspector/markup/test/doc_markup_events_chrome_listeners.html
@@ -1,9 +1,9 @@
 <!DOCTYPE html>
 <html lang="en">
 <head>
 	<meta charset="utf-8">
 </head>
 <body>
-	<video controls></video>
+	<div></div>
 </body>
 </html>
\ No newline at end of file
--- a/devtools/client/inspector/markup/test/head.js
+++ b/devtools/client/inspector/markup/test/head.js
@@ -349,16 +349,46 @@ var isEditingMenuEnabled = async functio
  */
 function promiseNextTick() {
   return new Promise(resolve => {
     executeSoon(resolve);
   });
 }
 
 /**
+ * `await` with timeout.
+ *
+ * Usage:
+ *   const badgeEventAdded = inspector.markup.once("badge-added-event");
+ *   ...
+ *   const result = await awaitWithTimeout(badgeEventAdded, 3000);
+ *   is(result, "timeout", "Ensure that no event badges were added");
+ *
+ * @param  {Promise} promise
+ *         Promise to resolve
+ * @param  {Number} ms
+ *         Milliseconds to wait.
+ * @return "timeout" on timeout, otherwise the result of the fulfilled promise.
+ */
+async function awaitWithTimeout(promise, ms) {
+  const timeout = new Promise(resolve => {
+    // eslint-disable-next-line
+    const wait = setTimeout(() => {
+      clearTimeout(wait);
+      resolve("timeout");
+    }, ms);
+  });
+
+  return Promise.race([
+    promise,
+    timeout,
+  ]);
+}
+
+/**
  * Collapses the current text selection in an input field and tabs to the next
  * field.
  */
 function collapseSelectionAndTab(inspector) {
   // collapse selection and move caret to end
   EventUtils.sendKey("tab", inspector.panelWin);
   // next element
   EventUtils.sendKey("tab", inspector.panelWin);
--- a/devtools/client/inspector/markup/test/helper_events_test_runner.js
+++ b/devtools/client/inspector/markup/test/helper_events_test_runner.js
@@ -89,17 +89,18 @@ async function checkEventsForNode(test, 
   }
 
   // Check values
   const headers = tooltip.panel.querySelectorAll(".event-header");
   const nodeFront = container.node;
   const cssSelector = nodeFront.nodeName + "#" + nodeFront.id;
 
   for (let i = 0; i < headers.length; i++) {
-    info("Processing header[" + i + "] for " + cssSelector);
+    const label = `${cssSelector}.${expected[i].type} (index ${i})`;
+    info(`${label} START`);
 
     const header = headers[i];
     const type = header.querySelector(".event-tooltip-event-type");
     const filename = header.querySelector(".event-tooltip-filename");
     const attributes = header.querySelectorAll(".event-tooltip-attributes");
     const contentBox = header.nextElementSibling;
 
     info("Looking for " + type.textContent);
@@ -131,19 +132,23 @@ async function checkEventsForNode(test, 
         "We are in expanded state and icon changed");
 
     const editor = tooltip.eventTooltip._eventEditors.get(contentBox).editor;
     const tidiedHandler = beautify.js(expected[i].handler, {
       "indent_size": 2,
     });
     testDiff(editor.getText(), tidiedHandler,
        "handler matches for " + cssSelector, ok);
+
+    info(`${label} END`);
   }
 
+  const tooltipHidden = tooltip.once("hidden");
   tooltip.hide();
+  await tooltipHidden;
 }
 
 /**
  * Create diff of two strings.
  *
  * @param  {String} text1
  *         String to compare with text2.
  * @param  {String} text2 [description]
--- a/devtools/client/inspector/markup/views/element-editor.js
+++ b/devtools/client/inspector/markup/views/element-editor.js
@@ -289,16 +289,17 @@ ElementEditor.prototype = {
   _createEventBadge: function() {
     this._eventBadge = this.doc.createElement("div");
     this._eventBadge.className = "inspector-badge interactive";
     this._eventBadge.dataset.event = "true";
     this._eventBadge.textContent = "event";
     this._eventBadge.title = INSPECTOR_L10N.getStr("markupView.event.tooltiptext");
     // Badges order is [event][display][custom], insert event badge before others.
     this.elt.insertBefore(this._eventBadge, this._displayBadge || this._customBadge);
+    this.markup.emit("badge-added-event");
   },
 
   updateScrollableBadge: function() {
     if (this.node.isScrollable && !this._scrollableBadge) {
       this._createScrollableBadge();
     } else if (this._scrollableBadge && !this.node.isScrollable) {
       this._scrollableBadge.remove();
       this._scrollableBadge = null;
--- a/devtools/client/locales/en-US/netmonitor.properties
+++ b/devtools/client/locales/en-US/netmonitor.properties
@@ -715,16 +715,20 @@ netmonitor.toolbar.toggleRecording=Pause
 # LOCALIZATION NOTE (netmonitor.toolbar.perf): This is the label displayed
 # in the network toolbar for the performance analysis button.
 netmonitor.toolbar.perf=Toggle performance analysis…
 
 # LOCALIZATION NOTE (netmonitor.toolbar.resetColumns): This is the label
 # displayed in the network table header context menu.
 netmonitor.toolbar.resetColumns=Reset Columns
 
+# LOCALIZATION NOTE (netmonitor.toolbar.resetSorting): This is the label
+# displayed in the network table header context menu to reset sorting
+netmonitor.toolbar.resetSorting=Reset Sorting
+
 # LOCALIZATION NOTE (netmonitor.toolbar.timings): This is the label
 # displayed in the network table header context menu for the timing submenu
 netmonitor.toolbar.timings=Timings
 
 # LOCALIZATION NOTE (netmonitor.toolbar.responseHeaders): This is the
 # label displayed in the network table header context menu for the
 # response headers submenu.
 netmonitor.toolbar.responseHeaders=Response Headers
--- a/devtools/client/netmonitor/src/components/RequestListHeader.js
+++ b/devtools/client/netmonitor/src/components/RequestListHeader.js
@@ -39,16 +39,17 @@ const RESIZE_COLUMNS =
  * Displays tick marks in the waterfall column header.
  * Also draws the waterfall background canvas and updates it when needed.
  */
 class RequestListHeader extends Component {
   static get propTypes() {
     return {
       columns: PropTypes.object.isRequired,
       resetColumns: PropTypes.func.isRequired,
+      resetSorting: PropTypes.func.isRequired,
       resizeWaterfall: PropTypes.func.isRequired,
       scale: PropTypes.number,
       sort: PropTypes.object,
       sortBy: PropTypes.func.isRequired,
       toggleColumn: PropTypes.func.isRequired,
       waterfallWidth: PropTypes.number,
       columnsData: PropTypes.object.isRequired,
       setColumnsWidth: PropTypes.func.isRequired,
@@ -59,22 +60,24 @@ class RequestListHeader extends Componen
     super(props);
     this.requestListHeader = createRef();
 
     this.onContextMenu = this.onContextMenu.bind(this);
     this.drawBackground = this.drawBackground.bind(this);
     this.resizeWaterfall = this.resizeWaterfall.bind(this);
     this.waterfallDivisionLabels = this.waterfallDivisionLabels.bind(this);
     this.waterfallLabel = this.waterfallLabel.bind(this);
+    this.onHeaderClick = this.onHeaderClick.bind(this);
   }
 
   componentWillMount() {
-    const { resetColumns, toggleColumn } = this.props;
+    const { resetColumns, resetSorting, toggleColumn } = this.props;
     this.contextMenu = new RequestListHeaderContextMenu({
       resetColumns,
+      resetSorting,
       toggleColumn,
     });
   }
 
   componentDidMount() {
     // Create the object that takes care of drawing the waterfall canvas background
     this.background = new WaterfallBackground(document);
     this.drawBackground();
@@ -104,16 +107,26 @@ class RequestListHeader extends Componen
     removeThemeObserver(this.drawBackground);
   }
 
   onContextMenu(evt) {
     evt.preventDefault();
     this.contextMenu.open(evt, this.props.columns);
   }
 
+  onHeaderClick(evt, headerName) {
+    const { sortBy, resetSorting } = this.props;
+    if (evt.button == 1) {
+      // reset sort state on middle click
+      resetSorting();
+    } else {
+      sortBy(headerName);
+    }
+  }
+
   drawBackground() {
     // The background component is theme dependent, so add the current theme to the props.
     const props = Object.assign({}, this.props, {
       theme: getTheme(),
     });
     this.background.draw(props);
   }
 
@@ -480,17 +493,17 @@ class RequestListHeader extends Componen
     const columnsData = this.props.columnsData;
     const visibleColumns = this.getVisibleColumns();
     const lastVisibleColumn = visibleColumns[visibleColumns.length - 1].name;
     const name = header.name;
     const boxName = header.boxName || name;
     const label = header.noLocalization
       ? name : L10N.getStr(`netmonitor.toolbar.${header.label || name}`);
 
-    const { scale, sort, sortBy, waterfallWidth } = this.props;
+    const { scale, sort, waterfallWidth } = this.props;
     let sorted, sortedTitle;
     const active = sort.type == name ? true : undefined;
 
     if (active) {
       sorted = sort.ascending ? "ascending" : "descending";
       sortedTitle = L10N.getStr(sort.ascending
         ? "networkMenu.sortedAsc"
         : "networkMenu.sortedDesc");
@@ -525,17 +538,17 @@ class RequestListHeader extends Componen
         // Used to style the next column.
         "data-active": active,
       },
         button({
           id: `requests-list-${name}-button`,
           className: `requests-list-header-button`,
           "data-sorted": sorted,
           title: sortedTitle ? `${label} (${sortedTitle})` : label,
-          onClick: () => sortBy(name),
+          onClick: (evt) => this.onHeaderClick(evt, name),
         },
           name === "waterfall"
             ? this.waterfallLabel(waterfallWidth, scale, label)
             : div({ className: "button-text" }, label),
           div({ className: "button-icon" })
         ),
         (name !== lastVisibleColumn) && draggable
       )
@@ -574,14 +587,15 @@ module.exports = connect(
     firstRequestStartedMillis: state.requests.firstStartedMillis,
     scale: getWaterfallScale(state),
     sort: state.sort,
     timingMarkers: state.timingMarkers,
     waterfallWidth: state.ui.waterfallWidth,
   }),
   (dispatch) => ({
     resetColumns: () => dispatch(Actions.resetColumns()),
+    resetSorting: () => dispatch(Actions.sortBy(null)),
     resizeWaterfall: (width) => dispatch(Actions.resizeWaterfall(width)),
     sortBy: (type) => dispatch(Actions.sortBy(type)),
     toggleColumn: (column) => dispatch(Actions.toggleColumn(column)),
     setColumnsWidth: (widths) => dispatch(Actions.setColumnsWidth(widths)),
   })
 )(RequestListHeader);
--- a/devtools/client/netmonitor/src/reducers/sort.js
+++ b/devtools/client/netmonitor/src/reducers/sort.js
@@ -1,36 +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";
 
-const { SORT_BY } = require("../constants");
+const { SORT_BY, RESET_COLUMNS } = require("../constants");
 
 function Sort() {
   return {
     // null means: sort by "waterfall", but don't highlight the table header
     type: null,
     ascending: true,
   };
 }
 
 function sortReducer(state = new Sort(), action) {
   switch (action.type) {
     case SORT_BY: {
       state = { ...state };
-      if (action.sortType == state.type) {
+      if (action.sortType != null && action.sortType == state.type) {
         state.ascending = !state.ascending;
       } else {
         state.type = action.sortType;
         state.ascending = true;
       }
       return state;
     }
+
+    case RESET_COLUMNS: {
+      state = { ...state };
+      state.type = null;
+      state.ascending = true;
+      return state;
+    }
+
     default:
       return state;
   }
 }
 
 module.exports = {
   Sort,
   sortReducer,
--- a/devtools/client/netmonitor/src/widgets/RequestListHeaderContextMenu.js
+++ b/devtools/client/netmonitor/src/widgets/RequestListHeaderContextMenu.js
@@ -65,16 +65,22 @@ class RequestListHeaderContextMenu {
 
     menu.push({ type: "separator" });
     menu.push({
       id: "request-list-header-reset-columns",
       label: L10N.getStr("netmonitor.toolbar.resetColumns"),
       click: () => this.props.resetColumns(),
     });
 
+    menu.push({
+      id: "request-list-header-reset-sorting",
+      label: L10N.getStr("netmonitor.toolbar.resetSorting"),
+      click: () => this.props.resetSorting(),
+    });
+
     showMenu(menu, {
       screenX: event.screenX,
       screenY: event.screenY,
     });
   }
 }
 
 module.exports = RequestListHeaderContextMenu;
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -185,16 +185,17 @@ skip-if = os == 'win' # bug 1391264
 [browser_net_send-beacon-other-tab.js]
 [browser_net_set-cookie-same-site.js]
 [browser_net_simple-request-data.js]
 [browser_net_simple-request-details.js]
 skip-if = true # Bug 1258809
 [browser_net_simple-request.js]
 [browser_net_sort-01.js]
 [browser_net_sort-02.js]
+[browser_net_sort-reset.js]
 [browser_net_statistics-01.js]
 skip-if = true # Bug 1373558
 [browser_net_statistics-02.js]
 [browser_net_status-bar-transferred-size.js]
 [browser_net_status-bar.js]
 [browser_net_status-codes.js]
 [browser_net_streaming-response.js]
 [browser_net_telemetry_edit_resend.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_sort-reset.js
@@ -0,0 +1,220 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Test if sorting columns in the network table works correctly.
+ */
+
+add_task(async function() {
+  const { L10N } = require("devtools/client/netmonitor/src/utils/l10n");
+
+  const { monitor } = await initNetMonitor(SORTING_URL);
+  info("Starting test... ");
+
+  // It seems that this test may be slow on debug builds. This could be because
+  // of the heavy dom manipulation associated with sorting.
+  requestLongerTimeout(2);
+
+  const { parent, document, store, windowRequire } = monitor.panelWin;
+  const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
+  const {
+    getDisplayedRequests,
+    getSelectedRequest,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/src/selectors/index");
+
+  store.dispatch(Actions.batchEnable(false));
+
+  // Loading the frame script and preparing the xhr request URLs so we can
+  // generate some requests later.
+  loadFrameScriptUtils();
+  const requests = [{
+    url: "sjs_sorting-test-server.sjs?index=1&" + Math.random(),
+    method: "GET1",
+  }, {
+    url: "sjs_sorting-test-server.sjs?index=5&" + Math.random(),
+    method: "GET5",
+  }, {
+    url: "sjs_sorting-test-server.sjs?index=2&" + Math.random(),
+    method: "GET2",
+  }, {
+    url: "sjs_sorting-test-server.sjs?index=4&" + Math.random(),
+    method: "GET4",
+  }, {
+    url: "sjs_sorting-test-server.sjs?index=3&" + Math.random(),
+    method: "GET3",
+  }];
+
+  const wait = waitForNetworkEvents(monitor, 5);
+  await performRequestsInContent(requests);
+  await wait;
+
+  store.dispatch(Actions.toggleNetworkDetails());
+
+  testHeaders();
+  await testContents([0, 2, 4, 3, 1]);
+
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#requests-list-status-button"));
+  info("Testing sort reset using middle click.");
+  EventUtils.sendMouseEvent({ type: "click", button: 1 },
+    document.querySelector("#requests-list-status-button"));
+  testHeaders();
+  await testContents([0, 2, 4, 3, 1]);
+
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#requests-list-status-button"));
+  info("Testing sort reset using context menu 'Reset Sorting'");
+  EventUtils.sendMouseEvent({ type: "contextmenu" },
+    document.querySelector("#requests-list-contentSize-button"));
+  parent.document.querySelector("#request-list-header-reset-sorting").click();
+  testHeaders();
+  await testContents([0, 2, 4, 3, 1]);
+
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#requests-list-status-button"));
+  info("Testing sort reset using context menu 'Reset Columns'");
+  EventUtils.sendMouseEvent({ type: "contextmenu" },
+    document.querySelector("#requests-list-contentSize-button"));
+  parent.document.querySelector("#request-list-header-reset-columns").click();
+  testHeaders();
+  // add columns because verifyRequestItemTarget expects some extra columns
+  showColumn(monitor, "protocol");
+  showColumn(monitor, "remoteip");
+  showColumn(monitor, "scheme");
+  showColumn(monitor, "duration");
+  showColumn(monitor, "latency");
+  await testContents([0, 2, 4, 3, 1]);
+
+  return teardown(monitor);
+
+  function getSelectedIndex(state) {
+    if (!state.requests.selectedId) {
+      return -1;
+    }
+    return getSortedRequests(state).findIndex(r => r.id === state.requests.selectedId);
+  }
+
+  function testHeaders(sortType, direction) {
+    const doc = monitor.panelWin.document;
+    const target = doc.querySelector("#requests-list-" + sortType + "-button");
+    const headers = doc.querySelectorAll(".requests-list-header-button");
+
+    for (const header of headers) {
+      if (header != target) {
+        ok(!header.hasAttribute("data-sorted"),
+          "The " + header.id + " header does not have a 'data-sorted' attribute.");
+        ok(!header.getAttribute("title").includes(L10N.getStr("networkMenu.sortedAsc")) &&
+          !header.getAttribute("title").includes(L10N.getStr("networkMenu.sortedDesc")),
+          "The " + header.id +
+          " header does not include any sorting in the 'title' attribute.");
+      } else {
+        is(header.getAttribute("data-sorted"), direction,
+          "The " + header.id + " header has a correct 'data-sorted' attribute.");
+        const sorted = direction == "ascending"
+          ? L10N.getStr("networkMenu.sortedAsc")
+          : L10N.getStr("networkMenu.sortedDesc");
+        ok(header.getAttribute("title").includes(sorted),
+          "The " + header.id +
+          " header includes the used sorting in the 'title' attribute.");
+      }
+    }
+  }
+
+  async function testContents([a, b, c, d, e]) {
+    isnot(getSelectedRequest(store.getState()), undefined,
+      "There should still be a selected item after sorting.");
+    is(getSelectedIndex(store.getState()), a,
+      "The first item should be still selected after sorting.");
+    is(!!document.querySelector(".network-details-panel"), true,
+      "The network details panel should still be visible after sorting.");
+
+    is(getSortedRequests(store.getState()).length, 5,
+      "There should be a total of 5 items in the requests menu.");
+    is(getDisplayedRequests(store.getState()).length, 5,
+      "There should be a total of 5 visible items in the requests menu.");
+    is(document.querySelectorAll(".request-list-item").length, 5,
+      "The visible items in the requests menu are, in fact, visible!");
+
+    const requestItems = document.querySelectorAll(".request-list-item");
+    for (const requestItem of requestItems) {
+      requestItem.scrollIntoView();
+      const requestsListStatus = requestItem.querySelector(".status-code");
+      EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
+      await waitUntil(() => requestsListStatus.title);
+    }
+
+    verifyRequestItemTarget(
+      document,
+      getDisplayedRequests(store.getState()),
+      getSortedRequests(store.getState()).get(a),
+      "GET1", SORTING_SJS + "?index=1", {
+        fuzzyUrl: true,
+        status: 101,
+        statusText: "Meh",
+        type: "1",
+        fullMimeType: "text/1",
+        transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 198),
+        size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 0),
+        time: true,
+      });
+    verifyRequestItemTarget(
+      document,
+      getDisplayedRequests(store.getState()),
+      getSortedRequests(store.getState()).get(b),
+      "GET2", SORTING_SJS + "?index=2", {
+        fuzzyUrl: true,
+        status: 200,
+        statusText: "Meh",
+        type: "2",
+        fullMimeType: "text/2",
+        transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 217),
+        size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 19),
+        time: true,
+      });
+    verifyRequestItemTarget(
+      document,
+      getDisplayedRequests(store.getState()),
+      getSortedRequests(store.getState()).get(c),
+      "GET3", SORTING_SJS + "?index=3", {
+        fuzzyUrl: true,
+        status: 300,
+        statusText: "Meh",
+        type: "3",
+        fullMimeType: "text/3",
+        transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 227),
+        size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 29),
+        time: true,
+      });
+    verifyRequestItemTarget(
+      document,
+      getDisplayedRequests(store.getState()),
+      getSortedRequests(store.getState()).get(d),
+      "GET4", SORTING_SJS + "?index=4", {
+        fuzzyUrl: true,
+        status: 400,
+        statusText: "Meh",
+        type: "4",
+        fullMimeType: "text/4",
+        transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 237),
+        size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 39),
+        time: true,
+      });
+    verifyRequestItemTarget(
+      document,
+      getDisplayedRequests(store.getState()),
+      getSortedRequests(store.getState()).get(e),
+      "GET5", SORTING_SJS + "?index=5", {
+        fuzzyUrl: true,
+        status: 500,
+        statusText: "Meh",
+        type: "5",
+        fullMimeType: "text/5",
+        transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 247),
+        size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 49),
+        time: true,
+      });
+  }
+});
--- a/devtools/client/shared/widgets/Graphs.js
+++ b/devtools/client/shared/widgets/Graphs.js
@@ -1,17 +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";
 
 const { setNamedTimeout } = require("devtools/client/shared/widgets/view-helpers");
 const { getCurrentZoom } = require("devtools/shared/layout/utils");
 
-loader.lazyRequireGetter(this, "defer", "devtools/shared/defer");
 loader.lazyRequireGetter(this, "EventEmitter",
   "devtools/shared/event-emitter");
 
 loader.lazyImporter(this, "DevToolsWorker",
   "resource://devtools/shared/worker/worker.js");
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const GRAPH_SRC = "chrome://devtools/content/shared/widgets/graphs-frame.xhtml";
@@ -89,74 +88,74 @@ this.GraphAreaResizer = function() {
  *        Currently supported: "line-graph" only.
  * @param number sharpness [optional]
  *        Defaults to the current device pixel ratio.
  */
 this.AbstractCanvasGraph = function(parent, name, sharpness) {
   EventEmitter.decorate(this);
 
   this._parent = parent;
-  this._ready = defer();
-
   this._uid = "canvas-graph-" + Date.now();
   this._renderTargets = new Map();
 
-  AbstractCanvasGraph.createIframe(GRAPH_SRC, parent, iframe => {
-    this._iframe = iframe;
-    this._window = iframe.contentWindow;
-    this._topWindow = this._window.top;
-    this._document = iframe.contentDocument;
-    this._pixelRatio = sharpness || this._window.devicePixelRatio;
+  this._ready = new Promise(resolve => {
+    AbstractCanvasGraph.createIframe(GRAPH_SRC, parent, iframe => {
+      this._iframe = iframe;
+      this._window = iframe.contentWindow;
+      this._topWindow = this._window.top;
+      this._document = iframe.contentDocument;
+      this._pixelRatio = sharpness || this._window.devicePixelRatio;
 
-    const container =
-      this._container = this._document.getElementById("graph-container");
-    container.className = name + "-widget-container graph-widget-container";
+      const container =
+        this._container = this._document.getElementById("graph-container");
+      container.className = name + "-widget-container graph-widget-container";
 
-    const canvas = this._canvas = this._document.getElementById("graph-canvas");
-    canvas.className = name + "-widget-canvas graph-widget-canvas";
+      const canvas = this._canvas = this._document.getElementById("graph-canvas");
+      canvas.className = name + "-widget-canvas graph-widget-canvas";
 
-    const bounds = parent.getBoundingClientRect();
-    bounds.width = this.fixedWidth || bounds.width;
-    bounds.height = this.fixedHeight || bounds.height;
-    iframe.setAttribute("width", bounds.width);
-    iframe.setAttribute("height", bounds.height);
+      const bounds = parent.getBoundingClientRect();
+      bounds.width = this.fixedWidth || bounds.width;
+      bounds.height = this.fixedHeight || bounds.height;
+      iframe.setAttribute("width", bounds.width);
+      iframe.setAttribute("height", bounds.height);
 
-    this._width = canvas.width = bounds.width * this._pixelRatio;
-    this._height = canvas.height = bounds.height * this._pixelRatio;
-    this._ctx = canvas.getContext("2d");
-    this._ctx.imageSmoothingEnabled = false;
+      this._width = canvas.width = bounds.width * this._pixelRatio;
+      this._height = canvas.height = bounds.height * this._pixelRatio;
+      this._ctx = canvas.getContext("2d");
+      this._ctx.imageSmoothingEnabled = false;
 
-    this._cursor = new GraphCursor();
-    this._selection = new GraphArea();
-    this._selectionDragger = new GraphAreaDragger();
-    this._selectionResizer = new GraphAreaResizer();
-    this._isMouseActive = false;
+      this._cursor = new GraphCursor();
+      this._selection = new GraphArea();
+      this._selectionDragger = new GraphAreaDragger();
+      this._selectionResizer = new GraphAreaResizer();
+      this._isMouseActive = false;
+
+      this._onAnimationFrame = this._onAnimationFrame.bind(this);
+      this._onMouseMove = this._onMouseMove.bind(this);
+      this._onMouseDown = this._onMouseDown.bind(this);
+      this._onMouseUp = this._onMouseUp.bind(this);
+      this._onMouseWheel = this._onMouseWheel.bind(this);
+      this._onMouseOut = this._onMouseOut.bind(this);
+      this._onResize = this._onResize.bind(this);
+      this.refresh = this.refresh.bind(this);
 
-    this._onAnimationFrame = this._onAnimationFrame.bind(this);
-    this._onMouseMove = this._onMouseMove.bind(this);
-    this._onMouseDown = this._onMouseDown.bind(this);
-    this._onMouseUp = this._onMouseUp.bind(this);
-    this._onMouseWheel = this._onMouseWheel.bind(this);
-    this._onMouseOut = this._onMouseOut.bind(this);
-    this._onResize = this._onResize.bind(this);
-    this.refresh = this.refresh.bind(this);
+      this._window.addEventListener("mousemove", this._onMouseMove);
+      this._window.addEventListener("mousedown", this._onMouseDown);
+      this._window.addEventListener("MozMousePixelScroll", this._onMouseWheel);
+      this._window.addEventListener("mouseout", this._onMouseOut);
 
-    this._window.addEventListener("mousemove", this._onMouseMove);
-    this._window.addEventListener("mousedown", this._onMouseDown);
-    this._window.addEventListener("MozMousePixelScroll", this._onMouseWheel);
-    this._window.addEventListener("mouseout", this._onMouseOut);
+      const ownerWindow = this._parent.ownerDocument.defaultView;
+      ownerWindow.addEventListener("resize", this._onResize);
 
-    const ownerWindow = this._parent.ownerDocument.defaultView;
-    ownerWindow.addEventListener("resize", this._onResize);
+      this._animationId =
+        this._window.requestAnimationFrame(this._onAnimationFrame);
 
-    this._animationId =
-      this._window.requestAnimationFrame(this._onAnimationFrame);
-
-    this._ready.resolve(this);
-    this.emit("ready", this);
+      resolve(this);
+      this.emit("ready", this);
+    });
   });
 };
 
 AbstractCanvasGraph.prototype = {
   /**
    * Read-only width and height of the canvas.
    * @return number
    */
@@ -174,17 +173,17 @@ AbstractCanvasGraph.prototype = {
   get isMouseActive() {
     return this._isMouseActive;
   },
 
   /**
    * Returns a promise resolved once this graph is ready to receive data.
    */
   ready: function() {
-    return this._ready.promise;
+    return this._ready;
   },
 
   /**
    * Destroys this graph.
    */
   async destroy() {
     await this.ready();
 
--- a/docshell/base/BrowsingContext.cpp
+++ b/docshell/base/BrowsingContext.cpp
@@ -172,22 +172,25 @@ BrowsingContext::BrowsingContext(Browsin
                                  uint64_t aBrowsingContextId, Type aType)
     : mName(aName),
       mClosed(false),
       mType(aType),
       mBrowsingContextId(aBrowsingContextId),
       mParent(aParent),
       mOpener(aOpener),
       mIsActivatedByUserGesture(false) {
+  mCrossOriginPolicy = nsILoadInfo::CROSS_ORIGIN_POLICY_NULL;
   // Specify our group in our constructor. We will explicitly join the group
   // when we are registered, as doing so will take a reference.
   if (mParent) {
     mGroup = mParent->Group();
+    mCrossOriginPolicy = mParent->CrossOriginPolicy();
   } else if (mOpener) {
     mGroup = mOpener->Group();
+    mCrossOriginPolicy = mOpener->CrossOriginPolicy();
   } else {
     // To ensure the group has a unique ID, we will use our ID, as the founder
     // of this BrowsingContextGroup.
     mGroup = new BrowsingContextGroup();
   }
 }
 
 void BrowsingContext::SetDocShell(nsIDocShell* aDocShell) {
--- a/docshell/base/BrowsingContext.h
+++ b/docshell/base/BrowsingContext.h
@@ -13,16 +13,17 @@
 #include "mozilla/WeakPtr.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIDocShell.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "nsWrapperCache.h"
+#include "nsILoadInfo.h"
 
 class nsGlobalWindowOuter;
 class nsIPrincipal;
 class nsOuterWindowProxy;
 class PickleIterator;
 
 namespace IPC {
 class Message;
@@ -64,16 +65,17 @@ class WindowProxyHolder;
 //
 // At all times the last line below should be __VA_ARGS__, since that
 // acts as a sentinel for callers of MOZ_FOR_EACH_SYNCED_FIELD.
 
 // clang-format off
 #define MOZ_FOR_EACH_SYNCED_BC_FIELD(declare, ...)        \
   declare(Name, nsString, nsAString)                   \
   declare(Closed, bool, bool)                          \
+  declare(CrossOriginPolicy, nsILoadInfo::CrossOriginPolicy, nsILoadInfo::CrossOriginPolicy) \
   __VA_ARGS__
 // clang-format on
 
 #define MOZ_SYNCED_BC_FIELD_NAME(name, ...) m##name
 #define MOZ_SYNCED_BC_FIELD_ARGUMENT(name, type, atype) \
   transaction->MOZ_SYNCED_BC_FIELD_NAME(name),
 #define MOZ_SYNCED_BC_FIELD_GETTER(name, type, atype) \
   const type& Get##name() const { return MOZ_SYNCED_BC_FIELD_NAME(name); }
@@ -283,16 +285,20 @@ class BrowsingContext : public nsWrapper
                       const Sequence<JSObject*>& aTransfer,
                       nsIPrincipal& aSubjectPrincipal, ErrorResult& aError);
   void PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> aMessage,
                       const WindowPostMessageOptions& aOptions,
                       nsIPrincipal& aSubjectPrincipal, ErrorResult& aError);
 
   JSObject* WrapObject(JSContext* aCx);
 
+  nsILoadInfo::CrossOriginPolicy CrossOriginPolicy() {
+    return mCrossOriginPolicy;
+  }
+
  protected:
   virtual ~BrowsingContext();
   BrowsingContext(BrowsingContext* aParent, BrowsingContext* aOpener,
                   const nsAString& aName, uint64_t aBrowsingContextId,
                   Type aType);
 
  private:
   // Find the special browsing context if aName is '_self', '_parent',
--- a/dom/fetch/FetchDriver.cpp
+++ b/dom/fetch/FetchDriver.cpp
@@ -419,16 +419,34 @@ nsresult FetchDriver::HttpFetch(
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsAutoCString url;
   mRequest->GetURL(url);
   nsCOMPtr<nsIURI> uri;
   rv = NS_NewURI(getter_AddRefs(uri), url, nullptr, nullptr, ios);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  if (StaticPrefs::browser_tabs_remote_useCrossOriginPolicy()) {
+    // Cross-Origin policy - bug 1525036
+    nsILoadInfo::CrossOriginPolicy corsCredentials =
+        nsILoadInfo::CROSS_ORIGIN_POLICY_NULL;
+    if (mDocument && mDocument->GetBrowsingContext()) {
+      corsCredentials = mDocument->GetBrowsingContext()->CrossOriginPolicy();
+    }  // TODO Bug 1532287: else use mClientInfo
+
+    if (mRequest->Mode() == RequestMode::No_cors &&
+        corsCredentials != nsILoadInfo::CROSS_ORIGIN_POLICY_NULL) {
+      mRequest->SetMode(RequestMode::Cors);
+      mRequest->SetCredentialsMode(RequestCredentials::Same_origin);
+      if (corsCredentials == nsILoadInfo::CROSS_ORIGIN_POLICY_USE_CREDENTIALS) {
+        mRequest->SetCredentialsMode(RequestCredentials::Include);
+      }
+    }
+  }
+
   // Unsafe requests aren't allowed with when using no-core mode.
   if (mRequest->Mode() == RequestMode::No_cors && mRequest->UnsafeRequest() &&
       (!mRequest->HasSimpleMethod() ||
        !mRequest->Headers()->HasOnlySimpleHeaders())) {
     MOZ_ASSERT(false, "The API should have caught this");
     return NS_ERROR_DOM_BAD_URI;
   }
 
--- a/dom/ipc/BrowserBridgeParent.cpp
+++ b/dom/ipc/BrowserBridgeParent.cpp
@@ -12,17 +12,21 @@ using namespace mozilla::ipc;
 using namespace mozilla::layout;
 using namespace mozilla::hal;
 
 namespace mozilla {
 namespace dom {
 
 BrowserBridgeParent::BrowserBridgeParent() : mIPCOpen(false) {}
 
-BrowserBridgeParent::~BrowserBridgeParent() {}
+BrowserBridgeParent::~BrowserBridgeParent() {
+  if (mTabParent) {
+    mTabParent->mBrowserBridgeParent = nullptr;
+  }
+}
 
 nsresult BrowserBridgeParent::Init(const nsString& aPresentationURL,
                                    const nsString& aRemoteType) {
   mIPCOpen = true;
 
   // FIXME: This should actually use a non-bogus TabContext, probably inherited
   // from our Manager().
   OriginAttributes attrs;
@@ -48,17 +52,17 @@ nsresult BrowserBridgeParent::Init(const
   TabId tabId(nsContentUtils::GenerateTabId());
   cpm->RegisterRemoteFrame(tabId, ContentParentId(0), TabId(0),
                            tabContext.AsIPCTabContext(),
                            constructorSender->ChildID());
 
   // Construct the TabParent object for our subframe.
   uint32_t chromeFlags = 0;
   RefPtr<TabParent> tabParent(
-      new TabParent(constructorSender, tabId, tabContext, chromeFlags));
+      new TabParent(constructorSender, tabId, tabContext, chromeFlags, this));
 
   PBrowserParent* browser = constructorSender->SendPBrowserConstructor(
       // DeallocPBrowserParent() releases this ref.
       tabParent.forget().take(), tabId, TabId(0), tabContext.AsIPCTabContext(),
       chromeFlags, constructorSender->ChildID(),
       constructorSender->IsForBrowser());
   if (NS_WARN_IF(!browser)) {
     MOZ_ASSERT(false, "Browser Constructor Failed");
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -139,17 +139,18 @@ namespace mozilla {
 namespace dom {
 
 TabParent::LayerToTabParentTable* TabParent::sLayerToTabParentTable = nullptr;
 
 NS_IMPL_ISUPPORTS(TabParent, nsITabParent, nsIAuthPromptProvider,
                   nsISupportsWeakReference)
 
 TabParent::TabParent(ContentParent* aManager, const TabId& aTabId,
-                     const TabContext& aContext, uint32_t aChromeFlags)
+                     const TabContext& aContext, uint32_t aChromeFlags,
+                     BrowserBridgeParent* aBrowserBridgeParent)
     : TabContext(aContext),
       mFrameElement(nullptr),
       mContentCache(*this),
       mRect(0, 0, 0, 0),
       mDimensions(0, 0),
       mOrientation(0),
       mDPI(0),
       mRounding(0),
@@ -157,16 +158,17 @@ TabParent::TabParent(ContentParent* aMan
       mUpdatedDimensions(false),
       mSizeMode(nsSizeMode_Normal),
       mManager(aManager),
       mDocShellIsActive(false),
       mMarkedDestroying(false),
       mIsDestroyed(false),
       mChromeFlags(aChromeFlags),
       mDragValid(false),
+      mBrowserBridgeParent(aBrowserBridgeParent),
       mTabId(aTabId),
       mCreatingWindow(false),
       mCursor(eCursorInvalid),
       mCustomCursorHotspotX(0),
       mCustomCursorHotspotY(0),
       mTabSetsCursor(false),
       mHasContentOpener(false)
 #ifdef DEBUG
@@ -2315,16 +2317,20 @@ TabId TabParent::GetTabIdFrom(nsIDocShel
 
 RenderFrame* TabParent::GetRenderFrame() {
   if (!mRenderFrame.IsInitialized()) {
     return nullptr;
   }
   return &mRenderFrame;
 }
 
+BrowserBridgeParent* TabParent::GetBrowserBridgeParent() const {
+  return mBrowserBridgeParent;
+}
+
 mozilla::ipc::IPCResult TabParent::RecvRequestIMEToCommitComposition(
     const bool& aCancel, bool* aIsCommitted, nsString* aCommittedString) {
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget) {
     *aIsCommitted = false;
     return IPC_OK();
   }
 
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -67,46 +67,49 @@ class DataSourceSurface;
 
 namespace dom {
 
 class CanonicalBrowsingContext;
 class ClonedMessageData;
 class ContentParent;
 class Element;
 class DataTransfer;
+class BrowserBridgeParent;
 
 namespace ipc {
 class StructuredCloneData;
 }  // namespace ipc
 
 class TabParent final : public PBrowserParent,
                         public nsIDOMEventListener,
                         public nsITabParent,
                         public nsIAuthPromptProvider,
                         public nsIKeyEventInPluginCallback,
                         public nsSupportsWeakReference,
                         public TabContext,
                         public LiveResizeListener {
   typedef mozilla::dom::ClonedMessageData ClonedMessageData;
 
   friend class PBrowserParent;
+  friend class BrowserBridgeParent;  // for clearing mBrowserBridgeParent
 
   virtual ~TabParent();
 
  public:
   // Helper class for ContentParent::RecvCreateWindow.
   struct AutoUseNewTab;
 
   // nsITabParent
   NS_DECL_NSITABPARENT
   // nsIDOMEventListener interfaces
   NS_DECL_NSIDOMEVENTLISTENER
 
   TabParent(ContentParent* aManager, const TabId& aTabId,
-            const TabContext& aContext, uint32_t aChromeFlags);
+            const TabContext& aContext, uint32_t aChromeFlags,
+            BrowserBridgeParent* aBrowserBridgeParent = nullptr);
 
   Element* GetOwnerElement() const { return mFrameElement; }
   already_AddRefed<nsPIDOMWindowOuter> GetParentWindowOuter();
 
   void SetOwnerElement(Element* aElement);
 
   void CacheFrameLoader(nsFrameLoader* aFrameLoader);
 
@@ -565,16 +568,20 @@ class TabParent final : public PBrowserP
   void AddInitialDnDDataTo(DataTransfer* aDataTransfer,
                            nsIPrincipal** aPrincipal);
 
   bool TakeDragVisualization(RefPtr<mozilla::gfx::SourceSurface>& aSurface,
                              LayoutDeviceIntRect* aDragRect);
 
   layout::RenderFrame* GetRenderFrame();
 
+  // Returns the BrowserBridgeParent if this TabParent is for an out-of-process
+  // iframe and nullptr otherwise.
+  BrowserBridgeParent* GetBrowserBridgeParent() const;
+
   mozilla::ipc::IPCResult RecvEnsureLayersConnected(
       CompositorOptions* aCompositorOptions);
 
   // LiveResizeListener implementation
   void LiveResizeStarted() override;
   void LiveResizeStopped() override;
 
   void SetReadyToHandleInputEvents() { mIsReadyToHandleInputEvents = true; }
@@ -699,16 +706,22 @@ class TabParent final : public PBrowserP
   // We keep a strong reference to the frameloader after we've sent the
   // Destroy message and before we've received __delete__. This allows us to
   // dispatch message manager messages during this time.
   RefPtr<nsFrameLoader> mFrameLoader;
 
   // The root browsing context loaded in this TabParent.
   RefPtr<CanonicalBrowsingContext> mBrowsingContext;
 
+  // Pointer back to BrowserBridgeParent if there is one associated with
+  // this TabParent. This is non-owning to avoid cycles and is managed
+  // by the BrowserBridgeParent instance, which has the strong reference
+  // to this TabParent.
+  BrowserBridgeParent* mBrowserBridgeParent;
+
   TabId mTabId;
 
   // When loading a new tab or window via window.open, the child is
   // responsible for loading the URL it wants into the new TabChild. When the
   // parent receives the CreateWindow message, though, it sends a LoadURL
   // message, usually for about:blank. It's important for the about:blank load
   // to get processed because the Firefox frontend expects every new window to
   // immediately start loading something (see bug 1123090). However, we want
--- a/dom/media/PeerConnection.jsm
+++ b/dom/media/PeerConnection.jsm
@@ -345,19 +345,19 @@ class RTCPeerConnection {
   constructor() {
     this._receiveStreams = new Map();
     // Used to fire onaddstream, remove when we don't do that anymore.
     this._newStreams = [];
     this._transceivers = [];
 
     this._pc = null;
     this._closed = false;
+    this._currentRole = null;
+    this._pendingRole = null;
 
-    this._localType = null;
-    this._remoteType = null;
     // http://rtcweb-wg.github.io/jsep/#rfc.section.4.1.9
     // canTrickle == null means unknown; when a remote description is received it
     // is set to true or false based on the presence of the "trickle" ice-option
     this._canTrickle = null;
 
     // States
     this._iceGatheringState = this._iceConnectionState = "new";
 
@@ -942,30 +942,34 @@ class RTCPeerConnection {
 
   setLocalDescription(desc, onSucc, onErr) {
     return this._auto(onSucc, onErr, () => this._setLocalDescription(desc));
   }
 
   async _setLocalDescription({ type, sdp }) {
     this._checkClosed();
 
-    this._localType = type;
-
     let action = this._actions[type];
 
     this._sanityCheckSdp(action, type, sdp);
 
     return this._chain(async () => {
       await this._getPermission();
       await new Promise((resolve, reject) => {
         this._onSetLocalDescriptionSuccess = resolve;
         this._onSetLocalDescriptionFailure = reject;
         this._impl.setLocalDescription(action, sdp);
       });
       this._negotiationNeeded = false;
+      if (type == "answer") {
+        this._currentRole = "answerer";
+        this._pendingRole = null;
+      } else {
+        this._pendingRole = "offerer";
+      }
       this.updateNegotiationNeeded();
     });
   }
 
   async _validateIdentity(sdp, origin) {
     let expectedIdentity;
 
     // Only run a single identity verification at a time.  We have to do this to
@@ -1011,17 +1015,16 @@ class RTCPeerConnection {
   }
 
   setRemoteDescription(desc, onSucc, onErr) {
     return this._auto(onSucc, onErr, () => this._setRemoteDescription(desc));
   }
 
   async _setRemoteDescription({ type, sdp }) {
     this._checkClosed();
-    this._remoteType = type;
 
     let action = this._actions[type];
 
     this._sanityCheckSdp(action, type, sdp);
 
     // Get caller's origin before hitting the promise chain
     let origin = Cu.getWebIDLCallerPrincipal().origin;
 
@@ -1037,16 +1040,22 @@ class RTCPeerConnection {
       })();
 
       if (action != Ci.IPeerConnection.kActionRollback) {
         // Do setRemoteDescription and identity validation in parallel
         await this._validateIdentity(sdp, origin);
       }
       await haveSetRemote;
       this._negotiationNeeded = false;
+      if (type == "answer") {
+        this._currentRole = "offerer";
+        this._pendingRole = null;
+      } else {
+        this._pendingRole = "answerer";
+      }
       this.updateNegotiationNeeded();
     });
   }
 
   setIdentityProvider(provider,
                       {protocol, usernameHint, peerIdentity} = {}) {
     this._checkClosed();
     this._localIdp.setIdentityProvider(provider,
@@ -1436,67 +1445,62 @@ class RTCPeerConnection {
     this._impl.disablePacketDump(level, type, sending);
   }
 
   getTransceivers() {
     return this._transceivers;
   }
 
   get localDescription() {
-    this._checkClosed();
-    let sdp = this._impl.localDescription;
-    if (sdp.length == 0) {
-      return null;
-    }
-    return new this._win.RTCSessionDescription({ type: this._localType, sdp });
+    return this.pendingLocalDescription || this.currentLocalDescription;
   }
 
   get currentLocalDescription() {
     this._checkClosed();
     let sdp = this._impl.currentLocalDescription;
     if (sdp.length == 0) {
       return null;
     }
-    return new this._win.RTCSessionDescription({ type: this._localType, sdp });
+    const type = this._currentRole == "answerer" ? "answer" : "offer";
+    return new this._win.RTCSessionDescription({ type, sdp });
   }
 
   get pendingLocalDescription() {
     this._checkClosed();
     let sdp = this._impl.pendingLocalDescription;
     if (sdp.length == 0) {
       return null;
     }
-    return new this._win.RTCSessionDescription({ type: this._localType, sdp });
+    const type = this._pendingRole == "answerer" ? "answer" : "offer";
+    return new this._win.RTCSessionDescription({ type, sdp });
   }
 
+
   get remoteDescription() {
-    this._checkClosed();
-    let sdp = this._impl.remoteDescription;
-    if (sdp.length == 0) {
-      return null;
-    }
-    return new this._win.RTCSessionDescription({ type: this._remoteType, sdp });
+    return this.pendingRemoteDescription || this.currentRemoteDescription;
   }
 
   get currentRemoteDescription() {
     this._checkClosed();
     let sdp = this._impl.currentRemoteDescription;
     if (sdp.length == 0) {
       return null;
     }
-    return new this._win.RTCSessionDescription({ type: this._remoteType, sdp });
+    const type = this._currentRole == "offerer" ? "answer" : "offer";
+    return new this._win.RTCSessionDescription({ type, sdp });
   }
 
   get pendingRemoteDescription() {
     this._checkClosed();
     let sdp = this._impl.pendingRemoteDescription;
     if (sdp.length == 0) {
       return null;
     }
-    return new this._win.RTCSessionDescription({ type: this._remoteType, sdp });
+    const type = this._pendingRole == "offerer" ? "answer" : "offer";
+    return new this._win.RTCSessionDescription({ type, sdp });
   }
 
   get peerIdentity() { return this._peerIdentity; }
   get idpLoginUrl() { return this._localIdp.idpLoginUrl; }
   get id() { return this._impl.id; }
   set id(s) { this._impl.id = s; }
   get iceGatheringState() { return this._iceGatheringState; }
   get iceConnectionState() { return this._iceConnectionState; }
@@ -1659,22 +1663,20 @@ class PeerConnectionObserver {
     this._dompc._syncTransceivers();
     this._dompc._processTrackAdditionsAndRemovals();
     this._dompc._fireLegacyAddStreamEvents();
     this._dompc._transceivers = this._dompc._transceivers.filter(t => !t.shouldRemove);
     this._dompc._onSetRemoteDescriptionSuccess();
   }
 
   onSetLocalDescriptionError(code, message) {
-    this._localType = null;
     this._dompc._onSetLocalDescriptionFailure(this.newError(message, code));
   }
 
   onSetRemoteDescriptionError(code, message) {
-    this._remoteType = null;
     this._dompc._onSetRemoteDescriptionFailure(this.newError(message, code));
   }
 
   onAddIceCandidateSuccess() {
     this._dompc._onAddIceCandidateSuccess();
   }
 
   onAddIceCandidateError(code, message) {
--- a/dom/webidl/PeerConnectionImpl.webidl
+++ b/dom/webidl/PeerConnectionImpl.webidl
@@ -106,20 +106,18 @@ interface PeerConnectionImpl  {
   /* Attributes */
   /* This provides the implementation with the certificate it uses to
    * authenticate itself.  The JS side must set this before calling
    * createOffer/createAnswer or retrieving the value of fingerprint.  This has
    * to be delayed because generating the certificate takes some time. */
   attribute RTCCertificate certificate;
   [Constant]
   readonly attribute DOMString fingerprint;
-  readonly attribute DOMString localDescription;
   readonly attribute DOMString currentLocalDescription;
   readonly attribute DOMString pendingLocalDescription;
-  readonly attribute DOMString remoteDescription;
   readonly attribute DOMString currentRemoteDescription;
   readonly attribute DOMString pendingRemoteDescription;
 
   readonly attribute PCImplIceConnectionState iceConnectionState;
   readonly attribute PCImplIceGatheringState iceGatheringState;
   readonly attribute PCImplSignalingState signalingState;
   attribute DOMString id;
 
--- a/ipc/glue/IPCMessageUtils.h
+++ b/ipc/glue/IPCMessageUtils.h
@@ -1084,11 +1084,24 @@ struct CrossOriginOpenerPolicyValidator 
   }
 };
 
 template <>
 struct ParamTraits<nsILoadInfo::CrossOriginOpenerPolicy>
     : EnumSerializer<nsILoadInfo::CrossOriginOpenerPolicy,
                      CrossOriginOpenerPolicyValidator> {};
 
+struct CrossOriginPolicyValidator {
+  static bool IsLegalValue(nsILoadInfo::CrossOriginPolicy e) {
+    return e == nsILoadInfo::CROSS_ORIGIN_POLICY_NULL ||
+           e == nsILoadInfo::CROSS_ORIGIN_POLICY_ANONYMOUS ||
+           e == nsILoadInfo::CROSS_ORIGIN_POLICY_USE_CREDENTIALS;
+  }
+};
+
+template <>
+struct ParamTraits<nsILoadInfo::CrossOriginPolicy>
+    : EnumSerializer<nsILoadInfo::CrossOriginPolicy,
+                     CrossOriginPolicyValidator> {};
+
 } /* namespace IPC */
 
 #endif /* __IPC_GLUE_IPCMESSAGEUTILS_H__ */
--- a/js/public/RootingAPI.h
+++ b/js/public/RootingAPI.h
@@ -906,17 +906,17 @@ class RootingContext {
 
 class JS_PUBLIC_API AutoGCRooter {
  protected:
   enum class Tag : uint8_t {
     Array,      /* js::AutoArrayRooter */
     ValueArray, /* js::AutoValueArray */
     Parser,     /* js::frontend::Parser */
 #if defined(JS_BUILD_BINAST)
-    BinParser,     /* js::frontend::BinSource */
+    BinASTParser,  /* js::frontend::BinASTParser */
 #endif             // defined(JS_BUILD_BINAST)
     WrapperVector, /* js::AutoWrapperVector */
     Wrapper,       /* js::AutoWrapperRooter */
     Custom         /* js::CustomAutoRooter */
   };
 
  public:
   AutoGCRooter(JSContext* cx, Tag tag)
--- a/js/src/frontend/BinASTParserBase.cpp
+++ b/js/src/frontend/BinASTParserBase.cpp
@@ -9,12 +9,13 @@
 #include "vm/JSContext-inl.h"
 
 namespace js {
 namespace frontend {
 
 BinASTParserBase::BinASTParserBase(JSContext* cx, LifoAlloc& alloc,
                                    UsedNameTracker& usedNames,
                                    HandleScriptSourceObject sourceObject)
-    : ParserSharedBase(cx, alloc, usedNames, sourceObject) {}
+    : ParserSharedBase(cx, alloc, usedNames, sourceObject,
+                       ParserSharedBase::Kind::BinASTParser) {}
 
 }  // namespace frontend
 }  // namespace js
--- a/js/src/frontend/BinASTParserPerTokenizer.cpp
+++ b/js/src/frontend/BinASTParserPerTokenizer.cpp
@@ -764,17 +764,17 @@ bool BinASTParserPerTokenizer<Tok>::comp
     errorOffset.is<NoOffset>();
     err->columnNumber = 0;
   }
 
   err->isMuted = options().mutedErrors();
   return true;
 }
 
-void TraceBinParser(JSTracer* trc, JS::AutoGCRooter* parser) {
+void TraceBinASTParser(JSTracer* trc, JS::AutoGCRooter* parser) {
   static_cast<BinASTParserBase*>(parser)->trace(trc);
 }
 
 template <typename Tok>
 void BinASTParserPerTokenizer<Tok>::doTrace(JSTracer* trc) {
   if (tokenizer_) {
     tokenizer_->traceMetadata(trc);
   }
--- a/js/src/frontend/BinASTParserPerTokenizer.h
+++ b/js/src/frontend/BinASTParserPerTokenizer.h
@@ -318,16 +318,16 @@ class BinParseContext : public ParseCont
  public:
   template <typename Tok>
   BinParseContext(JSContext* cx, BinASTParserPerTokenizer<Tok>* parser,
                   SharedContext* sc, Directives* newDirectives)
       : ParseContext(cx, parser->pc_, sc, *parser, parser->usedNames_,
                      newDirectives, /* isFull = */ true) {}
 };
 
-void TraceBinParser(JSTracer* trc, JS::AutoGCRooter* parser);
+void TraceBinASTParser(JSTracer* trc, JS::AutoGCRooter* parser);
 
 extern template class BinASTParserPerTokenizer<BinTokenReaderMultipart>;
 
 }  // namespace frontend
 }  // namespace js
 
 #endif  // frontend_BinASTParserPerTokenizer_h
--- a/js/src/frontend/BytecodeCompiler.h
+++ b/js/src/frontend/BytecodeCompiler.h
@@ -193,17 +193,17 @@ bool IsKeyword(JSLinearString* str);
 
 /* Trace all GC things reachable from parser. Defined in Parser.cpp. */
 void TraceParser(JSTracer* trc, JS::AutoGCRooter* parser);
 
 #if defined(JS_BUILD_BINAST)
 
 /* Trace all GC things reachable from binjs parser. Defined in
  * BinASTParserPerTokenizer.cpp. */
-void TraceBinParser(JSTracer* trc, JS::AutoGCRooter* parser);
+void TraceBinASTParser(JSTracer* trc, JS::AutoGCRooter* parser);
 
 #endif  // defined(JS_BUILD_BINAST)
 
 class MOZ_STACK_CLASS AutoFrontendTraceLog {
 #ifdef JS_TRACE_LOGGING
   TraceLoggerThread* logger_;
   mozilla::Maybe<TraceLoggerEvent> frontendEvent_;
   mozilla::Maybe<AutoTraceLog> frontendLog_;
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -132,18 +132,20 @@ bool GeneralParser<ParseHandler, Unit>::
     errorReport(actual);
     return false;
   }
   return true;
 }
 
 ParserSharedBase::ParserSharedBase(JSContext* cx, LifoAlloc& alloc,
                                    UsedNameTracker& usedNames,
-                                   ScriptSourceObject* sourceObject)
-    : JS::AutoGCRooter(cx, AutoGCRooter::Tag::Parser),
+                                   ScriptSourceObject* sourceObject, Kind kind)
+    : JS::AutoGCRooter(cx, kind == Kind::Parser
+                               ? JS::AutoGCRooter::Tag::Parser
+                               : JS::AutoGCRooter::Tag::BinASTParser),
       cx_(cx),
       alloc_(alloc),
       traceListHead_(nullptr),
       pc_(nullptr),
       usedNames_(usedNames),
       sourceObject_(cx, sourceObject),
       keepAtoms_(cx) {
   cx->frontendCollectionPool().addActiveCompilation();
@@ -162,17 +164,18 @@ ParserSharedBase::~ParserSharedBase() {
 
   cx_->frontendCollectionPool().removeActiveCompilation();
 }
 
 ParserBase::ParserBase(JSContext* cx, LifoAlloc& alloc,
                        const ReadOnlyCompileOptions& options,
                        bool foldConstants, UsedNameTracker& usedNames,
                        ScriptSourceObject* sourceObject, ParseGoal parseGoal)
-    : ParserSharedBase(cx, alloc, usedNames, sourceObject),
+    : ParserSharedBase(cx, alloc, usedNames, sourceObject,
+                       ParserSharedBase::Kind::Parser),
       anyChars(cx, options, thisForCtor()),
       ss(nullptr),
       foldConstants_(foldConstants),
 #ifdef DEBUG
       checkOptionsCalled_(false),
 #endif
       isUnexpectedEOF_(false),
       awaitHandling_(AwaitIsName),
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -235,18 +235,20 @@ enum AwaitHandling : uint8_t {
 template <class ParseHandler, typename Unit>
 class AutoAwaitIsKeyword;
 
 template <class ParseHandler, typename Unit>
 class AutoInParametersOfAsyncFunction;
 
 class MOZ_STACK_CLASS ParserSharedBase : private JS::AutoGCRooter {
  public:
+  enum class Kind { Parser, BinASTParser };
+
   ParserSharedBase(JSContext* cx, LifoAlloc& alloc, UsedNameTracker& usedNames,
-                   ScriptSourceObject* sourceObject);
+                   ScriptSourceObject* sourceObject, Kind kind);
   ~ParserSharedBase();
 
  public:
   JSContext* const cx_;
 
   LifoAlloc& alloc_;
 
   LifoAlloc::Mark tempPoolMark_;
@@ -266,18 +268,18 @@ class MOZ_STACK_CLASS ParserSharedBase :
   AutoKeepAtoms keepAtoms_;
 
  private:
   // This is needed to cast a parser to JS::AutoGCRooter.
   friend void js::frontend::TraceParser(JSTracer* trc,
                                         JS::AutoGCRooter* parser);
 
 #if defined(JS_BUILD_BINAST)
-  friend void js::frontend::TraceBinParser(JSTracer* trc,
-                                           JS::AutoGCRooter* parser);
+  friend void js::frontend::TraceBinASTParser(JSTracer* trc,
+                                              JS::AutoGCRooter* parser);
 #endif  // JS_BUILD_BINAST
 
  private:
   // Create a new traceable node and store it into the trace list.
   template <typename BoxT, typename ArgT>
   BoxT* newTraceListNode(ArgT* arg);
 
  public:
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -154,18 +154,18 @@ void JSRuntime::finishPersistentRoots() 
 
 inline void AutoGCRooter::trace(JSTracer* trc) {
   switch (tag_) {
     case Tag::Parser:
       frontend::TraceParser(trc, this);
       return;
 
 #if defined(JS_BUILD_BINAST)
-    case Tag::BinParser:
-      frontend::TraceBinParser(trc, this);
+    case Tag::BinASTParser:
+      frontend::TraceBinASTParser(trc, this);
       return;
 #endif  // defined(JS_BUILD_BINAST)
 
     case Tag::ValueArray: {
       /*
        * We don't know the template size parameter, but we can safely treat it
        * as an AutoValueArray<1> because the length is stored separately.
        */
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -613,18 +613,17 @@ XDRResult js::PrivateScriptData::XDR(XDR
   {
     MOZ_ASSERT(nscopes > 0);
     GCPtrScope* vector = data->scopes().data();
     for (uint32_t i = 0; i < nscopes; ++i) {
       RootedScope scope(cx);
       if (mode == XDR_ENCODE) {
         scope = vector[i];
       }
-      MOZ_TRY(
-          XDRScope(xdr, script->data_, scriptEnclosingScope, fun, i, &scope));
+      MOZ_TRY(XDRScope(xdr, data, scriptEnclosingScope, fun, i, &scope));
       if (mode == XDR_DECODE) {
         vector[i].init(scope);
       }
     }
 
     // Verify marker to detect data corruption after decoding scope data. A
     // mismatch here indicates we will almost certainly crash in release.
     MOZ_TRY(xdr->codeMarker(0x48922BAB));
@@ -636,17 +635,17 @@ XDRResult js::PrivateScriptData::XDR(XDR
    * after the enclosing block has been XDR'd.
    */
   if (nobjects) {
     for (GCPtrObject& elem : data->objects()) {
       RootedObject inner(cx);
       if (mode == XDR_ENCODE) {
         inner = elem;
       }
-      MOZ_TRY(XDRInnerObject(xdr, script->data_, sourceObject, &inner));
+      MOZ_TRY(XDRInnerObject(xdr, data, sourceObject, &inner));
       if (mode == XDR_DECODE) {
         elem.init(inner);
       }
     }
   }
 
   // Verify marker to detect data corruption after decoding object data. A
   // mismatch here indicates we will almost certainly crash in release.
@@ -668,16 +667,76 @@ XDRResult js::PrivateScriptData::XDR(XDR
     for (uint32_t& elem : data->resumeOffsets()) {
       MOZ_TRY(xdr->codeUint32(&elem));
     }
   }
 
   return Ok();
 }
 
+// Placement-new elements of an array. This should optimize away for types with
+// trivial default initiation.
+template <typename T>
+static void DefaultInitializeElements(void* arrayPtr, size_t length) {
+  uintptr_t elem = reinterpret_cast<uintptr_t>(arrayPtr);
+  MOZ_ASSERT(elem % alignof(T) == 0);
+
+  for (size_t i = 0; i < length; ++i) {
+    new (reinterpret_cast<void*>(elem)) T;
+    elem += sizeof(T);
+  }
+}
+
+/* static */ size_t SharedScriptData::AllocationSize(uint32_t codeLength,
+                                                     uint32_t noteLength,
+                                                     uint32_t natoms) {
+  size_t size = sizeof(SharedScriptData);
+
+  size += natoms * sizeof(GCPtrAtom);
+  size += codeLength * sizeof(jsbytecode);
+  size += noteLength * sizeof(jssrcnote);
+
+  return size;
+}
+
+// Placement-new elements of an array. This should optimize away for types with
+// trivial default initiation.
+template <typename T>
+void SharedScriptData::initElements(size_t offset, size_t length) {
+  uintptr_t base = reinterpret_cast<uintptr_t>(this);
+  DefaultInitializeElements<T>(reinterpret_cast<void*>(base + offset), length);
+}
+
+SharedScriptData::SharedScriptData(uint32_t codeLength, uint32_t noteLength,
+                                   uint32_t natoms)
+    : codeLength_(codeLength), noteLength_(noteLength), natoms_(natoms) {
+  // Variable-length data begins immediately after SharedScriptData itself.
+  size_t cursor = sizeof(*this);
+
+  // Default-initialize trailing arrays.
+
+  static_assert(alignof(SharedScriptData) >= alignof(GCPtrAtom),
+                "Incompatible alignment");
+  initElements<GCPtrAtom>(cursor, natoms);
+  cursor += natoms * sizeof(GCPtrAtom);
+
+  static_assert(alignof(GCPtrAtom) >= alignof(jsbytecode),
+                "Incompatible alignment");
+  initElements<jsbytecode>(cursor, codeLength);
+  cursor += codeLength * sizeof(jsbytecode);
+
+  static_assert(alignof(jsbytecode) >= alignof(jssrcnote),
+                "Incompatible alignment");
+  initElements<jssrcnote>(cursor, noteLength);
+  cursor += noteLength * sizeof(jssrcnote);
+
+  // Sanity check
+  MOZ_ASSERT(AllocationSize(codeLength, noteLength, natoms) == cursor);
+}
+
 template <XDRMode mode>
 /* static */
 XDRResult SharedScriptData::XDR(XDRState<mode>* xdr, HandleScript script) {
   uint32_t natoms = 0;
   uint32_t codeLength = 0;
   uint32_t noteLength = 0;
 
   JSContext* cx = xdr->cx();
@@ -2865,88 +2924,45 @@ bool ScriptSource::setSourceMapURL(JSCon
  * Array elements   Pointed to by         Length
  * --------------   -------------         ------
  * GCPtrAtom        atoms()               natoms()
  * jsbytecode       code()                codeLength()
  * jsscrnote        notes()               numNotes()
  */
 
 SharedScriptData* js::SharedScriptData::new_(JSContext* cx, uint32_t codeLength,
-                                             uint32_t srcnotesLength,
+                                             uint32_t noteLength,
                                              uint32_t natoms) {
-  size_t dataLength = natoms * sizeof(GCPtrAtom) + codeLength + srcnotesLength;
-  size_t allocLength = offsetof(SharedScriptData, data_) + dataLength;
-  auto entry =
-      reinterpret_cast<SharedScriptData*>(cx->pod_malloc<uint8_t>(allocLength));
-  if (!entry) {
-    ReportOutOfMemory(cx);
+  // Compute size including trailing arrays
+  size_t size = AllocationSize(codeLength, noteLength, natoms);
+
+  // Allocate contiguous raw buffer
+  void* raw = cx->pod_malloc<uint8_t>(size);
+  MOZ_ASSERT(uintptr_t(raw) % alignof(SharedScriptData) == 0);
+  if (!raw) {
     return nullptr;
   }
 
-  /* Diagnostic for Bug 1399373.
-   * We expect bytecode is always non-empty. */
-  MOZ_DIAGNOSTIC_ASSERT(codeLength > 0);
-
-  entry->refCount_ = 0;
-  entry->natoms_ = natoms;
-  entry->codeLength_ = codeLength;
-  entry->noteLength_ = srcnotesLength;
-
-  /*
-   * Call constructors to initialize the storage that will be accessed as a
-   * GCPtrAtom array via atoms().
-   */
-  static_assert(offsetof(SharedScriptData, data_) % sizeof(GCPtrAtom) == 0,
-                "atoms must have GCPtrAtom alignment");
-  GCPtrAtom* atoms = entry->atoms();
-  for (unsigned i = 0; i < natoms; ++i) {
-    new (&atoms[i]) GCPtrAtom();
-  }
-
-  // Sanity check the dataLength() computation
-  MOZ_ASSERT(entry->dataLength() == dataLength);
-
-  return entry;
+  // Constuct the SharedScriptData. Trailing arrays are uninitialized but
+  // GCPtrs are put into a safe state.
+  return new (raw) SharedScriptData(codeLength, noteLength, natoms);
 }
 
 inline js::ScriptBytecodeHasher::Lookup::Lookup(SharedScriptData* data)
     : scriptData(data),
-      hash(mozilla::HashBytes(scriptData->data(), scriptData->dataLength())) {
-  scriptData->incRefCount();
-}
-
-inline js::ScriptBytecodeHasher::Lookup::~Lookup() {
-  scriptData->decRefCount();
-}
+      hash(mozilla::HashBytes(scriptData->data(), scriptData->dataLength())) {}
 
 bool JSScript::createSharedScriptData(JSContext* cx, uint32_t codeLength,
                                       uint32_t noteLength, uint32_t natoms) {
-  MOZ_ASSERT(!scriptData());
-  SharedScriptData* ssd =
-      SharedScriptData::new_(cx, codeLength, noteLength, natoms);
-  if (!ssd) {
-    return false;
-  }
-
-  setScriptData(ssd);
-  return true;
+  MOZ_ASSERT(!scriptData_);
+  scriptData_ = SharedScriptData::new_(cx, codeLength, noteLength, natoms);
+  return !!scriptData_;
 }
 
-void JSScript::freeScriptData() {
-  if (scriptData_) {
-    scriptData_->decRefCount();
-    scriptData_ = nullptr;
-  }
-}
-
-void JSScript::setScriptData(js::SharedScriptData* data) {
-  MOZ_ASSERT(!scriptData_);
-  scriptData_ = data;
-  scriptData_->incRefCount();
-}
+void JSScript::freeScriptData() { scriptData_ = nullptr; }
 
 /*
  * Takes ownership of its *ssd parameter and either adds it into the runtime's
  * ScriptDataTable or frees it if a matching entry already exists.
  *
  * Sets the |code| and |atoms| fields on the given JSScript.
  */
 bool JSScript::shareScriptData(JSContext* cx) {
@@ -2958,44 +2974,44 @@ bool JSScript::shareScriptData(JSContext
   // counted, it also will be freed after releasing the lock if necessary.
   ScriptBytecodeHasher::Lookup lookup(ssd);
 
   AutoLockScriptData lock(cx->runtime());
 
   ScriptDataTable::AddPtr p = cx->scriptDataTable(lock).lookupForAdd(lookup);
   if (p) {
     MOZ_ASSERT(ssd != *p);
-    freeScriptData();
-    setScriptData(*p);
+    scriptData_ = *p;
   } else {
     if (!cx->scriptDataTable(lock).add(p, ssd)) {
-      freeScriptData();
       ReportOutOfMemory(cx);
       return false;
     }
 
     // Being in the table counts as a reference on the script data.
-    scriptData()->incRefCount();
-  }
-
+    ssd->AddRef();
+  }
+
+  // Refs: JSScript, ScriptDataTable
   MOZ_ASSERT(scriptData()->refCount() >= 2);
+
   return true;
 }
 
 void js::SweepScriptData(JSRuntime* rt) {
   // Entries are removed from the table when their reference count is one,
   // i.e. when the only reference to them is from the table entry.
 
   AutoLockScriptData lock(rt);
   ScriptDataTable& table = rt->scriptDataTable(lock);
 
   for (ScriptDataTable::Enum e(table); !e.empty(); e.popFront()) {
     SharedScriptData* scriptData = e.front();
     if (scriptData->refCount() == 1) {
-      scriptData->decRefCount();
+      scriptData->Release();
       e.removeFront();
     }
   }
 }
 
 void js::FreeScriptData(JSRuntime* rt) {
   AutoLockScriptData lock(rt);
 
@@ -3013,29 +3029,16 @@ void js::FreeScriptData(JSRuntime* rt) {
             scriptData, scriptData->refCount());
 #endif
     js_free(e.front());
   }
 
   table.clear();
 }
 
-// Placement-new elements of an array. This should optimize away for types with
-// trivial default initiation.
-template <typename T>
-static void DefaultInitializeElements(void* arrayPtr, size_t length) {
-  uintptr_t elem = reinterpret_cast<uintptr_t>(arrayPtr);
-  MOZ_ASSERT(elem % alignof(T) == 0);
-
-  for (size_t i = 0; i < length; ++i) {
-    new (reinterpret_cast<T*>(elem)) T;
-    elem += sizeof(T);
-  }
-}
-
 /* static */
 size_t PrivateScriptData::AllocationSize(uint32_t nscopes, uint32_t nconsts,
                                          uint32_t nobjects, uint32_t ntrynotes,
                                          uint32_t nscopenotes,
                                          uint32_t nresumeoffsets) {
   size_t size = sizeof(PrivateScriptData);
 
   if (nconsts) {
@@ -3257,17 +3260,17 @@ PrivateScriptData* PrivateScriptData::ne
   }
   if (nresumeoffsets) {
     bce->resumeOffsetList.finish(data->resumeOffsets());
   }
 
   return true;
 }
 
-void PrivateScriptData::traceChildren(JSTracer* trc) {
+void PrivateScriptData::trace(JSTracer* trc) {
   auto scopearray = scopes();
   TraceRange(trc, scopearray.size(), scopearray.data(), "scopes");
 
   if (hasConsts()) {
     auto constarray = consts();
     TraceRange(trc, constarray.size(), constarray.data(), "consts");
   }
 
@@ -3736,19 +3739,17 @@ void JSScript::finalize(FreeOp* fop) {
   }
 #endif
 
   if (data_) {
     AlwaysPoison(data_, 0xdb, computedSizeOfData(), MemCheckKind::MakeNoAccess);
     fop->free_(data_);
   }
 
-  if (scriptData_) {
-    scriptData_->decRefCount();
-  }
+  freeScriptData();
 
   // In most cases, our LazyScript's script pointer will reference this
   // script, and thus be nulled out by normal weakref processing. However, if
   // we unlazified the LazyScript during incremental sweeping, it will have a
   // completely different JSScript.
   MOZ_ASSERT_IF(
       lazyScript && !IsAboutToBeFinalizedUnbarriered(&lazyScript),
       !lazyScript->hasScript() || lazyScript->maybeScriptUnbarriered() != this);
@@ -4239,17 +4240,17 @@ JSScript* js::detail::CopyScript(JSConte
     return nullptr;
   }
 
   // The SharedScriptData can be reused by any zone in the Runtime as long as
   // we make sure to mark first (to sync Atom pointers).
   if (cx->zone() != src->zoneFromAnyThread()) {
     src->scriptData()->markForCrossZone(cx);
   }
-  dst->setScriptData(src->scriptData());
+  dst->scriptData_ = src->scriptData();
 
   return dst;
 }
 
 JSScript* js::CloneGlobalScript(JSContext* cx, ScopeKind scopeKind,
                                 HandleScript src) {
   MOZ_ASSERT(scopeKind == ScopeKind::Global ||
              scopeKind == ScopeKind::NonSyntactic);
@@ -4552,17 +4553,17 @@ void JSScript::traceChildren(JSTracer* t
   // JSScript::Create(), but not yet finished initializing it with
   // fullyInitFromEmitter() or fullyInitTrivial().
 
   MOZ_ASSERT_IF(trc->isMarkingTracer() &&
                     GCMarker::fromTracer(trc)->shouldCheckCompartments(),
                 zone()->isCollecting());
 
   if (data_) {
-    data_->traceChildren(trc);
+    data_->trace(trc);
   }
 
   if (scriptData()) {
     scriptData()->traceChildren(trc);
   }
 
   MOZ_ASSERT_IF(sourceObject(),
                 MaybeForwarded(sourceObject())->compartment() == compartment());
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -9,16 +9,17 @@
 #ifndef vm_JSScript_h
 #define vm_JSScript_h
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/MaybeOneOf.h"
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/RefPtr.h"
 #include "mozilla/Span.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/Utf8.h"
 #include "mozilla/Variant.h"
 
 #include <type_traits>  // std::is_same
 #include <utility>      // std::move
 
@@ -1350,17 +1351,17 @@ class alignas(JS::Value) PrivateScriptDa
   // the private data.
   struct alignas(uintptr_t) PackedSpan {
     uint32_t offset;
     uint32_t length;
   };
 
   // Concrete Fields
   PackedOffsets packedOffsets = {};  // zeroes
-  uint32_t nscopes;
+  uint32_t nscopes = 0;
 
   // Translate an offset into a concrete pointer.
   template <typename T>
   T* offsetToPointer(size_t offset) {
     uintptr_t base = reinterpret_cast<uintptr_t>(this);
     uintptr_t elem = base + offset;
     return reinterpret_cast<T*>(elem);
   }
@@ -1445,56 +1446,69 @@ class alignas(JS::Value) PrivateScriptDa
 
   // Clone src script data into dst script.
   static bool Clone(JSContext* cx, js::HandleScript src, js::HandleScript dst,
                     js::MutableHandle<JS::GCVector<js::Scope*>> scopes);
 
   static bool InitFromEmitter(JSContext* cx, js::HandleScript script,
                               js::frontend::BytecodeEmitter* bce);
 
-  void traceChildren(JSTracer* trc);
+  void trace(JSTracer* trc);
+
+  // PrivateScriptData has trailing data so isn't copyable or movable.
+  PrivateScriptData(const PrivateScriptData&) = delete;
+  PrivateScriptData& operator=(const PrivateScriptData&) = delete;
 };
 
 /*
  * Common data that can be shared between many scripts in a single runtime.
  */
-class SharedScriptData {
+class alignas(uintptr_t) SharedScriptData final {
   // This class is reference counted as follows: each pointer from a JSScript
   // counts as one reference plus there may be one reference from the shared
   // script data table.
   mozilla::Atomic<uint32_t, mozilla::SequentiallyConsistent,
                   mozilla::recordreplay::Behavior::DontPreserve>
-      refCount_;
-
-  uint32_t natoms_;
-  uint32_t codeLength_;
-  uint32_t noteLength_;
-  uintptr_t data_[1];
+      refCount_ = {};
+
+  uint32_t codeLength_ = 0;
+  uint32_t noteLength_ = 0;
+  uint32_t natoms_ = 0;
+
+  // Size to allocate
+  static size_t AllocationSize(uint32_t codeLength, uint32_t noteLength,
+                               uint32_t natoms);
+
+  template <typename T>
+  void initElements(size_t offset, size_t length);
+
+  // Initialize to GC-safe state
+  SharedScriptData(uint32_t codeLength, uint32_t noteLength, uint32_t natoms);
 
  public:
   static SharedScriptData* new_(JSContext* cx, uint32_t codeLength,
-                                uint32_t srcnotesLength, uint32_t natoms);
+                                uint32_t noteLength, uint32_t natoms);
 
   uint32_t refCount() const { return refCount_; }
-  void incRefCount() { refCount_++; }
-  void decRefCount() {
+  void AddRef() { refCount_++; }
+  void Release() {
     MOZ_ASSERT(refCount_ != 0);
     uint32_t remain = --refCount_;
     if (remain == 0) {
       js_free(this);
     }
   }
 
   size_t dataLength() const {
     return (natoms_ * sizeof(GCPtrAtom)) + codeLength_ + noteLength_;
   }
   const uint8_t* data() const {
-    return reinterpret_cast<const uint8_t*>(data_);
+    return reinterpret_cast<const uint8_t*>(this + 1);
   }
-  uint8_t* data() { return reinterpret_cast<uint8_t*>(data_); }
+  uint8_t* data() { return reinterpret_cast<uint8_t*>(this + 1); }
 
   uint32_t natoms() const { return natoms_; }
   GCPtrAtom* atoms() {
     if (!natoms_) {
       return nullptr;
     }
     return reinterpret_cast<GCPtrAtom*>(data());
   }
@@ -1507,49 +1521,44 @@ class SharedScriptData {
   uint32_t numNotes() const { return noteLength_; }
   jssrcnote* notes() {
     return reinterpret_cast<jssrcnote*>(data() + natoms_ * sizeof(GCPtrAtom) +
                                         codeLength_);
   }
 
   void traceChildren(JSTracer* trc);
 
-  static constexpr size_t offsetOfData() {
-    return offsetof(SharedScriptData, data_);
-  }
   static constexpr size_t offsetOfNatoms() {
     return offsetof(SharedScriptData, natoms_);
   }
 
   template <XDRMode mode>
   static MOZ_MUST_USE XDRResult XDR(js::XDRState<mode>* xdr,
                                     js::HandleScript script);
 
   static bool InitFromEmitter(JSContext* cx, js::HandleScript script,
                               js::frontend::BytecodeEmitter* bce);
 
   // Mark this SharedScriptData for use in a new zone
   void markForCrossZone(JSContext* cx);
 
- private:
-  SharedScriptData() = delete;
+  // SharedScriptData has trailing data so isn't copyable or movable.
   SharedScriptData(const SharedScriptData&) = delete;
   SharedScriptData& operator=(const SharedScriptData&) = delete;
 };
 
 struct ScriptBytecodeHasher {
   class Lookup {
     friend struct ScriptBytecodeHasher;
 
-    SharedScriptData* scriptData;
+    RefPtr<SharedScriptData> scriptData;
     HashNumber hash;
 
    public:
     explicit Lookup(SharedScriptData* data);
-    ~Lookup();
   };
 
   static HashNumber hash(const Lookup& l) { return l.hash; }
   static bool match(SharedScriptData* entry, const Lookup& lookup) {
     const SharedScriptData* data = lookup.scriptData;
     if (entry->natoms() != data->natoms()) {
       return false;
     }
@@ -1570,26 +1579,36 @@ using ScriptDataTable =
     HashSet<SharedScriptData*, ScriptBytecodeHasher, SystemAllocPolicy>;
 
 extern void SweepScriptData(JSRuntime* rt);
 
 extern void FreeScriptData(JSRuntime* rt);
 
 } /* namespace js */
 
+namespace JS {
+
+// Define a GCManagedDeletePolicy to allow deleting type outside of normal
+// sweeping.
+template <>
+struct DeletePolicy<js::PrivateScriptData>
+    : public js::GCManagedDeletePolicy<js::PrivateScriptData> {};
+
+} /* namespace JS */
+
 class JSScript : public js::gc::TenuredCell {
  private:
   // Pointer to baseline->method()->raw(), ion->method()->raw(), a wasm jit
   // entry, the JIT's EnterInterpreter stub, or the lazy link stub. Must be
   // non-null.
   uint8_t* jitCodeRaw_ = nullptr;
   uint8_t* jitCodeSkipArgCheck_ = nullptr;
 
   // Shareable script data
-  js::SharedScriptData* scriptData_ = nullptr;
+  RefPtr<js::SharedScriptData> scriptData_ = {};
 
   // Unshared variable-length data
   js::PrivateScriptData* data_ = nullptr;
 
  public:
   JS::Realm* realm_ = nullptr;
 
  private:
@@ -2518,17 +2537,16 @@ class JSScript : public js::gc::TenuredC
 
  private:
   bool makeTypes(JSContext* cx);
 
   bool createSharedScriptData(JSContext* cx, uint32_t codeLength,
                               uint32_t noteLength, uint32_t natoms);
   bool shareScriptData(JSContext* cx);
   void freeScriptData();
-  void setScriptData(js::SharedScriptData* data);
 
  public:
   uint32_t getWarmUpCount() const { return warmUpCount; }
   uint32_t incWarmUpCounter(uint32_t amount = 1) {
     return warmUpCount += amount;
   }
   uint32_t* addressOfWarmUpCounter() {
     return reinterpret_cast<uint32_t*>(&warmUpCount);
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -6521,18 +6521,20 @@ nsresult PresShell::EventHandler::Handle
   }
 
   // Activation events need to be dispatched even if no frame was found, since
   // we don't want the focus to be out of sync.
   if (!aFrame) {
     if (!NS_EVENT_NEEDS_FRAME(aGUIEvent)) {
       mPresShell->mCurrentEventFrame = nullptr;
       nsCOMPtr<nsIContent> overrideClickTarget;  // Required due to bug  1506439
-      return HandleEventInternal(aGUIEvent, aEventStatus, true,
-                                 overrideClickTarget);
+      // XXX Shouldn't we create AutoCurrentEventInfoSetter instance for this
+      //     call even if we set the target to nullptr.
+      return HandleEventWithCurrentEventInfo(aGUIEvent, aEventStatus, true,
+                                             overrideClickTarget);
     }
 
     if (aGUIEvent->HasKeyEventMessage()) {
       // Keypress events in new blank tabs should not be completely thrown away.
       // Retarget them -- the parent chrome shell might make use of them.
       return RetargetEventToParent(aGUIEvent, aEventStatus);
     }
 
@@ -6702,17 +6704,17 @@ nsresult PresShell::EventHandler::Handle
 
   // Handle the event in the correct shell.
   // We pass the subshell's root frame as the frame to start from. This is
   // the only correct alternative; if the event was captured then it
   // must have been captured by us or some ancestor shell and we
   // now ask the subshell to dispatch it normally.
   EventHandler eventHandler(*eventTargetData.mPresShell);
   AutoCurrentEventInfoSetter eventInfoSetter(eventHandler, eventTargetData);
-  nsresult rv = eventHandler.HandleEventInternal(
+  nsresult rv = eventHandler.HandleEventWithCurrentEventInfo(
       aGUIEvent, aEventStatus, true, eventTargetData.mOverrideClickTarget);
 #ifdef DEBUG
   eventTargetData.mPresShell->ShowEventTargetDebug();
 #endif
   return rv;
 }
 
 bool PresShell::EventHandler::MaybeFlushPendingNotifications(
@@ -7442,18 +7444,18 @@ nsresult PresShell::EventHandler::Handle
   mPresShell->mCurrentEventContent = eventTargetElement;
   if (!mPresShell->GetCurrentEventContent() ||
       !mPresShell->GetCurrentEventFrame() ||
       InZombieDocument(mPresShell->mCurrentEventContent)) {
     return RetargetEventToParent(aGUIEvent, aEventStatus);
   }
 
   nsCOMPtr<nsIContent> overrideClickTarget;  // Required due to bug  1506439
-  nsresult rv =
-      HandleEventInternal(aGUIEvent, aEventStatus, true, overrideClickTarget);
+  nsresult rv = HandleEventWithCurrentEventInfo(aGUIEvent, aEventStatus, true,
+                                                overrideClickTarget);
 
 #ifdef DEBUG
   mPresShell->ShowEventTargetDebug();
 #endif
 
   return rv;
 }
 
@@ -7548,18 +7550,18 @@ nsresult PresShell::EventHandler::Handle
   MOZ_ASSERT(aEventStatus);
 
   AutoCurrentEventInfoSetter eventInfoSetter(*this, aFrameForPresShell,
                                              nullptr);
 
   nsresult rv = NS_OK;
   if (mPresShell->GetCurrentEventFrame()) {
     nsCOMPtr<nsIContent> overrideClickTarget;  // Required due to bug  1506439
-    rv =
-        HandleEventInternal(aGUIEvent, aEventStatus, true, overrideClickTarget);
+    rv = HandleEventWithCurrentEventInfo(aGUIEvent, aEventStatus, true,
+                                         overrideClickTarget);
   }
 
 #ifdef DEBUG
   mPresShell->ShowEventTargetDebug();
 #endif
 
   return rv;
 }
@@ -7621,22 +7623,22 @@ nsresult PresShell::EventHandler::Handle
   }
 #endif
   NS_ENSURE_STATE(!aNewEventContent ||
                   aNewEventContent->GetComposedDoc() == GetDocument());
   AutoPointerEventTargetUpdater updater(mPresShell, aEvent, aNewEventFrame,
                                         aTargetContent);
   AutoCurrentEventInfoSetter eventInfoSetter(*this, aNewEventFrame,
                                              aNewEventContent);
-  nsresult rv =
-      HandleEventInternal(aEvent, aEventStatus, false, aOverrideClickTarget);
+  nsresult rv = HandleEventWithCurrentEventInfo(aEvent, aEventStatus, false,
+                                                aOverrideClickTarget);
   return rv;
 }
 
-nsresult PresShell::EventHandler::HandleEventInternal(
+nsresult PresShell::EventHandler::HandleEventWithCurrentEventInfo(
     WidgetEvent* aEvent, nsEventStatus* aEventStatus,
     bool aIsHandlingNativeEvent, nsIContent* aOverrideClickTarget) {
   MOZ_ASSERT(aEvent);
   MOZ_ASSERT(aEventStatus);
 
   RefPtr<EventStateManager> manager = GetPresContext()->EventStateManager();
 
   // If we cannot handle the event with mPresShell because of no target,
--- a/layout/base/PresShell.h
+++ b/layout/base/PresShell.h
@@ -504,17 +504,19 @@ class PresShell final : public nsIPresSh
     explicit EventHandler(PresShell& aPresShell)
         : mPresShell(aPresShell), mCurrentEventInfoSetter(nullptr) {}
     explicit EventHandler(RefPtr<PresShell>&& aPresShell)
         : mPresShell(aPresShell.forget()), mCurrentEventInfoSetter(nullptr) {}
 
     /**
      * HandleEvent() may dispatch aGUIEvent.  This may redirect the event to
      * another PresShell, or the event may be handled by other classes like
-     * AccessibleCaretEventHub, or discarded.
+     * AccessibleCaretEventHub, or discarded.  Otherwise, this sets current
+     * event info of mPresShell and calls HandleEventWithCurrentEventInfo()
+     * to dispatch the event into the DOM tree.
      *
      * @param aFrame                    aFrame of nsIPresShell::HandleEvent().
      *                                  (Perhaps, should be root frame of
      *                                  PresShell.)
      * @param aGUIEvent                 Event to be handled.
      * @param aDontRetargetEvents       true if this shouldn't redirect the
      *                                  event to different PresShell.
      *                                  false if this can redirect the event to
@@ -1010,18 +1012,18 @@ class PresShell final : public nsIPresSh
     nsresult HandleRetargetedEvent(WidgetGUIEvent* aGUIEvent,
                                    nsEventStatus* aEventStatus,
                                    nsIContent* aTarget) {
       AutoCurrentEventInfoSetter eventInfoSetter(*this, nullptr, aTarget);
       if (!mPresShell->GetCurrentEventFrame()) {
         return NS_OK;
       }
       nsCOMPtr<nsIContent> overrideClickTarget;
-      return HandleEventInternal(aGUIEvent, aEventStatus, true,
-                                 overrideClickTarget);
+      return HandleEventWithCurrentEventInfo(aGUIEvent, aEventStatus, true,
+                                             overrideClickTarget);
     }
 
     /**
      * HandleEventWithFrameForPresShell() handles aGUIEvent with the frame
      * for mPresShell.
      *
      * @param aFrameForPresShell        The frame for mPresShell.
      * @param aGUIEvent                 The handling event.  It shouldn't be
@@ -1030,31 +1032,31 @@ class PresShell final : public nsIPresSh
      * @param aEventStatus              [in/out] The status of aGUIEvent.
      */
     MOZ_CAN_RUN_SCRIPT
     nsresult HandleEventWithFrameForPresShell(nsIFrame* aFrameForPresShell,
                                               WidgetGUIEvent* aGUIEvent,
                                               nsEventStatus* aEventStatus);
 
     /**
-     * XXX Needs better name.
-     * HandleEventInternal() dispatches aEvent into the DOM tree and
-     * notify EventStateManager of that.
+     * HandleEventWithCurrentEventInfo() prepares to dispatch aEvent into the
+     * DOM, dispatches aEvent into the DOM with using current event info of
+     * mPresShell and notifies EventStateManager of that.
      *
      * @param aEvent                    Event to be dispatched.
      * @param aEventStatus              [in/out] EventStatus of aEvent.
      * @param aIsHandlingNativeEvent    true if aGUIEvent represents a native
      *                                  event.
      * @param aOverrideClickTarget      Override click event target.
      */
     MOZ_CAN_RUN_SCRIPT
-    nsresult HandleEventInternal(WidgetEvent* aEvent,
-                                 nsEventStatus* aEventStatus,
-                                 bool aIsHandlingNativeEvent,
-                                 nsIContent* aOverrideClickTarget);
+    nsresult HandleEventWithCurrentEventInfo(WidgetEvent* aEvent,
+                                             nsEventStatus* aEventStatus,
+                                             bool aIsHandlingNativeEvent,
+                                             nsIContent* aOverrideClickTarget);
 
     /**
      * HandlingTimeAccumulator() may accumulate handling time of telemetry
      * for each type of events.
      */
     class MOZ_STACK_CLASS HandlingTimeAccumulator final {
      public:
       HandlingTimeAccumulator() = delete;
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -2116,48 +2116,32 @@ PeerConnectionImpl::GetFingerprint(char*
   char* tmp = new char[fpStr.size() + 1];
   std::copy(fpStr.begin(), fpStr.end(), tmp);
   tmp[fpStr.size()] = '\0';
 
   *fingerprint = tmp;
   return NS_OK;
 }
 
-void PeerConnectionImpl::GetLocalDescription(nsAString& aSDP) {
-  PC_AUTO_ENTER_API_CALL_NO_CHECK();
-
-  std::string localSdp =
-      mJsepSession->GetLocalDescription(kJsepDescriptionPendingOrCurrent);
-  aSDP = NS_ConvertASCIItoUTF16(localSdp.c_str());
-}
-
 void PeerConnectionImpl::GetCurrentLocalDescription(nsAString& aSDP) {
   PC_AUTO_ENTER_API_CALL_NO_CHECK();
 
   std::string localSdp =
       mJsepSession->GetLocalDescription(kJsepDescriptionCurrent);
   aSDP = NS_ConvertASCIItoUTF16(localSdp.c_str());
 }
 
 void PeerConnectionImpl::GetPendingLocalDescription(nsAString& aSDP) {
   PC_AUTO_ENTER_API_CALL_NO_CHECK();
 
   std::string localSdp =
       mJsepSession->GetLocalDescription(kJsepDescriptionPending);
   aSDP = NS_ConvertASCIItoUTF16(localSdp.c_str());
 }
 
-void PeerConnectionImpl::GetRemoteDescription(nsAString& aSDP) {
-  PC_AUTO_ENTER_API_CALL_NO_CHECK();
-
-  std::string remoteSdp =
-      mJsepSession->GetRemoteDescription(kJsepDescriptionPendingOrCurrent);
-  aSDP = NS_ConvertASCIItoUTF16(remoteSdp.c_str());
-}
-
 void PeerConnectionImpl::GetCurrentRemoteDescription(nsAString& aSDP) {
   PC_AUTO_ENTER_API_CALL_NO_CHECK();
 
   std::string remoteSdp =
       mJsepSession->GetRemoteDescription(kJsepDescriptionCurrent);
   aSDP = NS_ConvertASCIItoUTF16(remoteSdp.c_str());
 }
 
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
@@ -415,21 +415,19 @@ class PeerConnectionImpl final
   NS_IMETHODIMP GetFingerprint(char** fingerprint);
   void GetFingerprint(nsAString& fingerprint) {
     char* tmp;
     GetFingerprint(&tmp);
     fingerprint.AssignASCII(tmp);
     delete[] tmp;
   }
 
-  void GetLocalDescription(nsAString& aSDP);
   void GetCurrentLocalDescription(nsAString& aSDP);
   void GetPendingLocalDescription(nsAString& aSDP);
 
-  void GetRemoteDescription(nsAString& aSDP);
   void GetCurrentRemoteDescription(nsAString& aSDP);
   void GetPendingRemoteDescription(nsAString& aSDP);
 
   NS_IMETHODIMP SignalingState(mozilla::dom::PCImplSignalingState* aState);
 
   mozilla::dom::PCImplSignalingState SignalingState() {
     mozilla::dom::PCImplSignalingState state;
     SignalingState(&state);
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -2091,16 +2091,23 @@ VARCACHE_PREF(
 
 // Blocked plugin content
 VARCACHE_PREF(
   "browser.safebrowsing.blockedURIs.enabled",
    browser_safebrowsing_blockedURIs_enabled,
   bool, true
 )
 
+// When this pref is enabled document loads with a mismatched
+// Cross-Origin header will fail to load
+VARCACHE_PREF("browser.tabs.remote.useCrossOriginPolicy",
+              browser_tabs_remote_useCrossOriginPolicy,
+              bool, false
+)
+
 // Prevent system colors from being exposed to CSS or canvas.
 VARCACHE_PREF(
   "ui.use_standins_for_native_colors",
    ui_use_standins_for_native_colors,
    RelaxedAtomicBool, false
 )
 
 //---------------------------------------------------------------------------
--- a/netwerk/base/nsILoadInfo.idl
+++ b/netwerk/base/nsILoadInfo.idl
@@ -1088,9 +1088,15 @@ interface nsILoadInfo : nsISupports
     OPENER_POLICY_SAME_ORIGIN    = 1,
     OPENER_POLICY_SAME_SITE      = 2,
     OPENER_POLICY_UNSAFE_ALLOW_OUTGOING_FLAG = 0x80,
     OPENER_POLICY_SAME_ORIGIN_ALLOW_OUTGOING = OPENER_POLICY_SAME_ORIGIN | OPENER_POLICY_UNSAFE_ALLOW_OUTGOING_FLAG,
     OPENER_POLICY_SAME_SITE_ALLOW_OUTGOING = OPENER_POLICY_SAME_SITE | OPENER_POLICY_UNSAFE_ALLOW_OUTGOING_FLAG
   };
 
   [infallible] attribute nsILoadInfo_CrossOriginOpenerPolicy openerPolicy;
+
+  cenum CrossOriginPolicy : 8 {
+    CROSS_ORIGIN_POLICY_NULL            = 0,
+    CROSS_ORIGIN_POLICY_ANONYMOUS       = 1,
+    CROSS_ORIGIN_POLICY_USE_CREDENTIALS = 2
+  };
 };
--- a/netwerk/protocol/http/nsHttpAtomList.h
+++ b/netwerk/protocol/http/nsHttpAtomList.h
@@ -36,16 +36,17 @@ HTTP_ATOM(Content_Encoding, "Content-Enc
 HTTP_ATOM(Content_Language, "Content-Language")
 HTTP_ATOM(Content_Length, "Content-Length")
 HTTP_ATOM(Content_Location, "Content-Location")
 HTTP_ATOM(Content_MD5, "Content-MD5")
 HTTP_ATOM(Content_Range, "Content-Range")
 HTTP_ATOM(Content_Type, "Content-Type")
 HTTP_ATOM(Cookie, "Cookie")
 HTTP_ATOM(Cross_Origin_Opener_Policy, "Cross-Origin-Opener-Policy")
+HTTP_ATOM(Cross_Origin, "Cross-Origin")
 HTTP_ATOM(Date, "Date")
 HTTP_ATOM(DAV, "DAV")
 HTTP_ATOM(Depth, "Depth")
 HTTP_ATOM(Destination, "Destination")
 HTTP_ATOM(DoNotTrack, "DNT")
 HTTP_ATOM(ETag, "Etag")
 HTTP_ATOM(Expect, "Expect")
 HTTP_ATOM(Expires, "Expires")
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -2446,16 +2446,23 @@ nsresult nsHttpChannel::ContinueProcessR
     rv = mAuthProvider->Disconnect(NS_ERROR_ABORT);
     if (NS_FAILED(rv)) {
       LOG(("  Disconnect failed (%08x)", static_cast<uint32_t>(rv)));
     }
     mAuthProvider = nullptr;
     LOG(("  continuation state has been reset"));
   }
 
+  rv = ProcessCrossOriginHeader();
+  if (NS_FAILED(rv)) {
+    mStatus = NS_ERROR_BLOCKED_BY_POLICY;
+    HandleAsyncAbort();
+    return NS_OK;
+  }
+
   rv = NS_OK;
   if (!mCanceled) {
     // notify "http-on-may-change-process" observers
     gHttpHandler->OnMayChangeProcess(this);
 
     if (mRedirectTabPromise) {
       MOZ_ASSERT(!mOnStartRequestCalled);
 
@@ -7306,16 +7313,83 @@ nsHttpChannel::HasCrossOriginOpenerPolic
       *aMismatch = true;
       return NS_OK;
     }
   }
 
   return NS_OK;
 }
 
+nsresult nsHttpChannel::GetResponseCrossOriginPolicy(
+    nsILoadInfo::CrossOriginPolicy *aResponseCrossOriginPolicy) {
+  if (!mResponseHead) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsILoadInfo::CrossOriginPolicy policy = nsILoadInfo::CROSS_ORIGIN_POLICY_NULL;
+
+  nsAutoCString content;
+  Unused << mResponseHead->GetHeader(nsHttp::Cross_Origin, content);
+
+  // Cross-Origin = %s"anonymous" / %s"use-credentials" ; case-sensitive
+
+  if (content.EqualsLiteral("anonymous")) {
+    policy = nsILoadInfo::CROSS_ORIGIN_POLICY_ANONYMOUS;
+  } else if (content.EqualsLiteral("use-credentials")) {
+    policy = nsILoadInfo::CROSS_ORIGIN_POLICY_USE_CREDENTIALS;
+  }
+
+  *aResponseCrossOriginPolicy = policy;
+  return NS_OK;
+}
+
+nsresult nsHttpChannel::ProcessCrossOriginHeader() {
+  nsresult rv;
+  if (!StaticPrefs::browser_tabs_remote_useCrossOriginPolicy()) {
+    return NS_OK;
+  }
+
+  // Only consider Cross-Origin for document loads.
+  if (mLoadInfo->GetExternalContentPolicyType() !=
+          nsIContentPolicy::TYPE_DOCUMENT &&
+      mLoadInfo->GetExternalContentPolicyType() !=
+          nsIContentPolicy::TYPE_SUBDOCUMENT) {
+    return NS_OK;
+  }
+
+  RefPtr<mozilla::dom::BrowsingContext> ctx;
+  if (mLoadInfo->GetExternalContentPolicyType() ==
+      nsIContentPolicy::TYPE_DOCUMENT) {
+    mLoadInfo->GetBrowsingContext(getter_AddRefs(ctx));
+  } else {
+    mLoadInfo->GetFrameBrowsingContext(getter_AddRefs(ctx));
+  }
+
+  if (!ctx) {
+    return NS_OK;
+  }
+
+  nsILoadInfo::CrossOriginPolicy documentPolicy = ctx->CrossOriginPolicy();
+  nsILoadInfo::CrossOriginPolicy resultPolicy =
+      nsILoadInfo::CROSS_ORIGIN_POLICY_NULL;
+  rv = GetResponseCrossOriginPolicy(&resultPolicy);
+  if (NS_FAILED(rv)) {
+    return NS_OK;
+  }
+
+  ctx->SetCrossOriginPolicy(resultPolicy);
+
+  if (documentPolicy != nsILoadInfo::CROSS_ORIGIN_POLICY_NULL &&
+      resultPolicy == nsILoadInfo::CROSS_ORIGIN_POLICY_NULL) {
+    return NS_ERROR_BLOCKED_BY_POLICY;
+  }
+
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 nsHttpChannel::OnStartRequest(nsIRequest *request) {
   nsresult rv;
 
   MOZ_ASSERT(mRequestObserversCalled);
 
   AUTO_PROFILER_LABEL("nsHttpChannel::OnStartRequest", NETWORK);
 
@@ -7421,16 +7495,23 @@ nsHttpChannel::OnStartRequest(nsIRequest
   }
 
   // avoid crashing if mListener happens to be null...
   if (!mListener) {
     MOZ_ASSERT_UNREACHABLE("mListener is null");
     return NS_OK;
   }
 
+  rv = ProcessCrossOriginHeader();
+  if (NS_FAILED(rv)) {
+    mStatus = NS_ERROR_BLOCKED_BY_POLICY;
+    HandleAsyncAbort();
+    return NS_OK;
+  }
+
   // before we check for redirects, check if the load should be shifted into a
   // new process.
   rv = NS_OK;
   if (!mCanceled) {
     // notify "http-on-may-change-process" observers
     gHttpHandler->OnMayChangeProcess(this);
 
     if (mRedirectTabPromise) {
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -470,16 +470,20 @@ class nsHttpChannel final : public HttpB
   MOZ_MUST_USE nsresult
   ProcessContentSignatureHeader(nsHttpResponseHead *aResponseHead);
 
   /**
    * A function that will, if the feature is enabled, send security reports.
    */
   void ProcessSecurityReport(nsresult status);
 
+  nsresult GetResponseCrossOriginPolicy(
+      nsILoadInfo::CrossOriginPolicy *aResponseCrossOriginPolicy);
+  nsresult ProcessCrossOriginHeader();
+
   /**
    * A function to process a single security header (STS or PKP), assumes
    * some basic sanity checks have been applied to the channel. Called
    * from ProcessSecurityHeaders.
    */
   MOZ_MUST_USE nsresult ProcessSingleSecurityHeader(
       uint32_t aType, nsITransportSecurityInfo *aSecInfo, uint32_t aFlags);
 
--- a/taskcluster/ci/test/raptor.yml
+++ b/taskcluster/ci/test/raptor.yml
@@ -290,16 +290,29 @@ raptor-tp6m-2-geckoview:
     tier: 2
     mozharness:
         extra-options:
             - --test=raptor-tp6m-2
             - --app=geckoview
             - --binary=org.mozilla.geckoview_example
             - --activity=GeckoViewActivity
 
+raptor-tp6m-4-geckoview:
+    description: "Raptor tp6m-4 on Geckoview"
+    try-name: raptor-tp6m-4-geckoview
+    treeherder-symbol: Rap(tp6m-4)
+    target: geckoview_example.apk
+    run-on-projects: ['try', 'mozilla-central']
+    tier: 2
+    mozharness:
+        extra-options:
+            - --test=raptor-tp6m-4
+            - --app=geckoview
+            - --binary=org.mozilla.geckoview_example
+
 raptor-tp6m-5-geckoview:
     description: "Raptor tp6m-5 on Geckoview"
     try-name: raptor-tp6m-5-geckoview
     treeherder-symbol: Rap(tp6m-5)
     run-on-projects:
         by-test-platform:
             android-hw-p2-8-0-arm7.*: ['try', 'mozilla-central']
             android-hw-g5.*: ['try', 'mozilla-central']
--- a/taskcluster/ci/test/test-sets.yml
+++ b/taskcluster/ci/test/test-sets.yml
@@ -408,25 +408,27 @@ android-hw-arm7-debug-unittests:
 
 android-hw-aarch64-opt-unittests:
     - jittest
 
 android-hw-arm7-raptor:
     - raptor-speedometer-geckoview
     - raptor-tp6m-1-geckoview
     - raptor-tp6m-2-geckoview
+    - raptor-tp6m-4-geckoview
     - raptor-tp6m-5-geckoview
     - raptor-tp6m-6-geckoview
     - raptor-tp6m-7-geckoview
     - raptor-tp6m-8-geckoview
 
 android-hw-aarch64-raptor:
     - raptor-speedometer-geckoview
     - raptor-tp6m-1-geckoview
     - raptor-tp6m-2-geckoview
+    - raptor-tp6m-4-geckoview
     - raptor-tp6m-5-geckoview
     - raptor-tp6m-6-geckoview
     - raptor-tp6m-7-geckoview
     - raptor-tp6m-8-geckoview
 
 android-hw-arm7-raptor-power:
     - raptor-speedometer-geckoview-power
 
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozlog/mozlog/capture.py
@@ -0,0 +1,89 @@
+from __future__ import absolute_import
+
+import sys
+import threading
+from io import BytesIO
+from multiprocessing import Queue
+
+
+class LogThread(threading.Thread):
+    def __init__(self, queue, logger, level):
+        self.queue = queue
+        self.log_func = getattr(logger, level)
+        threading.Thread.__init__(self, name="Thread-Log")
+        self.daemon = True
+
+    def run(self):
+        while True:
+            try:
+                msg = self.queue.get()
+            except (EOFError, IOError):
+                break
+            if msg is None:
+                break
+            else:
+                self.log_func(msg)
+
+
+class LoggingWrapper(BytesIO):
+    """Wrapper for file like objects to redirect output to logger
+    instead"""
+
+    def __init__(self, queue, prefix=None):
+        BytesIO.__init__(self)
+        self.queue = queue
+        self.prefix = prefix
+
+    def write(self, data):
+        if isinstance(data, bytes):
+            try:
+                data = data.decode("utf8")
+            except UnicodeDecodeError:
+                data = data.decode("unicode_escape")
+
+        if data.endswith("\n"):
+            data = data[:-1]
+        if data.endswith("\r"):
+            data = data[:-1]
+        if not data:
+            return
+        if self.prefix is not None:
+            data = "%s: %s" % (self.prefix, data)
+        self.queue.put(data)
+
+    def flush(self):
+        pass
+
+
+class CaptureIO(object):
+    def __init__(self, logger, do_capture):
+        self.logger = logger
+        self.do_capture = do_capture
+        self.logging_queue = None
+        self.logging_thread = None
+        self.original_stdio = None
+
+    def __enter__(self):
+        if self.do_capture:
+            self.original_stdio = (sys.stdout, sys.stderr)
+            self.logging_queue = Queue()
+            self.logging_thread = LogThread(self.logging_queue, self.logger, "info")
+            sys.stdout = LoggingWrapper(self.logging_queue, prefix="STDOUT")
+            sys.stderr = LoggingWrapper(self.logging_queue, prefix="STDERR")
+            self.logging_thread.start()
+
+    def __exit__(self, *args, **kwargs):
+        if self.do_capture:
+            sys.stdout, sys.stderr = self.original_stdio
+            if self.logging_queue is not None:
+                self.logger.info("Closing logging queue")
+                self.logging_queue.put(None)
+                if self.logging_thread is not None:
+                    self.logging_thread.join(10)
+                while not self.logging_queue.empty():
+                    try:
+                        self.logger.warning("Dropping log message: %r", self.logging_queue.get())
+                    except Exception:
+                        pass
+                self.logging_queue.close()
+                self.logger.info("queue closed")
--- a/testing/mozbase/mozlog/tests/manifest.ini
+++ b/testing/mozbase/mozlog/tests/manifest.ini
@@ -1,6 +1,7 @@
 [DEFAULT]
 subsuite = mozbase
 [test_logger.py]
 [test_logtypes.py]
 [test_formatters.py]
 [test_structured.py]
+[test_capture.py]
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozlog/tests/test_capture.py
@@ -0,0 +1,38 @@
+from __future__ import absolute_import, print_function
+import sys
+import unittest
+import mozunit
+
+from test_structured import TestHandler
+from mozlog import capture, structuredlog
+
+
+class TestCaptureIO(unittest.TestCase):
+    """Tests expected logging output of CaptureIO"""
+
+    def setUp(self):
+        self.logger = structuredlog.StructuredLogger("test")
+        self.handler = TestHandler()
+        self.logger.add_handler(self.handler)
+
+    def test_captureio_log(self):
+        """
+        CaptureIO takes in two arguments. The second argument must
+        be truthy in order for the code to run. Hence, the string
+        "capture_stdio" has been used in this test case.
+        """
+        with capture.CaptureIO(self.logger, "capture_stdio"):
+            print("message 1")
+            sys.stdout.write("message 2")
+            sys.stderr.write("message 3")
+            sys.stdout.write("\xff")
+        log = self.handler.items
+        messages = [item["message"] for item in log]
+        self.assertIn("STDOUT: message 1", messages)
+        self.assertIn("STDOUT: message 2", messages)
+        self.assertIn("STDERR: message 3", messages)
+        self.assertIn(u"STDOUT: \xff", messages)
+
+
+if __name__ == "__main__":
+    mozunit.main()
--- a/testing/raptor/raptor/control_server.py
+++ b/testing/raptor/raptor/control_server.py
@@ -70,17 +70,18 @@ def MakeCustomHandlerClass(results_handl
                 self.write_raw_gecko_profile(_test, _pagecycle, _raw_profile)
             elif data['type'] == 'webext_results':
                 LOG.info("received " + data['type'] + ": " + str(data['data']))
                 self.results_handler.add(data['data'])
             elif data['type'] == "webext_raptor-page-timeout":
                 LOG.info("received " + data['type'] + ": " + str(data['data']))
                 # pageload test has timed out; record it as a failure
                 self.results_handler.add_page_timeout(str(data['data'][0]),
-                                                      str(data['data'][1]))
+                                                      str(data['data'][1]),
+                                                      dict(data['data'][2]))
             elif data['data'] == "__raptor_shutdownBrowser":
                 LOG.info("received " + data['type'] + ": " + str(data['data']))
                 # webext is telling us it's done, and time to shutdown the browser
                 self.shutdown_browser()
             elif data['type'] == 'webext_screenshot':
                 LOG.info("received " + data['type'])
                 self.results_handler.add_image(str(data['data'][0]),
                                                str(data['data'][1]),
new file mode 100644
--- /dev/null
+++ b/testing/raptor/raptor/playback/mitmproxy-recordings-raptor-tp6m-booking.manifest
@@ -0,0 +1,10 @@
+[
+  {
+    "size": 5641697,
+    "visibility": "public",
+    "digest": "6ca2e92ba09d198dcefeb135c337b2ee7fe9a1532f778d397923e19ec8a32a906916d0d3dbdb5c2cd8fc0525616fd0a4aaed3cf4d69c875e20d42065be7f11eb",
+    "algorithm": "sha512",
+    "filename": "mitmproxy-tp6m-booking.zip",
+    "unpack": true
+  }
+]
new file mode 100644
--- /dev/null
+++ b/testing/raptor/raptor/playback/mitmproxy-recordings-raptor-tp6m-cnn-ampstories.manifest
@@ -0,0 +1,10 @@
+[
+  {
+    "size": 5737904,
+    "visibility": "public",
+    "digest": "a90641def190d085f0c18bca7c4bf593e4d488662b97eb567691560f852cc01aa81b4dc8a136ca8cada75c39fba216ccdd5fc43ce9db28fcf61b9034ace4cc12",
+    "algorithm": "sha512",
+    "filename": "mitmproxy-tp6m-cnn-ampstories.zip",
+    "unpack": true
+  }
+]
new file mode 100644
--- /dev/null
+++ b/testing/raptor/raptor/playback/mitmproxy-recordings-raptor-tp6m-cnn.manifest
@@ -0,0 +1,10 @@
+[
+  {
+    "size": 8627557,
+    "visibility": "public",
+    "digest": "fa12a9bb4afe612b2ffd0d4ce2730db3db2832e1488751232ccbd45591ef3789843cd00a801f5e6b187d056f5f8eee0aad5667fa5bc11abeafff08caf9ed9752",
+    "algorithm": "sha512",
+    "filename": "mitmproxy-tp6m-cnn.zip",
+    "unpack": true
+  }
+]
--- a/testing/raptor/raptor/raptor.ini
+++ b/testing/raptor/raptor/raptor.ini
@@ -11,16 +11,17 @@
 [include:tests/raptor-tp6-10.ini]
 
 # raptor pageload binast tests desktop
 [include:tests/raptor-tp6-binast-1.ini]
 
 # raptor pageload tests mobile
 [include:tests/raptor-tp6m-1.ini]
 [include:tests/raptor-tp6m-2.ini]
+[include:tests/raptor-tp6m-4.ini]
 [include:tests/raptor-tp6m-5.ini]
 [include:tests/raptor-tp6m-6.ini]
 [include:tests/raptor-tp6m-7.ini]
 [include:tests/raptor-tp6m-8.ini]
 
 # raptor benchmark tests
 [include:tests/raptor-assorted-dom.ini]
 [include:tests/raptor-motionmark-animometer.ini]
--- a/testing/raptor/raptor/raptor.py
+++ b/testing/raptor/raptor/raptor.py
@@ -10,17 +10,16 @@ import os
 import posixpath
 import shutil
 import sys
 import tempfile
 import time
 
 import mozcrash
 import mozinfo
-
 from mozdevice import ADBDevice
 from mozlog import commandline, get_default_logger
 from mozprofile import create_profile
 from mozrunner import runners
 
 # need this so raptor imports work both from /raptor and via mach
 here = os.path.abspath(os.path.dirname(__file__))
 paths = [here]
@@ -36,16 +35,17 @@ paths.append(webext_dir)
 
 for path in paths:
     if not os.path.exists(path):
         raise IOError("%s does not exist. " % path)
     sys.path.insert(0, path)
 
 try:
     from mozbuild.base import MozbuildObject
+
     build = MozbuildObject.from_environment(cwd=here)
 except ImportError:
     build = None
 
 from benchmark import Benchmark
 from cmdline import parse_args
 from control_server import RaptorControlServer
 from gen_test_config import gen_test_config
@@ -710,18 +710,21 @@ def main(args=sys.argv[1:]):
         os.sys.exit(1)
 
     # if we have results but one test page timed out (i.e. one tp6 test page didn't load
     # but others did) we still dumped PERFHERDER_DATA for the successfull pages but we
     # want the overall test job to marked as a failure
     pages_that_timed_out = raptor.get_page_timeout_list()
     if len(pages_that_timed_out) > 0:
         for _page in pages_that_timed_out:
-            LOG.critical("TEST-UNEXPECTED-FAIL: test '%s' timed out loading test page: %s"
-                         % (_page['test_name'], _page['url']))
+            LOG.critical("TEST-UNEXPECTED-FAIL: test '%s' timed out loading test page: %s "
+                         "pending metrics: %s"
+                         % (_page['test_name'],
+                            _page['url'],
+                            _page['pending_metrics']))
         os.sys.exit(1)
 
     # when running raptor locally with gecko profiling on, use the view-gecko-profile
     # tool to automatically load the latest gecko profile in profiler.firefox.com
     if args.gecko_profile and args.run_local:
         if os.environ.get('DISABLE_PROFILE_LAUNCH', '0') == '1':
             LOG.info("Not launching profiler.firefox.com because DISABLE_PROFILE_LAUNCH=1")
         else:
--- a/testing/raptor/raptor/results.py
+++ b/testing/raptor/raptor/results.py
@@ -1,20 +1,19 @@
 # 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/.
 
 # class to process, format, and report raptor test results
 # received from the raptor control server
 from __future__ import absolute_import
 
+from mozlog import get_proxy_logger
 from output import Output
 
-from mozlog import get_proxy_logger
-
 LOG = get_proxy_logger(component='results-handler')
 
 
 class RaptorResultsHandler():
     """Handle Raptor test results"""
 
     def __init__(self):
         self.results = []
@@ -30,18 +29,23 @@ class RaptorResultsHandler():
 
     def add_image(self, screenshot, test_name, page_cycle):
         # add to results
         LOG.info("received screenshot")
         self.images.append({'screenshot': screenshot,
                             'test_name': test_name,
                             'page_cycle': page_cycle})
 
-    def add_page_timeout(self, test_name, page_url):
-        self.page_timeout_list.append({'test_name': test_name, 'url': page_url})
+    def add_page_timeout(self, test_name, page_url, pending_metrics):
+
+        pending_metrics = [key for key, value in pending_metrics.items() if value]
+
+        self.page_timeout_list.append({'test_name': test_name,
+                                       'url': page_url,
+                                       'pending_metrics': ", ".join(pending_metrics)})
 
     def add_supporting_data(self, supporting_data):
         ''' Supporting data is additional data gathered outside of the regular
         Raptor test run (i.e. power data). Will arrive in a dict in the format of:
 
         supporting_data = {'type': 'data-type',
                            'test': 'raptor-test-ran-when-data-was-gathered',
                            'unit': 'unit that the values are in',
new file mode 100644
--- /dev/null
+++ b/testing/raptor/raptor/tests/raptor-tp6m-4.ini
@@ -0,0 +1,39 @@
+# 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/.
+
+# raptor tp6m-4
+
+[DEFAULT]
+type =  pageload
+playback = mitmproxy-android
+playback_binary_manifest = mitmproxy-rel-bin-{platform}.manifest
+page_cycles = 15
+unit = ms
+lower_is_better = true
+alert_threshold = 2.0
+page_timeout = 60000
+alert_on = fcp, loadtime
+
+[raptor-tp6m-booking-geckoview]
+apps = geckoview
+test_url = https://www.booking.com/
+playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-booking.manifest
+playback_recordings = android-booking.mp
+measure = fnbpaint, fcp, dcf, ttfi, loadtime
+
+[raptor-tp6m-cnn-geckoview]
+apps = geckoview
+test_url = https://edition.cnn.com/
+playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-cnn.manifest
+playback_recordings = android-cnn.mp
+measure = fnbpaint, fcp, dcf, ttfi, loadtime
+disabled = Bug 1533287 Intermittent timeouts running raptor-tp6m-cnn-geckoview
+
+[raptor-tp6m-cnn-ampstories-geckoview]
+apps = geckoview
+test_url = https://edition.cnn.com/ampstories/us/why-hurricane-michael-is-a-monster-unlike-any-other
+playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-cnn-ampstories.manifest
+playback_recordings = android-cnn-ampstories.mp
+measure = fnbpaint, fcp, dcf, ttfi, loadtime
+disabled = Bug 1533666 Intermittent timeouts running raptor-tp6m-cnn-ampstories-geckoview
--- a/testing/raptor/webext/raptor/manifest.json
+++ b/testing/raptor/webext/raptor/manifest.json
@@ -14,16 +14,18 @@
   "content_scripts": [
     {
       "matches": [
                   "*://*.allrecipes.com/*",
                   "*://*.apple.com/*",
                   "*://*.amazon.com/*",
                   "*://*.bbc.com/*",
                   "*://*.bing.com/*",
+                  "*://*.booking.com/*",
+                  "*://*.cnn.com/*",
                   "*://*.ebay.com/*",
                   "*://*.ebay-kleinanzeigen.de/*",
                   "*://*.espn.com/*",
                   "*://*.facebook.com/*",
                   "*://*.google.com/*",
                   "*://*.imdb.com/*",
                   "*://*.imgur.com/*",
                   "*://*.instagram.com/*",
--- a/testing/raptor/webext/raptor/runner.js
+++ b/testing/raptor/webext/raptor/runner.js
@@ -393,17 +393,28 @@ async function nextCycle() {
       }, pageCycleDelay);
     } else {
       verifyResults();
     }
 }
 
 async function timeoutAlarmListener() {
   console.error("raptor-page-timeout on %s" % testURL);
-  postToControlServer("raptor-page-timeout", [testName, testURL]);
+
+  var pendingMetrics = {
+    "hero": isHeroPending,
+    "fnb paint": isFNBPaintPending,
+    "fcp": isFCPPending,
+    "dcf": isDCFPending,
+    "ttfi": isTTFIPending,
+    "load time": isLoadTimePending,
+  };
+
+  postToControlServer("raptor-page-timeout", [testName, testURL, pendingMetrics]);
+
   // take a screen capture
   if (screenCapture) {
     await getScreenCapture();
   }
   // call clean-up to shutdown gracefully
   cleanUp();
 }
 
deleted file mode 100644
--- a/testing/web-platform/meta/webrtc/RTCPeerConnection-setRemoteDescription.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[RTCPeerConnection-setRemoteDescription.html]
-  [Switching role from offerer to answerer after going back to stable state should succeed]
-    expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1531156
-
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/testrunner.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/testrunner.py
@@ -2,19 +2,17 @@ from __future__ import unicode_literals
 
 import multiprocessing
 import threading
 import traceback
 from Queue import Empty
 from collections import namedtuple
 from multiprocessing import Process, current_process, Queue
 
-from mozlog import structuredlog
-
-import wptlogging
+from mozlog import structuredlog, capture
 
 # Special value used as a sentinal in various commands
 Stop = object()
 
 
 class MessageLogger(object):
     def __init__(self, message_func):
         self.send_message = message_func
@@ -133,17 +131,17 @@ def start_runner(runner_command_queue, r
         runner_result_queue.put((command, args))
 
     def handle_error(e):
         logger.critical(traceback.format_exc())
         stop_flag.set()
 
     logger = MessageLogger(send_message)
 
-    with wptlogging.CaptureIO(logger, capture_stdio):
+    with capture.CaptureIO(logger, capture_stdio):
         try:
             browser = executor_browser_cls(**executor_browser_kwargs)
             executor = executor_cls(browser, **executor_kwargs)
             with TestRunner(logger, runner_command_queue, runner_result_queue, executor) as runner:
                 try:
                     runner.run()
                 except KeyboardInterrupt:
                     stop_flag.set()
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/wptlogging.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/wptlogging.py
@@ -1,13 +1,9 @@
 import logging
-import sys
-import threading
-from StringIO import StringIO
-from multiprocessing import Queue
 
 from mozlog import commandline, stdadapter, set_default_logger
 from mozlog.structuredlog import StructuredLogger
 
 def setup(args, defaults):
     logger = args.pop('log', None)
     if logger:
         set_default_logger(logger)
@@ -44,91 +40,8 @@ class LogLevelRewriter(object):
         self.from_levels = [item.upper() for item in from_levels]
         self.to_level = to_level.upper()
 
     def __call__(self, data):
         if data["action"] == "log" and data["level"].upper() in self.from_levels:
             data = data.copy()
             data["level"] = self.to_level
         return self.inner(data)
-
-
-class LogThread(threading.Thread):
-    def __init__(self, queue, logger, level):
-        self.queue = queue
-        self.log_func = getattr(logger, level)
-        threading.Thread.__init__(self, name="Thread-Log")
-        self.daemon = True
-
-    def run(self):
-        while True:
-            try:
-                msg = self.queue.get()
-            except (EOFError, IOError):
-                break
-            if msg is None:
-                break
-            else:
-                self.log_func(msg)
-
-
-class LoggingWrapper(StringIO):
-    """Wrapper for file like objects to redirect output to logger
-    instead"""
-
-    def __init__(self, queue, prefix=None):
-        StringIO.__init__(self)
-        self.queue = queue
-        self.prefix = prefix
-
-    def write(self, data):
-        if isinstance(data, str):
-            try:
-                data = data.decode("utf8")
-            except UnicodeDecodeError:
-                data = data.encode("string_escape").decode("ascii")
-
-        if data.endswith("\n"):
-            data = data[:-1]
-        if data.endswith("\r"):
-            data = data[:-1]
-        if not data:
-            return
-        if self.prefix is not None:
-            data = "%s: %s" % (self.prefix, data)
-        self.queue.put(data)
-
-    def flush(self):
-        pass
-
-
-class CaptureIO(object):
-    def __init__(self, logger, do_capture):
-        self.logger = logger
-        self.do_capture = do_capture
-        self.logging_queue = None
-        self.logging_thread = None
-        self.original_stdio = None
-
-    def __enter__(self):
-        if self.do_capture:
-            self.original_stdio = (sys.stdout, sys.stderr)
-            self.logging_queue = Queue()
-            self.logging_thread = LogThread(self.logging_queue, self.logger, "info")
-            sys.stdout = LoggingWrapper(self.logging_queue, prefix="STDOUT")
-            sys.stderr = LoggingWrapper(self.logging_queue, prefix="STDERR")
-            self.logging_thread.start()
-
-    def __exit__(self, *args, **kwargs):
-        if self.do_capture:
-            sys.stdout, sys.stderr = self.original_stdio
-            if self.logging_queue is not None:
-                self.logger.info("Closing logging queue")
-                self.logging_queue.put(None)
-                if self.logging_thread is not None:
-                    self.logging_thread.join(10)
-                while not self.logging_queue.empty():
-                    try:
-                        self.logger.warning("Dropping log message: %r", self.logging_queue.get())
-                    except Exception:
-                        pass
-                self.logging_queue.close()
-                self.logger.info("queue closed")
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/wptrunner.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/wptrunner.py
@@ -7,16 +7,17 @@ import sys
 from wptserve import sslutils
 
 import environment as env
 import products
 import testloader
 import wptcommandline
 import wptlogging
 import wpttest
+from mozlog import capture
 from font import FontInstaller
 from testrunner import ManagerGroup
 from browsers.base import NullBrowser
 
 here = os.path.split(__file__)[0]
 
 logger = None
 
@@ -127,17 +128,17 @@ def get_pause_after_test(test_loader, **
             return False
         if kwargs["repeat"] == 1 and kwargs["rerun"] == 1 and total_tests == 1:
             return True
         return False
     return kwargs["pause_after_test"]
 
 
 def run_tests(config, test_paths, product, **kwargs):
-    with wptlogging.CaptureIO(logger, not kwargs["no_capture_stdio"]):
+    with capture.CaptureIO(logger, not kwargs["no_capture_stdio"]):
         env.do_delayed_imports(logger, test_paths)
 
         product = products.load_product(config, product, load_cls=True)
 
         env_extras = product.get_env_extras(**kwargs)
 
         product.check_args(**kwargs)
 
--- a/toolkit/components/places/Bookmarks.jsm
+++ b/toolkit/components/places/Bookmarks.jsm
@@ -156,18 +156,18 @@ var Bookmarks = Object.freeze({
   userContentRoots: ["toolbar_____", "menu________", "unfiled_____", "mobile______"],
 
   /**
    * GUIDs associated with virtual queries that are used for displaying bookmark
    * folders in the left pane.
    */
   virtualMenuGuid: "menu_______v",
   virtualToolbarGuid: "toolbar____v",
-  virtualUnfiledGuid: "unfiled___v",
-  virtualMobileGuid: "mobile____v",
+  virtualUnfiledGuid: "unfiled____v",
+  virtualMobileGuid: "mobile_____v",
 
   /**
    * Checks if a guid is a virtual root.
    *
    * @param {String} guid The guid of the item to look for.
    * @returns {Boolean} true if guid is a virtual root, false otherwise.
    */
   isVirtualRootItem(guid) {
--- a/toolkit/components/places/nsNavHistory.cpp
+++ b/toolkit/components/places/nsNavHistory.cpp
@@ -1614,17 +1614,17 @@ nsresult PlacesSQLQueryBuilder::SelectAs
           "VALUES(null, 'place:parent=" TOOLBAR_ROOT_GUID
           "', :BookmarksToolbarFolderTitle, null, null, null, "
           "null, null, 0, 0, null, null, null, null, 'toolbar____v', null), "
           "(null, 'place:parent=" MENU_ROOT_GUID
           "', :BookmarksMenuFolderTitle, null, null, null, "
           "null, null, 0, 0, null, null, null, null, 'menu_______v', null), "
           "(null, 'place:parent=" UNFILED_ROOT_GUID
           "', :OtherBookmarksFolderTitle, null, null, null, "
-          "null, null, 0, 0, null, null, null, null, 'unfiled___v', null) ") +
+          "null, null, 0, 0, null, null, null, null, 'unfiled____v', null) ") +
       mobileString + NS_LITERAL_CSTRING(")");
 
   return NS_OK;
 }
 
 nsresult PlacesSQLQueryBuilder::SelectAsLeftPane() {
   nsNavHistory* history = nsNavHistory::GetHistoryService();
   NS_ENSURE_STATE(history);
--- a/toolkit/components/places/nsNavHistory.h
+++ b/toolkit/components/places/nsNavHistory.h
@@ -55,17 +55,17 @@
     "places-autocomplete-feedback-updated"
 #endif
 
 // The preference we watch to know when the mobile bookmarks folder is filled by
 // sync.
 #define MOBILE_BOOKMARKS_PREF "browser.bookmarks.showMobileBookmarks"
 
 // The guid of the mobile bookmarks virtual query.
-#define MOBILE_BOOKMARKS_VIRTUAL_GUID "mobile____v"
+#define MOBILE_BOOKMARKS_VIRTUAL_GUID "mobile_____v"
 
 #define ROOT_GUID "root________"
 #define MENU_ROOT_GUID "menu________"
 #define TOOLBAR_ROOT_GUID "toolbar_____"
 #define UNFILED_ROOT_GUID "unfiled_____"
 #define TAGS_ROOT_GUID "tags________"
 #define MOBILE_ROOT_GUID "mobile______"