Merge mozilla-central to mozilla-inbound. a=merge CLOSED TREE
authorCiure Andrei <aciure@mozilla.com>
Fri, 04 Jan 2019 05:47:49 +0200
changeset 509636 81804f8f55d0b7971a16e7da807799f8de04d01b
parent 509635 1979ad1abb92120daf45e949079b1a818b17b285 (current diff)
parent 509603 ce9872f98d6274a8db6592cc8f0ad19ea332fafd (diff)
child 509637 f612a041c69c5a78452c6c361d2ba7691ad9f6d4
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone66.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/shadow-dom/slots-fallback.html.ini
testing/web-platform/meta/shadow-dom/slots.html.ini
--- a/Makefile.in
+++ b/Makefile.in
@@ -193,18 +193,22 @@ profiledbuild::
 	$(MAKE) maybe_clobber_profiledbuild
 	$(call BUILDSTATUS,TIER_FINISH pgo_clobber)
 	$(call BUILDSTATUS,TIER_START pgo_profile_use)
 	$(MAKE) default MOZ_PROFILE_USE=1 $(if $(CLANG_CL),MOZ_PROFILE_ORDER_FILE=$(topobjdir)/cygprofile.txt)
 	$(call BUILDSTATUS,TIER_FINISH pgo_profile_use)
 
 # Change default target to PGO build if PGO is enabled.
 ifdef MOZ_PGO
+# If one of these is already set in addition to PGO we are doing a single phase
+# of PGO in isolation, so don't override the default target.
+ifeq (,$(MOZ_PROFILE_GENERATE)$(MOZ_PROFILE_USE))
 OVERRIDE_DEFAULT_GOAL := profiledbuild
 endif
+endif
 
 include $(topsrcdir)/config/rules.mk
 
 ifdef SCCACHE_VERBOSE_STATS
 default::
 	-$(CCACHE) --show-stats --stats-format=json > sccache-stats.json
 	@echo "===SCCACHE STATS==="
 	-$(CCACHE) --show-stats
--- a/browser/base/content/browser-pageActions.js
+++ b/browser/base/content/browser-pageActions.js
@@ -1060,17 +1060,19 @@ BrowserPageActions.sendToDevice = {
     gSync.populateSendTabToDevicesMenu(bodyNode, url, title, multiselected, (clientId, name, clientType, lastModified) => {
       if (!name) {
         return document.createXULElement("toolbarseparator");
       }
       let item = document.createXULElement("toolbarbutton");
       item.classList.add("pageAction-sendToDevice-device", "subviewbutton");
       if (clientId) {
         item.classList.add("subviewbutton-iconic");
-        item.setAttribute("tooltiptext", gSync.formatLastSyncDate(lastModified));
+        if (lastModified) {
+          item.setAttribute("tooltiptext", gSync.formatLastSyncDate(lastModified));
+        }
       }
 
       item.addEventListener("command", event => {
         if (panelNode) {
           PanelMultiView.hidePopup(panelNode);
         }
         // There are items in the subview that don't represent devices: "Sign
         // in", "Learn about Sync", etc.  Device items will be .sendtab-target.
@@ -1081,24 +1083,24 @@ BrowserPageActions.sendToDevice = {
         }
       });
       return item;
     });
 
     bodyNode.removeAttribute("state");
     // In the first ~10 sec after startup, Sync may not be loaded and the list
     // of devices will be empty.
-    if (gSync.syncConfiguredAndLoading) {
+    if (gSync.sendTabConfiguredAndLoading) {
       bodyNode.setAttribute("state", "notready");
       // Force a background Sync
       Services.tm.dispatchToMainThread(async () => {
         await Weave.Service.sync({why: "pageactions", engines: []}); // [] = clients engine only
         // There's no way Sync is still syncing at this point, but we check
         // anyway to avoid infinite looping.
-        if (!window.closed && !gSync.syncConfiguredAndLoading) {
+        if (!window.closed && !gSync.sendTabConfiguredAndLoading) {
           this.onShowingSubview(panelViewNode);
         }
       });
     }
   },
 };
 
 // add search engine
--- a/browser/base/content/browser-sync.js
+++ b/browser/base/content/browser-sync.js
@@ -44,21 +44,21 @@ var gSync = {
       "chrome://weave/locale/sync.properties"
     );
   },
 
   get syncReady() {
     return Cc["@mozilla.org/weave/service;1"].getService().wrappedJSObject.ready;
   },
 
-  // Returns true if sync is configured but hasn't loaded or is yet to determine
-  // if any remote clients exist.
-  get syncConfiguredAndLoading() {
+  // Returns true if sync is configured but hasn't loaded or the send tab
+  // targets list isn't ready yet.
+  get sendTabConfiguredAndLoading() {
     return UIState.get().status == UIState.STATUS_SIGNED_IN &&
-           (!this.syncReady || Weave.Service.clientsEngine.isFirstSync);
+           (!this.syncReady || !Weave.Service.clientsEngine.hasSyncedThisSession);
   },
 
   get isSignedIn() {
     return UIState.get().status == UIState.STATUS_SIGNED_IN;
   },
 
   get sendTabTargets() {
     return Weave.Service.clientsEngine.fxaDevices
@@ -363,17 +363,17 @@ var gSync = {
     // remove existing menu items
     for (let i = devicesPopup.children.length - 1; i >= 0; --i) {
       let child = devicesPopup.children[i];
       if (child.classList.contains("sync-menuitem")) {
         child.remove();
       }
     }
 
-    if (gSync.syncConfiguredAndLoading) {
+    if (gSync.sendTabConfiguredAndLoading) {
       // We can only be in this case in the page action menu.
       return;
     }
 
     const fragment = document.createDocumentFragment();
 
     const state = UIState.get();
     if (state.status == UIState.STATUS_SIGNED_IN && this.sendTabTargets.length > 0) {
@@ -537,17 +537,17 @@ var gSync = {
     }
     let hasASendableURI = false;
     for (let tab of aTargetTab.multiselected ? gBrowser.selectedTabs : [aTargetTab]) {
       if (this.isSendableURI(tab.linkedBrowser.currentURI.spec)) {
         hasASendableURI = true;
         break;
       }
     }
-    const enabled = !this.syncConfiguredAndLoading && hasASendableURI;
+    const enabled = !this.sendTabConfiguredAndLoading && hasASendableURI;
 
     let sendTabsToDevice = document.getElementById("context_sendTabToDevice");
     sendTabsToDevice.disabled = !enabled;
 
     let tabCount = aTargetTab.multiselected ? gBrowser.multiSelectedTabsCount : 1;
     sendTabsToDevice.label = PluralForm.get(tabCount,
                                            gNavigatorBundle.getString("sendTabsToDevice.label"))
                                       .replace("#1", tabCount.toLocaleString());
@@ -577,17 +577,17 @@ var gSync = {
     .forEach(id => contextMenu.showItem(id, showSendLink));
 
     if (!showSendLink && !showSendPage) {
       return;
     }
 
     const targetURI = showSendLink ? contextMenu.linkURL :
                                      contextMenu.browser.currentURI.spec;
-    const enabled = !this.syncConfiguredAndLoading && this.isSendableURI(targetURI);
+    const enabled = !this.sendTabConfiguredAndLoading && this.isSendableURI(targetURI);
     contextMenu.setItemAttr(showSendPage ? "context-sendpagetodevice" :
                                            "context-sendlinktodevice",
                                            "disabled", !enabled || null);
   },
 
   // Functions called by observers
   onActivityStart() {
     clearTimeout(this._syncAnimationTimer);
--- a/browser/base/content/test/sync/browser_contextmenu_sendpage.js
+++ b/browser/base/content/test/sync/browser_contextmenu_sendpage.js
@@ -1,48 +1,50 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-const targetsFixture = [ { id: 1, name: "Foo"}, { id: 2, name: "Bar"} ];
+const fxaDevices = [
+  {id: 1, name: "Foo", availableCommands: {"https://identity.mozilla.com/cmd/open-uri": "baz"}},
+  {id: 2, name: "Bar", clientRecord: "bar"}, // Legacy send tab target (no availableCommands).
+  {id: 3, name: "Homer"}, // Incompatible target.
+];
 
 add_task(async function setup() {
   await promiseSyncReady();
   // gSync.init() is called in a requestIdleCallback. Force its initialization.
   gSync.init();
   sinon.stub(Weave.Service.clientsEngine, "getClientType").returns("desktop");
   await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
 });
 
 add_task(async function test_page_contextmenu() {
-  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, targets: targetsFixture,
-                                      state: UIState.STATUS_SIGNED_IN, isSendableURI: true });
+  const sandbox = setupSendTabMocks({fxaDevices});
 
   await openContentContextMenu("#moztext", "context-sendpagetodevice");
   is(document.getElementById("context-sendpagetodevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context-sendpagetodevice").disabled, false, "Send tab to device is enabled");
   checkPopup([
+    { label: "Bar" },
     { label: "Foo" },
-    { label: "Bar" },
     "----",
     { label: "Send to All Devices" },
   ]);
   await hideContentContextMenu();
 
   sandbox.restore();
 });
 
 add_task(async function test_link_contextmenu() {
-  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, targets: targetsFixture,
-                                      state: UIState.STATUS_SIGNED_IN, isSendableURI: true });
+  const sandbox = setupSendTabMocks({fxaDevices});
   let expectation = sandbox.mock(gSync)
                            .expects("sendTabToDevice")
                            .once()
-                           .withExactArgs("https://www.example.org/", [{id: 1, name: "Foo"}], "Click on me!!");
+                           .withExactArgs("https://www.example.org/", [fxaDevices[1]], "Click on me!!");
 
   // Add a link to the page
   await ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
     let a = content.document.createElement("a");
     a.href = "https://www.example.org";
     a.id = "testingLink";
     a.textContent = "Click on me!!";
     content.document.body.appendChild(a);
@@ -54,107 +56,100 @@ add_task(async function test_link_contex
   document.getElementById("context-sendlinktodevice-popup").querySelector("menuitem").click();
   await hideContentContextMenu();
 
   expectation.verify();
   sandbox.restore();
 });
 
 add_task(async function test_page_contextmenu_no_remote_clients() {
-  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, targets: [],
-                                      state: UIState.STATUS_SIGNED_IN, isSendableURI: true });
+  const sandbox = setupSendTabMocks({fxaDevices: []});
 
   await openContentContextMenu("#moztext", "context-sendpagetodevice");
   is(document.getElementById("context-sendpagetodevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context-sendpagetodevice").disabled, false, "Send tab to device is enabled");
   checkPopup([
     { label: "No Devices Connected", disabled: true },
     "----",
     { label: "Connect Another Device..." },
     { label: "Learn About Sending Tabs..." },
   ]);
   await hideContentContextMenu();
 
   sandbox.restore();
 });
 
 add_task(async function test_page_contextmenu_one_remote_client() {
-  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, targets: [{ id: 1, name: "Foo"}],
-                                      state: UIState.STATUS_SIGNED_IN, isSendableURI: true });
+  const sandbox = setupSendTabMocks({fxaDevices: [{id: 1, name: "Foo", availableCommands: {"https://identity.mozilla.com/cmd/open-uri": "baz"}}]});
 
   await openContentContextMenu("#moztext", "context-sendpagetodevice");
   is(document.getElementById("context-sendpagetodevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context-sendpagetodevice").disabled, false, "Send tab to device is enabled");
   checkPopup([
     { label: "Foo" },
   ]);
   await hideContentContextMenu();
 
   sandbox.restore();
 });
 
 add_task(async function test_page_contextmenu_not_sendable() {
-  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, targets: targetsFixture,
-                                      state: UIState.STATUS_SIGNED_IN, isSendableURI: false });
+  const sandbox = setupSendTabMocks({fxaDevices, isSendableURI: false});
 
   await openContentContextMenu("#moztext");
   is(document.getElementById("context-sendpagetodevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context-sendpagetodevice").disabled, true, "Send tab to device is disabled");
   checkPopup();
   await hideContentContextMenu();
 
   sandbox.restore();
 });
 
 add_task(async function test_page_contextmenu_not_synced_yet() {
-  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: false, targets: [],
-                                      state: UIState.STATUS_SIGNED_IN, isSendableURI: true });
+  const sandbox = setupSendTabMocks({fxaDevices: null});
 
   await openContentContextMenu("#moztext");
   is(document.getElementById("context-sendpagetodevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context-sendpagetodevice").disabled, true, "Send tab to device is disabled");
   checkPopup();
   await hideContentContextMenu();
 
   sandbox.restore();
 });
 
 add_task(async function test_page_contextmenu_sync_not_ready_configured() {
-  const sandbox = setupSendTabMocks({ syncReady: false, clientsSynced: false, targets: null,
-                                      state: UIState.STATUS_SIGNED_IN, isSendableURI: true });
+  const sandbox = setupSendTabMocks({syncReady: false});
 
   await openContentContextMenu("#moztext");
   is(document.getElementById("context-sendpagetodevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context-sendpagetodevice").disabled, true, "Send tab to device is disabled");
   checkPopup();
   await hideContentContextMenu();
 
   sandbox.restore();
 });
 
 add_task(async function test_page_contextmenu_sync_not_ready_other_state() {
-  const sandbox = setupSendTabMocks({ syncReady: false, clientsSynced: false, targets: null,
-                                      state: UIState.STATUS_NOT_VERIFIED, isSendableURI: true });
+  const sandbox = setupSendTabMocks({syncReady: false, state: UIState.STATUS_NOT_VERIFIED});
 
   await openContentContextMenu("#moztext", "context-sendpagetodevice");
   is(document.getElementById("context-sendpagetodevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context-sendpagetodevice").disabled, false, "Send tab to device is enabled");
   checkPopup([
     { label: "Account Not Verified", disabled: true },
     "----",
     { label: "Verify Your Account..." },
   ]);
   await hideContentContextMenu();
 
   sandbox.restore();
 });
 
 add_task(async function test_page_contextmenu_unconfigured() {
-  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, targets: null,
-                                      state: UIState.STATUS_NOT_CONFIGURED, isSendableURI: true });
+  const sandbox = setupSendTabMocks({state: UIState.STATUS_NOT_CONFIGURED});
 
   await openContentContextMenu("#moztext", "context-sendpagetodevice");
   is(document.getElementById("context-sendpagetodevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context-sendpagetodevice").disabled, false, "Send tab to device is enabled");
   checkPopup([
     { label: "Not Connected to Sync", disabled: true },
     "----",
     { label: "Sign in to Sync..." },
@@ -162,52 +157,47 @@ add_task(async function test_page_contex
   ]);
 
   await hideContentContextMenu();
 
   sandbox.restore();
 });
 
 add_task(async function test_page_contextmenu_not_verified() {
-  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, targets: null,
-                                      state: UIState.STATUS_NOT_VERIFIED, isSendableURI: true });
+  const sandbox = setupSendTabMocks({state: UIState.STATUS_NOT_VERIFIED});
 
   await openContentContextMenu("#moztext", "context-sendpagetodevice");
   is(document.getElementById("context-sendpagetodevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context-sendpagetodevice").disabled, false, "Send tab to device is enabled");
   checkPopup([
     { label: "Account Not Verified", disabled: true },
     "----",
     { label: "Verify Your Account..." },
   ]);
 
   await hideContentContextMenu();
 
   sandbox.restore();
 });
 
 add_task(async function test_page_contextmenu_login_failed() {
-  const syncReady = sinon.stub(gSync, "syncReady").get(() => true);
-  const getState = sinon.stub(UIState, "get").returns({ status: UIState.STATUS_LOGIN_FAILED });
-  const isSendableURI = sinon.stub(gSync, "isSendableURI").returns(true);
+  const sandbox = setupSendTabMocks({state: UIState.STATUS_LOGIN_FAILED});
 
   await openContentContextMenu("#moztext", "context-sendpagetodevice");
   is(document.getElementById("context-sendpagetodevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context-sendpagetodevice").disabled, false, "Send tab to device is enabled");
   checkPopup([
     { label: "Account Not Verified", disabled: true },
     "----",
     { label: "Verify Your Account..." },
   ]);
 
   await hideContentContextMenu();
 
-  syncReady.restore();
-  getState.restore();
-  isSendableURI.restore();
+  sandbox.restore();
 });
 
 add_task(async function test_page_contextmenu_fxa_disabled() {
   const getter = sinon.stub(gSync, "SYNC_ENABLED").get(() => false);
   gSync.onSyncDisabled(); // Would have been called on gSync initialization if SYNC_ENABLED had been set.
   await openContentContextMenu("#moztext");
   is(document.getElementById("context-sendpagetodevice").hidden, true, "Send tab to device is hidden");
   is(document.getElementById("context-sep-sendpagetodevice").hidden, true, "Separator is also hidden");
--- a/browser/base/content/test/sync/browser_contextmenu_sendtab.js
+++ b/browser/base/content/test/sync/browser_contextmenu_sendtab.js
@@ -2,24 +2,26 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const chrome_base = "chrome://mochitests/content/browser/browser/base/content/test/general/";
 Services.scriptloader.loadSubScript(chrome_base + "head.js", this);
 /* import-globals-from ../general/head.js */
 
-const targetsFixture = [ { id: 1, name: "Foo"}, { id: 2, name: "Bar"} ];
+const fxaDevices = [
+  {id: 1, name: "Foo", availableCommands: {"https://identity.mozilla.com/cmd/open-uri": "baz"}},
+  {id: 2, name: "Bar", clientRecord: "bar"}, // Legacy send tab target (no availableCommands).
+  {id: 3, name: "Homer"}, // Incompatible target.
+];
 
 let [testTab] = gBrowser.visibleTabs;
 
-function updateTabContextMenu(tab) {
+function updateTabContextMenu(tab = gBrowser.selectedTab) {
   let menu = document.getElementById("tabContextMenu");
-  if (!tab)
-    tab = gBrowser.selectedTab;
   var evt = new Event("");
   tab.dispatchEvent(evt);
   menu.openPopup(tab, "end_after", 0, 0, true, false, evt);
   is(window.TabContextMenu.contextTab, tab, "TabContextMenu context is the expected tab");
   menu.hidePopup();
 }
 
 add_task(async function setup() {
@@ -30,82 +32,76 @@ add_task(async function setup() {
   await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
   registerCleanupFunction(() => {
     gBrowser.removeCurrentTab();
   });
   is(gBrowser.visibleTabs.length, 2, "there are two visible tabs");
 });
 
 add_task(async function test_tab_contextmenu() {
-  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, targets: targetsFixture,
-                                      state: UIState.STATUS_SIGNED_IN, isSendableURI: true });
+  const sandbox = setupSendTabMocks({fxaDevices});
   let expectation = sandbox.mock(gSync)
                            .expects("sendTabToDevice")
                            .once()
-                           .withExactArgs("about:mozilla", [{id: 1, name: "Foo"}], "The Book of Mozilla, 11:14");
+                           .withExactArgs("about:mozilla", [fxaDevices[1]], "The Book of Mozilla, 11:14");
 
   updateTabContextMenu(testTab);
   await openTabContextMenu("context_sendTabToDevice");
   is(document.getElementById("context_sendTabToDevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context_sendTabToDevice").disabled, false, "Send tab to device is enabled");
 
   document.getElementById("context_sendTabToDevicePopupMenu").querySelector("menuitem").click();
 
   await hideTabContextMenu();
   expectation.verify();
   sandbox.restore();
 });
 
 add_task(async function test_tab_contextmenu_unconfigured() {
-  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, targets: targetsFixture,
-                                      state: UIState.STATUS_NOT_CONFIGURED, isSendableURI: true });
+  const sandbox = setupSendTabMocks({state: UIState.STATUS_NOT_CONFIGURED});
 
   updateTabContextMenu(testTab);
   is(document.getElementById("context_sendTabToDevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context_sendTabToDevice").disabled, false, "Send tab to device is enabled");
 
   sandbox.restore();
 });
 
 add_task(async function test_tab_contextmenu_not_sendable() {
-  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, targets: [{ id: 1, name: "Foo"}],
-                                      state: UIState.STATUS_SIGNED_IN, isSendableURI: false });
+  const sandbox = setupSendTabMocks({fxaDevices, isSendableURI: false});
 
   updateTabContextMenu(testTab);
   is(document.getElementById("context_sendTabToDevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context_sendTabToDevice").disabled, true, "Send tab to device is disabled");
 
   sandbox.restore();
 });
 
 add_task(async function test_tab_contextmenu_not_synced_yet() {
-  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: false, targets: [],
-                                      state: UIState.STATUS_SIGNED_IN, isSendableURI: true });
+  const sandbox = setupSendTabMocks({fxaDevices: null});
 
   updateTabContextMenu(testTab);
   is(document.getElementById("context_sendTabToDevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context_sendTabToDevice").disabled, true, "Send tab to device is disabled");
 
   sandbox.restore();
 });
 
 add_task(async function test_tab_contextmenu_sync_not_ready_configured() {
-  const sandbox = setupSendTabMocks({ syncReady: false, clientsSynced: false, targets: null,
-                                      state: UIState.STATUS_SIGNED_IN, isSendableURI: true });
+  const sandbox = setupSendTabMocks({syncReady: false});
 
   updateTabContextMenu(testTab);
   is(document.getElementById("context_sendTabToDevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context_sendTabToDevice").disabled, true, "Send tab to device is disabled");
 
   sandbox.restore();
 });
 
 add_task(async function test_tab_contextmenu_sync_not_ready_other_state() {
-  const sandbox = setupSendTabMocks({ syncReady: false, clientsSynced: false, targets: null,
-                                      state: UIState.STATUS_NOT_VERIFIED, isSendableURI: true });
+  const sandbox = setupSendTabMocks({syncReady: false, state: UIState.STATUS_NOT_VERIFIED});
 
   updateTabContextMenu(testTab);
   is(document.getElementById("context_sendTabToDevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context_sendTabToDevice").disabled, false, "Send tab to device is enabled");
 
   sandbox.restore();
 });
 
--- a/browser/base/content/test/sync/head.js
+++ b/browser/base/content/test/sync/head.js
@@ -9,17 +9,21 @@ registerCleanupFunction(function() {
 
 function promiseSyncReady() {
   let service = Cc["@mozilla.org/weave/service;1"]
                   .getService(Ci.nsISupports)
                   .wrappedJSObject;
   return service.whenLoaded();
 }
 
-function setupSendTabMocks({ syncReady, clientsSynced, targets, state, isSendableURI }) {
+function setupSendTabMocks({ syncReady = true, fxaDevices = null,
+                             state = UIState.STATUS_SIGNED_IN, isSendableURI = true }) {
   const sandbox = sinon.sandbox.create();
   sandbox.stub(gSync, "syncReady").get(() => syncReady);
-  sandbox.stub(Weave.Service.clientsEngine, "isFirstSync").get(() => !clientsSynced);
-  sandbox.stub(gSync, "sendTabTargets").get(() => targets);
+  if (fxaDevices) {
+    // Clone fxaDevices because it gets sorted in-place.
+    sandbox.stub(Weave.Service.clientsEngine, "fxaDevices").get(() => [...fxaDevices]);
+  }
+  sandbox.stub(Weave.Service.clientsEngine, "hasSyncedThisSession").get(() => !!fxaDevices);
   sandbox.stub(UIState, "get").returns({ status: state });
   sandbox.stub(gSync, "isSendableURI").returns(isSendableURI);
   return sandbox;
 }
--- a/browser/base/content/test/urlbar/browser_page_action_menu.js
+++ b/browser/base/content/test/urlbar/browser_page_action_menu.js
@@ -200,17 +200,16 @@ add_task(async function sendToDevice_non
 });
 
 add_task(async function sendToDevice_syncNotReady_other_states() {
   // Open a tab that's sendable.
   await BrowserTestUtils.withNewTab("http://example.com/", async () => {
     await promiseSyncReady();
     const sandbox = sinon.sandbox.create();
     sandbox.stub(gSync, "syncReady").get(() => false);
-    sandbox.stub(Weave.Service.clientsEngine, "isFirstSync").get(() => true);
     sandbox.stub(UIState, "get").returns({ status: UIState.STATUS_NOT_VERIFIED });
     sandbox.stub(gSync, "isSendableURI").returns(true);
 
     let cleanUp = () => {
       sandbox.restore();
     };
     registerCleanupFunction(cleanUp);
 
@@ -257,23 +256,23 @@ add_task(async function sendToDevice_syn
 });
 
 add_task(async function sendToDevice_syncNotReady_configured() {
   // Open a tab that's sendable.
   await BrowserTestUtils.withNewTab("http://example.com/", async () => {
     await promiseSyncReady();
     const sandbox = sinon.sandbox.create();
     const syncReady = sandbox.stub(gSync, "syncReady").get(() => false);
-    const lastSync = sandbox.stub(Weave.Service.clientsEngine, "isFirstSync").get(() => true);
+    const hasSyncedThisSession = sandbox.stub(Weave.Service.clientsEngine, "hasSyncedThisSession").get(() => false);
     sandbox.stub(UIState, "get").returns({ status: UIState.STATUS_SIGNED_IN });
     sandbox.stub(gSync, "isSendableURI").returns(true);
 
     sandbox.stub(Weave.Service, "sync").callsFake(() => {
       syncReady.get(() => true);
-      lastSync.get(() => Date.now());
+      hasSyncedThisSession.get(() => true);
       sandbox.stub(gSync, "sendTabTargets").get(() => mockTargets);
       sandbox.stub(Weave.Service.clientsEngine, "getClientType").callsFake(id => mockTargets.find(c => c.clientRecord && c.clientRecord.id == id).clientRecord.type);
     });
 
     let onShowingSubview = BrowserPageActions.sendToDevice.onShowingSubview;
     sandbox.stub(BrowserPageActions.sendToDevice, "onShowingSubview").callsFake((...args) => {
       this.numCall++ || (this.numCall = 1);
       onShowingSubview.call(BrowserPageActions.sendToDevice, ...args);
@@ -311,23 +310,26 @@ add_task(async function sendToDevice_syn
         let expectedItems = [
           {
             className: "pageAction-sendToDevice-notReady",
             display: "none",
             disabled: true,
           },
         ];
         for (let target of mockTargets) {
+          const attrs = {
+            clientId: target.id,
+            label: target.name,
+            clientType: target.type,
+          };
+          if (target.clientRecord && target.clientRecord.serverLastModified) {
+            attrs.tooltiptext = gSync.formatLastSyncDate(new Date(target.clientRecord.serverLastModified * 1000));
+          }
           expectedItems.push({
-            attrs: {
-              clientId: target.id,
-              label: target.name,
-              clientType: target.type,
-              tooltiptext: target.clientRecord ? gSync.formatLastSyncDate(new Date(lastModifiedFixture * 1000)) : "",
-            },
+            attrs,
           });
         }
         expectedItems.push(
           null,
           {
             attrs: {
               label: "Send to All Devices",
             },
@@ -398,20 +400,20 @@ add_task(async function sendToDevice_not
 });
 
 add_task(async function sendToDevice_noDevices() {
   // Open a tab that's sendable.
   await BrowserTestUtils.withNewTab("http://example.com/", async () => {
     await promiseSyncReady();
     const sandbox = sinon.sandbox.create();
     sandbox.stub(gSync, "syncReady").get(() => true);
-    sandbox.stub(Weave.Service.clientsEngine, "isFirstSync").get(() => false);
+    sandbox.stub(Weave.Service.clientsEngine, "hasSyncedThisSession").get(() => true);
+    sandbox.stub(Weave.Service.clientsEngine, "fxaDevices").get(() => []);
     sandbox.stub(UIState, "get").returns({ status: UIState.STATUS_SIGNED_IN });
     sandbox.stub(gSync, "isSendableURI").returns(true);
-    sandbox.stub(gSync, "sendTabTargets").get(() => []);
     sandbox.stub(Weave.Service.clientsEngine, "getClientType").callsFake(id => mockTargets.find(c => c.clientRecord && c.clientRecord.id == id).clientRecord.type);
 
     let cleanUp = () => {
       sandbox.restore();
     };
     registerCleanupFunction(cleanUp);
 
     // Open the panel.
@@ -464,17 +466,17 @@ add_task(async function sendToDevice_noD
 });
 
 add_task(async function sendToDevice_devices() {
   // Open a tab that's sendable.
   await BrowserTestUtils.withNewTab("http://example.com/", async () => {
     await promiseSyncReady();
     const sandbox = sinon.sandbox.create();
     sandbox.stub(gSync, "syncReady").get(() => true);
-    sandbox.stub(Weave.Service.clientsEngine, "isFirstSync").get(() => false);
+    sandbox.stub(Weave.Service.clientsEngine, "hasSyncedThisSession").get(() => true);
     sandbox.stub(UIState, "get").returns({ status: UIState.STATUS_SIGNED_IN });
     sandbox.stub(gSync, "isSendableURI").returns(true);
     sandbox.stub(gSync, "sendTabTargets").get(() => mockTargets);
     sandbox.stub(Weave.Service.clientsEngine, "getClientType").callsFake(id => mockTargets.find(c => c.clientRecord && c.clientRecord.id == id).clientRecord.type);
 
     let cleanUp = () => {
       sandbox.restore();
     };
@@ -530,17 +532,17 @@ add_task(async function sendToDevice_dev
 
 add_task(async function sendToDevice_title() {
   // Open two tabs that are sendable.
   await BrowserTestUtils.withNewTab("http://example.com/a", async otherBrowser => {
     await BrowserTestUtils.withNewTab("http://example.com/b", async () => {
       await promiseSyncReady();
       const sandbox = sinon.sandbox.create();
       sandbox.stub(gSync, "syncReady").get(() => true);
-      sandbox.stub(Weave.Service.clientsEngine, "isFirstSync").get(() => false);
+      sandbox.stub(Weave.Service.clientsEngine, "hasSyncedThisSession").get(() => true);
       sandbox.stub(UIState, "get").returns({ status: UIState.STATUS_SIGNED_IN });
       sandbox.stub(gSync, "isSendableURI").returns(true);
       sandbox.stub(gSync, "sendTabTargets").get(() => []);
       sandbox.stub(Weave.Service.clientsEngine, "getClientType").callsFake(id => mockTargets.find(c => c.clientRecord && c.clientRecord.id == id).clientRecord.type);
 
       let cleanUp = () => {
         sandbox.restore();
       };
@@ -587,17 +589,17 @@ add_task(async function sendToDevice_tit
 });
 
 add_task(async function sendToDevice_inUrlbar() {
   // Open a tab that's sendable.
   await BrowserTestUtils.withNewTab("http://example.com/", async () => {
     await promiseSyncReady();
     const sandbox = sinon.sandbox.create();
     sandbox.stub(gSync, "syncReady").get(() => true);
-    sandbox.stub(Weave.Service.clientsEngine, "isFirstSync").get(() => false);
+    sandbox.stub(Weave.Service.clientsEngine, "hasSyncedThisSession").get(() => true);
     sandbox.stub(UIState, "get").returns({ status: UIState.STATUS_SIGNED_IN });
     sandbox.stub(gSync, "isSendableURI").returns(true);
     sandbox.stub(gSync, "sendTabTargets").get(() => mockTargets);
     sandbox.stub(Weave.Service.clientsEngine, "getClientType").callsFake(id => mockTargets.find(c => c.clientRecord && c.clientRecord.id == id).clientRecord.type);
 
     let cleanUp = () => {
       sandbox.restore();
     };
new file mode 100644
--- /dev/null
+++ b/build/merge_profdata.py
@@ -0,0 +1,11 @@
+# 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/.
+
+import subprocess
+import buildconfig
+
+
+def main(_, profile_file):
+    subprocess.check_call([buildconfig.substs['LLVM_PROFDATA'], 'merge',
+                           '-o', 'merged.profdata', profile_file])
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -14,26 +14,73 @@ js_option('--enable-release',
 
 
 @depends('--enable-release')
 def developer_options(value):
     if not value:
         return True
 
 
+llvm_profdata = check_prog('LLVM_PROFDATA', ['llvm-profdata'],
+                           allow_missing=True)
+
+add_old_configure_assignment('LLVM_PROFDATA', llvm_profdata)
+
+
 add_old_configure_assignment('DEVELOPER_OPTIONS', developer_options)
 set_config('DEVELOPER_OPTIONS', developer_options)
 
 # PGO
 # ==============================================================
+js_option('--enable-profile-generate',
+          help='Build a PGO instrumented binary')
+
+imply_option('MOZ_PGO',
+             depends_if('--enable-profile-generate')(lambda _: True))
+
+set_config('MOZ_PROFILE_GENERATE',
+           depends_if('--enable-profile-generate')(lambda _: True))
+
+js_option('--enable-profile-use',
+          help='Use a generated profile during the build')
+
+js_option('--with-pgo-profile-path',
+          help='Path to the (unmerged) profile path to use during the build',
+          nargs=1)
+
+imply_option('MOZ_PGO',
+             depends_if('--enable-profile-use')(lambda _: True))
+
+set_config('MOZ_PROFILE_USE',
+           depends_if('--enable-profile-use')(lambda _: True))
+
 js_option(env='MOZ_PGO', help='Build with profile guided optimizations')
 
 set_config('MOZ_PGO', depends('MOZ_PGO')(lambda x: bool(x)))
 add_old_configure_assignment('MOZ_PGO', depends('MOZ_PGO')(lambda x: bool(x)))
 
+@depends('--with-pgo-profile-path', '--enable-profile-use', 'LLVM_PROFDATA')
+def pgo_profile_path(path, pgo_use, profdata):
+    if not path:
+        return
+    if path and not pgo_use:
+        die('Pass --enable-profile-use to use --with-pgo-profile-path.')
+    if path and not profdata:
+        die('LLVM_PROFDATA must be set to process the pgo profile.')
+    return path[0]
+
+set_config('PGO_PROFILE_PATH', pgo_profile_path)
+
+option('--with-pgo-jarlog',
+       help='Use the provided jarlog file when packaging during a profile-use '
+            'build',
+       nargs=1)
+
+set_config('PGO_JARLOG_PATH', depends_if('--with-pgo-jarlog')(lambda p: p))
+
 # Code optimization
 # ==============================================================
 
 js_option('--disable-optimize',
           nargs='?',
           help='Disable optimizations via compiler flags')
 
 
@@ -1459,21 +1506,16 @@ def pgo_flags(compiler, build_env, targe
         )
 
 
 set_config('PROFILE_GEN_CFLAGS', pgo_flags.gen_cflags)
 set_config('PROFILE_GEN_LDFLAGS', pgo_flags.gen_ldflags)
 set_config('PROFILE_USE_CFLAGS', pgo_flags.use_cflags)
 set_config('PROFILE_USE_LDFLAGS', pgo_flags.use_ldflags)
 
-llvm_profdata = check_prog('LLVM_PROFDATA', ['llvm-profdata'],
-                           allow_missing=True)
-
-add_old_configure_assignment('LLVM_PROFDATA', llvm_profdata)
-
 
 @depends(c_compiler)
 def preprocess_option(compiler):
     # The uses of PREPROCESS_OPTION depend on the spacing for -o/-Fi.
     if compiler.type in ('gcc', 'clang'):
         return '-E -o '
     else:
         return '-P -Fi'
--- a/caps/BasePrincipal.cpp
+++ b/caps/BasePrincipal.cpp
@@ -261,17 +261,17 @@ BasePrincipal::GetIsCodebasePrincipal(bo
 NS_IMETHODIMP
 BasePrincipal::GetIsExpandedPrincipal(bool* aResult) {
   *aResult = Kind() == eExpandedPrincipal;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 BasePrincipal::GetIsSystemPrincipal(bool* aResult) {
-  *aResult = Kind() == eSystemPrincipal;
+  *aResult = IsSystemPrincipal();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 BasePrincipal::GetIsAddonOrExpandedAddonPrincipal(bool* aResult) {
   *aResult = AddonPolicy() || ContentScriptAddonPolicy();
   return NS_OK;
 }
--- a/caps/BasePrincipal.h
+++ b/caps/BasePrincipal.h
@@ -132,16 +132,20 @@ class BasePrincipal : public nsJSPrincip
   virtual bool AddonHasPermission(const nsAtom* aPerm);
 
   virtual bool IsCodebasePrincipal() const { return false; };
 
   static BasePrincipal* Cast(nsIPrincipal* aPrin) {
     return static_cast<BasePrincipal*>(aPrin);
   }
 
+  static const BasePrincipal* Cast(const nsIPrincipal* aPrin) {
+    return static_cast<const BasePrincipal*>(aPrin);
+  }
+
   static already_AddRefed<BasePrincipal> CreateCodebasePrincipal(
       const nsACString& aOrigin);
 
   // These following method may not create a codebase principal in case it's
   // not possible to generate a correct origin from the passed URI. If this
   // happens, a NullPrincipal is returned.
 
   static already_AddRefed<BasePrincipal> CreateCodebasePrincipal(
@@ -177,16 +181,19 @@ class BasePrincipal : public nsJSPrincip
   // Call these to avoid the cost of virtual dispatch.
   inline bool FastEquals(nsIPrincipal* aOther);
   inline bool FastEqualsConsideringDomain(nsIPrincipal* aOther);
   inline bool FastSubsumes(nsIPrincipal* aOther);
   inline bool FastSubsumesConsideringDomain(nsIPrincipal* aOther);
   inline bool FastSubsumesIgnoringFPD(nsIPrincipal* aOther);
   inline bool FastSubsumesConsideringDomainIgnoringFPD(nsIPrincipal* aOther);
 
+  // Fast way to check whether we have a system principal.
+  inline bool IsSystemPrincipal() const;
+
   // Returns the principal to inherit when a caller with this principal loads
   // the given URI.
   //
   // For most principal types, this returns the principal itself. For expanded
   // principals, it returns the first sub-principal which subsumes the given URI
   // (or, if no URI is given, the last allowlist principal).
   nsIPrincipal* PrincipalToInherit(nsIURI* aRequestedURI = nullptr);
 
@@ -340,11 +347,19 @@ inline bool BasePrincipal::FastSubsumesI
   return FastSubsumesIgnoringFPD(aOther, DontConsiderDocumentDomain);
 }
 
 inline bool BasePrincipal::FastSubsumesConsideringDomainIgnoringFPD(
     nsIPrincipal* aOther) {
   return FastSubsumesIgnoringFPD(aOther, ConsiderDocumentDomain);
 }
 
+inline bool BasePrincipal::IsSystemPrincipal() const {
+  return Kind() == eSystemPrincipal;
+}
+
 }  // namespace mozilla
 
+inline bool nsIPrincipal::IsSystemPrincipal() const {
+  return mozilla::BasePrincipal::Cast(this)->IsSystemPrincipal();
+}
+
 #endif /* mozilla_BasePrincipal_h */
--- a/caps/nsIPrincipal.idl
+++ b/caps/nsIPrincipal.idl
@@ -326,19 +326,28 @@ interface nsIPrincipal : nsISerializable
     [infallible] readonly attribute boolean isCodebasePrincipal;
 
     /**
      * Returns true iff this is an expanded principal.
      */
     [infallible] readonly attribute boolean isExpandedPrincipal;
 
     /**
-     * Returns true iff this is the system principal.
+     * Returns true iff this is the system principal.  C++ callers should use
+     * IsSystemPrincipal() instead of this scriptable accessor.
      */
-    [infallible] readonly attribute boolean isSystemPrincipal;
+    readonly attribute boolean isSystemPrincipal;
+
+    /**
+     * Faster and nicer version callable from C++.  Callers must include
+     * BasePrincipal.h, where it's implemented.
+     */
+    %{C++
+      inline bool IsSystemPrincipal() const;
+    %}
 
     /**
      * Returns true iff the principal is either an addon principal or
      * an expanded principal, which contains at least one addon principal.
      */
     [infallible] readonly attribute boolean isAddonOrExpandedAddonPrincipal;
 };
 
--- a/caps/nsIScriptSecurityManager.idl
+++ b/caps/nsIScriptSecurityManager.idl
@@ -219,16 +219,20 @@ interface nsIScriptSecurityManager : nsI
      * aChannel must not be null.
      */
     nsIPrincipal getChannelURIPrincipal(in nsIChannel aChannel);
 
     /**
      * Check whether a given principal is a system principal.  This allows us
      * to avoid handing back the system principal to script while allowing
      * script to check whether a given principal is system.
+     *
+     * @deprecated use nsIPrincipal's accessors for this boolean.
+     * https://bugzilla.mozilla.org/show_bug.cgi?id=1517483 tracks removing
+     * this.
      */
     boolean isSystemPrincipal(in nsIPrincipal aPrincipal);
 %{C++
     bool IsSystemPrincipal(nsIPrincipal* aPrincipal) {
       bool isSystem = false;
       IsSystemPrincipal(aPrincipal, &isSystem);
       return isSystem;
     }
--- a/dom/base/nsContentPolicyUtils.h
+++ b/dom/base/nsContentPolicyUtils.h
@@ -9,30 +9,30 @@
  *
  * XXXbz it would be nice if some of this stuff could be out-of-lined in
  * nsContentUtils.  That would work for almost all the callers...
  */
 
 #ifndef __nsContentPolicyUtils_h__
 #define __nsContentPolicyUtils_h__
 
+#include "mozilla/BasePrincipal.h"
+
 #include "nsContentUtils.h"
 #include "nsIContentPolicy.h"
 #include "nsIContent.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIURI.h"
 #include "nsServiceManagerUtils.h"
 #include "nsStringFwd.h"
 
 // XXXtw sadly, this makes consumers of nsContentPolicyUtils depend on widget
 #include "mozilla/dom/Document.h"
 #include "nsPIDOMWindow.h"
 
-class nsIPrincipal;
-
 #define NS_CONTENTPOLICY_CONTRACTID "@mozilla.org/layout/content-policy;1"
 #define NS_CONTENTPOLICY_CATEGORY "content-policy"
 #define NS_CONTENTPOLICY_CID                         \
   {                                                  \
     0x0e3afd3d, 0xeb60, 0x4c2b, {                    \
       0x96, 0x3b, 0x56, 0xd7, 0xc4, 0x39, 0xf1, 0x24 \
     }                                                \
   }
@@ -167,17 +167,17 @@ inline const char *NS_CP_ContentTypeName
 #define CHECK_PRINCIPAL_AND_DATA(action)                                       \
   nsCOMPtr<nsIURI> requestOrigin;                                              \
   PR_BEGIN_MACRO                                                               \
   if (loadingPrincipal) {                                                      \
     /* We exempt most loads into any document with the system principal        \
      * from content policy checks, mostly as an optimization. Which means      \
      * that we need to apply this check to the loading principal, not the      \
      * principal that triggered the load. */                                   \
-    bool isSystem = loadingPrincipal->GetIsSystemPrincipal();                  \
+    bool isSystem = loadingPrincipal->IsSystemPrincipal();                     \
     if (isSystem && contentType != nsIContentPolicy::TYPE_DOCUMENT) {          \
       *decision = nsIContentPolicy::ACCEPT;                                    \
       nsCOMPtr<nsINode> n = do_QueryInterface(context);                        \
       if (!n) {                                                                \
         nsCOMPtr<nsPIDOMWindowOuter> win = do_QueryInterface(context);         \
         n = win ? win->GetExtantDoc() : nullptr;                               \
       }                                                                        \
       if (n) {                                                                 \
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -33,16 +33,17 @@
 #include "mozAutoDocUpdate.h"
 #include "mozilla/AntiTrackingCommon.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/AutoTimelineMarker.h"
 #include "mozilla/BackgroundHangMonitor.h"
 #include "mozilla/Base64.h"
+#include "mozilla/BasePrincipal.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/LoadInfo.h"
 #include "mozilla/dom/BlobURLProtocolHandler.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/CustomElementRegistry.h"
 #include "mozilla/dom/Document.h"
 #include "mozilla/dom/MessageBroadcaster.h"
@@ -5015,18 +5016,19 @@ void nsContentUtils::NotifyInstalledMenu
   nsCOMPtr<nsIURI> baseURI = NS_GetInnermostURI(aURI);
   NS_ENSURE_TRUE(baseURI, false);
 
   bool isScheme = false;
   return NS_SUCCEEDED(baseURI->SchemeIs(aScheme, &isScheme)) && isScheme;
 }
 
 bool nsContentUtils::IsSystemPrincipal(nsIPrincipal* aPrincipal) {
-  MOZ_ASSERT(IsInitialized());
-  return aPrincipal == sSystemPrincipal;
+  // Some consumers call us with a null aPrincipal and expect a false return
+  // value...
+  return aPrincipal && aPrincipal->IsSystemPrincipal();
 }
 
 bool nsContentUtils::IsExpandedPrincipal(nsIPrincipal* aPrincipal) {
   nsCOMPtr<nsIExpandedPrincipal> ep = do_QueryInterface(aPrincipal);
   return !!ep;
 }
 
 nsIPrincipal* nsContentUtils::GetSystemPrincipal() {
@@ -6355,17 +6357,17 @@ nsContentUtils::PersistentLayerManagerFo
   return LayerManagerForDocumentInternal(aDoc, true);
 }
 
 bool nsContentUtils::AllowXULXBLForPrincipal(nsIPrincipal* aPrincipal) {
   if (!aPrincipal) {
     return false;
   }
 
-  if (IsSystemPrincipal(aPrincipal)) {
+  if (aPrincipal->IsSystemPrincipal()) {
     return true;
   }
 
   nsCOMPtr<nsIURI> princURI;
   aPrincipal->GetURI(getter_AddRefs(princURI));
 
   return princURI && ((sAllowXULXBL_for_file && SchemeIs(princURI, "file")) ||
                       IsSitePermAllow(aPrincipal, "allowXULXBL"));
@@ -6546,17 +6548,17 @@ bool nsContentUtils::ChannelShouldInheri
         // to inherit the owner from the referrer so they can script each other.
         // If we don't set the owner explicitly then each file: gets an owner
         // based on its own codebase later.
         //
         (URIIsLocalFile(aURI) &&
          NS_SUCCEEDED(aLoadingPrincipal->CheckMayLoad(aURI, false, false)) &&
          // One more check here.  CheckMayLoad will always return true for the
          // system principal, but we do NOT want to inherit in that case.
-         !IsSystemPrincipal(aLoadingPrincipal));
+         !aLoadingPrincipal->IsSystemPrincipal());
   }
   return inherit;
 }
 
 /* static */
 bool nsContentUtils::IsFullscreenApiEnabled() {
   return sIsFullscreenApiEnabled;
 }
@@ -7387,101 +7389,114 @@ void nsContentUtils::TransferableToIPCTr
 
     for (uint32_t j = 0; j < flavorList.Length(); ++j) {
       nsCString& flavorStr = flavorList[j];
       if (!flavorStr.Length()) {
         continue;
       }
 
       nsCOMPtr<nsISupports> data;
-      aTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(data));
-
-      nsCOMPtr<nsISupportsString> text = do_QueryInterface(data);
-      nsCOMPtr<nsISupportsCString> ctext = do_QueryInterface(data);
-      if (text) {
+      nsresult rv =
+          aTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(data));
+
+      if (NS_FAILED(rv) || !data) {
+        if (aInSyncMessage) {
+          // Can't do anything.
+          continue;
+        }
+
+        // This is a hack to support kFilePromiseMime.
+        // On Windows there just needs to be an entry for it,
+        // and for OSX we need to create
+        // nsContentAreaDragDropDataProvider as nsIFlavorDataProvider.
+        if (flavorStr.EqualsLiteral(kFilePromiseMime)) {
+          IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
+          item->flavor() = flavorStr;
+          item->data() = NS_ConvertUTF8toUTF16(flavorStr);
+          continue;
+        }
+
+        // Empty element, transfer only the flavor
+        IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
+        item->flavor() = flavorStr;
+        item->data() = nsString();
+        continue;
+      }
+
+      if (nsCOMPtr<nsISupportsString> text = do_QueryInterface(data)) {
         nsAutoString dataAsString;
         text->GetData(dataAsString);
         IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
         item->flavor() = flavorStr;
         item->data() = dataAsString;
-      } else if (ctext) {
+      } else if (nsCOMPtr<nsISupportsCString> ctext = do_QueryInterface(data)) {
         nsAutoCString dataAsString;
         ctext->GetData(dataAsString);
         IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
         item->flavor() = flavorStr;
 
         Shmem dataAsShmem = ConvertToShmem(aChild, aParent, dataAsString);
         if (!dataAsShmem.IsReadable() || !dataAsShmem.Size<char>()) {
           continue;
         }
 
         item->data() = dataAsShmem;
-      } else {
+      } else if (nsCOMPtr<nsIInputStream> stream = do_QueryInterface(data)) {
         // Images to be pasted on the clipboard are nsIInputStreams
-        nsCOMPtr<nsIInputStream> stream(do_QueryInterface(data));
-        if (stream) {
-          IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
-          item->flavor() = flavorStr;
-
-          nsCString imageData;
-          NS_ConsumeStream(stream, UINT32_MAX, imageData);
-
-          Shmem imageDataShmem = ConvertToShmem(aChild, aParent, imageData);
-          if (!imageDataShmem.IsReadable() || !imageDataShmem.Size<char>()) {
-            continue;
-          }
-
-          item->data() = imageDataShmem;
+        IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
+        item->flavor() = flavorStr;
+
+        nsCString imageData;
+        NS_ConsumeStream(stream, UINT32_MAX, imageData);
+
+        Shmem imageDataShmem = ConvertToShmem(aChild, aParent, imageData);
+        if (!imageDataShmem.IsReadable() || !imageDataShmem.Size<char>()) {
+          continue;
+        }
+
+        item->data() = imageDataShmem;
+      } else if (nsCOMPtr<imgIContainer> image = do_QueryInterface(data)) {
+        // Images to be placed on the clipboard are imgIContainers.
+        RefPtr<mozilla::gfx::SourceSurface> surface = image->GetFrame(
+            imgIContainer::FRAME_CURRENT, imgIContainer::FLAG_SYNC_DECODE);
+        if (!surface) {
+          continue;
+        }
+        RefPtr<mozilla::gfx::DataSourceSurface> dataSurface =
+            surface->GetDataSurface();
+        if (!dataSurface) {
           continue;
         }
-
-        // Images to be placed on the clipboard are imgIContainers.
-        nsCOMPtr<imgIContainer> image(do_QueryInterface(data));
-        if (image) {
-          RefPtr<mozilla::gfx::SourceSurface> surface = image->GetFrame(
-              imgIContainer::FRAME_CURRENT, imgIContainer::FLAG_SYNC_DECODE);
-          if (!surface) {
-            continue;
-          }
-          RefPtr<mozilla::gfx::DataSourceSurface> dataSurface =
-              surface->GetDataSurface();
-          if (!dataSurface) {
-            continue;
-          }
-          size_t length;
-          int32_t stride;
-          IShmemAllocator* allocator =
-              aChild ? static_cast<IShmemAllocator*>(aChild)
-                     : static_cast<IShmemAllocator*>(aParent);
-          Maybe<Shmem> surfaceData =
-              GetSurfaceData(dataSurface, &length, &stride, allocator);
-
-          if (surfaceData.isNothing()) {
-            continue;
-          }
-
-          IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
-          item->flavor() = flavorStr;
-          // Turn item->data() into an nsCString prior to accessing it.
-          item->data() = surfaceData.ref();
-
-          IPCDataTransferImage& imageDetails = item->imageDetails();
-          mozilla::gfx::IntSize size = dataSurface->GetSize();
-          imageDetails.width() = size.width;
-          imageDetails.height() = size.height;
-          imageDetails.stride() = stride;
-          imageDetails.format() = dataSurface->GetFormat();
-
+        size_t length;
+        int32_t stride;
+        IShmemAllocator* allocator =
+            aChild ? static_cast<IShmemAllocator*>(aChild)
+                   : static_cast<IShmemAllocator*>(aParent);
+        Maybe<Shmem> surfaceData =
+            GetSurfaceData(dataSurface, &length, &stride, allocator);
+
+        if (surfaceData.isNothing()) {
           continue;
         }
 
+        IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
+        item->flavor() = flavorStr;
+        // Turn item->data() into an nsCString prior to accessing it.
+        item->data() = surfaceData.ref();
+
+        IPCDataTransferImage& imageDetails = item->imageDetails();
+        mozilla::gfx::IntSize size = dataSurface->GetSize();
+        imageDetails.width() = size.width;
+        imageDetails.height() = size.height;
+        imageDetails.stride() = stride;
+        imageDetails.format() = dataSurface->GetFormat();
+      } else {
         // Otherwise, handle this as a file.
         nsCOMPtr<BlobImpl> blobImpl;
-        nsCOMPtr<nsIFile> file = do_QueryInterface(data);
-        if (file) {
+        if (nsCOMPtr<nsIFile> file = do_QueryInterface(data)) {
           // If we can send this over as a blob, do so. Otherwise, we're
           // responding to a sync message and the child can't process the blob
           // constructor before processing our response, which would crash. In
           // that case, hope that the caller is nsClipboardProxy::GetData,
           // called from editor and send over images as raw data.
           if (aInSyncMessage) {
             nsAutoCString type;
             if (IsFileImage(file, type)) {
@@ -7555,34 +7570,16 @@ void nsContentUtils::TransferableToIPCTr
             }
 
             data = ipcBlob;
           }
 
           IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
           item->flavor() = flavorStr;
           item->data() = data;
-        } else {
-          // This is a hack to support kFilePromiseMime.
-          // On Windows there just needs to be an entry for it,
-          // and for OSX we need to create
-          // nsContentAreaDragDropDataProvider as nsIFlavorDataProvider.
-          if (flavorStr.EqualsLiteral(kFilePromiseMime)) {
-            IPCDataTransferItem* item =
-                aIPCDataTransfer->items().AppendElement();
-            item->flavor() = flavorStr;
-            item->data() = NS_ConvertUTF8toUTF16(flavorStr);
-          } else if (!data) {
-            // Empty element, transfer only the flavor
-            IPCDataTransferItem* item =
-                aIPCDataTransfer->items().AppendElement();
-            item->flavor() = flavorStr;
-            item->data() = nsString();
-            continue;
-          }
         }
       }
     }
   }
 }
 
 namespace {
 // The default type used for calling GetSurfaceData(). Gets surface data as
@@ -7953,17 +7950,17 @@ bool nsContentUtils::IsUpgradableDisplay
 nsresult nsContentUtils::SetFetchReferrerURIWithPolicy(
     nsIPrincipal* aPrincipal, Document* aDoc, nsIHttpChannel* aChannel,
     mozilla::net::ReferrerPolicy aReferrerPolicy) {
   NS_ENSURE_ARG_POINTER(aPrincipal);
   NS_ENSURE_ARG_POINTER(aChannel);
 
   nsCOMPtr<nsIURI> principalURI;
 
-  if (IsSystemPrincipal(aPrincipal)) {
+  if (aPrincipal->IsSystemPrincipal()) {
     return NS_OK;
   }
 
   aPrincipal->GetURI(getter_AddRefs(principalURI));
 
   if (!aDoc) {
     return aChannel->SetReferrerWithPolicy(principalURI, aReferrerPolicy);
   }
@@ -9226,34 +9223,34 @@ bool nsContentUtils::IsSpecificAboutPage
  */
 /* static */ bool nsContentUtils::HttpsStateIsModern(Document* aDocument) {
   if (!aDocument) {
     return false;
   }
 
   nsCOMPtr<nsIPrincipal> principal = aDocument->NodePrincipal();
 
-  if (principal->GetIsSystemPrincipal()) {
+  if (principal->IsSystemPrincipal()) {
     return true;
   }
 
   // If aDocument is sandboxed, try and get the principal that it would have
   // been given had it not been sandboxed:
   if (principal->GetIsNullPrincipal() &&
       (aDocument->GetSandboxFlags() & SANDBOXED_ORIGIN)) {
     nsIChannel* channel = aDocument->GetChannel();
     if (channel) {
       nsCOMPtr<nsIScriptSecurityManager> ssm =
           nsContentUtils::GetSecurityManager();
       nsresult rv = ssm->GetChannelResultPrincipalIfNotSandboxed(
           channel, getter_AddRefs(principal));
       if (NS_FAILED(rv)) {
         return false;
       }
-      if (principal->GetIsSystemPrincipal()) {
+      if (principal->IsSystemPrincipal()) {
         // If a document with the system principal is sandboxing a subdocument
         // that would normally inherit the embedding element's principal (e.g.
         // a srcdoc document) then the embedding document does not trust the
         // content that is written to the embedded document.  Unlike when the
         // embedding document is https, in this case we have no indication as
         // to whether the embedded document's contents are delivered securely
         // or not, and the sandboxing would possibly indicate that they were
         // not.  To play it safe we return false here.  (See bug 1162772
@@ -9843,17 +9840,17 @@ static void AppendNativeAnonymousChildre
 
   bool result = false;
   nsCOMPtr<nsIPrincipal> loadingPrincipal = aDefaultPrincipal;
   if (!loadingPrincipal) {
     loadingPrincipal = aLoadingNode->NodePrincipal();
   }
 
   // If aLoadingNode is content, bail out early.
-  if (!aLoadingNode->NodePrincipal()->GetIsSystemPrincipal()) {
+  if (!aLoadingNode->NodePrincipal()->IsSystemPrincipal()) {
     loadingPrincipal.forget(aTriggeringPrincipal);
     return result;
   }
 
   nsAutoString loadingStr;
   if (aLoadingNode->IsElement()) {
     aLoadingNode->AsElement()->GetAttr(
         kNameSpaceID_None, nsGkAtoms::triggeringprincipal, loadingStr);
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -1792,16 +1792,19 @@ class nsContentUtils {
    *
    * Note that this will check the innermost URI rather than that of
    * the nsIURI itself.
    */
   static bool SchemeIs(nsIURI* aURI, const char* aScheme);
 
   /**
    * Returns true if aPrincipal is the system principal.
+   *
+   * @deprecated Use nsIPrincipal::IsSystemPrincipal instead!
+   * https://bugzilla.mozilla.org/show_bug.cgi?id=1517588 tracks removing this.
    */
   static bool IsSystemPrincipal(nsIPrincipal* aPrincipal);
 
   /**
    * Returns true if aPrincipal is an ExpandedPrincipal.
    */
   static bool IsExpandedPrincipal(nsIPrincipal* aPrincipal);
 
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -5515,17 +5515,17 @@ bool nsGlobalWindowOuter::GetPrincipalFo
   nsCOMPtr<nsIPrincipal> providedPrincipal;
 
   if (aTargetOrigin.EqualsASCII("/")) {
     providedPrincipal = aCallerPrincipal;
   }
   // "*" indicates no specific origin is required.
   else if (!aTargetOrigin.EqualsASCII("*")) {
     OriginAttributes attrs = aSubjectPrincipal.OriginAttributesRef();
-    if (aSubjectPrincipal.GetIsSystemPrincipal()) {
+    if (aSubjectPrincipal.IsSystemPrincipal()) {
       auto principal = BasePrincipal::Cast(GetPrincipal());
 
       if (attrs != principal->OriginAttributesRef()) {
         nsCOMPtr<nsIURI> targetURI;
         nsAutoCString targetURL;
         nsAutoCString sourceOrigin;
         nsAutoCString targetOrigin;
 
@@ -5567,23 +5567,23 @@ bool nsGlobalWindowOuter::GetPrincipalFo
     OriginAttributes sourceAttrs = aSubjectPrincipal.OriginAttributesRef();
     // We have to exempt the check of OA if the subject prioncipal is a system
     // principal since there are many tests try to post messages to content from
     // chrome with a mismatch OA. For example, using the ContentTask.spawn() to
     // post a message into a private browsing window. The injected code in
     // ContentTask.spawn() will be executed under the system principal and the
     // OA of the system principal mismatches with the OA of a private browsing
     // window.
-    MOZ_DIAGNOSTIC_ASSERT(aSubjectPrincipal.GetIsSystemPrincipal() ||
+    MOZ_DIAGNOSTIC_ASSERT(aSubjectPrincipal.IsSystemPrincipal() ||
                           sourceAttrs.EqualsIgnoringFPD(targetAttrs));
 
     // If 'privacy.firstparty.isolate.block_post_message' is true, we will block
     // postMessage across different first party domains.
     if (OriginAttributes::IsBlockPostMessageForFPI() &&
-        !aSubjectPrincipal.GetIsSystemPrincipal() &&
+        !aSubjectPrincipal.IsSystemPrincipal() &&
         sourceAttrs.mFirstPartyDomain != targetAttrs.mFirstPartyDomain) {
       return false;
     }
   }
 
   providedPrincipal.forget(aProvidedPrincipal);
   return true;
 }
--- a/dom/bindings/ToJSValue.cpp
+++ b/dom/bindings/ToJSValue.cpp
@@ -68,16 +68,21 @@ bool ToJSValue(JSContext* aCx, const Win
     aValue.setNull();
     return true;
   }
   JS::Rooted<JSObject*> windowProxy(aCx);
   if (bc->GetDocShell()) {
     windowProxy = bc->GetWindowProxy();
     if (!windowProxy) {
       nsPIDOMWindowOuter* window = bc->GetDOMWindow();
+      if (!window) {
+        // Torn down enough that we should just return null.
+        aValue.setNull();
+        return true;
+      }
       if (!window->EnsureInnerWindow()) {
         return Throw(aCx, NS_ERROR_UNEXPECTED);
       }
       windowProxy = bc->GetWindowProxy();
     }
     return ToJSValue(aCx, windowProxy, aValue);
   }
 
--- a/dom/events/Clipboard.cpp
+++ b/dom/events/Clipboard.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/AbstractThread.h"
+#include "mozilla/BasePrincipal.h"
 #include "mozilla/dom/Clipboard.h"
 #include "mozilla/dom/ClipboardBinding.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/DataTransfer.h"
 #include "mozilla/dom/DataTransferItemList.h"
 #include "mozilla/dom/DataTransferItem.h"
 #include "nsIClipboard.h"
 #include "nsISupportsPrimitives.h"
@@ -178,17 +179,17 @@ JSObject* Clipboard::WrapObject(JSContex
 }
 
 /* static */ LogModule* Clipboard::GetClipboardLog() { return gClipboardLog; }
 
 /* static */ bool Clipboard::ReadTextEnabled(JSContext* aCx,
                                              JSObject* aGlobal) {
   nsIPrincipal* prin = nsContentUtils::SubjectPrincipal(aCx);
   return IsTestingPrefEnabled() || prin->GetIsAddonOrExpandedAddonPrincipal() ||
-         prin->GetIsSystemPrincipal();
+         prin->IsSystemPrincipal();
 }
 
 /* static */ bool Clipboard::IsTestingPrefEnabled() {
   static bool sPrefCached = false;
   static bool sPrefCacheValue = false;
 
   if (!sPrefCached) {
     sPrefCached = true;
--- a/dom/html/HTMLSlotElement.cpp
+++ b/dom/html/HTMLSlotElement.cpp
@@ -136,16 +136,27 @@ void HTMLSlotElement::AssignedNodes(cons
                                     nsTArray<RefPtr<nsINode>>& aNodes) {
   if (aOptions.mFlatten) {
     return FlattenAssignedNodes(this, aNodes);
   }
 
   aNodes = mAssignedNodes;
 }
 
+void HTMLSlotElement::AssignedElements(const AssignedNodesOptions& aOptions,
+                                       nsTArray<RefPtr<Element>>& aElements) {
+  AutoTArray<RefPtr<nsINode>, 128> assignedNodes;
+  AssignedNodes(aOptions, assignedNodes);
+  for (const RefPtr<nsINode>& assignedNode : assignedNodes) {
+    if (assignedNode->IsElement()) {
+      aElements.AppendElement(assignedNode->AsElement());
+    }
+  }
+}
+
 const nsTArray<RefPtr<nsINode>>& HTMLSlotElement::AssignedNodes() const {
   return mAssignedNodes;
 }
 
 void HTMLSlotElement::InsertAssignedNode(uint32_t aIndex, nsINode* aNode) {
   MOZ_ASSERT(!aNode->AsContent()->GetAssignedSlot(), "Losing track of a slot");
   mAssignedNodes.InsertElementAt(aIndex, aNode);
   aNode->AsContent()->SetAssignedSlot(this);
--- a/dom/html/HTMLSlotElement.h
+++ b/dom/html/HTMLSlotElement.h
@@ -45,16 +45,19 @@ class HTMLSlotElement final : public nsG
     SetHTMLAttr(nsGkAtoms::name, aName, aRv);
   }
 
   void GetName(nsAString& aName) { GetHTMLAttr(nsGkAtoms::name, aName); }
 
   void AssignedNodes(const AssignedNodesOptions& aOptions,
                      nsTArray<RefPtr<nsINode>>& aNodes);
 
+  void AssignedElements(const AssignedNodesOptions& aOptions,
+                        nsTArray<RefPtr<Element>>& aNodes);
+
   // Helper methods
   const nsTArray<RefPtr<nsINode>>& AssignedNodes() const;
   void InsertAssignedNode(uint32_t aIndex, nsINode* aNode);
   void AppendAssignedNode(nsINode* aNode);
   void RemoveAssignedNode(nsINode* aNode);
   void ClearAssignedNodes();
 
   void EnqueueSlotChangeEvent();
--- a/dom/html/nsGenericHTMLFrameElement.cpp
+++ b/dom/html/nsGenericHTMLFrameElement.cpp
@@ -71,17 +71,25 @@ nsGenericHTMLFrameElement::~nsGenericHTM
 
 Document* nsGenericHTMLFrameElement::GetContentDocument(
     nsIPrincipal& aSubjectPrincipal) {
   RefPtr<BrowsingContext> bc = GetContentWindowInternal();
   if (!bc) {
     return nullptr;
   }
 
-  Document* doc = bc->GetDOMWindow()->GetDoc();
+  nsPIDOMWindowOuter* window = bc->GetDOMWindow();
+  if (!window) {
+    // Either our browsing context contents are out-of-process (in which case
+    // clearly this is a cross-origin call and we should return null), or our
+    // browsing context is torn-down enough to no longer have a window or a
+    // document, and we should still return null.
+    return nullptr;
+  }
+  Document* doc = window->GetDoc();
   if (!doc) {
     return nullptr;
   }
 
   // Return null for cross-origin contentDocument.
   if (!aSubjectPrincipal.SubsumesConsideringDomain(doc->NodePrincipal())) {
     return nullptr;
   }
--- a/dom/media/DOMMediaStream.cpp
+++ b/dom/media/DOMMediaStream.cpp
@@ -8,16 +8,17 @@
 #include "AudioCaptureStream.h"
 #include "AudioChannelAgent.h"
 #include "AudioStreamTrack.h"
 #include "Layers.h"
 #include "MediaStreamGraph.h"
 #include "MediaStreamGraphImpl.h"
 #include "MediaStreamListener.h"
 #include "VideoStreamTrack.h"
+#include "mozilla/BasePrincipal.h"
 #include "mozilla/dom/AudioNode.h"
 #include "mozilla/dom/AudioTrack.h"
 #include "mozilla/dom/AudioTrackList.h"
 #include "mozilla/dom/DocGroup.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
 #include "mozilla/dom/MediaStreamBinding.h"
 #include "mozilla/dom/MediaStreamTrackEvent.h"
 #include "mozilla/dom/Promise.h"
@@ -727,17 +728,17 @@ void DOMMediaStream::NotifyPrincipalChan
     LOG(LogLevel::Info,
         ("DOMMediaStream %p Principal changed to nothing.", this));
   } else {
     LOG(LogLevel::Info, ("DOMMediaStream %p Principal changed. Now: "
                          "null=%d, codebase=%d, expanded=%d, system=%d",
                          this, mPrincipal->GetIsNullPrincipal(),
                          mPrincipal->GetIsCodebasePrincipal(),
                          mPrincipal->GetIsExpandedPrincipal(),
-                         mPrincipal->GetIsSystemPrincipal()));
+                         mPrincipal->IsSystemPrincipal()));
   }
 
   for (uint32_t i = 0; i < mPrincipalChangeObservers.Length(); ++i) {
     mPrincipalChangeObservers[i]->PrincipalChanged(this);
   }
 }
 
 bool DOMMediaStream::AddPrincipalChangeObserver(
--- a/dom/media/MediaStreamTrack.cpp
+++ b/dom/media/MediaStreamTrack.cpp
@@ -4,16 +4,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MediaStreamTrack.h"
 
 #include "DOMMediaStream.h"
 #include "MediaStreamError.h"
 #include "MediaStreamGraph.h"
 #include "MediaStreamListener.h"
+#include "mozilla/BasePrincipal.h"
 #include "mozilla/dom/Promise.h"
 #include "nsContentUtils.h"
 #include "nsIUUIDGenerator.h"
 #include "nsServiceManagerUtils.h"
 #include "systemservices/MediaUtils.h"
 
 #ifdef LOG
 #undef LOG
@@ -392,22 +393,22 @@ MediaStreamGraphImpl* MediaStreamTrack::
 }
 
 void MediaStreamTrack::SetPrincipal(nsIPrincipal* aPrincipal) {
   if (aPrincipal == mPrincipal) {
     return;
   }
   mPrincipal = aPrincipal;
 
-  LOG(LogLevel::Info, ("MediaStreamTrack %p principal changed to %p. Now: "
-                       "null=%d, codebase=%d, expanded=%d, system=%d",
-                       this, mPrincipal.get(), mPrincipal->GetIsNullPrincipal(),
-                       mPrincipal->GetIsCodebasePrincipal(),
-                       mPrincipal->GetIsExpandedPrincipal(),
-                       mPrincipal->GetIsSystemPrincipal()));
+  LOG(LogLevel::Info,
+      ("MediaStreamTrack %p principal changed to %p. Now: "
+       "null=%d, codebase=%d, expanded=%d, system=%d",
+       this, mPrincipal.get(), mPrincipal->GetIsNullPrincipal(),
+       mPrincipal->GetIsCodebasePrincipal(),
+       mPrincipal->GetIsExpandedPrincipal(), mPrincipal->IsSystemPrincipal()));
   for (PrincipalChangeObserver<MediaStreamTrack>* observer :
        mPrincipalChangeObservers) {
     observer->PrincipalChanged(this);
   }
 }
 
 void MediaStreamTrack::PrincipalChanged() {
   mPendingPrincipal = GetSource().GetPrincipal();
--- a/dom/security/nsContentSecurityManager.cpp
+++ b/dom/security/nsContentSecurityManager.cpp
@@ -16,16 +16,17 @@
 #include "nsContentUtils.h"
 #include "nsCORSListenerProxy.h"
 #include "nsIStreamListener.h"
 #include "nsCDefaultURIFixup.h"
 #include "nsIURIFixup.h"
 #include "nsIImageLoadingContent.h"
 #include "nsIRedirectHistoryEntry.h"
 
+#include "mozilla/BasePrincipal.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/nsMixedContentBlocker.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/Logging.h"
 
 NS_IMPL_ISUPPORTS(nsContentSecurityManager, nsIContentSecurityManager,
                   nsIChannelEventSink)
 
@@ -998,17 +999,17 @@ nsContentSecurityManager::PerformSecurit
 
 NS_IMETHODIMP
 nsContentSecurityManager::IsOriginPotentiallyTrustworthy(
     nsIPrincipal* aPrincipal, bool* aIsTrustWorthy) {
   MOZ_ASSERT(NS_IsMainThread());
   NS_ENSURE_ARG_POINTER(aPrincipal);
   NS_ENSURE_ARG_POINTER(aIsTrustWorthy);
 
-  if (aPrincipal->GetIsSystemPrincipal()) {
+  if (aPrincipal->IsSystemPrincipal()) {
     *aIsTrustWorthy = true;
     return NS_OK;
   }
 
   // The following implements:
   // https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy
 
   *aIsTrustWorthy = false;
--- a/dom/webidl/HTMLSlotElement.webidl
+++ b/dom/webidl/HTMLSlotElement.webidl
@@ -10,13 +10,14 @@
  * Opera Software ASA. You are granted a license to use, reproduce
  * and create derivative works of this document.
  */
 
 [Exposed=Window, HTMLConstructor]
 interface HTMLSlotElement : HTMLElement {
   [CEReactions, SetterThrows] attribute DOMString name;
   sequence<Node> assignedNodes(optional AssignedNodesOptions options);
+  sequence<Element> assignedElements(optional AssignedNodesOptions options);
 };
 
 dictionary AssignedNodesOptions {
   boolean flatten = false;
 };
--- a/dom/workers/WorkerLoadInfo.cpp
+++ b/dom/workers/WorkerLoadInfo.cpp
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WorkerLoadInfo.h"
 #include "WorkerPrivate.h"
 
+#include "mozilla/BasePrincipal.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/ipc/BackgroundUtils.h"
 #include "mozilla/ipc/PBackgroundSharedTypes.h"
 #include "mozilla/LoadContext.h"
 #include "nsContentUtils.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsINetworkInterceptController.h"
 #include "nsIProtocolHandler.h"
@@ -248,17 +249,17 @@ bool WorkerLoadInfo::PrincipalIsValid() 
 bool WorkerLoadInfo::PrincipalURIMatchesScriptURL() {
   AssertIsOnMainThread();
 
   nsAutoCString scheme;
   nsresult rv = mBaseURI->GetScheme(scheme);
   NS_ENSURE_SUCCESS(rv, false);
 
   // A system principal must either be a blob URL or a resource JSM.
-  if (mPrincipal->GetIsSystemPrincipal()) {
+  if (mPrincipal->IsSystemPrincipal()) {
     if (scheme == NS_LITERAL_CSTRING("blob")) {
       return true;
     }
 
     bool isResource = false;
     nsresult rv = NS_URIChainHasFlags(
         mBaseURI, nsIProtocolHandler::URI_IS_UI_RESOURCE, &isResource);
     NS_ENSURE_SUCCESS(rv, false);
--- a/gfx/ipc/GPUChild.cpp
+++ b/gfx/ipc/GPUChild.cpp
@@ -166,16 +166,26 @@ mozilla::ipc::IPCResult GPUChild::RecvCr
     if (vr) {
       vr->LaunchVRProcess();
     }
   }
 
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult GPUChild::RecvShutdownVRProcess() {
+  // Make sure stopping VR process at the main process
+  MOZ_ASSERT(XRE_IsParentProcess());
+  if (gfxPrefs::VRProcessEnabled()) {
+    VRProcessManager::Shutdown();
+  }
+
+  return IPC_OK();
+}
+
 mozilla::ipc::IPCResult GPUChild::RecvNotifyUiObservers(
     const nsCString& aTopic) {
   nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
   MOZ_ASSERT(obsSvc);
   if (obsSvc) {
     obsSvc->NotifyObservers(nullptr, aTopic.get(), nullptr);
   }
   return IPC_OK();
--- a/gfx/ipc/GPUChild.h
+++ b/gfx/ipc/GPUChild.h
@@ -44,16 +44,17 @@ class GPUChild final : public PGPUChild,
 
   // PGPUChild overrides.
   mozilla::ipc::IPCResult RecvInitComplete(const GPUDeviceData& aData) override;
   mozilla::ipc::IPCResult RecvReportCheckerboard(
       const uint32_t& aSeverity, const nsCString& aLog) override;
   mozilla::ipc::IPCResult RecvInitCrashReporter(
       Shmem&& shmem, const NativeThreadId& aThreadId) override;
   mozilla::ipc::IPCResult RecvCreateVRProcess() override;
+  mozilla::ipc::IPCResult RecvShutdownVRProcess() override;
 
   mozilla::ipc::IPCResult RecvAccumulateChildHistograms(
       InfallibleTArray<HistogramAccumulation>&& aAccumulations) override;
   mozilla::ipc::IPCResult RecvAccumulateChildKeyedHistograms(
       InfallibleTArray<KeyedHistogramAccumulation>&& aAccumulations) override;
   mozilla::ipc::IPCResult RecvUpdateChildScalars(
       InfallibleTArray<ScalarAction>&& aScalarActions) override;
   mozilla::ipc::IPCResult RecvUpdateChildKeyedScalars(
--- a/gfx/ipc/GPUParent.cpp
+++ b/gfx/ipc/GPUParent.cpp
@@ -454,17 +454,17 @@ mozilla::ipc::IPCResult GPUParent::RecvR
       [&](const uint32_t& aGeneration) {
         return GetSingleton()->SendFinishMemoryReport(aGeneration);
       });
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult GPUParent::RecvShutdownVR() {
   if (gfxPrefs::VRProcessEnabled()) {
-    VRGPUChild::ShutDown();
+    VRGPUChild::Shutdown();
   }
   return IPC_OK();
 }
 
 void GPUParent::ActorDestroy(ActorDestroyReason aWhy) {
   if (AbnormalShutdown == aWhy) {
     NS_WARNING("Shutting down GPU process early due to a crash!");
     ProcessChild::QuickExit();
--- a/gfx/ipc/PGPU.ipdl
+++ b/gfx/ipc/PGPU.ipdl
@@ -103,16 +103,17 @@ child:
   async ReportCheckerboard(uint32_t severity, nsCString log);
 
   // Graphics errors, analogous to PContent::GraphicsError
   async GraphicsError(nsCString aError);
 
   async InitCrashReporter(Shmem shmem, NativeThreadId threadId);
 
   async CreateVRProcess();
+  async ShutdownVRProcess();
 
   // Have a message be broadcasted to the UI process by the UI process
   // observer service.
   async NotifyUiObservers(nsCString aTopic);
 
   // Messages for reporting telemetry to the UI process.
   async AccumulateChildHistograms(HistogramAccumulation[] accumulations);
   async AccumulateChildKeyedHistograms(KeyedHistogramAccumulation[] accumulations);
--- a/gfx/layers/TransactionIdAllocator.h
+++ b/gfx/layers/TransactionIdAllocator.h
@@ -78,14 +78,16 @@ class TransactionIdAllocator {
   virtual void ResetInitialTransactionId(TransactionId aTransactionId) = 0;
 
   /**
    * Get the start time of the current refresh tick.
    */
   virtual mozilla::TimeStamp GetTransactionStart() = 0;
 
   virtual VsyncId GetVsyncId() = 0;
+
+  virtual mozilla::TimeStamp GetVsyncStart() = 0;
 };
 
 }  // namespace layers
 }  // namespace mozilla
 
 #endif /* GFX_TRANSACTION_ID_ALLOCATOR_H */
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -689,17 +689,18 @@ void ClientLayerManager::ForwardTransact
     mForwarder->SendPaintTime(mLatestTransactionId, mLastPaintTime);
   }
 
   // forward this transaction's changeset to our LayerManagerComposite
   bool sent = false;
   bool ok = mForwarder->EndTransaction(
       mRegionToClear, mLatestTransactionId, aScheduleComposite,
       mPaintSequenceNumber, mIsRepeatTransaction,
-      mTransactionIdAllocator->GetVsyncId(), refreshStart, mTransactionStart,
+      mTransactionIdAllocator->GetVsyncId(),
+      mTransactionIdAllocator->GetVsyncStart(), refreshStart, mTransactionStart,
       mURL, &sent);
   if (ok) {
     if (sent) {
       mNeedsComposite = false;
     }
   } else if (HasShadowManager()) {
     NS_WARNING("failed to forward Layers transaction");
   }
--- a/gfx/layers/ipc/CrossProcessCompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CrossProcessCompositorBridgeParent.cpp
@@ -385,17 +385,17 @@ void CrossProcessCompositorBridgeParent:
   }
 #endif
   Telemetry::Accumulate(
       Telemetry::CONTENT_FULL_PAINT_TIME,
       static_cast<uint32_t>(
           (endTime - aInfo.transactionStart()).ToMilliseconds()));
 
   aLayerTree->SetPendingTransactionId(
-      aInfo.id(), aInfo.vsyncId(), aInfo.refreshStart(),
+      aInfo.id(), aInfo.vsyncId(), aInfo.vsyncStart(), aInfo.refreshStart(),
       aInfo.transactionStart(), aInfo.url(), aInfo.fwdTime());
 }
 
 void CrossProcessCompositorBridgeParent::DidCompositeLocked(
     LayersId aId, const VsyncId& aVsyncId, TimeStamp& aCompositeStart,
     TimeStamp& aCompositeEnd) {
   sIndirectLayerTreesLock->AssertCurrentThreadOwns();
   if (LayerTransactionParent* layerTree = sIndirectLayerTrees[aId].mLayerTree) {
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -920,16 +920,24 @@ TransactionId LayerTransactionParent::Fl
         Telemetry::AccumulateCategorical(
             LABELS_CONTENT_FRAME_TIME_REASON::MissedComposite);
       } else {
         // Composite start on time, but must have taken too long.
         Telemetry::AccumulateCategorical(
             LABELS_CONTENT_FRAME_TIME_REASON::SlowComposite);
       }
     }
+
+    if (!(mTxnVsyncId == VsyncId()) && mVsyncStartTime) {
+      latencyMs = (aCompositeEnd - mVsyncStartTime).ToMilliseconds();
+      latencyNorm = latencyMs / mVsyncRate.ToMilliseconds();
+      fracLatencyNorm = lround(latencyNorm * 100.0);
+      Telemetry::Accumulate(Telemetry::CONTENT_FRAME_TIME_VSYNC,
+                            fracLatencyNorm);
+    }
   }
 
 #if defined(ENABLE_FRAME_LATENCY_LOG)
   if (mPendingTransaction.IsValid()) {
     if (mRefreshStartTime) {
       int32_t latencyMs =
           lround((aCompositeEnd - mRefreshStartTime).ToMilliseconds());
       printf_stderr(
--- a/gfx/layers/ipc/LayerTransactionParent.h
+++ b/gfx/layers/ipc/LayerTransactionParent.h
@@ -68,22 +68,24 @@ class LayerTransactionParent final : pub
                         ipc::Shmem* aShmem) override;
 
   void DeallocShmem(ipc::Shmem& aShmem) override;
 
   bool IsSameProcess() const override;
 
   const TransactionId& GetPendingTransactionId() { return mPendingTransaction; }
   void SetPendingTransactionId(TransactionId aId, const VsyncId& aVsyncId,
+                               const TimeStamp& aVsyncStartTime,
                                const TimeStamp& aRefreshStartTime,
                                const TimeStamp& aTxnStartTime,
                                const nsCString& aURL,
                                const TimeStamp& aFwdTime) {
     mPendingTransaction = aId;
     mTxnVsyncId = aVsyncId;
+    mVsyncStartTime = aVsyncStartTime;
     mRefreshStartTime = aRefreshStartTime;
     mTxnStartTime = aTxnStartTime;
     mTxnURL = aURL;
     mFwdTime = aFwdTime;
   }
   TransactionId FlushTransactionId(const VsyncId& aId,
                                    TimeStamp& aCompositeEnd);
 
@@ -200,16 +202,17 @@ class LayerTransactionParent final : pub
   // (via ObserveLayerUpdate).
   LayersObserverEpoch mChildEpoch;
   LayersObserverEpoch mParentEpoch;
 
   TimeDuration mVsyncRate;
 
   TransactionId mPendingTransaction;
   VsyncId mTxnVsyncId;
+  TimeStamp mVsyncStartTime;
   TimeStamp mRefreshStartTime;
   TimeStamp mTxnStartTime;
   TimeStamp mFwdTime;
   nsCString mTxnURL;
 
   // When the widget/frame/browser stuff in this process begins its
   // destruction process, we need to Disconnect() all the currently
   // live shadow layers, because some of them might be orphaned from
--- a/gfx/layers/ipc/LayersMessages.ipdlh
+++ b/gfx/layers/ipc/LayersMessages.ipdlh
@@ -550,16 +550,17 @@ struct TransactionInfo
   TargetConfig targetConfig;
   PluginWindowData[] plugins;
   bool isFirstPaint;
   FocusTarget focusTarget;
   bool scheduleComposite;
   uint32_t paintSequenceNumber;
   bool isRepeatTransaction;
   VsyncId vsyncId;
+  TimeStamp vsyncStart;
   TimeStamp refreshStart;
   TimeStamp transactionStart;
   nsCString url;
   TimeStamp fwdTime;
 };
 
 union MaybeTransform {
   Matrix4x4;
--- a/gfx/layers/ipc/PWebRenderBridge.ipdl
+++ b/gfx/layers/ipc/PWebRenderBridge.ipdl
@@ -44,21 +44,21 @@ parent:
   async NewCompositable(CompositableHandle handle, TextureInfo info);
   async ReleaseCompositable(CompositableHandle compositable);
 
   async DeleteCompositorAnimations(uint64_t[] aIds);
   async SetDisplayList(IntSize aSize, WebRenderParentCommand[] commands, OpDestroy[] toDestroy, uint64_t fwdTransactionId, TransactionId transactionId,
                        LayoutSize aContentSize, ByteBuf aDL, BuiltDisplayListDescriptor aDLDesc,
                        WebRenderScrollData aScrollData,
                        OpUpdateResource[] aResourceUpdates, RefCountedShmem[] aSmallShmems, Shmem[] aLargeShmems,
-                       IdNamespace aIdNamespace, bool containsSVGGroup, VsyncId vsyncId, TimeStamp refreshStartTime, TimeStamp txnStartTime, nsCString txnURL, TimeStamp fwdTime);
+                       IdNamespace aIdNamespace, bool containsSVGGroup, VsyncId vsyncId, TimeStamp vsyncStartTime, TimeStamp refreshStartTime, TimeStamp txnStartTime, nsCString txnURL, TimeStamp fwdTime);
   async EmptyTransaction(FocusTarget focusTarget, ScrollUpdatesMap scrollUpdates, uint32_t aPaintSequenceNumber,
                          WebRenderParentCommand[] commands, OpDestroy[] toDestroy, uint64_t fwdTransactionId, TransactionId transactionId,
                          OpUpdateResource[] aResourceUpdates, RefCountedShmem[] aSmallShmems, Shmem[] aLargeShmems,
-                         IdNamespace aIdNamespace, VsyncId vsyncId, TimeStamp refreshStartTime, TimeStamp txnStartTime, nsCString txnURL, TimeStamp fwdTime);
+                         IdNamespace aIdNamespace, VsyncId vsyncId, TimeStamp vsyncStartTime, TimeStamp refreshStartTime, TimeStamp txnStartTime, nsCString txnURL, TimeStamp fwdTime);
   async SetFocusTarget(FocusTarget focusTarget);
   async UpdateResources(OpUpdateResource[] aResourceUpdates, RefCountedShmem[] aSmallShmems, Shmem[] aLargeShmems);
   async ParentCommands(WebRenderParentCommand[] commands);
   sync GetSnapshot(PTexture texture);
   async SetLayersObserverEpoch(LayersObserverEpoch childEpoch);
   async ClearCachedResources();
   // Schedule a composite if one isn't already scheduled.
   async ScheduleComposite();
--- a/gfx/layers/ipc/ShadowLayers.cpp
+++ b/gfx/layers/ipc/ShadowLayers.cpp
@@ -524,16 +524,17 @@ void ShadowLayerForwarder::SendPaintTime
     NS_WARNING("Could not send paint times over IPC");
   }
 }
 
 bool ShadowLayerForwarder::EndTransaction(
     const nsIntRegion& aRegionToClear, TransactionId aId,
     bool aScheduleComposite, uint32_t aPaintSequenceNumber,
     bool aIsRepeatTransaction, const mozilla::VsyncId& aVsyncId,
+    const mozilla::TimeStamp& aVsyncStart,
     const mozilla::TimeStamp& aRefreshStart,
     const mozilla::TimeStamp& aTransactionStart, const nsCString& aURL,
     bool* aSent) {
   *aSent = false;
 
   TransactionInfo info;
 
   MOZ_ASSERT(IPCOpen(), "no manager to forward to");
@@ -667,16 +668,17 @@ bool ShadowLayerForwarder::EndTransactio
   info.id() = aId;
   info.plugins() = mPluginWindowData;
   info.isFirstPaint() = mIsFirstPaint;
   info.focusTarget() = mFocusTarget;
   info.scheduleComposite() = aScheduleComposite;
   info.paintSequenceNumber() = aPaintSequenceNumber;
   info.isRepeatTransaction() = aIsRepeatTransaction;
   info.vsyncId() = aVsyncId;
+  info.vsyncStart() = aVsyncStart;
   info.refreshStart() = aRefreshStart;
   info.transactionStart() = aTransactionStart;
   info.url() = aURL;
 #if defined(ENABLE_FRAME_LATENCY_LOG)
   info.fwdTime() = TimeStamp::Now();
 #endif
 
   TargetConfig targetConfig(mTxn->mTargetBounds, mTxn->mTargetRotation,
--- a/gfx/layers/ipc/ShadowLayers.h
+++ b/gfx/layers/ipc/ShadowLayers.h
@@ -248,16 +248,17 @@ class ShadowLayerForwarder final : publi
    * End the current transaction and forward it to LayerManagerComposite.
    * |aReplies| are directions from the LayerManagerComposite to the
    * caller of EndTransaction().
    */
   bool EndTransaction(const nsIntRegion& aRegionToClear, TransactionId aId,
                       bool aScheduleComposite, uint32_t aPaintSequenceNumber,
                       bool aIsRepeatTransaction,
                       const mozilla::VsyncId& aVsyncId,
+                      const mozilla::TimeStamp& aVsyncTime,
                       const mozilla::TimeStamp& aRefreshStart,
                       const mozilla::TimeStamp& aTransactionStart,
                       const nsCString& aURL, bool* aSent);
 
   /**
    * Set an actor through which layer updates will be pushed.
    */
   void SetShadowManager(PLayerTransactionChild* aShadowManager);
--- a/gfx/layers/wr/WebRenderBridgeChild.cpp
+++ b/gfx/layers/wr/WebRenderBridgeChild.cpp
@@ -101,16 +101,17 @@ void WebRenderBridgeChild::UpdateResourc
   this->SendUpdateResources(resourceUpdates, smallShmems, largeShmems);
 }
 
 void WebRenderBridgeChild::EndTransaction(
     const wr::LayoutSize& aContentSize, wr::BuiltDisplayList& aDL,
     wr::IpcResourceUpdateQueue& aResources, const gfx::IntSize& aSize,
     TransactionId aTransactionId, const WebRenderScrollData& aScrollData,
     bool aContainsSVGGroup, const mozilla::VsyncId& aVsyncId,
+    const mozilla::TimeStamp& aVsyncStartTime,
     const mozilla::TimeStamp& aRefreshStartTime,
     const mozilla::TimeStamp& aTxnStartTime, const nsCString& aTxnURL) {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(mIsInTransaction);
 
   ByteBuf dlData(aDL.dl.inner.data, aDL.dl.inner.length, aDL.dl.inner.capacity);
   aDL.dl.inner.capacity = 0;
   aDL.dl.inner.data = nullptr;
@@ -121,29 +122,29 @@ void WebRenderBridgeChild::EndTransactio
   nsTArray<RefCountedShmem> smallShmems;
   nsTArray<ipc::Shmem> largeShmems;
   aResources.Flush(resourceUpdates, smallShmems, largeShmems);
 
   this->SendSetDisplayList(aSize, mParentCommands, mDestroyedActors,
                            GetFwdTransactionId(), aTransactionId, aContentSize,
                            dlData, aDL.dl_desc, aScrollData, resourceUpdates,
                            smallShmems, largeShmems, mIdNamespace,
-                           aContainsSVGGroup, aVsyncId, aRefreshStartTime,
-                           aTxnStartTime, aTxnURL, fwdTime);
+                           aContainsSVGGroup, aVsyncId, aVsyncStartTime,
+                           aRefreshStartTime, aTxnStartTime, aTxnURL, fwdTime);
 
   mParentCommands.Clear();
   mDestroyedActors.Clear();
   mIsInTransaction = false;
 }
 
 void WebRenderBridgeChild::EndEmptyTransaction(
     const FocusTarget& aFocusTarget, const ScrollUpdatesMap& aUpdates,
     Maybe<wr::IpcResourceUpdateQueue>& aResources,
     uint32_t aPaintSequenceNumber, TransactionId aTransactionId,
-    const mozilla::VsyncId& aVsyncId,
+    const mozilla::VsyncId& aVsyncId, const mozilla::TimeStamp& aVsyncStartTime,
     const mozilla::TimeStamp& aRefreshStartTime,
     const mozilla::TimeStamp& aTxnStartTime, const nsCString& aTxnURL) {
   MOZ_ASSERT(!mDestroyed);
   MOZ_ASSERT(mIsInTransaction);
 
   TimeStamp fwdTime = TimeStamp::Now();
 
   nsTArray<OpUpdateResource> resourceUpdates;
@@ -152,18 +153,18 @@ void WebRenderBridgeChild::EndEmptyTrans
   if (aResources) {
     aResources->Flush(resourceUpdates, smallShmems, largeShmems);
     aResources.reset();
   }
 
   this->SendEmptyTransaction(
       aFocusTarget, aUpdates, aPaintSequenceNumber, mParentCommands,
       mDestroyedActors, GetFwdTransactionId(), aTransactionId, resourceUpdates,
-      smallShmems, largeShmems, mIdNamespace, aVsyncId, aRefreshStartTime,
-      aTxnStartTime, aTxnURL, fwdTime);
+      smallShmems, largeShmems, mIdNamespace, aVsyncId, aVsyncStartTime,
+      aRefreshStartTime, aTxnStartTime, aTxnURL, fwdTime);
   mParentCommands.Clear();
   mDestroyedActors.Clear();
   mIsInTransaction = false;
 }
 
 void WebRenderBridgeChild::ProcessWebRenderParentCommands() {
   MOZ_ASSERT(!mDestroyed);
 
--- a/gfx/layers/wr/WebRenderBridgeChild.h
+++ b/gfx/layers/wr/WebRenderBridgeChild.h
@@ -67,25 +67,27 @@ class WebRenderBridgeChild final : publi
   void UpdateResources(wr::IpcResourceUpdateQueue& aResources);
   void BeginTransaction();
   void EndTransaction(const wr::LayoutSize& aContentSize,
                       wr::BuiltDisplayList& dl,
                       wr::IpcResourceUpdateQueue& aResources,
                       const gfx::IntSize& aSize, TransactionId aTransactionId,
                       const WebRenderScrollData& aScrollData,
                       bool aContainsSVGroup, const mozilla::VsyncId& aVsyncId,
+                      const mozilla::TimeStamp& aVsyncStartTime,
                       const mozilla::TimeStamp& aRefreshStartTime,
                       const mozilla::TimeStamp& aTxnStartTime,
                       const nsCString& aTxtURL);
   void EndEmptyTransaction(const FocusTarget& aFocusTarget,
                            const ScrollUpdatesMap& aUpdates,
                            Maybe<wr::IpcResourceUpdateQueue>& aResources,
                            uint32_t aPaintSequenceNumber,
                            TransactionId aTransactionId,
                            const mozilla::VsyncId& aVsyncId,
+                           const mozilla::TimeStamp& aVsyncStartTime,
                            const mozilla::TimeStamp& aRefreshStartTime,
                            const mozilla::TimeStamp& aTxnStartTime,
                            const nsCString& aTxtURL);
   void ProcessWebRenderParentCommands();
 
   CompositorBridgeChild* GetCompositorBridgeChild();
 
   wr::PipelineId GetPipeline() { return mPipelineId; }
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -900,18 +900,19 @@ mozilla::ipc::IPCResult WebRenderBridgeP
     InfallibleTArray<OpDestroy>&& aToDestroy, const uint64_t& aFwdTransactionId,
     const TransactionId& aTransactionId, const wr::LayoutSize& aContentSize,
     ipc::ByteBuf&& dl, const wr::BuiltDisplayListDescriptor& dlDesc,
     const WebRenderScrollData& aScrollData,
     nsTArray<OpUpdateResource>&& aResourceUpdates,
     nsTArray<RefCountedShmem>&& aSmallShmems,
     nsTArray<ipc::Shmem>&& aLargeShmems, const wr::IdNamespace& aIdNamespace,
     const bool& aContainsSVGGroup, const VsyncId& aVsyncId,
-    const TimeStamp& aRefreshStartTime, const TimeStamp& aTxnStartTime,
-    const nsCString& aTxnURL, const TimeStamp& aFwdTime) {
+    const TimeStamp& aVsyncStartTime, const TimeStamp& aRefreshStartTime,
+    const TimeStamp& aTxnStartTime, const nsCString& aTxnURL,
+    const TimeStamp& aFwdTime) {
   if (mDestroyed) {
     for (const auto& op : aToDestroy) {
       DestroyActor(op);
     }
     return IPC_OK();
   }
 
   if (!IsRootWebRenderBridgeParent()) {
@@ -995,18 +996,18 @@ mozilla::ipc::IPCResult WebRenderBridgeP
     // We will schedule generating a frame after the scene
     // build is done, so we don't need to do it here.
   } else if (observeLayersUpdate) {
     mCompositorBridge->ObserveLayersUpdate(GetLayersId(),
                                            mChildLayersObserverEpoch, true);
   }
 
   HoldPendingTransactionId(wrEpoch, aTransactionId, aContainsSVGGroup, aVsyncId,
-                           aRefreshStartTime, aTxnStartTime, aTxnURL, aFwdTime,
-                           mIsFirstPaint);
+                           aVsyncStartTime, aRefreshStartTime, aTxnStartTime,
+                           aTxnURL, aFwdTime, mIsFirstPaint);
   mIsFirstPaint = false;
 
   if (!validTransaction) {
     // Pretend we composited since someone is wating for this event,
     // though DisplayList was not pushed to webrender.
     if (CompositorBridgeParent* cbp = GetRootCompositorBridgeParent()) {
       TimeStamp now = TimeStamp::Now();
       cbp->NotifyPipelineRendered(mPipelineId, wrEpoch, VsyncId(), now, now,
@@ -1023,19 +1024,19 @@ mozilla::ipc::IPCResult WebRenderBridgeP
     const FocusTarget& aFocusTarget, const ScrollUpdatesMap& aUpdates,
     const uint32_t& aPaintSequenceNumber,
     InfallibleTArray<WebRenderParentCommand>&& aCommands,
     InfallibleTArray<OpDestroy>&& aToDestroy, const uint64_t& aFwdTransactionId,
     const TransactionId& aTransactionId,
     nsTArray<OpUpdateResource>&& aResourceUpdates,
     nsTArray<RefCountedShmem>&& aSmallShmems,
     nsTArray<ipc::Shmem>&& aLargeShmems, const wr::IdNamespace& aIdNamespace,
-    const VsyncId& aVsyncId, const TimeStamp& aRefreshStartTime,
-    const TimeStamp& aTxnStartTime, const nsCString& aTxnURL,
-    const TimeStamp& aFwdTime) {
+    const VsyncId& aVsyncId, const TimeStamp& aVsyncStartTime,
+    const TimeStamp& aRefreshStartTime, const TimeStamp& aTxnStartTime,
+    const nsCString& aTxnURL, const TimeStamp& aFwdTime) {
   if (mDestroyed) {
     for (const auto& op : aToDestroy) {
       DestroyActor(op);
     }
     return IPC_OK();
   }
 
   if (!IsRootWebRenderBridgeParent()) {
@@ -1114,17 +1115,18 @@ mozilla::ipc::IPCResult WebRenderBridgeP
     // composite, then we leave sendDidComposite as true so we just send
     // the DidComposite notification now.
     sendDidComposite = false;
   }
 
   // Only register a value for CONTENT_FRAME_TIME telemetry if we actually drew
   // something. It is for consistency with disabling WebRender.
   HoldPendingTransactionId(mWrEpoch, aTransactionId, false, aVsyncId,
-                           aRefreshStartTime, aTxnStartTime, aTxnURL, aFwdTime,
+                           aVsyncStartTime, aRefreshStartTime, aTxnStartTime,
+                           aTxnURL, aFwdTime,
                            /* aIsFirstPaint */ false,
                            /* aUseForTelemetry */ scheduleComposite);
 
   if (scheduleComposite) {
     // This is actually not necessary, since ScheduleGenerateFrame() is
     // triggered via SceneBuilder thread. But if we remove it, it causes talos
     // regression. The SceneBuilder thread seems not trigger next vsync right
     // away. For now, we call ScheduleGenerateFrame() here.
@@ -1817,23 +1819,25 @@ void WebRenderBridgeParent::MaybeGenerat
 
   mApi->SendTransaction(fastTxn);
   mMostRecentComposite = TimeStamp::Now();
 }
 
 void WebRenderBridgeParent::HoldPendingTransactionId(
     const wr::Epoch& aWrEpoch, TransactionId aTransactionId,
     bool aContainsSVGGroup, const VsyncId& aVsyncId,
-    const TimeStamp& aRefreshStartTime, const TimeStamp& aTxnStartTime,
-    const nsCString& aTxnURL, const TimeStamp& aFwdTime,
-    const bool aIsFirstPaint, const bool aUseForTelemetry) {
+    const TimeStamp& aVsyncStartTime, const TimeStamp& aRefreshStartTime,
+    const TimeStamp& aTxnStartTime, const nsCString& aTxnURL,
+    const TimeStamp& aFwdTime, const bool aIsFirstPaint,
+    const bool aUseForTelemetry) {
   MOZ_ASSERT(aTransactionId > LastPendingTransactionId());
   mPendingTransactionIds.push_back(PendingTransactionId(
-      aWrEpoch, aTransactionId, aContainsSVGGroup, aVsyncId, aRefreshStartTime,
-      aTxnStartTime, aTxnURL, aFwdTime, aIsFirstPaint, aUseForTelemetry));
+      aWrEpoch, aTransactionId, aContainsSVGGroup, aVsyncId, aVsyncStartTime,
+      aRefreshStartTime, aTxnStartTime, aTxnURL, aFwdTime, aIsFirstPaint,
+      aUseForTelemetry));
 }
 
 TransactionId WebRenderBridgeParent::LastPendingTransactionId() {
   TransactionId id{0};
   if (!mPendingTransactionIds.empty()) {
     id = mPendingTransactionIds.back().mId;
   }
   return id;
@@ -2021,16 +2025,25 @@ TransactionId WebRenderBridgeParent::Flu
                 LABELS_CONTENT_FRAME_TIME_REASON::MissedComposite);
           }
         } else {
           // Composite start on time, but must have taken too long.
           Telemetry::AccumulateCategorical(
               LABELS_CONTENT_FRAME_TIME_REASON::SlowComposite);
         }
       }
+
+      if (!(transactionId.mVsyncId == VsyncId()) &&
+          transactionId.mVsyncStartTime) {
+        latencyMs = (aEndTime - transactionId.mVsyncStartTime).ToMilliseconds();
+        latencyNorm = latencyMs / mVsyncRate.ToMilliseconds();
+        fracLatencyNorm = lround(latencyNorm * 100.0);
+        Telemetry::Accumulate(Telemetry::CONTENT_FRAME_TIME_VSYNC,
+                              fracLatencyNorm);
+      }
     }
 
 #if defined(ENABLE_FRAME_LATENCY_LOG)
     if (transactionId.mRefreshStartTime) {
       int32_t latencyMs =
           lround((aEndTime - transactionId.mRefreshStartTime).ToMilliseconds());
       printf_stderr(
           "From transaction start to end of generate frame latencyMs %d this "
--- a/gfx/layers/wr/WebRenderBridgeParent.h
+++ b/gfx/layers/wr/WebRenderBridgeParent.h
@@ -99,30 +99,31 @@ class WebRenderBridgeParent final : publ
       const uint64_t& aFwdTransactionId, const TransactionId& aTransactionId,
       const wr::LayoutSize& aContentSize, ipc::ByteBuf&& dl,
       const wr::BuiltDisplayListDescriptor& dlDesc,
       const WebRenderScrollData& aScrollData,
       nsTArray<OpUpdateResource>&& aResourceUpdates,
       nsTArray<RefCountedShmem>&& aSmallShmems,
       nsTArray<ipc::Shmem>&& aLargeShmems, const wr::IdNamespace& aIdNamespace,
       const bool& aContainsSVGGroup, const VsyncId& aVsyncId,
-      const TimeStamp& aRefreshStartTime, const TimeStamp& aTxnStartTime,
-      const nsCString& aTxnURL, const TimeStamp& aFwdTime) override;
+      const TimeStamp& aVsyncStartTime, const TimeStamp& aRefreshStartTime,
+      const TimeStamp& aTxnStartTime, const nsCString& aTxnURL,
+      const TimeStamp& aFwdTime) override;
   mozilla::ipc::IPCResult RecvEmptyTransaction(
       const FocusTarget& aFocusTarget, const ScrollUpdatesMap& aUpdates,
       const uint32_t& aPaintSequenceNumber,
       InfallibleTArray<WebRenderParentCommand>&& aCommands,
       InfallibleTArray<OpDestroy>&& aToDestroy,
       const uint64_t& aFwdTransactionId, const TransactionId& aTransactionId,
       nsTArray<OpUpdateResource>&& aResourceUpdates,
       nsTArray<RefCountedShmem>&& aSmallShmems,
       nsTArray<ipc::Shmem>&& aLargeShmems, const wr::IdNamespace& aIdNamespace,
-      const VsyncId& aVsyncId, const TimeStamp& aRefreshStartTime,
-      const TimeStamp& aTxnStartTime, const nsCString& aTxnURL,
-      const TimeStamp& aFwdTime) override;
+      const VsyncId& aVsyncId, const TimeStamp& aVsyncStartTime,
+      const TimeStamp& aRefreshStartTime, const TimeStamp& aTxnStartTime,
+      const nsCString& aTxnURL, const TimeStamp& aFwdTime) override;
   mozilla::ipc::IPCResult RecvSetFocusTarget(
       const FocusTarget& aFocusTarget) override;
   mozilla::ipc::IPCResult RecvParentCommands(
       nsTArray<WebRenderParentCommand>&& commands) override;
   mozilla::ipc::IPCResult RecvGetSnapshot(PTextureParent* aTexture) override;
 
   mozilla::ipc::IPCResult RecvValidateFontDescriptor(
       nsTArray<uint8_t>&& aData) override;
@@ -175,19 +176,20 @@ class WebRenderBridgeParent final : publ
   void SendAsyncMessage(
       const InfallibleTArray<AsyncParentMessageData>& aMessage) override;
   void SendPendingAsyncMessages() override;
   void SetAboutToSendAsyncMessages() override;
 
   void HoldPendingTransactionId(
       const wr::Epoch& aWrEpoch, TransactionId aTransactionId,
       bool aContainsSVGGroup, const VsyncId& aVsyncId,
-      const TimeStamp& aRefreshStartTime, const TimeStamp& aTxnStartTime,
-      const nsCString& aTxnURL, const TimeStamp& aFwdTime,
-      const bool aIsFirstPaint, const bool aUseForTelemetry = true);
+      const TimeStamp& aVsyncStartTime, const TimeStamp& aRefreshStartTime,
+      const TimeStamp& aTxnStartTime, const nsCString& aTxnURL,
+      const TimeStamp& aFwdTime, const bool aIsFirstPaint,
+      const bool aUseForTelemetry = true);
   TransactionId LastPendingTransactionId();
   TransactionId FlushTransactionIdsForEpoch(
       const wr::Epoch& aEpoch, const VsyncId& aCompositeStartId,
       const TimeStamp& aCompositeStartTime, const TimeStamp& aRenderStartTime,
       const TimeStamp& aEndTime, UiCompositorControllerParent* aUiController,
       wr::RendererStats* aStats = nullptr,
       nsTArray<FrameStats>* aOutputStats = nullptr);
   void NotifySceneBuiltForEpoch(const wr::Epoch& aEpoch,
@@ -332,34 +334,37 @@ class WebRenderBridgeParent final : publ
     }
     return VsyncId();
   }
 
  private:
   struct PendingTransactionId {
     PendingTransactionId(const wr::Epoch& aEpoch, TransactionId aId,
                          bool aContainsSVGGroup, const VsyncId& aVsyncId,
+                         const TimeStamp& aVsyncStartTime,
                          const TimeStamp& aRefreshStartTime,
                          const TimeStamp& aTxnStartTime,
                          const nsCString& aTxnURL, const TimeStamp& aFwdTime,
                          const bool aIsFirstPaint, const bool aUseForTelemetry)
         : mEpoch(aEpoch),
           mId(aId),
           mVsyncId(aVsyncId),
+          mVsyncStartTime(aVsyncStartTime),
           mRefreshStartTime(aRefreshStartTime),
           mTxnStartTime(aTxnStartTime),
           mTxnURL(aTxnURL),
           mFwdTime(aFwdTime),
           mSkippedComposites(0),
           mContainsSVGGroup(aContainsSVGGroup),
           mIsFirstPaint(aIsFirstPaint),
           mUseForTelemetry(aUseForTelemetry) {}
     wr::Epoch mEpoch;
     TransactionId mId;
     VsyncId mVsyncId;
+    TimeStamp mVsyncStartTime;
     TimeStamp mRefreshStartTime;
     TimeStamp mTxnStartTime;
     nsCString mTxnURL;
     TimeStamp mFwdTime;
     TimeStamp mSceneBuiltTime;
     uint32_t mSkippedComposites;
     bool mContainsSVGGroup;
     bool mIsFirstPaint;
--- a/gfx/layers/wr/WebRenderLayerManager.cpp
+++ b/gfx/layers/wr/WebRenderLayerManager.cpp
@@ -229,16 +229,17 @@ bool WebRenderLayerManager::EndEmptyTran
       WrBridge()->GetSyncObject()->Synchronize();
     }
   }
 
   WrBridge()->EndEmptyTransaction(mFocusTarget, mPendingScrollUpdates,
                                   mAsyncResourceUpdates, mPaintSequenceNumber,
                                   mLatestTransactionId,
                                   mTransactionIdAllocator->GetVsyncId(),
+                                  mTransactionIdAllocator->GetVsyncStart(),
                                   refreshStart, mTransactionStart, mURL);
   ClearPendingScrollInfoUpdate();
 
   mTransactionStart = TimeStamp();
 
   MakeSnapshotIfRequired(size);
   return true;
 }
@@ -361,16 +362,17 @@ void WebRenderLayerManager::EndTransacti
   mLastDisplayListSize = dl.dl.inner.capacity;
 
   {
     AUTO_PROFILER_TRACING("Paint", "ForwardDPTransaction");
     WrBridge()->EndTransaction(contentSize, dl, resourceUpdates,
                                size.ToUnknownSize(), mLatestTransactionId,
                                mScrollData, containsSVGGroup,
                                mTransactionIdAllocator->GetVsyncId(),
+                               mTransactionIdAllocator->GetVsyncStart(),
                                refreshStart, mTransactionStart, mURL);
   }
 
   mTransactionStart = TimeStamp();
 
   MakeSnapshotIfRequired(size);
   mNeedsComposite = false;
 }
--- a/gfx/vr/VRManager.cpp
+++ b/gfx/vr/VRManager.cpp
@@ -1,35 +1,34 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "VRManager.h"
 #include "VRManagerParent.h"
-#include "VRGPUChild.h"
 #include "VRThread.h"
 #include "gfxVR.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/dom/VRDisplay.h"
 #include "mozilla/dom/GamepadEventTypes.h"
 #include "mozilla/layers/TextureHost.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/Unused.h"
-#include "mozilla/gfx/GPUParent.h"
 
 #include "gfxPrefs.h"
 #include "gfxVR.h"
 #include "gfxVRExternal.h"
 
 #include "gfxVRPuppet.h"
 #include "ipc/VRLayerParent.h"
 #if !defined(MOZ_WIDGET_ANDROID)
 #include "service/VRService.h"
+#include "service/VRServiceManager.h"
 #endif
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
 using namespace mozilla::gl;
 
 namespace mozilla {
@@ -75,25 +74,21 @@ VRManager::VRManager()
   MOZ_ASSERT(sVRManagerSingleton == nullptr);
 
   RefPtr<VRSystemManager> mgr;
 
 #if !defined(MOZ_WIDGET_ANDROID)
   // The VR Service accesses all hardware from a separate process
   // and replaces the other VRSystemManager when enabled.
   if (!gfxPrefs::VRProcessEnabled()) {
-    mVRService = VRService::Create();
-  } else if (gfxPrefs::VRProcessEnabled() && XRE_IsGPUProcess()) {
-    gfx::GPUParent* gpu = GPUParent::GetSingleton();
-    MOZ_ASSERT(gpu);
-    Unused << gpu->SendCreateVRProcess();
+    VRServiceManager::Get().CreateService();
   }
-  if (mVRService) {
+  if (VRServiceManager::Get().IsServiceValid()) {
     mExternalManager =
-        VRSystemManagerExternal::Create(mVRService->GetAPIShmem());
+        VRSystemManagerExternal::Create(VRServiceManager::Get().GetAPIShmem());
   }
   if (mExternalManager) {
     mManagers.AppendElement(mExternalManager);
   }
 #endif
 
   if (!mExternalManager) {
     mExternalManager = VRSystemManagerExternal::Create();
@@ -107,52 +102,53 @@ VRManager::VRManager()
   if (XRE_IsParentProcess() && gfxPrefs::VREnabled()) {
     Preferences::SetBool("dom.gamepad.extensions.enabled", true);
   }
 }
 
 VRManager::~VRManager() {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mInitialized);
+#if !defined(MOZ_WIDGET_ANDROID)
+  if (VRServiceManager::Get().IsServiceValid()) {
+    VRServiceManager::Get().Shutdown();
+  }
+#endif
   MOZ_COUNT_DTOR(VRManager);
 }
 
 void VRManager::Destroy() {
   StopTasks();
   mVRDisplays.Clear();
   mVRControllers.Clear();
   for (uint32_t i = 0; i < mManagers.Length(); ++i) {
     mManagers[i]->Destroy();
   }
 #if !defined(MOZ_WIDGET_ANDROID)
-  if (mVRService) {
-    mVRService->Stop();
-    mVRService = nullptr;
+  if (VRServiceManager::Get().IsServiceValid()) {
+    VRServiceManager::Get().Shutdown();
   }
 #endif
   mInitialized = false;
 }
 
 void VRManager::Shutdown() {
   mVRDisplays.Clear();
   mVRControllers.Clear();
   for (uint32_t i = 0; i < mManagers.Length(); ++i) {
     mManagers[i]->Shutdown();
   }
 #if !defined(MOZ_WIDGET_ANDROID)
-  if (mVRService) {
-    mVRService->Stop();
+  if (VRServiceManager::Get().IsServiceValid()) {
+    VRServiceManager::Get().Stop();
   }
-  if (gfxPrefs::VRProcessEnabled() && VRGPUChild::IsCreated()) {
-    RefPtr<Runnable> task =
-        NS_NewRunnableFunction("VRGPUChild::SendStopVRService", []() -> void {
-          VRGPUChild* vrGPUChild = VRGPUChild::Get();
-          vrGPUChild->SendStopVRService();
-        });
-
+  if (gfxPrefs::VRProcessEnabled() && mVRServiceStarted) {
+    RefPtr<Runnable> task = NS_NewRunnableFunction(
+        "VRServiceManager::ShutdownVRProcess",
+        []() -> void { VRServiceManager::Get().ShutdownVRProcess(); });
     NS_DispatchToMainThread(task.forget());
   }
 #endif
   mVRServiceStarted = false;
 }
 
 void VRManager::Init() { mInitialized = true; }
 
@@ -434,29 +430,22 @@ void VRManager::EnumerateVRDisplays() {
    * and VR Process before enumeration.
    * We don't want to start this until we will
    * actualy enumerate, to avoid continuously
    * re-launching the thread/process when
    * no hardware is found or a VR software update
    * is in progress
    */
 #if !defined(MOZ_WIDGET_ANDROID)
-  // Tell VR process to start VR service.
   if (gfxPrefs::VRProcessEnabled() && !mVRServiceStarted) {
-    RefPtr<Runnable> task =
-        NS_NewRunnableFunction("VRGPUChild::SendStartVRService", []() -> void {
-          VRGPUChild* vrGPUChild = VRGPUChild::Get();
-          vrGPUChild->SendStartVRService();
-        });
-
-    NS_DispatchToMainThread(task.forget());
+    VRServiceManager::Get().CreateVRProcess();
     mVRServiceStarted = true;
   } else if (!gfxPrefs::VRProcessEnabled()) {
-    if (mVRService) {
-      mVRService->Start();
+    if (VRServiceManager::Get().IsServiceValid()) {
+      VRServiceManager::Get().Start();
       mVRServiceStarted = true;
     }
   }
 #endif
 
   /**
    * VRSystemManagers are inserted into mManagers in
    * a strict order of priority.  The managers for the
@@ -484,19 +473,17 @@ void VRManager::RefreshVRDisplays(bool a
    * If we aren't viewing WebVR content, don't enumerate
    * new hardware, as it will cause some devices to power on
    * or interrupt other VR activities.
    */
   if (mVRDisplaysRequested || aMustDispatch) {
     EnumerateVRDisplays();
   }
 #if !defined(MOZ_WIDGET_ANDROID)
-  if (mVRService) {
-    mVRService->Refresh();
-  }
+  VRServiceManager::Get().Refresh();
 #endif
 
   /**
    * VRSystemManager::GetHMDs will not activate new hardware
    * or result in interruption of other VR activities.
    * We can call it even when suppressing enumeration to get
    * the already-enumerated displays.
    */
--- a/gfx/vr/VRManager.h
+++ b/gfx/vr/VRManager.h
@@ -107,19 +107,16 @@ class VRManager {
 
   TimeStamp mLastControllerEnumerationTime;
   TimeStamp mLastDisplayEnumerationTime;
   TimeStamp mLastActiveTime;
   TimeStamp mLastTickTime;
   double mAccumulator100ms;
   RefPtr<VRSystemManagerPuppet> mPuppetManager;
   RefPtr<VRSystemManagerExternal> mExternalManager;
-#if !defined(MOZ_WIDGET_ANDROID)
-  RefPtr<VRService> mVRService;
-#endif
   bool mVRDisplaysRequested;
   bool mVRDisplaysRequestedNonFocus;
   bool mVRControllersRequested;
   bool mVRServiceStarted;
   uint32_t mTaskInterval;
   RefPtr<nsITimer> mTaskTimer;
 };
 
--- a/gfx/vr/gfxVR.h
+++ b/gfx/vr/gfxVR.h
@@ -47,16 +47,23 @@ enum class VRDeviceType : uint16_t {
   OpenVR,
   OSVR,
   GVR,
   Puppet,
   External,
   NumVRDeviceTypes
 };
 
+enum class OpenVRControllerType : uint16_t {
+  Vive,
+  WMR,
+  Knuckles,
+  NumOpenVRControllerTypes
+};
+
 struct VRDisplayInfo {
   uint32_t mDisplayID;
   VRDeviceType mType;
   uint32_t mPresentingGroups;
   uint32_t mGroupMask;
   uint64_t mFrameId;
   VRDisplayState mDisplayState;
   VRControllerState mControllerState[kVRControllerMaxCount];
--- a/gfx/vr/gfxVRExternal.cpp
+++ b/gfx/vr/gfxVRExternal.cpp
@@ -434,20 +434,16 @@ VRSystemManagerExternal::VRSystemManager
   mShmemFD = 0;
 #elif defined(XP_WIN)
   mShmemFile = NULL;
 #elif defined(MOZ_WIDGET_ANDROID)
   mExternalStructFailed = false;
   mEnumerationCompleted = false;
 #endif
   mDoShutdown = false;
-
-  if (!aAPIShmem) {
-    OpenShmem();
-  }
 }
 
 VRSystemManagerExternal::~VRSystemManagerExternal() { CloseShmem(); }
 
 void VRSystemManagerExternal::OpenShmem() {
   if (mExternalShmem) {
     return;
 #if defined(MOZ_WIDGET_ANDROID)
--- a/gfx/vr/ipc/PVR.ipdl
+++ b/gfx/vr/ipc/PVR.ipdl
@@ -1,26 +1,33 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 using mozilla::TimeStamp from "mozilla/TimeStamp.h";
+using mozilla::gfx::OpenVRControllerType from "gfxVR.h";
 
 include GraphicsMessages;
 include protocol PVRGPU;
 
 namespace mozilla {
 namespace gfx {
 
 async protocol PVR
 {
 parent:
   async NewGPUVRManager(Endpoint<PVRGPUParent> endpoint);
   async Init(GfxPrefSetting[] prefs, GfxVarUpdate[] vars, DevicePrefs devicePrefs);
   async NotifyVsync(TimeStamp vsyncTimestamp);
 
   async UpdatePref(GfxPrefSetting pref);
   async UpdateVar(GfxVarUpdate var);
+  async OpenVRControllerActionPathToVR(nsCString aPath);
+  async OpenVRControllerManifestPathToVR(OpenVRControllerType aType, nsCString aPath);
+
+child:
+  async OpenVRControllerActionPathToParent(nsCString aPath);
+  async OpenVRControllerManifestPathToParent(OpenVRControllerType aType, nsCString aPath);
 };
 
 } // namespace gfx
 } // namespace mozilla
\ No newline at end of file
--- a/gfx/vr/ipc/VRChild.cpp
+++ b/gfx/vr/ipc/VRChild.cpp
@@ -10,24 +10,72 @@
 
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/SystemGroup.h"
 #include "mozilla/VsyncDispatcher.h"
 
 namespace mozilla {
 namespace gfx {
 
+class OpenVRControllerManifestManager {
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OpenVRControllerManifestManager)
+ public:
+  explicit OpenVRControllerManifestManager() = default;
+
+  void SetOpenVRControllerActionPath(const nsCString& aPath) {
+    mAction = aPath;
+  }
+
+  void SetOpenVRControllerManifestPath(OpenVRControllerType aType,
+                                       const nsCString& aPath) {
+    mManifest.Put(static_cast<uint32_t>(aType), aPath);
+  }
+
+  bool GetActionPath(nsCString* aPath) {
+    if (!mAction.IsEmpty()) {
+      *aPath = mAction;
+      return true;
+    }
+    return false;
+  }
+
+  bool GetManifestPath(OpenVRControllerType aType, nsCString* aPath) {
+    return mManifest.Get(static_cast<uint32_t>(aType), aPath);
+  }
+
+ private:
+  ~OpenVRControllerManifestManager() {
+    if (!mAction.IsEmpty() && remove(mAction.BeginReading()) != 0) {
+      MOZ_ASSERT(false, "Delete controller action file failed.");
+    }
+    mAction = "";
+
+    for (auto iter = mManifest.Iter(); !iter.Done(); iter.Next()) {
+      nsCString path(iter.Data());
+      if (!path.IsEmpty() && remove(path.BeginReading()) != 0) {
+        MOZ_ASSERT(false, "Delete controller manifest file failed.");
+      }
+    }
+    mManifest.Clear();
+  }
+
+  nsCString mAction;
+  nsDataHashtable<nsUint32HashKey, nsCString> mManifest;
+  DISALLOW_COPY_AND_ASSIGN(OpenVRControllerManifestManager);
+};
+
+StaticRefPtr<OpenVRControllerManifestManager> sOpenVRControllerManifestManager;
+
 VRChild::VRChild(VRProcessParent* aHost) : mHost(aHost) {
   MOZ_ASSERT(XRE_IsParentProcess());
 }
 
 void VRChild::ActorDestroy(ActorDestroyReason aWhy) {
   gfxVars::RemoveReceiver(this);
   mHost->OnChannelClosed();
-  XRE_ShutdownChildProcess();
 }
 
 void VRChild::Init() {
   // Build a list of prefs the VR process will need. Note that because we
   // limit the VR process to prefs contained in gfxPrefs, we can simplify
   // the message in two ways: one, we only need to send its index in gfxPrefs
   // rather than its name, and two, we only need to send prefs that don't
   // have their default value.
@@ -50,19 +98,57 @@ void VRChild::Init() {
   devicePrefs.d3d11Compositing() =
       gfxConfig::GetValue(Feature::D3D11_COMPOSITING);
   devicePrefs.oglCompositing() =
       gfxConfig::GetValue(Feature::OPENGL_COMPOSITING);
   devicePrefs.advancedLayers() = gfxConfig::GetValue(Feature::ADVANCED_LAYERS);
   devicePrefs.useD2D1() = gfxConfig::GetValue(Feature::DIRECT2D);
 
   SendInit(prefs, updates, devicePrefs);
+
+  if (!sOpenVRControllerManifestManager) {
+    sOpenVRControllerManifestManager = new OpenVRControllerManifestManager();
+    NS_DispatchToMainThread(NS_NewRunnableFunction(
+        "ClearOnShutdown OpenVRControllerManifestManager",
+        []() { ClearOnShutdown(&sOpenVRControllerManifestManager); }));
+  }
+
+  nsCString output;
+  if (sOpenVRControllerManifestManager->GetActionPath(&output)) {
+    SendOpenVRControllerActionPathToVR(output);
+  }
+  if (sOpenVRControllerManifestManager->GetManifestPath(
+          OpenVRControllerType::Vive, &output)) {
+    SendOpenVRControllerManifestPathToVR(OpenVRControllerType::Vive, output);
+  }
+  if (sOpenVRControllerManifestManager->GetManifestPath(
+          OpenVRControllerType::WMR, &output)) {
+    SendOpenVRControllerManifestPathToVR(OpenVRControllerType::WMR, output);
+  }
+  if (sOpenVRControllerManifestManager->GetManifestPath(
+          OpenVRControllerType::Knuckles, &output)) {
+    SendOpenVRControllerManifestPathToVR(OpenVRControllerType::Knuckles,
+                                         output);
+  }
   gfxVars::AddReceiver(this);
 }
 
+mozilla::ipc::IPCResult VRChild::RecvOpenVRControllerActionPathToParent(
+    const nsCString& aPath) {
+  sOpenVRControllerManifestManager->SetOpenVRControllerActionPath(aPath);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult VRChild::RecvOpenVRControllerManifestPathToParent(
+    const OpenVRControllerType& aType, const nsCString& aPath) {
+  sOpenVRControllerManifestManager->SetOpenVRControllerManifestPath(aType,
+                                                                    aPath);
+  return IPC_OK();
+}
+
 void VRChild::OnVarChanged(const GfxVarUpdate& aVar) { SendUpdateVar(aVar); }
 
 class DeferredDeleteVRChild : public Runnable {
  public:
   explicit DeferredDeleteVRChild(UniquePtr<VRChild>&& aChild)
       : Runnable("gfx::DeferredDeleteVRChild"), mChild(std::move(aChild)) {}
 
   NS_IMETHODIMP Run() override { return NS_OK; }
--- a/gfx/vr/ipc/VRChild.h
+++ b/gfx/vr/ipc/VRChild.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef GFX_VR_CHILD_H
 #define GFX_VR_CHILD_H
 
 #include "mozilla/gfx/PVRChild.h"
 #include "mozilla/gfx/gfxVarReceiver.h"
 #include "mozilla/VsyncDispatcher.h"
+#include "gfxVR.h"
 
 namespace mozilla {
 namespace gfx {
 
 class VRProcessParent;
 class VRChild;
 
 class VRChild final : public PVRChild, public gfxVarReceiver {
@@ -23,16 +24,20 @@ class VRChild final : public PVRChild, p
   ~VRChild() = default;
 
   static void Destroy(UniquePtr<VRChild>&& aChild);
   void Init();
   virtual void OnVarChanged(const GfxVarUpdate& aVar) override;
 
  protected:
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+  virtual mozilla::ipc::IPCResult RecvOpenVRControllerActionPathToParent(
+      const nsCString& aPath) override;
+  virtual mozilla::ipc::IPCResult RecvOpenVRControllerManifestPathToParent(
+      const OpenVRControllerType& aType, const nsCString& aPath) override;
 
  private:
   VRProcessParent* mHost;
 };
 
 }  // namespace gfx
 }  // namespace mozilla
 
--- a/gfx/vr/ipc/VRGPUChild.cpp
+++ b/gfx/vr/ipc/VRGPUChild.cpp
@@ -16,25 +16,34 @@ static StaticRefPtr<VRGPUChild> sVRGPUCh
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!sVRGPUChildSingleton);
 
   RefPtr<VRGPUChild> child(new VRGPUChild());
   if (!aEndpoint.Bind(child)) {
     return false;
   }
   sVRGPUChildSingleton = child;
+
+  RefPtr<Runnable> task =
+      NS_NewRunnableFunction("VRGPUChild::SendStartVRService", []() -> void {
+        VRGPUChild* vrGPUChild = VRGPUChild::Get();
+        vrGPUChild->SendStartVRService();
+      });
+
+  NS_DispatchToMainThread(task.forget());
+
   return true;
 }
 
 /* static */ bool VRGPUChild::IsCreated() { return !!sVRGPUChildSingleton; }
 
 /* static */ VRGPUChild* VRGPUChild::Get() {
   MOZ_ASSERT(IsCreated(), "VRGPUChild haven't initialized yet.");
   return sVRGPUChildSingleton;
 }
 
-/*static*/ void VRGPUChild::ShutDown() {
+/*static*/ void VRGPUChild::Shutdown() {
   MOZ_ASSERT(NS_IsMainThread());
   sVRGPUChildSingleton = nullptr;
 }
 
 }  // namespace gfx
 }  // namespace mozilla
--- a/gfx/vr/ipc/VRGPUChild.h
+++ b/gfx/vr/ipc/VRGPUChild.h
@@ -14,17 +14,17 @@ namespace gfx {
 
 class VRGPUChild final : public PVRGPUChild {
  public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRGPUChild);
 
   static VRGPUChild* Get();
   static bool InitForGPUProcess(Endpoint<PVRGPUChild>&& aEndpoint);
   static bool IsCreated();
-  static void ShutDown();
+  static void Shutdown();
 
  protected:
   explicit VRGPUChild() {}
   ~VRGPUChild() {}
 
  private:
   DISALLOW_COPY_AND_ASSIGN(VRGPUChild);
 };
--- a/gfx/vr/ipc/VRGPUParent.h
+++ b/gfx/vr/ipc/VRGPUParent.h
@@ -3,41 +3,43 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef GFX_VR_GPU_PARENT_H
 #define GFX_VR_GPU_PARENT_H
 
 #include "mozilla/gfx/PVRGPUParent.h"
+#include "VRService.h"
 
 namespace mozilla {
 namespace gfx {
 
 class VRGPUParent final : public PVRGPUParent {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRGPUParent)
 
  public:
   explicit VRGPUParent(ProcessId aChildProcessId);
 
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
   static RefPtr<VRGPUParent> CreateForGPU(Endpoint<PVRGPUParent>&& aEndpoint);
 
  protected:
-  ~VRGPUParent() {}
+  ~VRGPUParent() = default;
 
   void Bind(Endpoint<PVRGPUParent>&& aEndpoint);
   virtual mozilla::ipc::IPCResult RecvStartVRService() override;
   virtual mozilla::ipc::IPCResult RecvStopVRService() override;
 
  private:
   void DeferredDestroy();
 
   RefPtr<VRGPUParent> mSelfRef;
 #if !defined(MOZ_WIDGET_ANDROID)
   RefPtr<VRService> mVRService;
 #endif
+  DISALLOW_COPY_AND_ASSIGN(VRGPUParent);
 };
 
 }  // namespace gfx
 }  // namespace mozilla
 
 #endif  // GFX_VR_CONTENT_PARENT_H
--- a/gfx/vr/ipc/VRMessageUtils.h
+++ b/gfx/vr/ipc/VRMessageUtils.h
@@ -25,16 +25,23 @@ struct ParamTraits<mozilla::gfx::VRDevic
 
 template <>
 struct ParamTraits<mozilla::gfx::VRDisplayCapabilityFlags>
     : public BitFlagsEnumSerializer<
           mozilla::gfx::VRDisplayCapabilityFlags,
           mozilla::gfx::VRDisplayCapabilityFlags::Cap_All> {};
 
 template <>
+struct ParamTraits<mozilla::gfx::OpenVRControllerType>
+    : public ContiguousEnumSerializer<
+          mozilla::gfx::OpenVRControllerType,
+          mozilla::gfx::OpenVRControllerType::Vive,
+          mozilla::gfx::OpenVRControllerType::NumOpenVRControllerTypes> {};
+
+template <>
 struct ParamTraits<mozilla::gfx::VRDisplayState> {
   typedef mozilla::gfx::VRDisplayState paramType;
 
   static void Write(Message* aMsg, const paramType& aParam) {
     // TODO - VRDisplayState is asserted to be a POD type
     //        A simple memcpy may be sufficient here, or
     //        this code can be refactored out if we use
     //        shmem between the VR and content process.
--- a/gfx/vr/ipc/VRParent.cpp
+++ b/gfx/vr/ipc/VRParent.cpp
@@ -74,29 +74,44 @@ IPCResult VRParent::RecvUpdatePref(const
   return IPC_OK();
 }
 
 IPCResult VRParent::RecvUpdateVar(const GfxVarUpdate& aUpdate) {
   gfxVars::ApplyUpdate(aUpdate);
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult VRParent::RecvOpenVRControllerActionPathToVR(
+    const nsCString& aPath) {
+  mOpenVRControllerAction = aPath;
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult VRParent::RecvOpenVRControllerManifestPathToVR(
+    const OpenVRControllerType& aType, const nsCString& aPath) {
+  mOpenVRControllerManifest.Put(static_cast<uint32_t>(aType), aPath);
+  return IPC_OK();
+}
+
 void VRParent::ActorDestroy(ActorDestroyReason aWhy) {
   if (AbnormalShutdown == aWhy) {
     NS_WARNING("Shutting down VR process early due to a crash!");
     ProcessChild::QuickExit();
   }
 
-  mVRGPUParent->Close();
+  mVRGPUParent = nullptr;
 #if defined(XP_WIN)
   DeviceManagerDx::Shutdown();
 #endif
   gfxVars::Shutdown();
   gfxConfig::Shutdown();
   gfxPrefs::DestroySingleton();
+  // Only calling XRE_ShutdownChildProcess() at the child process
+  // instead of the main process. Otherwise, it will close all child processes
+  // that are spawned from the main process.
   XRE_ShutdownChildProcess();
 }
 
 bool VRParent::Init(base::ProcessId aParentPid, const char* aParentBuildID,
                     MessageLoop* aIOLoop, IPC::Channel* aChannel) {
   // Initialize the thread manager before starting IPC. Otherwise, messages
   // may be posted to the main thread and we won't be able to process them.
   if (NS_WARN_IF(NS_FAILED(nsThreadManager::get().Init()))) {
@@ -127,10 +142,24 @@ bool VRParent::Init(base::ProcessId aPar
 #endif
   if (NS_FAILED(NS_InitMinimalXPCOM())) {
     return false;
   }
 
   return true;
 }
 
+bool VRParent::GetOpenVRControllerActionPath(nsCString* aPath) {
+  if (!mOpenVRControllerAction.IsEmpty()) {
+    *aPath = mOpenVRControllerAction;
+    return true;
+  }
+
+  return false;
+}
+
+bool VRParent::GetOpenVRControllerManifestPath(OpenVRControllerType aType,
+                                               nsCString* aPath) {
+  return mOpenVRControllerManifest.Get(static_cast<uint32_t>(aType), aPath);
+}
+
 }  // namespace gfx
 }  // namespace mozilla
\ No newline at end of file
--- a/gfx/vr/ipc/VRParent.h
+++ b/gfx/vr/ipc/VRParent.h
@@ -3,44 +3,59 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef GFX_VR_PARENT_H
 #define GFX_VR_PARENT_H
 
 #include "mozilla/gfx/PVRParent.h"
+#include "VRGPUParent.h"
 
 namespace mozilla {
 namespace gfx {
 
-class VRGPUParent;
 class VRService;
 class VRSystemManagerExternal;
 
 class VRParent final : public PVRParent {
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRParent);
+
  public:
-  VRParent();
+  explicit VRParent();
+
   bool Init(base::ProcessId aParentPid, const char* aParentBuildID,
             MessageLoop* aIOLoop, IPC::Channel* aChannel);
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+  bool GetOpenVRControllerActionPath(nsCString* aPath);
+  bool GetOpenVRControllerManifestPath(OpenVRControllerType aType,
+                                       nsCString* aPath);
 
  protected:
+  ~VRParent() = default;
+
   virtual mozilla::ipc::IPCResult RecvNewGPUVRManager(
       Endpoint<PVRGPUParent>&& aEndpoint) override;
   virtual mozilla::ipc::IPCResult RecvInit(
       nsTArray<GfxPrefSetting>&& prefs, nsTArray<GfxVarUpdate>&& vars,
       const DevicePrefs& devicePrefs) override;
   virtual mozilla::ipc::IPCResult RecvNotifyVsync(
       const TimeStamp& vsyncTimestamp) override;
   virtual mozilla::ipc::IPCResult RecvUpdatePref(
       const GfxPrefSetting& setting) override;
   virtual mozilla::ipc::IPCResult RecvUpdateVar(
       const GfxVarUpdate& pref) override;
+  virtual mozilla::ipc::IPCResult RecvOpenVRControllerActionPathToVR(
+      const nsCString& aPath) override;
+  virtual mozilla::ipc::IPCResult RecvOpenVRControllerManifestPathToVR(
+      const OpenVRControllerType& aType, const nsCString& aPath) override;
 
  private:
+  nsCString mOpenVRControllerAction;
+  nsDataHashtable<nsUint32HashKey, nsCString> mOpenVRControllerManifest;
   RefPtr<VRGPUParent> mVRGPUParent;
+  DISALLOW_COPY_AND_ASSIGN(VRParent);
 };
 
 }  // namespace gfx
 }  // namespace mozilla
 
 #endif  // GFX_VR_PARENT_H
\ No newline at end of file
--- a/gfx/vr/ipc/VRProcessChild.cpp
+++ b/gfx/vr/ipc/VRProcessChild.cpp
@@ -8,39 +8,44 @@
 
 #include "mozilla/BackgroundHangMonitor.h"
 #include "mozilla/ipc/IOThreadChild.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 using mozilla::ipc::IOThreadChild;
 
+StaticRefPtr<VRParent> sVRParent;
+
 VRProcessChild::VRProcessChild(ProcessId aParentPid)
-    : ProcessChild(aParentPid)
-#if defined(aParentPid)
-      ,
-      mVR(nullptr)
-#endif
-{
+    : ProcessChild(aParentPid) {}
+
+VRProcessChild::~VRProcessChild() { sVRParent = nullptr; }
+
+/*static*/ VRParent* VRProcessChild::GetVRParent() {
+  MOZ_ASSERT(sVRParent);
+  return sVRParent;
 }
 
-VRProcessChild::~VRProcessChild() {}
-
 bool VRProcessChild::Init(int aArgc, char* aArgv[]) {
   BackgroundHangMonitor::Startup();
 
   char* parentBuildID = nullptr;
   for (int i = 1; i < aArgc; i++) {
     if (!aArgv[i]) {
       continue;
     }
     if (strcmp(aArgv[i], "-parentBuildID") == 0) {
       parentBuildID = aArgv[i + 1];
     }
   }
 
-  mVR.Init(ParentPid(), parentBuildID, IOThreadChild::message_loop(),
-           IOThreadChild::channel());
+  sVRParent = new VRParent();
+  sVRParent->Init(ParentPid(), parentBuildID, IOThreadChild::message_loop(),
+                  IOThreadChild::channel());
 
   return true;
 }
 
-void VRProcessChild::CleanUp() { NS_ShutdownXPCOM(nullptr); }
\ No newline at end of file
+void VRProcessChild::CleanUp() {
+  sVRParent = nullptr;
+  NS_ShutdownXPCOM(nullptr);
+}
\ No newline at end of file
--- a/gfx/vr/ipc/VRProcessChild.h
+++ b/gfx/vr/ipc/VRProcessChild.h
@@ -20,22 +20,23 @@ namespace gfx {
 class VRProcessChild final : public mozilla::ipc::ProcessChild {
  protected:
   typedef mozilla::ipc::ProcessChild ProcessChild;
 
  public:
   explicit VRProcessChild(ProcessId aParentPid);
   ~VRProcessChild();
 
+  // IPC channel for VR process talk to the parent process.
+  static VRParent* GetVRParent();
+
   // ProcessChild functions.
   virtual bool Init(int aArgc, char* aArgv[]) override;
   virtual void CleanUp() override;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(VRProcessChild);
-
-  VRParent mVR;
 };
 
 }  // namespace gfx
 }  // namespace mozilla
 
 #endif /* GFX_VR_PROCESS_CHILD_H */
\ No newline at end of file
--- a/gfx/vr/ipc/VRProcessManager.cpp
+++ b/gfx/vr/ipc/VRProcessManager.cpp
@@ -30,16 +30,21 @@ VRProcessManager::VRProcessManager() : m
 
   mObserver = new Observer(this);
   nsContentUtils::RegisterShutdownObserver(mObserver);
 }
 
 VRProcessManager::~VRProcessManager() {
   MOZ_COUNT_DTOR(VRProcessManager);
 
+  if (mObserver) {
+    nsContentUtils::UnregisterShutdownObserver(mObserver);
+    mObserver = nullptr;
+  }
+
   DestroyProcess();
   // The VR process should have already been shut down.
   MOZ_ASSERT(!mProcess);
 }
 
 void VRProcessManager::LaunchVRProcess() {
   if (mProcess) {
     return;
--- a/gfx/vr/ipc/VRProcessManager.h
+++ b/gfx/vr/ipc/VRProcessManager.h
@@ -21,32 +21,32 @@ class VRProcessManager final {
   static VRProcessManager* Get();
   static void Initialize();
   static void Shutdown();
 
   ~VRProcessManager();
 
   // If not using a VR process, launch a new VR process asynchronously.
   void LaunchVRProcess();
-  void DestroyProcess();
 
   bool CreateGPUBridges(base::ProcessId aOtherProcess,
                         mozilla::ipc::Endpoint<PVRGPUChild>* aOutVRBridge);
 
   VRChild* GetVRChild();
 
  private:
   VRProcessManager();
 
   DISALLOW_COPY_AND_ASSIGN(VRProcessManager);
 
   bool CreateGPUVRManager(base::ProcessId aOtherProcess,
                           mozilla::ipc::Endpoint<PVRGPUChild>* aOutEndpoint);
   void OnXPCOMShutdown();
   void CleanShutdown();
+  void DestroyProcess();
 
   // Permanently disable the VR process and record a message why.
   void DisableVRProcess(const char* aMessage);
 
   class Observer final : public nsIObserver {
    public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIOBSERVER
--- a/gfx/vr/ipc/VRProcessParent.cpp
+++ b/gfx/vr/ipc/VRProcessParent.cpp
@@ -86,18 +86,20 @@ void VRProcessParent::Shutdown() {
 }
 
 static void DelayedDeleteSubprocess(GeckoChildProcessHost* aSubprocess) {
   XRE_GetIOMessageLoop()->PostTask(
       mozilla::MakeAndAddRef<DeleteTask<GeckoChildProcessHost>>(aSubprocess));
 }
 
 void VRProcessParent::DestroyProcess() {
-  mLaunchThread->Dispatch(NewRunnableFunction("DestroyProcessRunnable",
-                                              DelayedDeleteSubprocess, this));
+  if (mLaunchThread) {
+    mLaunchThread->Dispatch(NewRunnableFunction("DestroyProcessRunnable",
+                                                DelayedDeleteSubprocess, this));
+  }
 }
 
 void VRProcessParent::InitAfterConnect(bool aSucceeded) {
   if (aSucceeded) {
     mVRChild = MakeUnique<VRChild>(this);
 
     DebugOnly<bool> rv =
         mVRChild->Open(GetChannel(), base::GetProcId(GetChildProcessHandle()));
--- a/gfx/vr/moz.build
+++ b/gfx/vr/moz.build
@@ -14,16 +14,17 @@ EXPORTS += [
     'ipc/VRLayerChild.h',
     'ipc/VRManagerChild.h',
     'ipc/VRManagerParent.h',
     'ipc/VRMessageUtils.h',
     'ipc/VRParent.h',
     'ipc/VRProcessChild.h',
     'ipc/VRProcessManager.h',
     'ipc/VRProcessParent.h',
+    'service/VRService.h',
     'VRDisplayClient.h',
     'VRDisplayHost.h',
     'VRDisplayPresentation.h',
     'VRManager.h',
     'VRThread.h',
 ]
 
 LOCAL_INCLUDES += [
--- a/gfx/vr/service/OpenVRSession.cpp
+++ b/gfx/vr/service/OpenVRSession.cpp
@@ -22,16 +22,18 @@
 #include "mozilla/dom/GamepadEventTypes.h"
 #include "mozilla/dom/GamepadBinding.h"
 #include "binding/OpenVRKnucklesBinding.h"
 #include "binding/OpenVRViveBinding.h"
 #if defined(XP_WIN)  // Windows Mixed Reality is only available in Windows.
 #include "binding/OpenVRWMRBinding.h"
 #endif
 
+#include "VRParent.h"
+#include "VRProcessChild.h"
 #include "VRThread.h"
 
 #if !defined(M_PI)
 #define M_PI 3.14159265358979323846264338327950288
 #endif
 
 #define BTN_MASK_FROM_ID(_id) ::vr::ButtonMaskFromId(vr::EVRButtonId::_id)
 
@@ -164,16 +166,23 @@ void UpdateButton(VRControllerState& aSt
     // not touched
     aState.buttonTouched &= ~mask;
   } else {
     // touched
     aState.buttonTouched |= mask;
   }
 }
 
+bool FileIsExisting(const nsCString& aPath) {
+  if (aPath.IsEmpty() || !std::ifstream(aPath.BeginReading())) {
+    return false;
+  }
+  return true;
+}
+
 };  // anonymous namespace
 
 OpenVRSession::OpenVRSession()
     : VRSession(),
       mVRSystem(nullptr),
       mVRChaperone(nullptr),
       mVRCompositor(nullptr),
       mControllerDeviceIndexObsolete{},
@@ -256,63 +265,141 @@ bool OpenVRSession::Initialize(mozilla::
 
   // Succeeded
   return true;
 }
 
 void OpenVRSession::SetupContollerActions() {
   // Check if this device binding file has been created.
   // If it didn't exist yet, create a new temp file.
-  if (!sViveBindingFile) {
-    sViveBindingFile = ControllerManifestFile::CreateManifest();
-    NS_DispatchToMainThread(
-        NS_NewRunnableFunction("ClearOnShutdown ControllerManifestFile",
-                               []() { ClearOnShutdown(&sViveBindingFile); }));
-  }
-  if (!sViveBindingFile->IsExisting()) {
-    sViveBindingFile->SetFileName(std::tmpnam(nullptr));
-    OpenVRViveBinding viveBinding;
-    std::ofstream viveBindingFile(sViveBindingFile->GetFileName());
-    if (viveBindingFile.is_open()) {
-      viveBindingFile << viveBinding.binding;
-      viveBindingFile.close();
+  nsCString controllerAction;
+  nsCString viveManifest;
+  nsCString WMRManifest;
+  nsCString knucklesManifest;
+
+  if (gfxPrefs::VRProcessEnabled()) {
+    VRParent* vrParent = VRProcessChild::GetVRParent();
+    nsCString output;
+
+    if (vrParent->GetOpenVRControllerActionPath(&output)) {
+      controllerAction = output;
+    } else {
+      controllerAction = std::tmpnam(nullptr);
+    }
+
+    if (vrParent->GetOpenVRControllerManifestPath(OpenVRControllerType::Vive,
+                                                  &output)) {
+      viveManifest = output;
+    } else {
+      viveManifest = std::tmpnam(nullptr);
+    }
+    if (!FileIsExisting(viveManifest)) {
+      OpenVRViveBinding viveBinding;
+      std::ofstream viveBindingFile(viveManifest.BeginReading());
+      if (viveBindingFile.is_open()) {
+        viveBindingFile << viveBinding.binding;
+        viveBindingFile.close();
+      }
+    }
+
+#if defined(XP_WIN)
+    if (vrParent->GetOpenVRControllerManifestPath(OpenVRControllerType::WMR,
+                                                  &output)) {
+      WMRManifest = output;
+    } else {
+      WMRManifest = std::tmpnam(nullptr);
+    }
+    if (!FileIsExisting(WMRManifest)) {
+      OpenVRWMRBinding WMRBinding;
+      std::ofstream WMRBindingFile(WMRManifest.BeginReading());
+      if (WMRBindingFile.is_open()) {
+        WMRBindingFile << WMRBinding.binding;
+        WMRBindingFile.close();
+      }
+    }
+#endif
+
+    if (vrParent->GetOpenVRControllerManifestPath(
+            OpenVRControllerType::Knuckles, &output)) {
+      knucklesManifest = output;
+    } else {
+      knucklesManifest = std::tmpnam(nullptr);
+    }
+    if (!FileIsExisting(knucklesManifest)) {
+      OpenVRKnucklesBinding knucklesBinding;
+      std::ofstream knucklesBindingFile(knucklesManifest.BeginReading());
+      if (knucklesBindingFile.is_open()) {
+        knucklesBindingFile << knucklesBinding.binding;
+        knucklesBindingFile.close();
+      }
     }
-  }
-  if (!sKnucklesBindingFile) {
-    sKnucklesBindingFile = ControllerManifestFile::CreateManifest();
-    NS_DispatchToMainThread(NS_NewRunnableFunction(
-        "ClearOnShutdown ControllerManifestFile",
-        []() { ClearOnShutdown(&sKnucklesBindingFile); }));
+  } else {
+    if (!sControllerActionFile) {
+      sControllerActionFile = ControllerManifestFile::CreateManifest();
+      NS_DispatchToMainThread(NS_NewRunnableFunction(
+          "ClearOnShutdown ControllerManifestFile",
+          []() { ClearOnShutdown(&sControllerActionFile); }));
+
+      sControllerActionFile->SetFileName(std::tmpnam(nullptr));
+    }
+    controllerAction = sControllerActionFile->GetFileName();
+
+    if (!sViveBindingFile) {
+      sViveBindingFile = ControllerManifestFile::CreateManifest();
+      NS_DispatchToMainThread(
+          NS_NewRunnableFunction("ClearOnShutdown ControllerManifestFile",
+                                 []() { ClearOnShutdown(&sViveBindingFile); }));
+    }
+    if (!sViveBindingFile->IsExisting()) {
+      sViveBindingFile->SetFileName(std::tmpnam(nullptr));
+      OpenVRViveBinding viveBinding;
+      std::ofstream viveBindingFile(sViveBindingFile->GetFileName());
+      if (viveBindingFile.is_open()) {
+        viveBindingFile << viveBinding.binding;
+        viveBindingFile.close();
+      }
+    }
+    viveManifest = sViveBindingFile->GetFileName();
+
+    if (!sKnucklesBindingFile) {
+      sKnucklesBindingFile = ControllerManifestFile::CreateManifest();
+      NS_DispatchToMainThread(NS_NewRunnableFunction(
+          "ClearOnShutdown ControllerManifestFile",
+          []() { ClearOnShutdown(&sKnucklesBindingFile); }));
+    }
+    if (!sKnucklesBindingFile->IsExisting()) {
+      sKnucklesBindingFile->SetFileName(std::tmpnam(nullptr));
+      OpenVRKnucklesBinding knucklesBinding;
+      std::ofstream knucklesBindingFile(sKnucklesBindingFile->GetFileName());
+      if (knucklesBindingFile.is_open()) {
+        knucklesBindingFile << knucklesBinding.binding;
+        knucklesBindingFile.close();
+      }
+    }
+    knucklesManifest = sKnucklesBindingFile->GetFileName();
+
+#if defined(XP_WIN)
+    if (!sWMRBindingFile) {
+      sWMRBindingFile = ControllerManifestFile::CreateManifest();
+      NS_DispatchToMainThread(
+          NS_NewRunnableFunction("ClearOnShutdown ControllerManifestFile",
+                                 []() { ClearOnShutdown(&sWMRBindingFile); }));
+    }
+    if (!sWMRBindingFile->IsExisting()) {
+      sWMRBindingFile->SetFileName(std::tmpnam(nullptr));
+      OpenVRWMRBinding WMRBinding;
+      std::ofstream WMRBindingFile(sWMRBindingFile->GetFileName());
+      if (WMRBindingFile.is_open()) {
+        WMRBindingFile << WMRBinding.binding;
+        WMRBindingFile.close();
+      }
+    }
+    WMRManifest = sWMRBindingFile->GetFileName();
+#endif
   }
-  if (!sKnucklesBindingFile->IsExisting()) {
-    sKnucklesBindingFile->SetFileName(std::tmpnam(nullptr));
-    OpenVRKnucklesBinding knucklesBinding;
-    std::ofstream knucklesBindingFile(sKnucklesBindingFile->GetFileName());
-    if (knucklesBindingFile.is_open()) {
-      knucklesBindingFile << knucklesBinding.binding;
-      knucklesBindingFile.close();
-    }
-  }
-#if defined(XP_WIN)
-  if (!sWMRBindingFile) {
-    sWMRBindingFile = ControllerManifestFile::CreateManifest();
-    NS_DispatchToMainThread(
-        NS_NewRunnableFunction("ClearOnShutdown ControllerManifestFile",
-                               []() { ClearOnShutdown(&sWMRBindingFile); }));
-  }
-  if (!sWMRBindingFile->IsExisting()) {
-    sWMRBindingFile->SetFileName(std::tmpnam(nullptr));
-    OpenVRWMRBinding WMRBinding;
-    std::ofstream WMRBindingFile(sWMRBindingFile->GetFileName());
-    if (WMRBindingFile.is_open()) {
-      WMRBindingFile << WMRBinding.binding;
-      WMRBindingFile.close();
-    }
-  }
-#endif
 
   ControllerInfo leftContollerInfo;
   leftContollerInfo.mActionPose =
       ControllerAction("/actions/firefox/in/LHand_pose", "pose");
   leftContollerInfo.mActionHaptic =
       ControllerAction("/actions/firefox/out/LHand_haptic", "vibration");
   leftContollerInfo.mActionTrackpad_Analog =
       ControllerAction("/actions/firefox/in/LHand_trackpad_analog", "vector2");
@@ -403,50 +490,40 @@ void OpenVRSession::SetupContollerAction
   rightContollerInfo.mActionFingerRing_Value = ControllerAction(
       "/actions/firefox/in/RHand_finger_ring_value", "vector1");
   rightContollerInfo.mActionFingerPinky_Value = ControllerAction(
       "/actions/firefox/in/RHand_finger_pinky_value", "vector1");
 
   mControllerHand[OpenVRHand::Left] = leftContollerInfo;
   mControllerHand[OpenVRHand::Right] = rightContollerInfo;
 
-  // Check if the action file has been created,
-  // if it doesn't exist, create a new temp file.
-  if (!sControllerActionFile) {
-    sControllerActionFile = ControllerManifestFile::CreateManifest();
-    NS_DispatchToMainThread(NS_NewRunnableFunction(
-        "ClearOnShutdown ControllerManifestFile",
-        []() { ClearOnShutdown(&sControllerActionFile); }));
-  }
-  if (sControllerActionFile->IsExisting()) {
+  if (FileIsExisting(controllerAction)) {
     return;
   }
 
-  sControllerActionFile->SetFileName(std::tmpnam(nullptr));
   nsAutoString actionData;
   JSONWriter actionWriter(MakeUnique<StringWriteFunc>(actionData));
   actionWriter.Start();
 
   actionWriter.StringProperty("version",
                               "0.1.0");  // TODO: adding a version check.
   // "default_bindings": []
   actionWriter.StartArrayProperty("default_bindings");
   actionWriter.StartObjectElement();
   actionWriter.StringProperty("controller_type", "vive_controller");
-  actionWriter.StringProperty("binding_url", sViveBindingFile->GetFileName());
+  actionWriter.StringProperty("binding_url", viveManifest.BeginReading());
   actionWriter.EndObject();
   actionWriter.StartObjectElement();
   actionWriter.StringProperty("controller_type", "knuckles");
-  actionWriter.StringProperty("binding_url",
-                              sKnucklesBindingFile->GetFileName());
+  actionWriter.StringProperty("binding_url", knucklesManifest.BeginReading());
   actionWriter.EndObject();
 #if defined(XP_WIN)
   actionWriter.StartObjectElement();
   actionWriter.StringProperty("controller_type", "holographic_controller");
-  actionWriter.StringProperty("binding_url", sWMRBindingFile->GetFileName());
+  actionWriter.StringProperty("binding_url", WMRManifest.BeginReading());
   actionWriter.EndObject();
 #endif
   actionWriter.EndArray();  // End "default_bindings": []
 
   // "actions": [] Action paths must take the form: "/actions/<action
   // set>/in|out/<action>"
   actionWriter.StartArrayProperty("actions");
 
@@ -610,24 +687,41 @@ void OpenVRSession::SetupContollerAction
                                 controller.mActionHaptic.name.BeginReading());
     actionWriter.StringProperty("type",
                                 controller.mActionHaptic.type.BeginReading());
     actionWriter.EndObject();
   }
   actionWriter.EndArray();  // End "actions": []
   actionWriter.End();
 
-  std::ofstream actionfile(sControllerActionFile->GetFileName());
+  std::ofstream actionfile(controllerAction.BeginReading());
   nsCString actionResult(NS_ConvertUTF16toUTF8(actionData.get()));
   if (actionfile.is_open()) {
     actionfile << actionResult.get();
     actionfile.close();
   }
 
-  vr::VRInput()->SetActionManifestPath(sControllerActionFile->GetFileName());
+  vr::VRInput()->SetActionManifestPath(controllerAction.BeginReading());
+
+  // Notify the parent process these manifest files are already been recorded.
+  if (gfxPrefs::VRProcessEnabled()) {
+    NS_DispatchToMainThread(NS_NewRunnableFunction(
+        "SendOpenVRControllerActionPathToParent",
+        [controllerAction, viveManifest, WMRManifest, knucklesManifest]() {
+          VRParent* vrParent = VRProcessChild::GetVRParent();
+          Unused << vrParent->SendOpenVRControllerActionPathToParent(
+              controllerAction);
+          Unused << vrParent->SendOpenVRControllerManifestPathToParent(
+              OpenVRControllerType::Vive, viveManifest);
+          Unused << vrParent->SendOpenVRControllerManifestPathToParent(
+              OpenVRControllerType::WMR, WMRManifest);
+          Unused << vrParent->SendOpenVRControllerManifestPathToParent(
+              OpenVRControllerType::Knuckles, knucklesManifest);
+        }));
+  }
 }
 
 #if defined(XP_WIN)
 bool OpenVRSession::CreateD3DObjects() {
   RefPtr<ID3D11Device> device = gfx::DeviceManagerDx::Get()->GetVRDevice();
   if (!device) {
     return false;
   }
@@ -1814,17 +1908,20 @@ void OpenVRSession::ProcessEvents(mozill
         break;
       case ::vr::EVREventType::VREvent_TrackedDeviceDeactivated:
         if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) {
           aSystemState.displayState.mIsConnected = false;
         }
         break;
       case ::vr::EVREventType::VREvent_DriverRequestedQuit:
       case ::vr::EVREventType::VREvent_Quit:
-      case ::vr::EVREventType::VREvent_ProcessQuit:
+      // When SteamVR runtime haven't been launched before viewing VR,
+      // SteamVR will send a VREvent_ProcessQuit event. It will tell the parent
+      // process to shutdown the VR process, and we need to avoid it.
+      // case ::vr::EVREventType::VREvent_ProcessQuit:
       case ::vr::EVREventType::VREvent_QuitAcknowledged:
       case ::vr::EVREventType::VREvent_QuitAborted_UserPrompt:
         mShouldQuit = true;
         break;
       default:
         // ignore
         break;
     }
new file mode 100644
--- /dev/null
+++ b/gfx/vr/service/VRServiceManager.cpp
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "VRServiceManager.h"
+#include "VRGPUChild.h"
+#include "mozilla/gfx/GPUParent.h"
+
+namespace mozilla {
+namespace gfx {
+
+VRServiceManager::VRServiceManager()
+#if !defined(MOZ_WIDGET_ANDROID)
+    : mVRService(nullptr)
+#endif
+{
+}
+
+VRServiceManager& VRServiceManager::Get() {
+  static VRServiceManager instance;
+  return instance;
+}
+
+void VRServiceManager::CreateVRProcess() {
+  // Using PGPU channel to tell the main process
+  // to create VR process.
+  RefPtr<Runnable> task =
+      NS_NewRunnableFunction("GPUParent::SendCreateVRProcess", []() -> void {
+        gfx::GPUParent* gpu = GPUParent::GetSingleton();
+        MOZ_ASSERT(gpu);
+        Unused << gpu->SendCreateVRProcess();
+      });
+
+  NS_DispatchToMainThread(task.forget());
+}
+
+void VRServiceManager::ShutdownVRProcess() {
+  if (VRGPUChild::IsCreated()) {
+    VRGPUChild* vrGPUChild = VRGPUChild::Get();
+    vrGPUChild->SendStopVRService();
+    vrGPUChild->Close();
+    VRGPUChild::Shutdown();
+  }
+  if (gfxPrefs::VRProcessEnabled()) {
+    // Using PGPU channel to tell the main process
+    // to shutdown VR process.
+    gfx::GPUParent* gpu = GPUParent::GetSingleton();
+    MOZ_ASSERT(gpu);
+    Unused << gpu->SendShutdownVRProcess();
+  }
+}
+
+void VRServiceManager::CreateService() {
+  if (!gfxPrefs::VRProcessEnabled()) {
+    mVRService = VRService::Create();
+  }
+}
+
+void VRServiceManager::Start() {
+  if (mVRService) {
+    mVRService->Start();
+  }
+}
+
+void VRServiceManager::Stop() {
+  if (mVRService) {
+    mVRService->Stop();
+  }
+}
+
+void VRServiceManager::Shutdown() {
+  Stop();
+  mVRService = nullptr;
+}
+
+void VRServiceManager::Refresh() {
+  if (mVRService) {
+    mVRService->Refresh();
+  }
+}
+
+bool VRServiceManager::IsServiceValid() { return (mVRService != nullptr); }
+
+VRExternalShmem* VRServiceManager::GetAPIShmem() {
+#if !defined(MOZ_WIDGET_ANDROID)
+  return mVRService->GetAPIShmem();
+#endif
+  return nullptr;
+}
+
+}  // namespace gfx
+}  // namespace mozilla
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/gfx/vr/service/VRServiceManager.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_VR_SERVICE_MANAGER_H
+#define GFX_VR_SERVICE_MANAGER_H
+
+namespace mozilla {
+namespace gfx {
+
+class VRServiceManager {
+ public:
+  static VRServiceManager& Get();
+
+  void Refresh();
+  void Start();
+  void Stop();
+  void Shutdown();
+  void CreateVRProcess();
+  void ShutdownVRProcess();
+  void CreateService();
+  bool IsServiceValid();
+
+  VRExternalShmem* GetAPIShmem();
+
+ protected:
+ private:
+  VRServiceManager();
+  ~VRServiceManager() = default;
+
+#if !defined(MOZ_WIDGET_ANDROID)
+  RefPtr<VRService> mVRService;
+#endif
+};
+
+}  // namespace gfx
+}  // namespace mozilla
+
+#endif  // GFX_VR_SERVICE_MANAGER_H
\ No newline at end of file
--- a/gfx/vr/service/moz.build
+++ b/gfx/vr/service/moz.build
@@ -10,16 +10,17 @@ if CONFIG['OS_TARGET'] == 'WINNT':
         'OculusSession.cpp',
     ]
 
 # Build OSVR on all platforms except Android
 if CONFIG['OS_TARGET'] != 'Android':
     UNIFIED_SOURCES += [
         'OSVRSession.cpp',
         'VRService.cpp',
+        'VRServiceManager.cpp',
         'VRSession.cpp',
     ]
     include('/ipc/chromium/chromium-config.mozbuild')
 
 # Build OpenVR on Windows, Linux, and macOS desktop targets
 if CONFIG['OS_TARGET'] in ('WINNT', 'Linux', 'Darwin'):
     DIRS += [
         'openvr',
--- a/gfx/webrender_bindings/Cargo.toml
+++ b/gfx/webrender_bindings/Cargo.toml
@@ -2,17 +2,17 @@
 name = "webrender_bindings"
 version = "0.1.0"
 authors = ["The Mozilla Project Developers"]
 license = "MPL-2.0"
 
 [dependencies]
 rayon = "1"
 thread_profiler = "0.1.1"
-euclid = { version = "0.19.3", features = ["serde"] }
+euclid = { version = "0.19.4", features = ["serde"] }
 app_units = "0.7"
 gleam = "0.6.8"
 log = "0.4"
 nsstring = { path = "../../servo/support/gecko/nsstring" }
 bincode = "1.0"
 uuid = { version = "0.6", features = ["v4"] }
 fxhash = "0.2.1"
 
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-477d395e08a9f6b891fa748defd2fa2c35d0e5be
+4ff07fe75b0c28ee1987092b3eefed3017f5a25e
--- a/gfx/wr/webrender/src/clip.rs
+++ b/gfx/wr/webrender/src/clip.rs
@@ -18,17 +18,17 @@ use image::{self, Repetition};
 use intern;
 use internal_types::FastHashSet;
 use prim_store::{ClipData, ImageMaskData, SpaceMapper, VisibleMaskImageTile};
 use prim_store::{PointKey, PrimitiveInstance, SizeKey, RectangleKey};
 use render_task::to_cache_size;
 use resource_cache::{ImageRequest, ResourceCache};
 use std::{cmp, u32};
 use std::os::raw::c_void;
-use util::{extract_inner_rect_safe, project_rect, ScaleOffset, MaxRect};
+use util::{extract_inner_rect_safe, project_rect, ScaleOffset};
 
 /*
 
  Module Overview
 
  There are a number of data structures involved in the clip module:
 
  ClipStore - Main interface used by other modules.
@@ -1300,56 +1300,16 @@ impl ClipNodeCollector {
     }
 
     pub fn insert(
         &mut self,
         clip_chain_id: ClipChainId,
     ) {
         self.clips.insert(clip_chain_id);
     }
-
-    pub fn clear(
-        &mut self,
-    ) {
-        self.clips.clear();
-    }
-
-    /// Build the world clip rect for this clip node collector.
-    // NOTE: This ignores any complex clips that may be present.
-    pub fn get_world_clip_rect(
-        &self,
-        clip_store: &ClipStore,
-        clip_data_store: &ClipDataStore,
-        clip_scroll_tree: &ClipScrollTree,
-    ) -> Option<WorldRect> {
-        let mut clip_rect = WorldRect::max_rect();
-
-        let mut map_local_to_world = SpaceMapper::new(
-            ROOT_SPATIAL_NODE_INDEX,
-            WorldRect::zero(),
-        );
-
-        for clip_chain_id in &self.clips {
-            let clip_chain_node = clip_store.get_clip_chain(*clip_chain_id);
-            let clip_node = &clip_data_store[clip_chain_node.handle];
-
-            if let Some(local_rect) = clip_node.item.get_local_clip_rect(clip_chain_node.local_pos) {
-                map_local_to_world.set_target_spatial_node(
-                    clip_chain_node.spatial_node_index,
-                    clip_scroll_tree,
-                );
-
-                if let Some(world_rect) = map_local_to_world.map(&local_rect) {
-                    clip_rect = clip_rect.intersection(&world_rect)?;
-                }
-            }
-        }
-
-        Some(clip_rect)
-    }
 }
 
 // Add a clip node into the list of clips to be processed
 // for the current clip chain. Returns false if the clip
 // results in the entire primitive being culled out.
 fn add_clip_node_to_current_chain(
     node: &ClipChainNode,
     spatial_node_index: SpatialNodeIndex,
--- a/gfx/wr/webrender/src/picture.rs
+++ b/gfx/wr/webrender/src/picture.rs
@@ -1,19 +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/. */
 
 use api::{DeviceRect, FilterOp, MixBlendMode, PipelineId, PremultipliedColorF, PictureRect, PicturePoint, WorldPoint};
 use api::{DeviceIntRect, DevicePoint, LayoutRect, PictureToRasterTransform, LayoutPixel, PropertyBinding, PropertyBindingId};
-use api::{DevicePixelScale, RasterRect, RasterSpace, ColorF, ImageKey, DirtyRect, WorldSize, LayoutSize};
+use api::{DevicePixelScale, RasterRect, RasterSpace, ColorF, ImageKey, DirtyRect, WorldSize, LayoutSize, ClipMode};
 use api::{PicturePixel, RasterPixel, WorldPixel, WorldRect, ImageFormat, ImageDescriptor, WorldVector2D};
 use box_shadow::{BLUR_SAMPLE_SCALE};
-use clip::{ClipNodeCollector, ClipStore, ClipChainId, ClipChainNode};
-use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex};
+use clip::{ClipNodeCollector, ClipStore, ClipChainId, ClipChainNode, ClipItem};
+use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex, CoordinateSystemId};
 use device::TextureFilter;
 use euclid::{TypedScale, vec3, TypedRect, TypedPoint2D, TypedSize2D};
 use euclid::approxeq::ApproxEq;
 use intern::ItemUid;
 use internal_types::{FastHashMap, PlaneSplitter};
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState, PictureContext};
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
 use gpu_types::{TransformPalette, TransformPaletteId, UvRectKind};
@@ -103,39 +103,39 @@ pub struct OpacityBindingInfo {
 /// Information about a cached tile.
 #[derive(Debug)]
 pub struct Tile {
     /// The current world rect of thie tile.
     world_rect: WorldRect,
     /// The current local rect of this tile.
     pub local_rect: LayoutRect,
     /// The valid rect within this tile.
-    pixel_rect: Option<DeviceIntRect>,
+    valid_rect: WorldRect,
     /// Uniquely describes the content of this tile, in a way that can be
     /// (reasonably) efficiently hashed and compared.
     descriptor: TileDescriptor,
     /// Handle to the cached texture for this tile.
     pub handle: TextureCacheHandle,
     /// If true, this tile is marked valid, and the existing texture
     /// cache handle can be used. Tiles are invalidated during the
     /// build_dirty_regions method.
     is_valid: bool,
 }
 
 impl Tile {
     /// Construct a new, invalid tile.
     fn new(
     ) -> Self {
         Tile {
-            handle: TextureCacheHandle::invalid(),
             local_rect: LayoutRect::zero(),
             world_rect: WorldRect::zero(),
+            valid_rect: WorldRect::zero(),
+            handle: TextureCacheHandle::invalid(),
             descriptor: TileDescriptor::new(),
             is_valid: false,
-            pixel_rect: None,
         }
     }
 
     /// Clear the dependencies for a tile.
     fn clear(&mut self) {
         self.descriptor.clear();
     }
 }
@@ -172,46 +172,76 @@ pub struct TileDescriptor {
     clip_vertices: ComparableVec<VectorKey>,
 
     /// List of image keys that this tile depends on.
     image_keys: ComparableVec<ImageKey>,
 
     /// The set of opacity bindings that this tile depends on.
     // TODO(gw): Ugh, get rid of all opacity binding support!
     opacity_bindings: ComparableVec<PropertyBindingId>,
+
+    /// List of the required valid rectangles for each primitive.
+    needed_rects: Vec<WorldRect>,
+
+    /// List of the currently valid rectangles for each primitive.
+    current_rects: Vec<WorldRect>,
 }
 
 impl TileDescriptor {
     fn new() -> Self {
         TileDescriptor {
             prims: ComparableVec::new(),
             clip_uids: ComparableVec::new(),
             clip_vertices: ComparableVec::new(),
             opacity_bindings: ComparableVec::new(),
             image_keys: ComparableVec::new(),
+            needed_rects: Vec::new(),
+            current_rects: Vec::new(),
         }
     }
 
     /// Clear the dependency information for a tile, when the dependencies
     /// are being rebuilt.
     fn clear(&mut self) {
         self.prims.reset();
         self.clip_uids.reset();
         self.clip_vertices.reset();
         self.opacity_bindings.reset();
         self.image_keys.reset();
+        self.needed_rects.clear();
     }
 
     /// Check if the dependencies of this tile are valid.
     fn is_valid(&self) -> bool {
+        // For a tile to be valid, it needs to ensure that the currently valid
+        // rect of each primitive encloses the required valid rect.
+        // TODO(gw): This is only needed for tiles that are partially rendered
+        //           (i.e. those clipped to edge of screen). We can make this much
+        //           faster by skipping this step for tiles that are not clipped!
+        // TODO(gw): For partial tiles that *do* need this test, we can probably
+        //           make it faster again by caching and checking the relative
+        //           transforms of primitives on this tile.
+        let rects_valid = if self.needed_rects.len() == self.current_rects.len() {
+            for (needed, current) in self.needed_rects.iter().zip(self.current_rects.iter()) {
+                if !current.contains_rect(needed) {
+                    return false;
+                }
+            }
+
+            true
+        } else {
+            false
+        };
+
         self.image_keys.is_valid() &&
         self.opacity_bindings.is_valid() &&
         self.clip_uids.is_valid() &&
         self.clip_vertices.is_valid() &&
-        self.prims.is_valid()
+        self.prims.is_valid() &&
+        rects_valid
     }
 }
 
 /// Represents the dirty region of a tile cache picture.
 /// In future, we will want to support multiple dirty
 /// regions.
 #[derive(Debug)]
 pub struct DirtyRegion {
@@ -249,18 +279,22 @@ pub struct TileCache {
     world_tile_size: WorldSize,
     /// Current number of tiles in the allocated grid.
     tile_count: TileSize,
     /// The current scroll offset for this frame builder. Reset when
     /// a new scene arrives.
     scroll_offset: Option<WorldVector2D>,
     /// A list of blits from the framebuffer to be applied during this frame.
     pub pending_blits: Vec<TileBlit>,
-    /// Collects the clips that apply to this surface.
-    clip_node_collector: ClipNodeCollector,
+    /// The current world bounding rect of this tile cache. This is used
+    /// to derive a local clip rect, such that we don't obscure in the
+    /// z-buffer any items placed earlier in the render order (such as
+    /// scroll bars in gecko, when the content overflows under the
+    /// scroll bar).
+    world_bounding_rect: WorldRect,
 }
 
 impl TileCache {
     pub fn new(spatial_node_index: SpatialNodeIndex) -> Self {
         TileCache {
             spatial_node_index,
             tiles: Vec::new(),
             map_local_to_world: SpaceMapper::new(
@@ -272,17 +306,17 @@ impl TileCache {
             opacity_bindings: FastHashMap::default(),
             dirty_region: None,
             needs_update: true,
             world_origin: WorldPoint::zero(),
             world_tile_size: WorldSize::zero(),
             tile_count: TileSize::zero(),
             scroll_offset: None,
             pending_blits: Vec::new(),
-            clip_node_collector: ClipNodeCollector::new(spatial_node_index),
+            world_bounding_rect: WorldRect::zero(),
         }
     }
 
     /// Get the tile coordinates for a given rectangle.
     fn get_tile_coords_for_rect(
         &self,
         rect: &WorldRect,
     ) -> (TileOffset, TileOffset) {
@@ -509,17 +543,17 @@ impl TileCache {
         if !old_tiles.is_empty() {
             // TODO(gw): Should we explicitly drop the tile texture cache handles here?
         }
 
         // TODO(gw): We don't actually need to update the prim dependencies each frame.
         //           For common cases, such as only being one main scroll root, we could
         //           detect this and skip the dependency update on scroll frames.
         self.needs_update = true;
-        self.clip_node_collector.clear();
+        self.world_bounding_rect = WorldRect::zero();
 
         // Do tile invalidation for any dependencies that we know now.
         for tile in &mut self.tiles {
             // Invalidate the tile if any images have changed
             for image_key in tile.descriptor.image_keys.items() {
                 if resource_cache.is_image_dirty(*image_key) {
                     tile.is_valid = false;
                     break;
@@ -553,16 +587,17 @@ impl TileCache {
         prim_list: &PrimitiveList,
         clip_scroll_tree: &ClipScrollTree,
         resources: &FrameResources,
         clip_chain_nodes: &[ClipChainNode],
         pictures: &[PicturePrimitive],
         resource_cache: &ResourceCache,
         opacity_binding_store: &OpacityBindingStorage,
         image_instances: &ImageInstanceStorage,
+        screen_world_rect: &WorldRect,
     ) {
         if !self.needs_update {
             return;
         }
 
         // We need to ensure that if a primitive belongs to a cluster that has
         // been marked invisible, we exclude it here. Otherwise, we may end up
         // with a primitive that is outside the bounding rect of the calculated
@@ -682,61 +717,106 @@ impl TileCache {
             PrimitiveInstanceKind::RadialGradient { .. } |
             PrimitiveInstanceKind::ImageBorder { .. } => {
                 // These don't contribute dependencies
             }
         }
 
         // The transforms of any clips that are relative to the picture may affect
         // the content rendered by this primitive.
+        let mut world_clip_rect = world_rect;
         while current_clip_chain_id != ClipChainId::NONE {
             let clip_chain_node = &clip_chain_nodes[current_clip_chain_id.0 as usize];
-            // We only care about clip nodes that have transforms that are children
-            // of the surface, since clips that are positioned by parents will be
-            // handled by the clip collector when these tiles are composited.
-            if clip_chain_node.spatial_node_index < self.spatial_node_index {
-                self.clip_node_collector.insert(current_clip_chain_id)
-            } else {
+            let clip_node = &resources.clip_data_store[clip_chain_node.handle];
+
+            self.map_local_to_world.set_target_spatial_node(
+                clip_chain_node.spatial_node_index,
+                clip_scroll_tree,
+            );
+
+            // Clips that are simple rects and handled by collapsing them into a single
+            // clip rect. This avoids the need to store vertices for these cases, and also
+            // allows easy calculation of the overall bounds of the tile cache.
+            let add_to_clip_deps = match clip_node.item {
+                ClipItem::Rectangle(size, ClipMode::Clip) => {
+                    let clip_spatial_node = &clip_scroll_tree.spatial_nodes[clip_chain_node.spatial_node_index.0 as usize];
+
+                    // Clips that are not in the root coordinate system are not axis-aligned,
+                    // so we need to treat them as normal style clips with vertices.
+                    if clip_spatial_node.coordinate_system_id == CoordinateSystemId(0) {
+                        let local_rect = LayoutRect::new(
+                            clip_chain_node.local_pos,
+                            size,
+                        );
+
+                        if let Some(clip_world_rect) = self.map_local_to_world.map(&local_rect) {
+                            world_clip_rect = match world_clip_rect.intersection(&clip_world_rect) {
+                                Some(rect) => rect,
+                                None => return,
+                            };
+                        }
+
+                        false
+                    } else {
+                        true
+                    }
+                }
+                ClipItem::Rectangle(_, ClipMode::ClipOut) |
+                ClipItem::RoundedRectangle(..) |
+                ClipItem::Image { .. } |
+                ClipItem::BoxShadow(..) => {
+                    true
+                }
+            };
+
+            if add_to_clip_deps {
                 // TODO(gw): Constructing a rect here rather than mapping a point
                 //           is wasteful. We can optimize this by extending the
                 //           SpaceMapper struct to support mapping a point.
                 let local_rect = LayoutRect::new(
                     clip_chain_node.local_pos,
                     LayoutSize::zero(),
                 );
 
-                self.map_local_to_world.set_target_spatial_node(
-                    clip_chain_node.spatial_node_index,
-                    clip_scroll_tree,
-                );
+                if let Some(clip_world_rect) = self.map_local_to_world.map(&local_rect) {
+                    clip_vertices.push(clip_world_rect.origin);
+                }
 
-                let clip_world_rect = self
-                    .map_local_to_world
-                    .map(&local_rect)
-                    .expect("bug: unable to map clip rect to world");
-
-                clip_vertices.push(clip_world_rect.origin);
                 clip_chain_uids.push(clip_chain_node.handle.uid());
             }
+
             current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
         }
 
+        self.world_bounding_rect = self.world_bounding_rect.union(&world_clip_rect);
+
         // Normalize the tile coordinates before adding to tile dependencies.
         // For each affected tile, mark any of the primitive dependencies.
         for y in p0.y .. p1.y {
             for x in p0.x .. p1.x {
                 // If the primitive exists on tiles outside the selected tile cache
                 // area, just ignore those.
                 if x < 0 || x >= self.tile_count.width || y < 0 || y >= self.tile_count.height {
                     continue;
                 }
 
                 let index = (y * self.tile_count.width + x) as usize;
                 let tile = &mut self.tiles[index];
 
+                // Work out the needed rect for the primitive on this tile.
+                // TODO(gw): We should be able to remove this for any tile that is not
+                //           a partially clipped tile, which would be a significant
+                //           optimization for the common case (non-clipped tiles).
+                let needed_rect = match world_clip_rect.intersection(&tile.world_rect).and_then(|r| r.intersection(screen_world_rect)) {
+                    Some(rect) => rect.translate(&-tile.world_rect.origin.to_vector()),
+                    None => continue,
+                };
+
+                tile.descriptor.needed_rects.push(needed_rect);
+
                 // Mark if the tile is cacheable at all.
                 tile.is_valid &= is_cacheable;
 
                 // Include any image keys this tile depends on.
                 tile.descriptor.image_keys.extend_from_slice(&image_keys);
 
                 // // Include any opacity bindings this primitive depends on.
                 tile.descriptor.opacity_bindings.extend_from_slice(&opacity_bindings);
@@ -779,120 +859,80 @@ impl TileCache {
 
     /// Apply any updates after prim dependency updates. This applies
     /// any late tile invalidations, and sets up the dirty rect and
     /// set of tile blits.
     pub fn post_update(
         &mut self,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
-        clip_store: &ClipStore,
         frame_context: &FrameBuildingContext,
-        resources: &FrameResources,
     ) -> LayoutRect {
         let mut dirty_world_rect = WorldRect::zero();
 
         self.dirty_region = None;
         self.pending_blits.clear();
 
         let descriptor = ImageDescriptor::new(
             TILE_SIZE_WIDTH,
             TILE_SIZE_HEIGHT,
             ImageFormat::BGRA8,
             true,
             false,
         );
 
-        // Get the world clip rect that we will use to determine
-        // which parts of the tiles are valid / required.
-        let clip_rect = match self
-            .clip_node_collector
-            .get_world_clip_rect(
-                clip_store,
-                &resources.clip_data_store,
-                frame_context.clip_scroll_tree,
-            ) {
-            Some(clip_rect) => clip_rect,
-            None => return LayoutRect::zero(),
-        };
+        // Skip all tiles if completely off-screen.
+        if !self.world_bounding_rect.intersects(&frame_context.screen_world_rect) {
+            return LayoutRect::zero();
+        }
 
         let map_surface_to_world: SpaceMapper<LayoutPixel, WorldPixel> = SpaceMapper::new_with_target(
             ROOT_SPATIAL_NODE_INDEX,
             self.spatial_node_index,
             frame_context.screen_world_rect,
             frame_context.clip_scroll_tree,
         );
 
         let local_clip_rect = map_surface_to_world
-            .unmap(&clip_rect)
+            .unmap(&self.world_bounding_rect)
             .expect("bug: unable to map local clip rect");
 
-        let clip_rect = match clip_rect.intersection(&frame_context.screen_world_rect) {
-            Some(clip_rect) => clip_rect,
-            None => return LayoutRect::zero(),
-        };
-
-        let clipped = (clip_rect * frame_context.device_pixel_scale).round().to_i32();
-
         // Step through each tile and invalidate if the dependencies have changed.
         for (i, tile) in self.tiles.iter_mut().enumerate() {
+            let visible_rect = match tile
+                .world_rect
+                .intersection(&frame_context.screen_world_rect)
+            {
+                Some(rect) => rect,
+                None => continue,
+            };
+
             // Check the content of the tile is the same
             tile.is_valid &= tile.descriptor.is_valid();
 
             // Invalidate if the backing texture was evicted.
             if !resource_cache.texture_cache.is_allocated(&tile.handle) {
                 tile.is_valid = false;
             }
 
-            // Work out which part of the tile rect is valid.
-            let tile_device_rect = (tile.world_rect * frame_context.device_pixel_scale).round().to_i32();
-
-            // Get the part of the tile rect that is actually going to be rendered on screen.
-            let src_rect = clipped.intersection(&tile_device_rect);
-
-            // If that was completely off-screen, nothing to do.
-            let src_rect = match src_rect {
-                Some(rect) => rect,
-                None => {
-                    continue;
-                }
-            };
-
-            // Get this space relative to the tile itself.
-            let unit_rect = src_rect
-                .translate(&-tile_device_rect.origin.to_vector());
-
-            // Work out if the currently valid rect of this tile is large
-            // enough for what we need to draw.
-            let valid_pixel_rect = match tile.pixel_rect {
-                Some(pixel_rect) => pixel_rect.contains_rect(&unit_rect),
-                None => false,
-            };
-            if !valid_pixel_rect {
-                tile.is_valid = false;
-            }
-
             // Request the backing texture so it won't get evicted this frame.
             resource_cache.texture_cache.request(&tile.handle, gpu_cache);
 
             // Decide how to handle this tile when drawing this frame.
             if tile.is_valid {
                 // If the tile is valid, we will generally want to draw it
                 // on screen. However, if there are no primitives there is
                 // no need to draw it.
                 if !tile.descriptor.prims.is_empty() {
                     self.tiles_to_draw.push(TileIndex(i));
                 }
             } else {
                 // Add the tile rect to the dirty rect.
                 dirty_world_rect = dirty_world_rect.union(&tile.world_rect);
 
-                // Store the currently valid rect for this tile.
-                tile.pixel_rect = Some(unit_rect);
-
                 // Ensure that this texture is allocated.
                 resource_cache.texture_cache.update(
                     &mut tile.handle,
                     descriptor,
                     TextureFilter::Linear,
                     None,
                     [0.0; 3],
                     DirtyRect::All,
@@ -900,28 +940,35 @@ impl TileCache {
                     None,
                     UvRectKind::Rect,
                     Eviction::Eager,
                 );
 
                 let cache_item = resource_cache
                     .get_texture_cache_item(&tile.handle);
 
+                let src_origin = (visible_rect.origin * frame_context.device_pixel_scale).round().to_i32();
+                tile.valid_rect = visible_rect.translate(&-tile.world_rect.origin.to_vector());
+
                 // Store a blit operation to be done after drawing the
                 // frame in order to update the cached texture tile.
-                let dest_offset = unit_rect.origin;
+                let dest_rect = (tile.valid_rect * frame_context.device_pixel_scale).round().to_i32();
                 self.pending_blits.push(TileBlit {
                     target: cache_item,
-                    src_offset: src_rect.origin,
-                    dest_offset,
-                    size: unit_rect.size,
+                    src_offset: src_origin,
+                    dest_offset: dest_rect.origin,
+                    size: dest_rect.size,
                 });
 
                 // We can consider this tile valid now.
                 tile.is_valid = true;
+                tile.descriptor.current_rects = mem::replace(
+                    &mut tile.descriptor.needed_rects,
+                    Vec::new(),
+                );
             }
         }
 
         // Store the dirty region for drawing the main scene.
         self.dirty_region = if dirty_world_rect.is_empty() {
             None
         } else {
             let dirty_device_rect = (dirty_world_rect * frame_context.device_pixel_scale).round().to_i32();
@@ -1933,16 +1980,17 @@ impl PicturePrimitive {
                 &self.prim_list,
                 &frame_context.clip_scroll_tree,
                 resources,
                 &clip_store.clip_chain_nodes,
                 pictures,
                 resource_cache,
                 opacity_binding_store,
                 image_instances,
+                &frame_context.screen_world_rect,
             );
         }
     }
 
     /// Called after updating child pictures during the initial
     /// picture traversal.
     pub fn post_update(
         &mut self,
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -1729,19 +1729,17 @@ impl PrimitiveStore {
         let pic = &mut self.pictures[pic_index.0];
         if let Some(RasterConfig { composite_mode: PictureCompositeMode::TileCache { .. }, .. }) = pic.raster_config {
             let mut tile_cache = state.tile_cache.take().unwrap();
 
             // Build the dirty region(s) for this tile cache.
             pic.local_clip_rect = tile_cache.post_update(
                 resource_cache,
                 gpu_cache,
-                clip_store,
                 frame_context,
-                resources,
             );
 
             pic.tile_cache = Some(tile_cache);
         }
 
         pic.prim_list.pictures = children;
     }
 
--- a/gfx/wr/webrender_api/Cargo.toml
+++ b/gfx/wr/webrender_api/Cargo.toml
@@ -13,17 +13,17 @@ serialize = []
 deserialize = []
 
 [dependencies]
 app_units = "0.7"
 bincode = "1.0"
 bitflags = "1.0"
 byteorder = "1.2.1"
 ipc-channel = {version = "0.11.0", optional = true}
-euclid = { version = "0.19.3", features = ["serde"] }
+euclid = { version = "0.19.4", features = ["serde"] }
 serde = { version = "=1.0.80", features = ["rc"] }
 serde_derive = { version = "=1.0.80", features = ["deserialize_in_place"] }
 serde_bytes = "0.10"
 time = "0.1"
 
 [target.'cfg(target_os = "macos")'.dependencies]
 core-foundation = "0.6"
 core-graphics = "0.17.1"
--- a/js/src/vm/JSContext.cpp
+++ b/js/src/vm/JSContext.cpp
@@ -172,17 +172,16 @@ JSContext* js::NewContext(uint32_t maxBy
   return cx;
 }
 
 static void FreeJobQueueHandling(JSContext* cx) {
   if (!cx->jobQueue) {
     return;
   }
 
-  cx->jobQueue->reset();
   FreeOp* fop = cx->defaultFreeOp();
   fop->delete_(cx->jobQueue.ref());
   cx->getIncumbentGlobalCallback = nullptr;
   cx->enqueuePromiseJobCallback = nullptr;
   cx->enqueuePromiseJobCallbackData = nullptr;
 }
 
 void js::DestroyContext(JSContext* cx) {
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -1045,17 +1045,17 @@ struct DefaultHasher<js::AbstractFramePt
 };
 
 }  // namespace mozilla
 
 namespace js {
 
 /*****************************************************************************/
 
-// SavedFrame caching to minimize stack walking.
+// [SMDOC] LiveSavedFrameCache: SavedFrame caching to minimize stack walking
 //
 // Since each SavedFrame object includes a 'parent' pointer to the SavedFrame
 // for its caller, if we could easily find the right SavedFrame for a given
 // stack frame, we wouldn't need to walk the rest of the stack. Traversing deep
 // stacks can be expensive, and when we're profiling or instrumenting code, we
 // may want to capture JavaScript stacks frequently, so such cases would benefit
 // if we could avoid walking the entire stack.
 //
--- a/js/xpconnect/loader/mozJSLoaderUtils.cpp
+++ b/js/xpconnect/loader/mozJSLoaderUtils.cpp
@@ -4,17 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/scache/StartupCache.h"
 
 #include "jsapi.h"
 #include "jsfriendapi.h"
 
-#include "nsJSPrincipals.h"
+#include "mozilla/BasePrincipal.h"
 
 using namespace JS;
 using namespace mozilla::scache;
 using mozilla::UniquePtr;
 
 // We only serialize scripts with system principals. So we don't serialize the
 // principals when writing a script. Instead, when reading it back, we set the
 // principals to the system principals.
@@ -40,18 +40,18 @@ nsresult ReadCachedScript(StartupCache* 
 
   MOZ_ASSERT((code & JS::TranscodeResult_Throw) != 0);
   JS_ClearPendingException(cx);
   return NS_ERROR_OUT_OF_MEMORY;
 }
 
 nsresult WriteCachedScript(StartupCache* cache, nsACString& uri, JSContext* cx,
                            HandleScript script) {
-  MOZ_ASSERT(nsJSPrincipals::get(JS_GetScriptPrincipals(script))
-                 ->GetIsSystemPrincipal());
+  MOZ_ASSERT(
+      nsJSPrincipals::get(JS_GetScriptPrincipals(script))->IsSystemPrincipal());
 
   JS::TranscodeBuffer buffer;
   JS::TranscodeResult code = JS::EncodeScript(cx, buffer, script);
   if (code != JS::TranscodeResult_Ok) {
     if ((code & JS::TranscodeResult_Failure) != 0) {
       return NS_ERROR_FAILURE;
     }
     MOZ_ASSERT((code & JS::TranscodeResult_Throw) != 0);
--- a/js/xpconnect/src/Sandbox.cpp
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -59,16 +59,17 @@
 #include "mozilla/dom/TextDecoderBinding.h"
 #include "mozilla/dom/TextEncoderBinding.h"
 #include "mozilla/dom/UnionConversions.h"
 #include "mozilla/dom/URLBinding.h"
 #include "mozilla/dom/URLSearchParamsBinding.h"
 #include "mozilla/dom/XMLHttpRequest.h"
 #include "mozilla/dom/XMLSerializerBinding.h"
 #include "mozilla/dom/FormDataBinding.h"
+#include "mozilla/BasePrincipal.h"
 #include "mozilla/DeferredFinalize.h"
 #include "mozilla/NullPrincipal.h"
 
 using namespace mozilla;
 using namespace JS;
 using namespace xpc;
 
 using mozilla::dom::DestroyProtoAndIfaceCache;
@@ -1040,17 +1041,17 @@ nsresult xpc::CreateSandboxObject(JSCont
   JS::RealmOptions realmOptions;
 
   auto& creationOptions = realmOptions.creationOptions();
 
   // XXXjwatt: Consider whether/when sandboxes should be able to see
   // [SecureContext] API (bug 1273687).  In that case we'd call
   // creationOptions.setSecureContext(true).
 
-  bool isSystemPrincipal = principal == nsXPConnect::SystemPrincipal();
+  bool isSystemPrincipal = principal->IsSystemPrincipal();
   if (isSystemPrincipal) {
     creationOptions.setClampAndJitterTime(false);
   }
 
   xpc::SetPrefableRealmOptions(realmOptions);
   if (options.sameZoneAs) {
     creationOptions.setNewCompartmentInExistingZone(
         js::UncheckedUnwrap(options.sameZoneAs));
@@ -1166,17 +1167,17 @@ nsresult xpc::CreateSandboxObject(JSCont
       }
 
       ok = JS_SplicePrototype(cx, sandbox, options.proto);
       if (!ok) {
         return NS_ERROR_XPC_UNEXPECTED;
       }
     }
 
-    bool allowComponents = principal == nsXPConnect::SystemPrincipal();
+    bool allowComponents = principal->IsSystemPrincipal();
     if (options.wantComponents && allowComponents &&
         !ObjectScope(sandbox)->AttachComponentsObject(cx))
       return NS_ERROR_XPC_UNEXPECTED;
 
     if (!XPCNativeWrapper::AttachNewConstructorObject(cx, sandbox)) {
       return NS_ERROR_XPC_UNEXPECTED;
     }
 
@@ -1343,17 +1344,16 @@ static bool GetExpandedPrincipal(JSConte
 
   // First pass:
   for (uint32_t i = 0; i < length; ++i) {
     RootedValue allowed(cx);
     if (!JS_GetElement(cx, arrayObj, i, &allowed)) {
       return false;
     }
 
-    nsresult rv;
     nsCOMPtr<nsIPrincipal> principal;
     if (allowed.isObject()) {
       // In case of object let's see if it's a Principal or a
       // ScriptObjectPrincipal.
       nsCOMPtr<nsISupports> prinOrSop;
       RootedObject obj(cx, &allowed.toObject());
       if (!GetPrincipalOrSOP(cx, obj, getter_AddRefs(prinOrSop))) {
         return false;
@@ -1376,20 +1376,17 @@ static bool GetExpandedPrincipal(JSConte
           // principal we have here.
           // If attrs comes from OriginAttributes, we don't need
           // this check.
           return false;
         }
       }
 
       // We do not allow ExpandedPrincipals to contain any system principals.
-      bool isSystem;
-      rv = nsXPConnect::SecurityManager()->IsSystemPrincipal(principal,
-                                                             &isSystem);
-      NS_ENSURE_SUCCESS(rv, false);
+      bool isSystem = principal->IsSystemPrincipal();
       if (isSystem) {
         JS_ReportErrorASCII(
             cx, "System principal is not allowed in an expanded principal");
         return false;
       }
       allowedDomains[i] = principal;
     } else if (allowed.isString()) {
       // Skip any string arguments - we handle them in the next pass.
--- a/layout/base/AccessibleCaretManager.cpp
+++ b/layout/base/AccessibleCaretManager.cpp
@@ -239,17 +239,17 @@ void AccessibleCaretManager::UpdateCaret
     return;
   }
 
   PositionChangedResult result = mFirstCaret->SetPosition(frame, offset);
 
   switch (result) {
     case PositionChangedResult::NotChanged:
     case PositionChangedResult::Changed:
-      if (aHints == UpdateCaretsHint::Default) {
+      if (!aHints.contains(UpdateCaretsHint::RespectOldAppearance)) {
         if (HasNonEmptyTextContent(GetEditingHostForFrame(frame))) {
           mFirstCaret->SetAppearance(Appearance::Normal);
         } else if (
             StaticPrefs::
                 layout_accessiblecaret_caret_shown_when_long_tapping_on_empty_content()) {
           if (mFirstCaret->IsLogicallyVisible()) {
             // Possible cases are: 1) SelectWordOrShortcut() sets the
             // appearance to Normal. 2) When the caret is out of viewport and
@@ -263,19 +263,16 @@ void AccessibleCaretManager::UpdateCaret
             //
             // Do nothing to make the appearance remains None so that it can
             // be distinguished from case 2). Also do not set the appearance
             // to NormalNotShown here like the default update behavior.
           }
         } else {
           mFirstCaret->SetAppearance(Appearance::NormalNotShown);
         }
-      } else if (aHints.contains(UpdateCaretsHint::RespectOldAppearance)) {
-        // Do nothing to preserve the appearance of the caret set by the
-        // caller.
       }
       break;
 
     case PositionChangedResult::Invisible:
       mFirstCaret->SetAppearance(Appearance::NormalNotShown);
       break;
   }
 
@@ -306,21 +303,18 @@ void AccessibleCaretManager::UpdateCaret
 
   auto updateSingleCaret = [aHints](AccessibleCaret* aCaret, nsIFrame* aFrame,
                                     int32_t aOffset) -> PositionChangedResult {
     PositionChangedResult result = aCaret->SetPosition(aFrame, aOffset);
 
     switch (result) {
       case PositionChangedResult::NotChanged:
       case PositionChangedResult::Changed:
-        if (aHints == UpdateCaretsHint::Default) {
+        if (!aHints.contains(UpdateCaretsHint::RespectOldAppearance)) {
           aCaret->SetAppearance(Appearance::Normal);
-        } else if (aHints.contains(UpdateCaretsHint::RespectOldAppearance)) {
-          // Do nothing to preserve the appearance of the caret set by the
-          // caller.
         }
         break;
 
       case PositionChangedResult::Invisible:
         aCaret->SetAppearance(Appearance::NormalNotShown);
         break;
     }
     return result;
@@ -334,19 +328,20 @@ void AccessibleCaretManager::UpdateCaret
   if (firstCaretResult == PositionChangedResult::Changed ||
       secondCaretResult == PositionChangedResult::Changed) {
     // Flush layout to make the carets intersection correct.
     if (!FlushLayout()) {
       return;
     }
   }
 
-  if (aHints == UpdateCaretsHint::Default) {
-    // Only check for tilt carets with default update hint. Otherwise we might
-    // override the appearance set by the caller.
+  if (!aHints.contains(UpdateCaretsHint::RespectOldAppearance)) {
+    // Only check for tilt carets when the caller doesn't ask us to preserve
+    // old appearance. Otherwise we might override the appearance set by the
+    // caller.
     if (StaticPrefs::layout_accessiblecaret_always_tilt()) {
       UpdateCaretsForAlwaysTilt(startFrame, endFrame);
     } else {
       UpdateCaretsForOverlappingTilt();
     }
   }
 
   if (!aHints.contains(UpdateCaretsHint::DispatchNoEvent) && !mActiveCaret) {
@@ -579,30 +574,36 @@ nsresult AccessibleCaretManager::SelectW
   ProvideHapticFeedback();
 
   return rv;
 }
 
 void AccessibleCaretManager::OnScrollStart() {
   AC_LOG("%s", __FUNCTION__);
 
+  AutoRestore<bool> saveAllowFlushingLayout(mAllowFlushingLayout);
+  mAllowFlushingLayout = false;
+
   mIsScrollStarted = true;
 
   if (mFirstCaret->IsLogicallyVisible() || mSecondCaret->IsLogicallyVisible()) {
     // Dispatch the event only if one of the carets is logically visible like in
     // HideCarets().
     DispatchCaretStateChangedEvent(CaretChangedReason::Scroll);
   }
 }
 
 void AccessibleCaretManager::OnScrollEnd() {
   if (mLastUpdateCaretMode != GetCaretMode()) {
     return;
   }
 
+  AutoRestore<bool> saveAllowFlushingLayout(mAllowFlushingLayout);
+  mAllowFlushingLayout = false;
+
   mIsScrollStarted = false;
 
   if (GetCaretMode() == CaretMode::Cursor) {
     if (!mFirstCaret->IsLogicallyVisible()) {
       // If the caret is hidden (Appearance::None) due to blur, no
       // need to update it.
       return;
     }
@@ -620,16 +621,19 @@ void AccessibleCaretManager::OnScrollEnd
   UpdateCarets();
 }
 
 void AccessibleCaretManager::OnScrollPositionChanged() {
   if (mLastUpdateCaretMode != GetCaretMode()) {
     return;
   }
 
+  AutoRestore<bool> saveAllowFlushingLayout(mAllowFlushingLayout);
+  mAllowFlushingLayout = false;
+
   if (mFirstCaret->IsLogicallyVisible() || mSecondCaret->IsLogicallyVisible()) {
     if (mIsScrollStarted) {
       // We don't want extra CaretStateChangedEvents dispatched when user is
       // scrolling the page.
       AC_LOG("%s: UpdateCarets(RespectOldAppearance | DispatchNoEvent)",
              __FUNCTION__);
       UpdateCarets({UpdateCaretsHint::RespectOldAppearance,
                     UpdateCaretsHint::DispatchNoEvent});
@@ -640,16 +644,19 @@ void AccessibleCaretManager::OnScrollPos
   }
 }
 
 void AccessibleCaretManager::OnReflow() {
   if (mLastUpdateCaretMode != GetCaretMode()) {
     return;
   }
 
+  AutoRestore<bool> saveAllowFlushingLayout(mAllowFlushingLayout);
+  mAllowFlushingLayout = false;
+
   if (mFirstCaret->IsLogicallyVisible() || mSecondCaret->IsLogicallyVisible()) {
     AC_LOG("%s: UpdateCarets(RespectOldAppearance)", __FUNCTION__);
     UpdateCarets(UpdateCaretsHint::RespectOldAppearance);
   }
 }
 
 void AccessibleCaretManager::OnBlur() {
   AC_LOG("%s: HideCarets()", __FUNCTION__);
@@ -895,17 +902,17 @@ void AccessibleCaretManager::ClearMainta
   // word selection. We should clear it so that we can drag caret freely.
   RefPtr<nsFrameSelection> fs = GetFrameSelection();
   if (fs) {
     fs->MaintainSelection(eSelectNoAmount);
   }
 }
 
 bool AccessibleCaretManager::FlushLayout() {
-  if (mPresShell) {
+  if (mPresShell && mAllowFlushingLayout) {
     AutoRestore<bool> flushing(mFlushingLayout);
     mFlushingLayout = true;
 
     if (Document* doc = mPresShell->GetDocument()) {
       doc->FlushPendingNotifications(FlushType::Layout);
     }
   }
 
--- a/layout/base/AccessibleCaretManager.h
+++ b/layout/base/AccessibleCaretManager.h
@@ -313,16 +313,20 @@ class AccessibleCaretManager {
   uint16_t mLastInputSource = dom::MouseEvent_Binding::MOZ_SOURCE_UNKNOWN;
 
   // Set to true in OnScrollStart() and set to false in OnScrollEnd().
   bool mIsScrollStarted = false;
 
   // Whether we're flushing layout, used for sanity-checking.
   bool mFlushingLayout = false;
 
+  // Set to false to disallow flushing layout in some callbacks such as
+  // OnReflow(), OnScrollStart(), OnScrollStart(), or OnScrollPositionChanged().
+  bool mAllowFlushingLayout = true;
+
   static const int32_t kAutoScrollTimerDelay = 30;
 
   // Clicking on the boundary of input or textarea will move the caret to the
   // front or end of the content. To avoid this, we need to deflate the content
   // boundary by 61 app units, which is 1 pixel + 1 app unit as defined in
   // AppUnit.h.
   static const int32_t kBoundaryAppUnits = 61;
 
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -1735,16 +1735,17 @@ void nsRefreshDriver::Tick(VsyncId aId, 
   mResizeSuppressed = false;
 
   AutoRestore<bool> restoreInRefresh(mInRefresh);
   mInRefresh = true;
 
   AutoRestore<TimeStamp> restoreTickStart(mTickStart);
   mTickStart = TimeStamp::Now();
   mTickVsyncId = aId;
+  mTickVsyncTime = aNowTime;
 
   gfxPlatform::GetPlatform()->SchedulePaintIfDeviceReset();
 
   // We want to process any pending APZ metrics ahead of their positions
   // in the queue. This will prevent us from spending precious time
   // painting a stale displayport.
   if (gfxPrefs::APZPeekMessages()) {
     nsLayoutUtils::UpdateDisplayPortMarginsFromPendingMessages();
@@ -2116,16 +2117,18 @@ void nsRefreshDriver::ResetInitialTransa
   mCompletedTransaction = mOutstandingTransactionId = mNextTransactionId =
       aTransactionId;
 }
 
 mozilla::TimeStamp nsRefreshDriver::GetTransactionStart() { return mTickStart; }
 
 VsyncId nsRefreshDriver::GetVsyncId() { return mTickVsyncId; }
 
+mozilla::TimeStamp nsRefreshDriver::GetVsyncStart() { return mTickVsyncTime; }
+
 void nsRefreshDriver::NotifyTransactionCompleted(
     mozilla::layers::TransactionId aTransactionId) {
   if (aTransactionId > mCompletedTransaction) {
     if (mOutstandingTransactionId - mCompletedTransaction > 1 &&
         mWaitingForTransaction) {
       mCompletedTransaction = aTransactionId;
       FinishedWaitingForTransaction();
     } else {
--- a/layout/base/nsRefreshDriver.h
+++ b/layout/base/nsRefreshDriver.h
@@ -378,16 +378,17 @@ class nsRefreshDriver final : public moz
   TransactionId GetTransactionId(bool aThrottle) override;
   TransactionId LastTransactionId() const override;
   void NotifyTransactionCompleted(TransactionId aTransactionId) override;
   void RevokeTransactionId(TransactionId aTransactionId) override;
   void ClearPendingTransactions() override;
   void ResetInitialTransactionId(TransactionId aTransactionId) override;
   mozilla::TimeStamp GetTransactionStart() override;
   mozilla::VsyncId GetVsyncId() override;
+  mozilla::TimeStamp GetVsyncStart() override;
 
   bool IsWaitingForPaint(mozilla::TimeStamp aTime);
 
   // nsARefreshObserver
   NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override {
     return TransactionIdAllocator::AddRef();
   }
   NS_IMETHOD_(MozExternalRefCountType) Release(void) override {
@@ -530,16 +531,17 @@ class nsRefreshDriver final : public moz
 
   // Number of seconds that the refresh driver is blocked waiting for a
   // compositor transaction to be completed before we append a note to the gfx
   // critical log. The number is doubled every time the threshold is hit.
   uint64_t mWarningThreshold;
   mozilla::TimeStamp mMostRecentRefresh;
   mozilla::TimeStamp mTickStart;
   mozilla::VsyncId mTickVsyncId;
+  mozilla::TimeStamp mTickVsyncTime;
   mozilla::TimeStamp mNextThrottledFrameRequestTick;
   mozilla::TimeStamp mNextRecomputeVisibilityTick;
 
   // separate arrays for each flush type we support
   ObserverArray mObservers[4];
   // These observers should NOT be included in HasObservers() since that method
   // is used to determine whether or not to stop the timer, or restore it when
   // thawing the refresh driver. On the other hand these observers are intended
--- a/layout/painting/nsCSSRenderingBorders.cpp
+++ b/layout/painting/nsCSSRenderingBorders.cpp
@@ -3767,16 +3767,19 @@ nsCSSBorderImageRenderer::nsCSSBorderIma
         value = coord.GetPercentValue() * borderDimension;
         break;
       case eStyleUnit_Factor:
         value = coord.GetFactorValue() * borderWidths.Side(s);
         break;
       case eStyleUnit_Auto:  // same as the slice value, in CSS pixels
         value = mSlice.Side(s);
         break;
+      case eStyleUnit_Calc:
+        value = std::max(0, coord.ComputeComputedCalc(borderDimension));
+        break;
       default:
         MOZ_ASSERT_UNREACHABLE(
             "unexpected CSS unit for border image area "
             "division");
         value = 0;
         break;
     }
     // NSToCoordRoundWithClamp rounds towards infinity, but that's OK
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -1404,17 +1404,17 @@ var gCSSProperties = {
     invalid_values: [ "-10%", "-10", "10 10 10 10 10", "10 10 10 10 -10", "10px", "-10px", "fill", "fill fill 10px", "10px fill fill" ]
   },
   "border-image-width": {
     domProp: "borderImageWidth",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     applies_to_first_letter: true,
     initial_values: [ "1", "1 1 1 1" ],
-    other_values: [ "0", "0%", "0px", "auto auto auto auto", "10 10% auto 15px", "10px 10px 10px 10px", "10", "10 10", "10 10 10" ],
+    other_values: [ "0", "0%", "0px", "auto auto auto auto", "10 10% auto 15px", "10px 10px 10px 10px", "10", "10 10", "10 10 10", "calc(10px)", "calc(10px + 5%)" ],
     invalid_values: [ "-10", "-10px", "-10%", "10 10 10 10 10", "10 10 10 10 auto", "auto auto auto auto auto", "10px calc(nonsense)", "1px red" ],
     unbalanced_values: [ "10px calc(" ]
   },
   "border-image-outset": {
     domProp: "borderImageOutset",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     applies_to_first_letter: true,
--- a/moz.build
+++ b/moz.build
@@ -112,16 +112,26 @@ if not CONFIG['JS_STANDALONE'] or not CO
 
     GENERATED_FILES['buildid.h'].script = 'build/variables.py:buildid_header'
     GENERATED_FILES['source-repo.h'].script = 'build/variables.py:source_repo_header'
 
     DIRS += [
         'build',
     ]
 
+if CONFIG['PGO_PROFILE_PATH']:
+    profdata_gen = ('merged.profdata.stub', 'merged.profdata')
+    GENERATED_FILES += [
+        profdata_gen
+    ]
+    GENERATED_FILES[profdata_gen].script = 'build/merge_profdata.py'
+    GENERATED_FILES[profdata_gen].inputs = [
+        CONFIG['PGO_PROFILE_PATH'],
+    ]
+
 DIRS += [
     'mfbt',
 ]
 
 if CONFIG['MOZ_BUILD_APP']:
     # Bring in the configuration for the configured application.
     include('/' + CONFIG['MOZ_BUILD_APP'] + '/app.mozbuild')
 else:
--- a/netwerk/base/nsNetUtil.cpp
+++ b/netwerk/base/nsNetUtil.cpp
@@ -259,17 +259,17 @@ void AssertLoadingPrincipalAndClientInfo
   //  2. Null principals currently require exact object identity for
   //     nsIPrincipal::Equals() to return true.  This doesn't work here because
   //     ClientInfo::GetPrincipal() uses PrincipalInfoToPrincipal() to allocate
   //     a new object.  To work around this we compare the principal origin
   //     string itself.  If bug 1431771 is fixed then we could switch to
   //     Equals().
 
   // Allow worker debugger to load with a system principal.
-  if (aLoadingPrincipal->GetIsSystemPrincipal() &&
+  if (aLoadingPrincipal->IsSystemPrincipal() &&
       (aType == nsIContentPolicy::TYPE_INTERNAL_WORKER ||
        aType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER ||
        aType == nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER ||
        aType == nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS)) {
     return;
   }
 
   // Perform a fast comparison for most principal checks.
--- a/python/mozbuild/mozbuild/frontend/data.py
+++ b/python/mozbuild/mozbuild/frontend/data.py
@@ -1169,16 +1169,17 @@ class GeneratedFile(ContextDerived):
             '.c',
             '.cpp',
             '.h',
             '.inc',
             '.py',
             '.rs',
             'node.stub', # To avoid VPATH issues with installing node files: https://bugzilla.mozilla.org/show_bug.cgi?id=1461714#c55
             'android_apks', # We need to compile Java to generate JNI wrappers for native code compilation to consume.
+            '.profdata',
         )
         self.required_for_compile = [f for f in self.outputs if f.endswith(suffixes) or 'stl_wrappers/' in f]
 
 
 class ChromeManifestEntry(ContextDerived):
     """Represents a chrome.manifest entry."""
 
     __slots__ = (
--- a/services/sync/modules/engines.js
+++ b/services/sync/modules/engines.js
@@ -780,16 +780,18 @@ SyncEngine.prototype = {
   // If this is false, we'll throw, otherwise, we'll ignore the record and
   // continue. This currently can only happen due to the record being larger
   // than the record upload limit.
   allowSkippedRecord: true,
 
   // Which sortindex to use when retrieving records for this engine.
   _defaultSort: undefined,
 
+  _hasSyncedThisSession: false,
+
   _metadataPostProcessor(json) {
     if (Array.isArray(json)) {
       // Pre-`JSONFile` storage stored an array, but `JSONFile` defaults to
       // an object, so we wrap the array for consistency.
       json = { ids: json };
     }
     if (!json.ids) {
       json.ids = [];
@@ -972,16 +974,24 @@ SyncEngine.prototype = {
     // Store the value as a string to keep floating point precision
     Svc.Prefs.set(this.name + ".lastSync", lastSync.toString());
   },
   async resetLastSync() {
     this._log.debug("Resetting " + this.name + " last sync time");
     await this.setLastSync(0);
   },
 
+  get hasSyncedThisSession() {
+    return this._hasSyncedThisSession;
+  },
+
+  set hasSyncedThisSession(hasSynced) {
+    this._hasSyncedThisSession = hasSynced;
+  },
+
   get toFetch() {
     this._toFetchStorage.ensureDataReady();
     return this._toFetchStorage.data.ids;
   },
 
   set toFetch(ids) {
     if (ids.constructor.name != "SerializableSet") {
       throw new Error("Bug: Attempted to set toFetch to something that isn't a SerializableSet");
@@ -1803,16 +1813,17 @@ SyncEngine.prototype = {
       } else {
         // For many ids, split into chunks of at most 100
         while (val.length > 0) {
           await doDelete(key, val.slice(0, 100));
           val = val.slice(100);
         }
       }
     }
+    this.hasSyncedThisSession = true;
     await this._tracker.asyncObserver.promiseObserversComplete();
   },
 
   async _syncCleanup() {
     this._needWeakUpload.clear();
     if (!this._modified) {
       return;
     }
@@ -1980,16 +1991,17 @@ SyncEngine.prototype = {
    * local user data.
    */
   async resetClient() {
     return this._notify("reset-client", this.name, this._resetClient)();
   },
 
   async _resetClient() {
     await this.resetLastSync();
+    this.hasSyncedThisSession = false;
     this.previousFailed = new SerializableSet();
     this.toFetch = new SerializableSet();
     this._needWeakUpload.clear();
   },
 
   /**
    * Removes all local Sync metadata and user data for this engine.
    */
--- a/services/sync/modules/engines/bookmarks.js
+++ b/services/sync/modules/engines/bookmarks.js
@@ -317,44 +317,16 @@ BaseBookmarksEngine.prototype = {
   _recordObj: PlacesItem,
   _trackerObj: BookmarksTracker,
   version: 2,
   _defaultSort: "index",
 
   syncPriority: 4,
   allowSkippedRecord: false,
 
-  _migratedSyncMetadata: false,
-  async _migrateSyncMetadata({ migrateLastSync = true } = {}) {
-    if (this._migratedSyncMetadata) {
-      return;
-    }
-    let shouldWipeRemote = await PlacesSyncUtils.bookmarks.shouldWipeRemote();
-    if (!shouldWipeRemote) {
-      // Migrate the bookmarks sync ID and last sync time from prefs, to avoid
-      // triggering a full sync on upgrade. This can be removed in bug 1443021.
-      let existingSyncID = await super.getSyncID();
-      if (existingSyncID) {
-        this._log.debug("Migrating existing sync ID ${existingSyncID} from " +
-                        "prefs", { existingSyncID });
-        await this._ensureCurrentSyncID(existingSyncID);
-      }
-      if (migrateLastSync) {
-        let existingLastSync = await super.getLastSync();
-        if (existingLastSync) {
-          this._log.debug("Migrating existing last sync time " +
-                          "${existingLastSync} from prefs",
-                          { existingLastSync });
-          await PlacesSyncUtils.bookmarks.setLastSync(existingLastSync);
-        }
-      }
-    }
-    this._migratedSyncMetadata = true;
-  },
-
   // Exposed so that the buffered engine can override to store the sync ID in
   // the mirror.
   _ensureCurrentSyncID(newSyncID) {
     return PlacesSyncUtils.bookmarks.ensureCurrentSyncId(newSyncID);
   },
 
   async ensureCurrentSyncID(newSyncID) {
     let shouldWipeRemote = await PlacesSyncUtils.bookmarks.shouldWipeRemote();
@@ -612,17 +584,16 @@ BookmarksEngine.prototype = {
       return dupe;
     }
 
     this._log.trace("No dupe found for key " + key + ".");
     return undefined;
   },
 
   async _syncStartup() {
-    await this._migrateSyncMetadata();
     await SyncEngine.prototype._syncStartup.call(this);
 
     try {
       // For first-syncs, make a backup for the user to restore
       let lastSync = await this.getLastSync();
       if (!lastSync) {
         this._log.debug("Bookmarks backup starting.");
         await PlacesBackups.create(null, true);
@@ -790,23 +761,16 @@ BufferedBookmarksEngine.prototype = {
   // errors that happen when the buffered engine is enabled vs when the
   // non-buffered engine is enabled.
   overrideTelemetryName: "bookmarks-buffered",
 
   // Needed to ensure we don't miss items when resuming a sync that failed or
   // aborted early.
   _defaultSort: "oldest",
 
-  async _syncStartup() {
-    await this._migrateSyncMetadata({
-      migrateLastSync: false,
-    });
-    await super._syncStartup();
-  },
-
   async _ensureCurrentSyncID(newSyncID) {
     await super._ensureCurrentSyncID(newSyncID);
     let buf = await this._store.ensureOpenMirror();
     await buf.ensureCurrentSyncId(newSyncID);
   },
 
   async getSyncID() {
     return PlacesSyncUtils.bookmarks.getSyncId();
--- a/services/sync/modules/engines/history.js
+++ b/services/sync/modules/engines/history.js
@@ -40,38 +40,16 @@ HistoryEngine.prototype = {
   __proto__: SyncEngine.prototype,
   _recordObj: HistoryRec,
   _storeObj: HistoryStore,
   _trackerObj: HistoryTracker,
   downloadLimit: MAX_HISTORY_DOWNLOAD,
 
   syncPriority: 7,
 
-  _migratedSyncMetadata: false,
-  async _migrateSyncMetadata() {
-    if (this._migratedSyncMetadata) {
-      return;
-    }
-    // Migrate the history sync ID and last sync time from prefs, to avoid
-    // triggering a full sync on upgrade. This can be removed in bug 1443021.
-    let existingSyncID = await super.getSyncID();
-    if (existingSyncID) {
-      this._log.debug("Migrating existing sync ID ${existingSyncID} from prefs",
-                      { existingSyncID });
-      await PlacesSyncUtils.history.ensureCurrentSyncId(existingSyncID);
-    }
-    let existingLastSync = await super.getLastSync();
-    if (existingLastSync) {
-      this._log.debug("Migrating existing last sync time ${existingLastSync} " +
-                      "from prefs", { existingLastSync });
-      await PlacesSyncUtils.history.setLastSync(existingLastSync);
-    }
-    this._migratedSyncMetadata = true;
-  },
-
   async getSyncID() {
     return PlacesSyncUtils.history.getSyncId();
   },
 
   async ensureCurrentSyncID(newSyncID) {
     this._log.debug("Checking if server sync ID ${newSyncID} matches existing",
                     { newSyncID });
     await PlacesSyncUtils.history.ensureCurrentSyncId(newSyncID);
@@ -100,21 +78,16 @@ HistoryEngine.prototype = {
     return lastSync;
   },
 
   async setLastSync(lastSync) {
     await PlacesSyncUtils.history.setLastSync(lastSync);
     await super.setLastSync(lastSync); // Remove in bug 1443021.
   },
 
-  async _syncStartup() {
-    await this._migrateSyncMetadata();
-    await super._syncStartup();
-  },
-
   shouldSyncURL(url) {
     return !url.startsWith("file:");
   },
 
   async pullNewChanges() {
     const changedIDs = await this._tracker.getChangedIDs();
     let modifiedGUIDs = Object.keys(changedIDs);
     if (!modifiedGUIDs.length) {
--- a/services/sync/modules/engines/tabs.js
+++ b/services/sync/modules/engines/tabs.js
@@ -39,21 +39,16 @@ Utils.deferGetSet(TabSetRecord, "clearte
 function TabEngine(service) {
   SyncEngine.call(this, "Tabs", service);
 }
 TabEngine.prototype = {
   __proto__: SyncEngine.prototype,
   _storeObj: TabStore,
   _trackerObj: TabTracker,
   _recordObj: TabSetRecord,
-  // A flag to indicate if we have synced in this session. This is to help
-  // consumers of remote tabs that may want to differentiate between "I've an
-  // empty tab list as I haven't yet synced" vs "I've an empty tab list
-  // as there really are no tabs"
-  hasSyncedThisSession: false,
 
   syncPriority: 3,
 
   async initialize() {
     await SyncEngine.prototype.initialize.call(this);
 
     // Reset the client on every startup so that we fetch recent tabs.
     await this._resetClient();
@@ -76,17 +71,16 @@ TabEngine.prototype = {
   getClientById(id) {
     return this._store._remoteClients[id];
   },
 
   async _resetClient() {
     await SyncEngine.prototype._resetClient.call(this);
     await this._store.wipe();
     this._tracker.modified = true;
-    this.hasSyncedThisSession = false;
   },
 
   async removeClientData() {
     let url = this.engineURL + "/" + this.service.clientsEngine.localID;
     await this.service.resource(url).delete();
   },
 
   async _reconcile(item) {
@@ -94,21 +88,16 @@ TabEngine.prototype = {
     // TabStore.itemExists tests only against our local client ID.
     if ((await this._store.itemExists(item.id))) {
       this._log.trace("Ignoring incoming tab item because of its id: " + item.id);
       return false;
     }
 
     return SyncEngine.prototype._reconcile.call(this, item);
   },
-
-  async _syncFinish() {
-    this.hasSyncedThisSession = true;
-    return SyncEngine.prototype._syncFinish.call(this);
-  },
 };
 
 
 function TabStore(name, engine) {
   Store.call(this, name, engine);
 }
 TabStore.prototype = {
   __proto__: Store.prototype,
--- a/services/sync/tests/unit/test_bookmark_engine.js
+++ b/services/sync/tests/unit/test_bookmark_engine.js
@@ -1099,98 +1099,16 @@ add_task(async function test_resume_buff
     Assert.deepEqual(toolbarRecord.children.sort(), children.sort());
 
   } finally {
     await cleanup(engine, server);
     await engine.finalize();
   }
 });
 
-add_task(async function test_legacy_migrate_sync_metadata() {
-  let legacyEngine = new BookmarksEngine(Service);
-  await legacyEngine.initialize();
-  await legacyEngine.resetClient();
-
-  let syncID = Utils.makeGUID();
-  let lastSync = Date.now() / 1000;
-
-  Svc.Prefs.set(`${legacyEngine.name}.syncID`, syncID);
-  Svc.Prefs.set(`${legacyEngine.name}.lastSync`, lastSync.toString());
-
-  strictEqual(await legacyEngine.getSyncID(), "",
-    "Legacy engine should start with empty sync ID");
-  strictEqual(await legacyEngine.getLastSync(), 0,
-    "Legacy engine should start with empty last sync");
-
-  info("Migrate Sync metadata prefs");
-  await legacyEngine._migrateSyncMetadata();
-
-  equal(await legacyEngine.getSyncID(), syncID,
-    "Initializing legacy engine should migrate sync ID");
-  equal(await legacyEngine.getLastSync(), lastSync,
-    "Initializing legacy engine should migrate last sync time");
-
-  let newSyncID = Utils.makeGUID();
-  await legacyEngine.ensureCurrentSyncID(newSyncID);
-
-  equal(await legacyEngine.getSyncID(), newSyncID,
-    "Changing legacy engine sync ID should update Places");
-  strictEqual(await legacyEngine.getLastSync(), 0,
-    "Changing legacy engine sync ID should clear last sync in Places");
-
-  equal(Svc.Prefs.get(`${legacyEngine.name}.syncID`), newSyncID,
-    "Changing legacy engine sync ID should update prefs");
-  strictEqual(Svc.Prefs.get(`${legacyEngine.name}.lastSync`), "0",
-    "Changing legacy engine sync ID should clear last sync pref");
-
-  await cleanupEngine(legacyEngine);
-  await legacyEngine.finalize();
-});
-
-add_task(async function test_buffered_migate_sync_metadata() {
-  let bufferedEngine = new BufferedBookmarksEngine(Service);
-  await bufferedEngine.initialize();
-  await bufferedEngine.resetClient();
-
-  let syncID = Utils.makeGUID();
-  let lastSync = Date.now() / 1000;
-
-  Svc.Prefs.set(`${bufferedEngine.name}.syncID`, syncID);
-  Svc.Prefs.set(`${bufferedEngine.name}.lastSync`, lastSync.toString());
-
-  strictEqual(await bufferedEngine.getSyncID(), "",
-    "Buffered engine should start with empty sync ID");
-  strictEqual(await bufferedEngine.getLastSync(), 0,
-    "Buffered engine should start with empty last sync");
-
-  info("Migrate Sync metadata prefs");
-  await bufferedEngine._migrateSyncMetadata({
-    migrateLastSync: false,
-  });
-
-  equal(await bufferedEngine.getSyncID(), syncID,
-    "Initializing buffered engine should migrate sync ID");
-  strictEqual(await bufferedEngine.getLastSync(), 0,
-    "Initializing buffered engine should not migrate last sync time");
-
-  let newSyncID = Utils.makeGUID();
-  await bufferedEngine.ensureCurrentSyncID(newSyncID);
-
-  equal(await bufferedEngine.getSyncID(), newSyncID,
-    "Changing buffered engine sync ID should update Places");
-
-  equal(Svc.Prefs.get(`${bufferedEngine.name}.syncID`), newSyncID,
-    "Changing buffered engine sync ID should update prefs");
-  strictEqual(Svc.Prefs.get(`${bufferedEngine.name}.lastSync`), "0",
-    "Changing buffered engine sync ID should clear last sync pref");
-
-  await cleanupEngine(bufferedEngine);
-  await bufferedEngine.finalize();
-});
-
 // The buffered engine stores the sync ID and last sync time in three places:
 // prefs, Places, and the mirror. We can remove the prefs entirely in bug
 // 1443021, and drop the last sync time from Places once we remove the legacy
 // engine. This test ensures we keep them in sync (^_^), and handle mismatches
 // in case the user copies Places or the mirror between accounts. See
 // bug 1199077, comment 84 for the gory details.
 add_task(async function test_mirror_syncID() {
   let bufferedEngine = new BufferedBookmarksEngine(Service);
--- a/services/sync/tests/unit/test_history_engine.js
+++ b/services/sync/tests/unit/test_history_engine.js
@@ -255,48 +255,8 @@ add_task(async function test_history_vis
   ok(allVisits.find(x => x.date.getTime() === Date.UTC(2017, 11, 4)),
      "Should contain the Dec. 4th visit");
   ok(allVisits.find(x => x.date.getTime() === Date.UTC(2017, 11, 5)),
      "Should contain the Dec. 5th visit");
 
   await engine.wipeClient();
   await engine.finalize();
 });
-
-add_task(async function test_migrate_sync_metadata() {
-  let engine = new HistoryEngine(Service);
-  await engine.initialize();
-  await engine.resetClient();
-
-  let syncID = Utils.makeGUID();
-  let lastSync = Date.now() / 1000;
-
-  Svc.Prefs.set(`${engine.name}.syncID`, syncID);
-  Svc.Prefs.set(`${engine.name}.lastSync`, lastSync.toString());
-
-  strictEqual(await engine.getSyncID(), "",
-    "Engine should start with empty sync ID");
-  strictEqual(await engine.getLastSync(), 0,
-    "Engine should start with empty last sync");
-
-  info("Migrate Sync metadata prefs");
-  await engine._migrateSyncMetadata();
-
-  equal(await engine.getSyncID(), syncID,
-    "Initializing engine should migrate sync ID");
-  equal(await engine.getLastSync(), lastSync,
-    "Initializing engine should migrate last sync time");
-
-  let newSyncID = Utils.makeGUID();
-  await engine.ensureCurrentSyncID(newSyncID);
-
-  equal(await engine.getSyncID(), newSyncID,
-    "Changing engine sync ID should update Places");
-  strictEqual(await engine.getLastSync(), 0,
-    "Changing engine sync ID should clear last sync in Places");
-
-  equal(Svc.Prefs.get(`${engine.name}.syncID`), newSyncID,
-    "Changing engine sync ID should update prefs");
-  strictEqual(Svc.Prefs.get(`${engine.name}.lastSync`), "0",
-    "Changing engine sync ID should clear last sync pref");
-
-  await engine.wipeClient();
-});
--- a/taskcluster/ci/test/raptor.yml
+++ b/taskcluster/ci/test/raptor.yml
@@ -344,16 +344,32 @@ raptor-speedometer-geckoview-power:
         extra-options:
             - --test=raptor-speedometer
             - --app=geckoview
             - --binary=org.mozilla.geckoview_example
             - --power-test
             - --page-cycles 5
             - --host HOST_IP
 
+raptor-speedometer-fennec:
+    description: "Raptor Speedometer on Fennec"
+    try-name: raptor-speedometer-fennec
+    treeherder-symbol: Rap(sp)
+    target: target.apk
+    run-on-projects:
+        by-test-platform:
+            android-hw.*: built-projects
+    tier: 2
+    max-run-time: 900
+    mozharness:
+        extra-options:
+            - --test=raptor-speedometer-fennec
+            - --app=fennec
+            - --binary=org.mozilla.fennec_aurora
+
 raptor-speedometer-chrome:
     description: "Raptor Speedometer on Chrome"
     try-name: raptor-speedometer-chrome
     treeherder-symbol: Rap-C(sp)
     run-on-projects: ['try', 'mozilla-central']
     tier: 2
     max-run-time: 1500
     mozharness:
--- a/taskcluster/ci/test/test-platforms.yml
+++ b/taskcluster/ci/test/test-platforms.yml
@@ -359,8 +359,18 @@ android-hw-p2-8-0-arm7-api-16/debug:
         - android-hw-arm7-debug-unittests
 
 android-hw-p2-8-0-android-aarch64/opt:
     build-platform: android-aarch64/opt
     test-sets:
         - android-hw-aarch64-opt-unittests
         - android-hw-aarch64-raptor
         - android-hw-aarch64-raptor-power
+
+android-hw-p2-8-0-arm7-api-16-nightly/opt:
+    build-platform: android-api-16-nightly/opt
+    test-sets:
+        - android-hw-arm7-raptor-nightly
+
+android-hw-p2-8-0-android-aarch64-nightly/opt:
+    build-platform: android-aarch64-nightly/opt
+    test-sets:
+        - android-hw-aarch64-raptor-nightly
--- a/taskcluster/ci/test/test-sets.yml
+++ b/taskcluster/ci/test/test-sets.yml
@@ -409,8 +409,14 @@ android-hw-arm7-raptor:
 android-hw-aarch64-raptor:
     - raptor-speedometer-geckoview
 
 android-hw-arm7-raptor-power:
     - raptor-speedometer-geckoview-power
 
 android-hw-aarch64-raptor-power:
     - raptor-speedometer-geckoview-power
+
+android-hw-arm7-raptor-nightly:
+    - raptor-speedometer-fennec
+
+android-hw-aarch64-raptor-nightly:
+    - raptor-speedometer-fennec
--- a/taskcluster/taskgraph/transforms/job/mozharness_test.py
+++ b/taskcluster/taskgraph/transforms/job/mozharness_test.py
@@ -46,17 +46,24 @@ mozharness_test_run_schema = Schema({
     Required('test'): test_description_schema,
     # Base work directory used to set up the task.
     Required('workdir'): basestring,
 })
 
 
 def test_packages_url(taskdesc):
     """Account for different platforms that name their test packages differently"""
-    return get_artifact_url('<build>', get_artifact_path(taskdesc, 'target.test_packages.json'))
+    artifact_url = get_artifact_url('<build>', get_artifact_path(taskdesc,
+                                    'target.test_packages.json'))
+    # for android nightly we need to add 'en-US' to the artifact url
+    test = taskdesc['run']['test']
+    if get_variant(test['test-platform']) == "nightly" and 'android' in test['test-platform']:
+        head, tail = os.path.split(artifact_url)
+        artifact_url = os.path.join(head, 'en-US', tail)
+    return artifact_url
 
 
 @run_job_using('docker-engine', 'mozharness-test', schema=mozharness_test_run_schema)
 @run_job_using('docker-worker', 'mozharness-test', schema=mozharness_test_run_schema)
 def mozharness_test_on_docker(config, job, taskdesc):
     run = job['run']
     test = taskdesc['run']['test']
     mozharness = test['mozharness']
--- a/testing/mozharness/mozharness/mozilla/testing/raptor.py
+++ b/testing/mozharness/mozharness/mozilla/testing/raptor.py
@@ -52,17 +52,17 @@ class Raptor(TestingMixin, MercurialScri
     config_options = [
         [["--test"],
          {"action": "store",
           "dest": "test",
           "help": "Raptor test to run"
           }],
         [["--app"],
          {"default": "firefox",
-          "choices": ["firefox", "chrome", "geckoview"],
+          "choices": ["firefox", "chrome", "geckoview", "fennec"],
           "dest": "app",
           "help": "name of the application we are testing (default: firefox)"
           }],
         [["--is-release-build"],
          {"action": "store_true",
           "dest": "is_release_build",
           "help": "Whether the build is a release build which requires work arounds "
                   "using MOZ_DISABLE_NONLOCAL_CONNECTIONS to support installing unsigned "
@@ -175,17 +175,17 @@ class Raptor(TestingMixin, MercurialScri
         if self.run_local:
             # raptor initiated locally, get app from command line args
             # which are passed in from mach inside 'raptor_cmd_line_args'
             # cmd line args can be in two formats depending on how user entered them
             # i.e. "--app=geckoview" or separate as "--app", "geckoview" so we have to
             # check each cmd line arg individually
             self.app = "firefox"
             if 'raptor_cmd_line_args' in self.config:
-                for app in ['chrome', 'geckoview']:
+                for app in ['chrome', 'geckoview', 'fennec']:
                     for next_arg in self.config['raptor_cmd_line_args']:
                         if app in next_arg:
                             self.app = app
                             break
         else:
             # raptor initiated in production via mozharness
             self.test = self.config['test']
             self.app = self.config.get("app", "firefox")
@@ -326,17 +326,17 @@ class Raptor(TestingMixin, MercurialScri
     def raptor_options(self, args=None, **kw):
         """return options to raptor"""
         options = []
         kw_options = {}
 
         # binary path; if testing on firefox the binary path already came from mozharness/pro;
         # otherwise the binary path is forwarded from cmd line arg (raptor_cmd_line_args)
         kw_options['app'] = self.app
-        if self.app == "firefox" or (self.app == "geckoview" and not self.run_local):
+        if self.app == "firefox" or (self.app in["geckoview", "fennec"] and not self.run_local):
             binary_path = self.binary_path or self.config.get('binary_path')
             if not binary_path:
                 self.fatal("Raptor requires a path to the binary.")
             kw_options['binary'] = binary_path
         else:  # running on google chrome
             if not self.run_local:
                 # when running locally we already set the chrome binary above in init; here
                 # in production we aready installed chrome, so set the binary path to our install
@@ -450,17 +450,17 @@ class Raptor(TestingMixin, MercurialScri
                                        'requirements.txt')]
         )
 
         # if  running gecko profiling  install the requirements
         if self.gecko_profile:
             self._install_view_gecko_profile_req()
 
     def install(self):
-        if self.app == "geckoview":
+        if self.app in ["geckoview", "fennec"]:
             self.install_apk(self.installer_path)
         else:
             super(Raptor, self).install()
 
     def _install_view_gecko_profile_req(self):
         # if running locally and gecko profiing is on, we will be using the
         # view-gecko-profile tool which has its own requirements too
         if self.gecko_profile and self.run_local:
@@ -571,30 +571,30 @@ class Raptor(TestingMixin, MercurialScri
                                               fname_pattern % 'raw'))
 
         def launch_in_debug_mode(cmdline):
             cmdline = set(cmdline)
             debug_opts = {'--debug', '--debugger', '--debugger_args'}
 
             return bool(debug_opts.intersection(cmdline))
 
-        if self.app == "geckoview":
+        if self.app in ["geckoview", "fennec"]:
             self.logcat_start()
 
         command = [python, run_tests] + options + mozlog_opts
         if launch_in_debug_mode(command):
             raptor_process = subprocess.Popen(command, cwd=self.workdir, env=env)
             raptor_process.wait()
         else:
             self.return_code = self.run_command(command, cwd=self.workdir,
                                                 output_timeout=output_timeout,
                                                 output_parser=parser,
                                                 env=env)
 
-        if self.app == "geckoview":
+        if self.app in ["geckoview", "fennec"]:
             self.logcat_stop()
 
         if parser.minidump_output:
             self.info("Looking at the minidump files for debugging purposes...")
             for item in parser.minidump_output:
                 self.run_command(["ls", "-l", item])
 
         elif '--no-upload-results' not in options:
--- a/testing/raptor/mach_commands.py
+++ b/testing/raptor/mach_commands.py
@@ -43,17 +43,18 @@ class RaptorRunner(MozbuildObject):
         return self.run_mozharness()
 
     def init_variables(self, raptor_args, kwargs):
         self.raptor_dir = os.path.join(self.topsrcdir, 'testing', 'raptor')
         self.mozharness_dir = os.path.join(self.topsrcdir, 'testing',
                                            'mozharness')
         self.config_file_path = os.path.join(self._topobjdir, 'testing',
                                              'raptor-in_tree_conf.json')
-        self.binary_path = self.get_binary_path() if kwargs['app'] != 'geckoview' else None
+        self.binary_path = self.get_binary_path() if kwargs['app'] not in \
+            ['geckoview', 'fennec'] else None
         self.virtualenv_script = os.path.join(self.topsrcdir, 'third_party', 'python',
                                               'virtualenv', 'virtualenv.py')
         self.virtualenv_path = os.path.join(self._topobjdir, 'testing',
                                             'raptor-venv')
         self.python_interp = sys.executable
         self.raptor_args = raptor_args
         if kwargs.get('host', None) == 'HOST_IP':
             kwargs['host'] = os.environ['HOST_IP']
@@ -174,17 +175,17 @@ def create_parser():
 class MachRaptor(MachCommandBase):
     @Command('raptor-test', category='testing',
              description='Run raptor performance tests.',
              parser=create_parser)
     def run_raptor_test(self, **kwargs):
 
         build_obj = MozbuildObject.from_environment(cwd=HERE)
 
-        if conditions.is_android(build_obj) or kwargs['app'] == 'geckoview':
+        if conditions.is_android(build_obj) or kwargs['app'] in ['geckoview', 'fennec']:
             from mozrunner.devices.android_device import verify_android_device
             from mozdevice import ADBAndroid, ADBHost
             if not verify_android_device(build_obj, install=True, app=kwargs['binary']):
                 return 1
 
         debug_command = '--debug-command'
         if debug_command in sys.argv:
             sys.argv.remove(debug_command)
--- a/testing/raptor/raptor/cmdline.py
+++ b/testing/raptor/raptor/cmdline.py
@@ -12,17 +12,17 @@ from mozlog.commandline import add_loggi
 def create_parser(mach_interface=False):
     parser = argparse.ArgumentParser()
     add_arg = parser.add_argument
 
     add_arg('-t', '--test', required=True, dest='test',
             help="name of raptor test to run")
     add_arg('--app', default='firefox', dest='app',
             help="name of the application we are testing (default: firefox)",
-            choices=['firefox', 'chrome', 'geckoview'])
+            choices=['firefox', 'chrome', 'geckoview', 'fennec'])
     add_arg('-b', '--binary', dest='binary',
             help="path to the browser executable that we are testing")
     add_arg('--host', dest='host',
             help="Hostname from which to serve urls, defaults to 127.0.0.1. "
             "The value HOST_IP will cause the value of host to be "
             "loaded from the environment variable HOST_IP.",
             default='127.0.0.1')
     add_arg('--power-test', dest="power_test", action="store_true",
@@ -70,17 +70,17 @@ def create_parser(mach_interface=False):
 
 
 def verify_options(parser, args):
     ctx = vars(args)
     if args.binary is None:
         parser.error("--binary is required!")
 
     # if running on a desktop browser make sure the binary exists
-    if args.app != "geckoview":
+    if args.app not in ["geckoview", "fennec"]:
         if not os.path.isfile(args.binary):
             parser.error("{binary} does not exist!".format(**ctx))
 
     # if geckoProfile specified but not running on Firefox, not supported
     if args.gecko_profile is True and args.app != "firefox":
         parser.error("Gecko profiling is only supported when running raptor on Firefox!")
 
     # if --power-test specified, must be on geckview with --host specified.
@@ -112,17 +112,17 @@ class _StopAction(argparse.Action):
 
 class _PrintTests(_StopAction):
     def __call__(self, parser, namespace, values, option_string=None):
         from manifestparser import TestManifest
 
         here = os.path.abspath(os.path.dirname(__file__))
         raptor_ini = os.path.join(here, 'raptor.ini')
 
-        for _app in ["firefox", "chrome", "geckoview", "chrome-android"]:
+        for _app in ["firefox", "chrome", "geckoview", "chrome-android", "fennec"]:
             test_manifest = TestManifest([raptor_ini], strict=False)
             info = {"app": _app}
             available_tests = test_manifest.active_tests(exists=False,
                                                          disabled=False,
                                                          filters=[self.filter_app],
                                                          **info)
             if len(available_tests) == 0:
                 # none for that app, skip to next
@@ -174,13 +174,15 @@ class _PrintTests(_StopAction):
         if app == "firefox":
             return "Firefox Desktop"
         elif app == "chrome":
             return "Google Chrome Desktop"
         elif app == "geckoview":
             return "Firefox Geckoview on Android"
         elif app == "chrome-android":
             return "Google Chrome on Android"
+        elif app == "fennec":
+            return "Firefox Fennec on Android"
 
     def filter_app(self, tests, values):
         for test in tests:
             if values["app"] in test['apps']:
                 yield test
--- a/testing/raptor/raptor/raptor.py
+++ b/testing/raptor/raptor/raptor.py
@@ -90,18 +90,18 @@ class Raptor(object):
         self.debug_mode = debug_mode if self.config['run_local'] else False
 
         # if running debug-mode reduce the pause after browser startup
         if self.debug_mode:
             self.post_startup_delay = 3000
             self.log.info("debug-mode enabled, reducing post-browser startup pause to %d ms"
                           % self.post_startup_delay)
 
-        # Create the profile; for geckoview we want a firefox profile type
-        if self.config['app'] == 'geckoview':
+        # Create the profile; for geckoview/fennec we want a firefox profile type
+        if self.config['app'] in ["geckoview", "fennec"]:
             self.profile = create_profile('firefox')
         else:
             self.profile = create_profile(self.config['app'])
 
         # Merge in base profiles
         with open(os.path.join(self.profile_data_dir, 'profiles.json'), 'r') as fh:
             base_profiles = json.load(fh)['raptor']
 
@@ -114,17 +114,17 @@ class Raptor(object):
         self.config['local_profile_dir'] = self.profile.profile
 
         # create results holder
         self.results_handler = RaptorResultsHandler()
 
         # when testing desktop browsers we use mozrunner to start the browser; when
         # testing on android (i.e. geckoview) we use mozdevice to control the device app
 
-        if self.config['app'] == "geckoview":
+        if self.config['app'] in ["geckoview", "fennec"]:
             # create the android device handler; it gets initiated and sets up adb etc
             self.log.info("creating android device handler using mozdevice")
             self.device = ADBDevice(verbose=True)
             self.device.clear_logcat()
             if self.config['power_test']:
                 init_geckoview_power_test(self)
         else:
             # create the desktop browser runner
@@ -148,17 +148,18 @@ class Raptor(object):
             return os.path.join(build.topsrcdir, 'testing', 'profiles')
         return os.path.join(here, 'profile_data')
 
     def start_control_server(self):
         self.control_server = RaptorControlServer(self.results_handler, self.debug_mode)
         self.control_server.start()
 
         # for android we must make the control server available to the device
-        if self.config['app'] == "geckoview" and self.config['host'] in ('localhost', '127.0.0.1'):
+        if self.config['app'] in ['geckoview', 'fennec'] and \
+                self.config['host'] in ('localhost', '127.0.0.1'):
             self.log.info("making the raptor control server port available to device")
             _tcp_port = "tcp:%s" % self.control_server.port
             self.device.create_socket_connection('reverse', _tcp_port, _tcp_port)
 
     def get_playback_config(self, test):
         self.config['playback_tool'] = test.get('playback')
         self.log.info("test uses playback tool: %s " % self.config['playback_tool'])
         self.config['playback_binary_manifest'] = test.get('playback_binary_manifest', None)
@@ -174,56 +175,56 @@ class Raptor(object):
         self.log.info("starting raptor test: %s" % test['name'])
         self.log.info("test settings: %s" % str(test))
         self.log.info("raptor config: %s" % str(self.config))
 
         # benchmark-type tests require the benchmark test to be served out
         if test.get('type') == "benchmark":
             self.benchmark = Benchmark(self.config, test)
             benchmark_port = int(self.benchmark.port)
-
-            # for android we must make the benchmarks server available to the device
-            if self.config['app'] == "geckoview" and self.config['host'] \
-                    in ('localhost', '127.0.0.1'):
-                self.log.info("making the raptor benchmarks server port available to device")
-                _tcp_port = "tcp:%s" % benchmark_port
-                self.device.create_socket_connection('reverse', _tcp_port, _tcp_port)
         else:
             benchmark_port = 0
 
         gen_test_config(self.config['app'],
                         test['name'],
                         self.control_server.port,
                         self.post_startup_delay,
                         host=self.config['host'],
                         b_port=benchmark_port,
                         debug_mode=1 if self.debug_mode else 0)
 
+        # for android we must make the benchmarks server available to the device
+        if self.config['app'] in ['geckoview', 'fennec'] and \
+                self.config['host'] in ('localhost', '127.0.0.1'):
+            self.log.info("making the raptor benchmarks server port available to device")
+            _tcp_port = "tcp:%s" % benchmark_port
+            self.device.create_socket_connection('reverse', _tcp_port, _tcp_port)
+
         # must intall raptor addon each time because we dynamically update some content
         # note: for chrome the addon is just a list of paths that ultimately are added
         # to the chromium command line '--load-extension' argument
         raptor_webext = os.path.join(webext_dir, 'raptor')
         self.log.info("installing webext %s" % raptor_webext)
         self.profile.addons.install(raptor_webext)
 
         # add test specific preferences
         if test.get("preferences", None) is not None:
             if self.config['app'] == "firefox":
                 self.profile.set_preferences(json.loads(test['preferences']))
             else:
                 self.log.info("preferences were configured for the test, \
                               but we do not install them on non Firefox browsers.")
 
         # on firefox we can get an addon id; chrome addon actually is just cmd line arg
-        if self.config['app'] in ["firefox", "geckoview"]:
+        if self.config['app'] in ['firefox', 'geckoview', 'fennec']:
             webext_id = self.profile.addons.addon_details(raptor_webext)['id']
 
         # for android/geckoview, create a top-level raptor folder on the device
         # sdcard; if it already exists remove it so we start fresh each time
-        if self.config['app'] == "geckoview":
+        if self.config['app'] in ["geckoview", "fennec"]:
             self.device_raptor_dir = "/sdcard/raptor"
             self.config['device_raptor_dir'] = self.device_raptor_dir
             if self.device.is_dir(self.device_raptor_dir):
                 self.log.info("deleting existing device raptor dir: %s" % self.device_raptor_dir)
                 self.device.rm(self.device_raptor_dir, recursive=True)
             self.log.info("creating raptor folder on sdcard: %s" % self.device_raptor_dir)
             self.device.mkdir(self.device_raptor_dir)
             self.device.chmod(self.device_raptor_dir, recursive=True)
@@ -236,17 +237,17 @@ class Raptor(object):
 
             # for android we must make the playback server available to the device
             if self.config['app'] == "geckoview" and self.config['host'] \
                     in ('localhost', '127.0.0.1'):
                 self.log.info("making the raptor playback server port available to device")
                 _tcp_port = "tcp:8080"
                 self.device.create_socket_connection('reverse', _tcp_port, _tcp_port)
 
-        if self.config['app'] in ("geckoview", "firefox") and \
+        if self.config['app'] in ('geckoview', 'firefox', 'fennec') and \
            self.config['host'] not in ('localhost', '127.0.0.1'):
             # Must delete the proxy settings from the profile if running
             # the test with a host different from localhost.
             userjspath = os.path.join(self.profile.profile, 'user.js')
             with open(userjspath) as userjsfile:
                 prefs = userjsfile.readlines()
             prefs = [pref for pref in prefs if 'network.proxy' not in pref]
             with open(userjspath, 'w') as userjsfile:
@@ -263,54 +264,73 @@ class Raptor(object):
             proxy_prefs["network.proxy.http"] = self.config['host']
             proxy_prefs["network.proxy.http_port"] = 8080
             proxy_prefs["network.proxy.ssl"] = self.config['host']
             proxy_prefs["network.proxy.ssl_port"] = 8080
             proxy_prefs["network.proxy.no_proxies_on"] = no_proxies_on
             self.profile.set_preferences(proxy_prefs)
 
         # now some final settings, and then startup of the browser under test
-        if self.config['app'] == "geckoview":
-            # for android/geckoview we must copy the profile onto the device and set perms
+        if self.config['app'] in ["geckoview", "fennec"]:
+            # for geckoview/fennec we must copy the profile onto the device and set perms
             if not self.device.is_app_installed(self.config['binary']):
                 raise Exception('%s is not installed' % self.config['binary'])
             self.device_profile = os.path.join(self.device_raptor_dir, "profile")
+
             if self.device.is_dir(self.device_profile):
                 self.log.info("deleting existing device profile folder: %s" % self.device_profile)
                 self.device.rm(self.device_profile, recursive=True)
             self.log.info("creating profile folder on device: %s" % self.device_profile)
             self.device.mkdir(self.device_profile)
+
             self.log.info("copying firefox profile onto the device")
             self.log.info("note: the profile folder being copied is: %s" % self.profile.profile)
             self.log.info('the adb push cmd copies that profile dir to a new temp dir before copy')
             self.device.push(self.profile.profile, self.device_profile)
             self.device.chmod(self.device_profile, recursive=True)
 
-            # now start the geckoview app
+            # now start the geckoview/fennec app
             self.log.info("starting %s" % self.config['app'])
 
             extra_args = ["-profile", self.device_profile,
                           "--es", "env0", "LOG_VERBOSE=1",
                           "--es", "env1", "R_LOG_LEVEL=6"]
 
-            try:
-                # make sure the geckoview app is not running before
-                # attempting to start.
-                self.device.stop_application(self.config['binary'])
-                self.device.launch_activity(self.config['binary'],
-                                            "GeckoViewActivity",
-                                            extra_args=extra_args,
-                                            url='about:blank',
-                                            e10s=True,
-                                            fail_if_running=False)
-            except Exception:
-                self.log.error("Exception launching %s" % self.config['binary'])
-                if self.config['power_test']:
-                    finish_geckoview_power_test(self)
-                raise
+            if self.config['app'] == 'geckoview':
+                # launch geckoview example app
+                try:
+                    # make sure the geckoview app is not running before
+                    # attempting to start.
+                    self.device.stop_application(self.config['binary'])
+                    self.device.launch_activity(self.config['binary'],
+                                                "GeckoViewActivity",
+                                                extra_args=extra_args,
+                                                url='about:blank',
+                                                e10s=True,
+                                                fail_if_running=False)
+                except Exception:
+                    self.log.error("Exception launching %s" % self.config['binary'])
+                    if self.config['power_test']:
+                        finish_geckoview_power_test(self)
+                    raise
+            else:
+                # launch fennec
+                try:
+                    # if fennec is already running, shut it down first
+                    self.device.stop_application(self.config['binary'])
+                    self.device.launch_fennec(self.config['binary'],
+                                              extra_args=extra_args,
+                                              url='about:blank',
+                                              fail_if_running=False)
+                except Exception:
+                    self.log.error("Exception launching %s" % self.config['binary'])
+                    if self.config['power_test']:
+                        finish_geckoview_power_test(self)
+                    raise
+
             self.control_server.device = self.device
             self.control_server.app_name = self.config['binary']
 
         else:
             # For Firefox we need to set
             # MOZ_DISABLE_NONLOCAL_CONNECTIONS=1 env var before
             # startup when testing release builds from mozilla-beta or
             # mozilla-release. This is because of restrictions on
@@ -395,33 +415,33 @@ class Raptor(object):
                     finish_geckoview_power_test(self)
             self.check_for_crashes()
 
         if self.playback is not None:
             self.playback.stop()
 
         # remove the raptor webext; as it must be reloaded with each subtest anyway
         self.log.info("removing webext %s" % raptor_webext)
-        if self.config['app'] in ["firefox", "geckoview"]:
+        if self.config['app'] in ['firefox', 'geckoview', 'fennec']:
             self.profile.addons.remove_addon(webext_id)
 
         # for chrome the addon is just a list (appended to cmd line)
         if self.config['app'] in ["chrome", "chrome-android"]:
             self.profile.addons.remove(raptor_webext)
 
         # gecko profiling symbolication
         if self.config['gecko_profile'] is True:
             self.gecko_profiler.symbolicate()
             # clean up the temp gecko profiling folders
             self.log.info("cleaning up after gecko profiling")
             self.gecko_profiler.clean()
 
         # browser should be closed by now but this is a backup-shutdown (if not in debug-mode)
         if not self.debug_mode:
-            if self.config['app'] != "geckoview":
+            if self.config['app'] not in ['geckoview', 'fennec']:
                 if self.runner.is_running():
                     self.runner.stop()
             # TODO the geckoview app should have been shutdown by this point by the
             # control server, but we can double-check here to make sure
         else:
             # in debug mode, and running locally, leave the browser running
             if self.config['run_local']:
                 self.log.info("* debug-mode enabled - please shutdown the browser manually...")
@@ -451,17 +471,17 @@ class Raptor(object):
 
         self.config['raptor_json_path'] = raptor_json_path
         return self.results_handler.summarize_and_output(self.config)
 
     def get_page_timeout_list(self):
         return self.results_handler.page_timeout_list
 
     def check_for_crashes(self):
-        if self.config['app'] == "geckoview":
+        if self.config['app'] in ["geckoview", "fennec"]:
             logcat = self.device.get_logcat()
             if logcat:
                 if mozcrash.check_for_java_exception(logcat, "raptor"):
                     return
             try:
                 dump_dir = tempfile.mkdtemp()
                 remote_dir = posixpath.join(self.device_profile, 'minidumps')
                 if not self.device.is_dir(remote_dir):
@@ -477,19 +497,19 @@ class Raptor(object):
         else:
             try:
                 self.runner.check_for_crashes()
             except NotImplementedError:  # not implemented for Chrome
                 pass
 
     def clean_up(self):
         self.control_server.stop()
-        if self.config['app'] != "geckoview":
+        if self.config['app'] not in ['geckoview', 'fennec']:
             self.runner.stop()
-        elif self.config['app'] == 'geckoview':
+        elif self.config['app'] in ['geckoview', 'fennec']:
             self.log.info('removing reverse socket connections')
             self.device.remove_socket_connections('reverse')
         else:
             pass
         self.log.info("finished")
 
 
 def view_gecko_profile(ffox_bin):
--- a/testing/raptor/raptor/tests/raptor-speedometer.ini
+++ b/testing/raptor/raptor/tests/raptor-speedometer.ini
@@ -22,8 +22,13 @@ apps = firefox
 
 [raptor-speedometer-chrome]
 apps = chrome
 
 [raptor-speedometer-geckoview]
 page_timeout = 900000  # 15 min
 page_cycles = 1
 apps = geckoview
+
+[raptor-speedometer-fennec]
+page_timeout = 900000  # 15 min
+page_cycles = 1
+apps = fennec
--- a/testing/web-platform/meta/html/dom/interfaces.https.html.ini
+++ b/testing/web-platform/meta/html/dom/interfaces.https.html.ini
@@ -626,25 +626,16 @@
     expected: FAIL
 
   [HTMLDialogElement interface: operation showModal()]
     expected: FAIL
 
   [HTMLDialogElement interface: operation close(DOMString)]
     expected: FAIL
 
-  [HTMLSlotElement interface: operation assignedElements(AssignedNodesOptions)]
-    expected: FAIL
-
-  [HTMLSlotElement interface: document.createElement("slot") must inherit property "assignedElements(AssignedNodesOptions)" with the proper type]
-    expected: FAIL
-
-  [HTMLSlotElement interface: calling assignedElements(AssignedNodesOptions) on document.createElement("slot") with too few arguments must throw TypeError]
-    expected: FAIL
-
   [HTMLCanvasElement interface: operation transferControlToOffscreen()]
     expected: FAIL
 
   [HTMLCanvasElement interface: document.createElement("canvas") must inherit property "transferControlToOffscreen()" with the proper type]
     expected: FAIL
 
   [HTMLFrameSetElement interface: attribute onrejectionhandled]
     expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/shadow-dom/slots-fallback.html.ini
+++ /dev/null
@@ -1,10 +0,0 @@
-[slots-fallback.html]
-  [Slots fallback: Basic, elements only.]
-    expected: FAIL
-
-  [Slots fallback: Slots in Slots, elements only.]
-    expected: FAIL
-
-  [Slots fallback: Complex case, elements only.]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/shadow-dom/slots.html.ini
+++ /dev/null
@@ -1,10 +0,0 @@
-[slots.html]
-  [Slots: Basic, elements only.]
-    expected: FAIL
-
-  [Slots: Slots in closed, elements only.]
-    expected: FAIL
-
-  [Slots: Slots not in a shadow tree, elements only.]
-    expected: FAIL
-
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-backgrounds/border-image-calc-ref.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<title>CSS Test Reference</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="author" title="Mozilla" href="https://mozilla.org">
+<style>
+  #test {
+    background-color: green;
+    width: 200px;
+    height: 200px;
+  }
+</style>
+<p>Test passes if there is a green 200px times 200px square.</p>
+<div id="test"></div>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-backgrounds/border-image-calc.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>CSS Test: border-image honors calc() lengths / percentages</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="author" title="Mozilla" href="https://mozilla.org">
+<link rel="help" href="https://bugzil.la/1517521">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds/#border-image-width">
+<link rel="match" href="border-image-calc-ref.html">
+<style>
+  #test {
+    background-color: red;
+    border: 100px solid red;
+    border-image-slice: 10;
+    border-image-source: url("support/green_color.png");
+    border-image-width: 100px calc(100px) calc(100%) calc(50% + 50px);
+    height: 0;
+    width: 0;
+  }
+</style>
+<p>Test passes if there is a green 200px times 200px square.</p>
+<div id="test"></div>
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -13179,16 +13179,28 @@
     "bug_numbers": [1470528, 1509536],
     "expires_in_version": "never",
     "releaseChannelCollection": "opt-out",
     "kind": "exponential",
     "high": 5000,
     "n_buckets": 50,
     "description": "The time, in percentage of a vsync interval, spent from beginning a paint in the content process until that frame is presented in the compositor."
   },
+  "CONTENT_FRAME_TIME_VSYNC": {
+    "record_in_processes": ["main", "gpu"],
+    "alert_emails": ["gfx-telemetry-alerts@mozilla.com", "mwoodrow@mozilla.com"],
+    "bug_numbers": [1517355],
+    "expires_in_version": "never",
+    "releaseChannelCollection": "opt-out",
+    "kind": "linear",
+    "low": 8,
+    "high": 792,
+    "n_buckets": 100,
+    "description": "The time, in percentage of a vsync interval, spent from the vsync that started a paint in the content process until that frame is presented in the compositor."
+  },
   "CONTENT_FRAME_TIME_WITH_SVG": {
     "record_in_processes": ["main", "gpu"],
     "alert_emails": ["gfx-telemetry-alerts@mozilla.com", "mwoodrow@mozilla.com"],
     "bug_numbers": [1483549, 1509536],
     "expires_in_version": "70",
     "releaseChannelCollection": "opt-out",
     "kind": "exponential",
     "high": 5000,
--- a/toolkit/mozapps/extensions/AddonManagerWebAPI.cpp
+++ b/toolkit/mozapps/extensions/AddonManagerWebAPI.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AddonManagerWebAPI.h"
 
+#include "mozilla/BasePrincipal.h"
 #include "mozilla/dom/Navigator.h"
 #include "mozilla/dom/NavigatorBinding.h"
 
 #include "mozilla/Preferences.h"
 #include "nsGlobalWindow.h"
 #include "xpcpublic.h"
 
 #include "nsIDocShell.h"
@@ -115,17 +116,17 @@ bool AddonManagerWebAPI::IsAPIEnabled(JS
 
     nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
     if (!principal) {
       return false;
     }
 
     // Reaching a window with a system principal means we have reached
     // privileged UI of some kind so stop at this point and allow access.
-    if (principal->GetIsSystemPrincipal()) {
+    if (principal->IsSystemPrincipal()) {
       return true;
     }
 
     nsCOMPtr<nsIDocShell> docShell = win->GetDocShell();
     if (!docShell) {
       // This window has been torn down so don't allow access to the API.
       return false;
     }
--- a/toolkit/mozapps/installer/packager.mk
+++ b/toolkit/mozapps/installer/packager.mk
@@ -28,17 +28,17 @@ stage-package: multilocale.txt locale-ma
 	$(PYTHON) $(MOZILLA_DIR)/toolkit/mozapps/installer/packager.py $(DEFINES) $(ACDEFINES) \
 		--format $(MOZ_PACKAGER_FORMAT) \
 		$(addprefix --removals ,$(MOZ_PKG_REMOVALS)) \
 		$(if $(filter-out 0,$(MOZ_PKG_FATAL_WARNINGS)),,--ignore-errors) \
 		$(if $(MOZ_PACKAGER_MINIFY),--minify) \
 		$(if $(MOZ_PACKAGER_MINIFY_JS),--minify-js \
 		  $(addprefix --js-binary ,$(JS_BINARY)) \
 		) \
-		$(if $(JARLOG_DIR),$(addprefix --jarlog ,$(wildcard $(JARLOG_FILE_AB_CD)))) \
+		$(addprefix --jarlog ,$(wildcard $(JARLOG_FILE_AB_CD))) \
 		$(if $(OPTIMIZEJARS),--optimizejars) \
 		$(addprefix --compress ,$(JAR_COMPRESSION)) \
 		$(MOZ_PKG_MANIFEST) '$(DIST)' '$(DIST)'/$(MOZ_PKG_DIR)$(if $(MOZ_PKG_MANIFEST),,$(_BINPATH)) \
 		$(if $(filter omni,$(MOZ_PACKAGER_FORMAT)),$(if $(NON_OMNIJAR_FILES),--non-resource $(NON_OMNIJAR_FILES)))
 ifdef RUN_FIND_DUPES
 	$(PYTHON) $(MOZILLA_DIR)/toolkit/mozapps/installer/find-dupes.py $(DEFINES) $(ACDEFINES) $(MOZ_PKG_DUPEFLAGS) $(DIST)/$(MOZ_PKG_DIR)
 endif # RUN_FIND_DUPES
 ifndef MOZ_IS_COMM_TOPDIR
--- a/toolkit/mozapps/installer/upload-files.mk
+++ b/toolkit/mozapps/installer/upload-files.mk
@@ -88,18 +88,21 @@ ifdef MOZ_CLANG_RT_ASAN_LIB_PATH
 endif
 
 ifdef FUZZING_INTERFACES
   JSSHELL_BINS += fuzz-tests$(BIN_SUFFIX)
 endif
 
 MAKE_JSSHELL  = $(call py_action,zip,-C $(DIST)/bin --strip $(abspath $(PKG_JSSHELL)) $(JSSHELL_BINS))
 
-JARLOG_DIR = $(topobjdir)/jarlog/
-JARLOG_FILE_AB_CD = $(JARLOG_DIR)/$(AB_CD).log
+ifneq (,$(PGO_JARLOG_PATH))
+  JARLOG_FILE_AB_CD = $(PGO_JARLOG_PATH)
+else
+  JARLOG_FILE_AB_CD = $(topobjdir)/jarlog/$(AB_CD).log
+endif
 
 TAR_CREATE_FLAGS := --exclude=.mkdir.done $(TAR_CREATE_FLAGS)
 CREATE_FINAL_TAR = $(TAR) -c --owner=0 --group=0 --numeric-owner \
   --mode=go-w --exclude=.mkdir.done -f
 UNPACK_TAR       = tar -xf-
 
 ifeq ($(MOZ_PKG_FORMAT),TAR)
   PKG_SUFFIX	= .tar