Merge inbound to m-c a=merge
authorWes Kocher <wkocher@mozilla.com>
Thu, 17 Aug 2017 16:16:51 -0700
changeset 424899 a6a1f5c1d971dbee67ba6eec7ead7902351ddca2
parent 424804 2fb6d8f7bac1be26b2b49b68fc34cc6decd7f339 (current diff)
parent 424898 6613ef541bb1228dab236aed11e36eae9e61d1d1 (diff)
child 424900 6ed3d96de0cc7d9b59b5368d20a378886aadfa82
child 424977 cf69bd1dca31bc80e55ba597bdbfb5f062c6a5a1
child 425080 d213e7a427ff16a6e3437eee727d27ee93a51958
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone57.0a1
first release with
nightly linux32
a6a1f5c1d971 / 57.0a1 / 20170818100226 / files
nightly linux64
a6a1f5c1d971 / 57.0a1 / 20170818100226 / files
nightly mac
a6a1f5c1d971 / 57.0a1 / 20170818100226 / files
nightly win32
a6a1f5c1d971 / 57.0a1 / 20170818100226 / files
nightly win64
a6a1f5c1d971 / 57.0a1 / 20170818100226 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to m-c a=merge MozReview-Commit-ID: CQeEqtGlaXh
browser/themes/osx/urlbar-history-dropmarker.png
browser/themes/osx/urlbar-history-dropmarker@2x.png
browser/themes/shared/compacttheme.inc.css
browser/themes/shared/compacttheme/urlbar-history-dropmarker.svg
browser/themes/windows/urlbar-history-dropmarker-win7.png
browser/themes/windows/urlbar-history-dropmarker-win7@2x.png
browser/themes/windows/urlbar-history-dropmarker.png
browser/themes/windows/urlbar-history-dropmarker@2x.png
image/test/mochitest/test_bug1325080.html
js/src/jit/JitFrameIterator-inl.h
js/src/jit/JitFrameIterator.cpp
js/src/jit/JitFrameIterator.h
testing/mochitest/runtests.py
xpcom/threads/nsEventQueue.cpp
xpcom/threads/nsEventQueue.h
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -5189,18 +5189,18 @@ var TabsProgressListener = {
 function nsBrowserAccess() { }
 
 nsBrowserAccess.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow, Ci.nsISupports]),
 
   _openURIInNewTab(aURI, aReferrer, aReferrerPolicy, aIsPrivate,
                    aIsExternal, aForceNotRemote = false,
                    aUserContextId = Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID,
-                   aOpener = null, aTriggeringPrincipal = null,
-                   aNextTabParentId = 0, aName = "") {
+                   aOpenerWindow = null, aOpenerBrowser = null,
+                   aTriggeringPrincipal = null, aNextTabParentId = 0, aName = "") {
     let win, needToFocusWin;
 
     // try the current window.  if we're in a popup, fall back on the most recent browser window
     if (window.toolbar.visible)
       win = window;
     else {
       win = RecentWindow.getMostRecentBrowserWindow({private: aIsPrivate});
       needToFocusWin = true;
@@ -5222,17 +5222,18 @@ nsBrowserAccess.prototype = {
     let tab = win.gBrowser.loadOneTab(aURI ? aURI.spec : "about:blank", {
                                       triggeringPrincipal: aTriggeringPrincipal,
                                       referrerURI: aReferrer,
                                       referrerPolicy: aReferrerPolicy,
                                       userContextId: aUserContextId,
                                       fromExternal: aIsExternal,
                                       inBackground: loadInBackground,
                                       forceNotRemote: aForceNotRemote,
-                                      opener: aOpener,
+                                      opener: aOpenerWindow,
+                                      openerBrowser: aOpenerBrowser,
                                       nextTabParentId: aNextTabParentId,
                                       name: aName,
                                       });
     let browser = win.gBrowser.getBrowserForTab(tab);
 
     if (needToFocusWin || (!loadInBackground && aIsExternal))
       win.focus();
 
@@ -5316,17 +5317,17 @@ nsBrowserAccess.prototype = {
         let forceNotRemote = !!aOpener;
         let userContextId = aOpener && aOpener.document
                               ? aOpener.document.nodePrincipal.originAttributes.userContextId
                               : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID;
         let openerWindow = (aFlags & Ci.nsIBrowserDOMWindow.OPEN_NO_OPENER) ? null : aOpener;
         let browser = this._openURIInNewTab(aURI, referrer, referrerPolicy,
                                             isPrivate, isExternal,
                                             forceNotRemote, userContextId,
-                                            openerWindow, aTriggeringPrincipal);
+                                            openerWindow, null, aTriggeringPrincipal);
         if (browser)
           newWindow = browser.contentWindow;
         break;
       default : // OPEN_CURRENTWINDOW or an illegal value
         newWindow = window.content;
         if (aURI) {
           let loadflags = isExternal ?
                             Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL :
@@ -5358,17 +5359,17 @@ nsBrowserAccess.prototype = {
                           ? aParams.openerOriginAttributes.userContextId
                           : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID
 
     let referrer = aParams.referrer ? makeURI(aParams.referrer) : null;
     let browser = this._openURIInNewTab(aURI, referrer,
                                         aParams.referrerPolicy,
                                         aParams.isPrivate,
                                         isExternal, false,
-                                        userContextId, null,
+                                        userContextId, null, aParams.openerBrowser,
                                         aParams.triggeringPrincipal,
                                         aNextTabParentId, aName);
     if (browser)
       return browser.QueryInterface(Ci.nsIFrameLoaderOwner);
 
     return null;
   },
 
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -77,18 +77,18 @@
         document.getAnonymousElementByAttribute(this, "anonid", "panelcontainer");
       </field>
       <field name="mStringBundle">
         document.getAnonymousElementByAttribute(this, "anonid", "tbstringbundle");
       </field>
       <field name="mCurrentTab">
         null
       </field>
-      <field name="_lastRelatedTab">
-        null
+      <field name="_lastRelatedTabMap">
+        new WeakMap();
       </field>
       <field name="mCurrentBrowser">
         null
       </field>
       <field name="mProgressListeners">
         []
       </field>
       <field name="mTabsProgressListeners">
@@ -1119,21 +1119,22 @@
             }
 
             var oldTab = this.mCurrentTab;
 
             // Preview mode should not reset the owner
             if (!this._previewMode && !oldTab.selected)
               oldTab.owner = null;
 
-            if (this._lastRelatedTab) {
-              if (!this._lastRelatedTab.selected)
-                this._lastRelatedTab.owner = null;
-              this._lastRelatedTab = null;
-            }
+            let lastRelatedTab = this._lastRelatedTabMap.get(oldTab);
+            if (lastRelatedTab) {
+              if (!lastRelatedTab.selected)
+                lastRelatedTab.owner = null;
+            }
+            this._lastRelatedTabMap = new WeakMap();
 
             var oldBrowser = this.mCurrentBrowser;
 
             if (!gMultiProcessBrowser) {
               oldBrowser.removeAttribute("primary");
               oldBrowser.docShellIsActive = false;
               newBrowser.setAttribute("primary", "true");
               newBrowser.docShellIsActive =
@@ -1608,16 +1609,17 @@
             var aSkipAnimation;
             var aForceNotRemote;
             var aPreferredRemoteType;
             var aNoReferrer;
             var aUserContextId;
             var aSameProcessAsFrameLoader;
             var aOriginPrincipal;
             var aOpener;
+            var aOpenerBrowser;
             var aIsPrerendered;
             var aCreateLazyBrowser;
             var aNextTabParentId;
             var aFocusUrlBar;
             var aName;
             if (arguments.length == 2 &&
                 typeof arguments[1] == "object" &&
                 !(arguments[1] instanceof Ci.nsIURI)) {
@@ -1635,16 +1637,17 @@
               aSkipAnimation            = params.skipAnimation;
               aForceNotRemote           = params.forceNotRemote;
               aPreferredRemoteType      = params.preferredRemoteType;
               aNoReferrer               = params.noReferrer;
               aUserContextId            = params.userContextId;
               aSameProcessAsFrameLoader = params.sameProcessAsFrameLoader;
               aOriginPrincipal          = params.originPrincipal;
               aOpener                   = params.opener;
+              aOpenerBrowser            = params.openerBrowser;
               aIsPrerendered            = params.isPrerendered;
               aCreateLazyBrowser        = params.createLazyBrowser;
               aNextTabParentId          = params.nextTabParentId;
               aFocusUrlBar              = params.focusUrlBar;
               aName                     = params.name;
             }
 
             var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
@@ -1666,16 +1669,17 @@
                                   forceNotRemote: aForceNotRemote,
                                   createLazyBrowser: aCreateLazyBrowser,
                                   preferredRemoteType: aPreferredRemoteType,
                                   noReferrer: aNoReferrer,
                                   userContextId: aUserContextId,
                                   originPrincipal: aOriginPrincipal,
                                   sameProcessAsFrameLoader: aSameProcessAsFrameLoader,
                                   opener: aOpener,
+                                  openerBrowser: aOpenerBrowser,
                                   isPrerendered: aIsPrerendered,
                                   nextTabParentId: aNextTabParentId,
                                   focusUrlBar: aFocusUrlBar,
                                   name: aName });
             if (!bgLoad)
               this.selectedTab = tab;
 
             return tab;
@@ -2102,21 +2106,21 @@
               aParams.remoteType = E10SUtils.DEFAULT_REMOTE_TYPE;
             }
 
             if (aParams.remoteType) {
               b.setAttribute("remoteType", aParams.remoteType);
               b.setAttribute("remote", "true");
             }
 
-            if (aParams.opener) {
+            if (aParams.openerWindow) {
               if (aParams.remoteType) {
                 throw new Error("Cannot set opener window on a remote browser!");
               }
-              b.QueryInterface(Ci.nsIFrameLoaderOwner).presetOpenerWindow(aParams.opener);
+              b.QueryInterface(Ci.nsIFrameLoaderOwner).presetOpenerWindow(aParams.openerWindow);
             }
 
             if (!aParams.isPreloadBrowser && this.hasAttribute("autocompletepopup")) {
               b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
             }
 
             if (aParams.isPreloadBrowser) {
               b.setAttribute("isPreloadBrowser", "true");
@@ -2127,16 +2131,19 @@
 
             if (this.hasAttribute("datetimepicker")) {
               b.setAttribute("datetimepicker", this.getAttribute("datetimepicker"));
             }
 
             b.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
 
             if (aParams.nextTabParentId) {
+              if (!aParams.remoteType) {
+                throw new Error("Cannot have nextTabParentId without a remoteType");
+              }
               // Gecko is going to read this attribute and use it.
               b.setAttribute("nextTabParentId", aParams.nextTabParentId.toString());
             }
 
             if (aParams.sameProcessAsFrameLoader) {
               b.sameProcessAsFrameLoader = aParams.sameProcessAsFrameLoader;
             }
 
@@ -2395,16 +2402,17 @@
             var aPreferredRemoteType;
             var aNoReferrer;
             var aUserContextId;
             var aEventDetail;
             var aSameProcessAsFrameLoader;
             var aOriginPrincipal;
             var aDisallowInheritPrincipal;
             var aOpener;
+            var aOpenerBrowser;
             var aIsPrerendered;
             var aCreateLazyBrowser;
             var aSkipBackgroundNotify;
             var aNextTabParentId;
             var aNoInitialLabel;
             var aFocusUrlBar;
             var aName;
             if (arguments.length == 2 &&
@@ -2426,31 +2434,52 @@
               aPreferredRemoteType      = params.preferredRemoteType;
               aNoReferrer               = params.noReferrer;
               aUserContextId            = params.userContextId;
               aEventDetail              = params.eventDetail;
               aSameProcessAsFrameLoader = params.sameProcessAsFrameLoader;
               aOriginPrincipal          = params.originPrincipal;
               aDisallowInheritPrincipal = params.disallowInheritPrincipal;
               aOpener                   = params.opener;
+              aOpenerBrowser            = params.openerBrowser;
               aIsPrerendered            = params.isPrerendered;
               aCreateLazyBrowser        = params.createLazyBrowser;
               aSkipBackgroundNotify     = params.skipBackgroundNotify;
               aNextTabParentId          = params.nextTabParentId;
               aNoInitialLabel           = params.noInitialLabel;
               aFocusUrlBar              = params.focusUrlBar;
               aName                     = params.name;
             }
 
             // if we're adding tabs, we're past interrupt mode, ditch the owner
             if (this.mCurrentTab.owner)
               this.mCurrentTab.owner = null;
 
+            // Find the tab that opened this one, if any. This is used for
+            // determining positioning, and inherited attributes such as the
+            // user context ID.
+            //
+            // If we have a browser opener (which is usually the browser
+            // element from a remote window.open() call), use that.
+            //
+            // Otherwise, if the tab is related to the current tab (e.g.,
+            // because it was opened by a link click), use the selected tab as
+            // the owner. If aReferrerURI is set, and we don't have an
+            // explicit relatedToCurrent arg, we assume that the tab is
+            // related to the current tab, since aReferrerURI is null or
+            // undefined if the tab is opened from an external application or
+            // bookmark (i.e. somewhere other than an existing tab).
+            let relatedToCurrent = aRelatedToCurrent == null ? !!aReferrerURI : aRelatedToCurrent;
+            let openerTab = ((aOpenerBrowser && this.getTabForBrowser(aOpenerBrowser)) ||
+                             (relatedToCurrent && this.selectedTab));
+
             var t = document.createElementNS(NS_XUL, "tab");
 
+            t.openerTab = openerTab;
+
             aURI = aURI || "about:blank";
             let aURIObject = null;
             try {
               aURIObject = Services.io.newURI(aURI);
             } catch (ex) { /* we'll try to fix up this URL later */ }
 
             let lazyBrowserURI;
             if (aCreateLazyBrowser && aURI != "about:blank") {
@@ -2470,19 +2499,18 @@
             }
 
             if (aIsPrerendered) {
               t.setAttribute("hidden", "true");
             }
 
             // Related tab inherits current tab's user context unless a different
             // usercontextid is specified
-            if (aUserContextId == null &&
-                (aRelatedToCurrent == null ? aReferrerURI : aRelatedToCurrent)) {
-              aUserContextId = this.mCurrentTab.getAttribute("usercontextid") || 0;
+            if (aUserContextId == null && openerTab) {
+              aUserContextId = openerTab.getAttribute("usercontextid") || 0;
             }
 
             if (aUserContextId) {
               t.setAttribute("usercontextid", aUserContextId);
               ContextualIdentityService.setTabStyle(t);
             }
 
             t.setAttribute("onerror", "this.removeAttribute('image');");
@@ -2521,16 +2549,22 @@
               t.owner = aOwner;
 
             var position = this.tabs.length - 1;
             t._tPos = position;
             this.tabContainer._setPositionalAttributes();
 
             this.tabContainer.updateVisibility();
 
+            // If we don't have a preferred remote type, and we have a remote
+            // opener, use the opener's remote type.
+            if (!aPreferredRemoteType && aOpenerBrowser) {
+              aPreferredRemoteType = aOpenerBrowser.remoteType;
+            }
+
             // If URI is about:blank and we don't have a preferred remote type,
             // then we need to use the referrer, if we have one, to get the
             // correct remote type for the new tab.
             if (uriIsAboutBlank && !aPreferredRemoteType && aReferrerURI) {
               aPreferredRemoteType =
                 E10SUtils.getRemoteTypeForURI(aReferrerURI.spec,
                                               gMultiProcessBrowser);
             }
@@ -2557,17 +2591,17 @@
             }
 
             if (!b) {
               // No preloaded browser found, create one.
               b = this._createBrowser({ remoteType,
                                         uriIsAboutBlank,
                                         userContextId: aUserContextId,
                                         sameProcessAsFrameLoader: aSameProcessAsFrameLoader,
-                                        opener: aOpener,
+                                        openerWindow: aOpener,
                                         isPrerendered: aIsPrerendered,
                                         nextTabParentId: aNextTabParentId,
                                         name: aName });
             }
 
             t.linkedBrowser = b;
 
             if (aFocusUrlBar) {
@@ -2639,31 +2673,29 @@
                   charset: aCharset,
                   postData: aPostData,
                 });
               } catch (ex) {
                 Cu.reportError(ex);
               }
             }
 
-            // Check if we're opening a tab related to the current tab and
-            // move it to after the current tab.
-            // aReferrerURI is null or undefined if the tab is opened from
-            // an external application or bookmark, i.e. somewhere other
-            // than the current tab.
-            if ((aRelatedToCurrent == null ? aReferrerURI : aRelatedToCurrent) &&
+            // If we're opening a tab related to the an existing tab, move it
+            // to a position after that tab.
+            if (openerTab &&
                 Services.prefs.getBoolPref("browser.tabs.insertRelatedAfterCurrent")) {
-              let newTabPos = (this._lastRelatedTab ||
-                               this.selectedTab)._tPos + 1;
-              if (this._lastRelatedTab)
-                this._lastRelatedTab.owner = null;
+
+              let lastRelatedTab = this._lastRelatedTabMap.get(openerTab);
+              let newTabPos = (lastRelatedTab || openerTab)._tPos + 1;
+              if (lastRelatedTab)
+                lastRelatedTab.owner = null;
               else
-                t.owner = this.selectedTab;
-              this.moveTabTo(t, newTabPos);
-              this._lastRelatedTab = t;
+                t.owner = openerTab;
+              this.moveTabTo(t, newTabPos, true);
+              this._lastRelatedTabMap.set(openerTab, t);
             }
 
             // This field is updated regardless if we actually animate
             // since it's important that we keep this count correct in all cases.
             this.tabAnimationsInProgress++;
 
             if (animate) {
               requestAnimationFrame(function() {
@@ -3050,17 +3082,17 @@
 
             if (this._windowIsClosing) {
               aCloseWindow = false;
               aNewTab = false;
             }
 
             this.tabAnimationsInProgress--;
 
-            this._lastRelatedTab = null;
+            this._lastRelatedTabMap = new WeakMap();
 
             // update the UI early for responsiveness
             aTab.collapsed = true;
             this._blurTab(aTab);
 
             this._removingTabs.splice(this._removingTabs.indexOf(aTab), 1);
 
             if (aCloseWindow) {
@@ -3700,31 +3732,34 @@
             return window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no,non-remote", url);
           ]]>
         </body>
       </method>
 
       <method name="moveTabTo">
         <parameter name="aTab"/>
         <parameter name="aIndex"/>
+        <parameter name="aKeepRelatedTabs"/>
         <body>
         <![CDATA[
           var oldPosition = aTab._tPos;
           if (oldPosition == aIndex)
             return;
 
           // Don't allow mixing pinned and unpinned tabs.
           if (aTab.pinned)
             aIndex = Math.min(aIndex, this._numPinnedTabs - 1);
           else
             aIndex = Math.max(aIndex, this._numPinnedTabs);
           if (oldPosition == aIndex)
             return;
 
-          this._lastRelatedTab = null;
+          if (!aKeepRelatedTabs) {
+            this._lastRelatedTabMap = new WeakMap();
+          }
 
           let wasFocused = (document.activeElement == this.mCurrentTab);
 
           aIndex = aIndex < aTab._tPos ? aIndex : aIndex + 1;
 
           // invalidate cache
           this._visibleTabs = null;
 
--- a/browser/base/content/test/performance/browser_startup_images.js
+++ b/browser/base/content/test/performance/browser_startup_images.js
@@ -43,20 +43,16 @@ const whitelist = [
   },
 
   // Shared entries
   {
     file: "chrome://browser/skin/arrow-left.svg",
     platforms: ["linux", "win", "macosx"],
   },
   {
-    file: "chrome://browser/skin/arrow-dropdown-16.svg",
-    platforms: ["linux", "win", "macosx"],
-  },
-  {
     file: "chrome://browser/skin/tabbrowser/tab-overflow-indicator.png",
     platforms: ["linux", "win", "macosx"],
   },
 
   {
     file: "chrome://browser/skin/places/toolbarDropMarker.png",
     platforms: ["linux", "win", "macosx"],
   },
--- a/browser/components/extensions/ext-browser.js
+++ b/browser/components/extensions/ext-browser.js
@@ -280,16 +280,30 @@ class TabTracker extends TabTrackerBase 
     }
     if (default_ !== undefined) {
       return default_;
     }
     throw new ExtensionError(`Invalid tab ID: ${tabId}`);
   }
 
   /**
+   * Sets the opener of `tab` to the ID `openerTab`. Both tabs must be in the
+   * same window, or this function will throw a type error.
+   *
+   * @param {Element} tab The tab for which to set the owner.
+   * @param {Element} openerTab The opener of <tab>.
+   */
+  setOpener(tab, openerTab) {
+    if (tab.ownerDocument !== openerTab.ownerDocument) {
+      throw new Error("Tab must be in the same window as its opener");
+    }
+    tab.openerTab = openerTab;
+  }
+
+  /**
    * @param {Event} event
    *        The DOM Event to handle.
    * @private
    */
   handleEvent(event) {
     let nativeTab = event.target;
 
     switch (event.type) {
@@ -560,16 +574,24 @@ class Tab extends TabBase {
     // height.
     return super.frameLoader || {lazyWidth: 0, lazyHeight: 0};
   }
 
   get cookieStoreId() {
     return getCookieStoreIdForTab(this, this.nativeTab);
   }
 
+  get openerTabId() {
+    let opener = this.nativeTab.openerTab;
+    if (opener && opener.parentNode && opener.ownerDocument == this.nativeTab.ownerDocument) {
+      return tabTracker.getId(opener);
+    }
+    return null;
+  }
+
   get height() {
     return this.frameLoader.lazyHeight;
   }
 
   get index() {
     return this.nativeTab._tPos;
   }
 
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -358,16 +358,25 @@ this.tabs = class extends ExtensionAPI {
             }
 
             // Make sure things like about:blank and data: URIs never inherit,
             // and instead always get a NullPrincipal.
             options.disallowInheritPrincipal = true;
 
             tabListener.initTabReady();
             let currentTab = window.gBrowser.selectedTab;
+
+            if (createProperties.openerTabId !== null) {
+              options.ownerTab = tabTracker.getTab(createProperties.openerTabId);
+              options.openerBrowser = options.ownerTab.linkedBrowser;
+              if (options.ownerTab.ownerGlobal !== window) {
+                return Promise.reject({message: "Opener tab must be in the same window as the tab being created"});
+              }
+            }
+
             let nativeTab = window.gBrowser.addTab(url || window.BROWSER_NEW_TAB_URL, options);
 
             let active = true;
             if (createProperties.active !== null) {
               active = createProperties.active;
             }
             if (active) {
               window.gBrowser.selectedTab = nativeTab;
@@ -441,17 +450,23 @@ this.tabs = class extends ExtensionAPI {
           }
           if (updateProperties.pinned !== null) {
             if (updateProperties.pinned) {
               tabbrowser.pinTab(nativeTab);
             } else {
               tabbrowser.unpinTab(nativeTab);
             }
           }
-          // FIXME: highlighted/selected, openerTabId
+          if (updateProperties.openerTabId !== null) {
+            let opener = tabTracker.getTab(updateProperties.openerTabId);
+            if (opener.ownerDocument !== nativeTab.ownerDocument) {
+              return Promise.reject({message: "Opener tab must be in the same window as the tab being updated"});
+            }
+            tabTracker.setOpener(nativeTab, opener);
+          }
 
           return tabManager.convert(nativeTab);
         },
 
         async reload(tabId, reloadProperties) {
           let nativeTab = getTabOrActive(tabId);
 
           let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
--- a/browser/components/extensions/schemas/tabs.json
+++ b/browser/components/extensions/schemas/tabs.json
@@ -54,17 +54,17 @@
       },
       {
         "id": "Tab",
         "type": "object",
         "properties": {
           "id": {"type": "integer", "minimum": -1, "optional": true, "description": "The ID of the tab. Tab IDs are unique within a browser session. Under some circumstances a Tab may not be assigned an ID, for example when querying foreign tabs using the $(ref:sessions) API, in which case a session ID may be present. Tab ID can also be set to $(ref:tabs.TAB_ID_NONE) for apps and devtools windows."},
           "index": {"type": "integer", "minimum": -1, "description": "The zero-based index of the tab within its window."},
           "windowId": {"type": "integer", "optional": true, "minimum": 0, "description": "The ID of the window the tab is contained within."},
-          "openerTabId": {"unsupported": true, "type": "integer", "minimum": 0, "optional": true, "description": "The ID of the tab that opened this tab, if any. This property is only present if the opener tab still exists."},
+          "openerTabId": {"type": "integer", "minimum": 0, "optional": true, "description": "The ID of the tab that opened this tab, if any. This property is only present if the opener tab still exists."},
           "selected": {"type": "boolean", "description": "Whether the tab is selected.", "deprecated": "Please use $(ref:tabs.Tab.highlighted).", "unsupported": true},
           "highlighted": {"type": "boolean", "description": "Whether the tab is highlighted. Works as an alias of active"},
           "active": {"type": "boolean", "description": "Whether the tab is active in its window. (Does not necessarily mean the window is focused.)"},
           "pinned": {"type": "boolean", "description": "Whether the tab is pinned."},
           "lastAccessed": {"type": "integer", "optional": true, "description": "The last time the tab was accessed as the number of milliseconds since epoch."},
           "audible": {"type": "boolean", "optional": true, "description": "Whether the tab has produced sound over the past couple of seconds (but it might not be heard if also muted). Equivalent to whether the speaker audio indicator is showing."},
           "mutedInfo": {"$ref": "MutedInfo", "optional": true, "description": "Current tab muted state and the reason for the last state change."},
           "url": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The URL the tab is displaying. This property is only present if the extension's manifest includes the <code>\"tabs\"</code> permission."},
@@ -480,17 +480,16 @@
                 "description": "Whether the tab should become the selected tab in the window. Defaults to <var>true</var>"
               },
               "pinned": {
                 "type": "boolean",
                 "optional": true,
                 "description": "Whether the tab should be pinned. Defaults to <var>false</var>"
               },
               "openerTabId": {
-                "unsupported": true,
                 "type": "integer",
                 "minimum": 0,
                 "optional": true,
                 "description": "The ID of the tab that opened this tab. If specified, the opener tab must be in the same window as the newly created tab."
               },
               "cookieStoreId": {
                 "type": "string",
                 "optional": true,
@@ -619,16 +618,22 @@
                 "optional": true,
                 "minimum": 0,
                 "description": "The position of the tabs within their windows."
               },
               "cookieStoreId": {
                 "type": "string",
                 "optional": true,
                 "description": "The CookieStoreId used for the tab."
+              },
+              "openerTabId": {
+                "type": "integer",
+                "minimum": 0,
+                "optional": true,
+                "description": "The ID of the tab that opened this tab. If specified, the opener tab must be in the same window as this tab."
               }
             }
           },
           {
             "type": "function",
             "name": "callback",
             "parameters": [
               {
@@ -728,17 +733,16 @@
                 "description": "Whether the tab should be pinned."
               },
               "muted": {
                 "type": "boolean",
                 "optional": true,
                 "description": "Whether the tab should be muted."
               },
               "openerTabId": {
-                "unsupported": true,
                 "type": "integer",
                 "minimum": 0,
                 "optional": true,
                 "description": "The ID of the tab that opened this tab. If specified, the opener tab must be in the same window as this tab."
               }
             }
           },
           {
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -135,16 +135,17 @@ skip-if = debug || asan # Bug 1354681
 [browser_ext_tabs_lazy.js]
 [browser_ext_tabs_removeCSS.js]
 [browser_ext_tabs_move_array.js]
 [browser_ext_tabs_move_window.js]
 [browser_ext_tabs_move_window_multiple.js]
 [browser_ext_tabs_move_window_pinned.js]
 [browser_ext_tabs_onHighlighted.js]
 [browser_ext_tabs_onUpdated.js]
+[browser_ext_tabs_opener.js]
 [browser_ext_tabs_printPreview.js]
 [browser_ext_tabs_query.js]
 [browser_ext_tabs_reload.js]
 [browser_ext_tabs_reload_bypass_cache.js]
 [browser_ext_tabs_sendMessage.js]
 [browser_ext_tabs_cookieStoreId.js]
 [browser_ext_tabs_update.js]
 [browser_ext_tabs_zoom.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_opener.js
@@ -0,0 +1,78 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(async function() {
+  let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank?1");
+  let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank?2");
+
+  gBrowser.selectedTab = tab1;
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["tabs"],
+    },
+
+    background() {
+      let activeTab;
+      let tabId;
+      let tabIds;
+      browser.tabs.query({lastFocusedWindow: true}).then(tabs => {
+        browser.test.assertEq(3, tabs.length, "We have three tabs");
+
+        browser.test.assertTrue(tabs[1].active, "Tab 1 is active");
+        activeTab = tabs[1];
+
+        tabIds = tabs.map(tab => tab.id);
+
+        return browser.tabs.create({openerTabId: activeTab.id, active: false});
+      }).then(tab => {
+        browser.test.assertEq(activeTab.id, tab.openerTabId, "Tab opener ID is correct");
+        browser.test.assertEq(activeTab.index + 1, tab.index, "Tab was inserted after the related current tab");
+
+        tabId = tab.id;
+        return browser.tabs.get(tabId);
+      }).then(tab => {
+        browser.test.assertEq(activeTab.id, tab.openerTabId, "Tab opener ID is still correct");
+
+        return browser.tabs.update(tabId, {openerTabId: tabIds[0]});
+      }).then(tab => {
+        browser.test.assertEq(tabIds[0], tab.openerTabId, "Updated tab opener ID is correct");
+
+        return browser.tabs.get(tabId);
+      }).then(tab => {
+        browser.test.assertEq(tabIds[0], tab.openerTabId, "Updated tab opener ID is still correct");
+
+        return browser.tabs.create({openerTabId: tabId, active: false});
+      }).then(tab => {
+        browser.test.assertEq(tabId, tab.openerTabId, "New tab opener ID is correct");
+        browser.test.assertEq(tabIds.length, tab.index, "New tab was not inserted after the unrelated current tab");
+
+        let promise = browser.tabs.remove(tabId);
+
+        tabId = tab.id;
+        return promise;
+      }).then(() => {
+        return browser.tabs.get(tabId);
+      }).then(tab => {
+        browser.test.assertEq(undefined, tab.openerTabId, "Tab opener ID was cleared after opener tab closed");
+
+        return browser.tabs.remove(tabId);
+      }).then(() => {
+        browser.test.notifyPass("tab-opener");
+      }).catch(e => {
+        browser.test.fail(`${e} :: ${e.stack}`);
+        browser.test.notifyFail("tab-opener");
+      });
+    },
+  });
+
+  await extension.startup();
+
+  await extension.awaitFinish("tab-opener");
+
+  await extension.unload();
+
+  await BrowserTestUtils.removeTab(tab1);
+  await BrowserTestUtils.removeTab(tab2);
+});
--- a/browser/extensions/onboarding/content/onboarding.css
+++ b/browser/extensions/onboarding/content/onboarding.css
@@ -150,50 +150,56 @@
   margin-inline-start: 27px;
   margin-inline-end: 10px;
 }
 
 /* Onboarding tour list */
 #onboarding-tour-list {
   margin: 40px 0 0 0;
   padding: 0;
+  margin-inline-start: 16px;
 }
 
-#onboarding-tour-list > li {
+#onboarding-tour-list .onboarding-tour-item-container {
   list-style: none;
+  outline: none;
+}
+
+#onboarding-tour-list .onboarding-tour-item {
+  pointer-events: none;
+  display: list-item;
   padding-inline-start: 49px;
   padding-top: 14px;
   padding-bottom: 14px;
-  margin-inline-start: 16px;
   margin-bottom: 9px;
   background-repeat: no-repeat;
   background-position: left 17px top 14px;
   background-size: 20px;
   font-size: 16px;
   cursor: pointer;
 }
 
-#onboarding-tour-list > li:dir(rtl) {
+#onboarding-tour-list .onboarding-tour-item:dir(rtl) {
   background-position-x: right 17px;
 }
 
-#onboarding-tour-list > li.onboarding-complete::before {
+#onboarding-tour-list .onboarding-tour-item.onboarding-complete::before {
   content: url("img/icons_tour-complete.svg");
   position: relative;
   offset-inline-start: 3px;
   top: -10px;
   float: inline-start;
 }
 
-#onboarding-tour-list > li.onboarding-complete {
+#onboarding-tour-list .onboarding-tour-item.onboarding-complete {
   padding-inline-start: 29px;
 }
 
-#onboarding-tour-list > li.onboarding-active,
-#onboarding-tour-list > li:hover {
+#onboarding-tour-list .onboarding-tour-item.onboarding-active,
+#onboarding-tour-list .onboarding-tour-item-container:hover .onboarding-tour-item {
   color: #0A84FF;
   /* With 1px transparent outline, could see a border in the high-constrast mode */
   outline: 1px solid transparent;
 }
 
 /* Default browser tour */
 #onboarding-tour-is-default-browser-msg {
   font-size: 16px;
@@ -322,17 +328,18 @@
 /* Remove default dotted outline around buttons' text */
 .onboarding-tour-action-button::-moz-focus-inner,
 #onboarding-overlay-button::-moz-focus-inner {
   border: 0;
 }
 
 /* Keyboard focus specific outline */
 .onboarding-tour-action-button:-moz-focusring,
-#onboarding-notification-action-btn:-moz-focusring {
+#onboarding-notification-action-btn:-moz-focusring,
+#onboarding-tour-list .onboarding-tour-item:focus {
   outline: 2px solid rgba(0,149,221,0.5);
   outline-offset: 1px;
   -moz-outline-radius: 2px;
 }
 
 .onboarding-tour-action-button:hover:not([disabled]) ,
 #onboarding-notification-action-btn:hover {
   background: #0060df;
--- a/browser/extensions/onboarding/content/onboarding.js
+++ b/browser/extensions/onboarding/content/onboarding.js
@@ -367,16 +367,17 @@ class Onboarding {
 
     let { body } = this._window.document;
     this._overlayIcon = this._renderOverlayButton();
     this._overlayIcon.addEventListener("click", this);
     body.insertBefore(this._overlayIcon, body.firstChild);
 
     this._overlay = this._renderOverlay();
     this._overlay.addEventListener("click", this);
+    this._overlay.addEventListener("keypress", this);
     body.appendChild(this._overlay);
 
     this._loadJS(TOUR_AGENT_JS_URI);
 
     this._initPrefObserver();
     // Doing tour notification takes some effort. Let's do it on idle.
     this._window.requestIdleCallback(() => this._initNotification());
   }
@@ -430,26 +431,25 @@ class Onboarding {
     if (this._prefsObserved) {
       for (let [name, callback] of this._prefsObserved) {
         Services.prefs.removeObserver(name, callback);
       }
       this._prefsObserved = null;
     }
   }
 
-  handleEvent(evt) {
-    if (evt.type === "resize") {
-      this._window.cancelIdleCallback(this._resizeTimerId);
-      this._resizeTimerId =
-        this._window.requestIdleCallback(() => this._resizeUI());
-
-      return;
+  handleClick(target) {
+    let { id, classList } = target;
+    // Only containers receive pointer events in onboarding tour tab list,
+    // actual semantic tab is their first child.
+    if (classList.contains("onboarding-tour-item-container")) {
+      ({ id, classList } = target.firstChild);
     }
 
-    switch (evt.target.id) {
+    switch (id) {
       case "onboarding-overlay-button":
       case "onboarding-overlay-close-btn":
       // If the clicking target is directly on the outer-most overlay,
       // that means clicking outside the tour content area.
       // Let's toggle the overlay.
       case "onboarding-overlay":
         this.toggleOverlay();
         let selectedTour = this._tours.find(tour => !this.isTourCompleted(tour.id)) || this._tours[0];
@@ -461,25 +461,89 @@ class Onboarding {
         break;
       case "onboarding-notification-action-btn":
         let tourId = this._notificationBar.dataset.targetTourId;
         this.toggleOverlay();
         this.gotoPage(tourId);
         this._removeTourFromNotificationQueue(tourId);
         break;
     }
-    let classList = evt.target.classList;
     if (classList.contains("onboarding-tour-item")) {
-      this.gotoPage(evt.target.id);
+      this.gotoPage(id);
+      // Keep focus (not visible) on current item for potential keyboard
+      // navigation.
+      target.focus();
     } else if (classList.contains("onboarding-tour-action-button")) {
       let activeItem = this._tourItems.find(item => item.classList.contains("onboarding-active"));
       this.setToursCompleted([ activeItem.id ]);
     }
   }
 
+  handleKeypress(event) {
+    let { target, key } = event;
+    // Current focused item can be tab container if previous navigation was done
+    // via mouse.
+    if (target.classList.contains("onboarding-tour-item-container")) {
+      target = target.firstChild;
+    }
+    let targetIndex;
+    switch (key) {
+      case " ":
+      case "Enter":
+        // Assume that the handle function should be identical for keyboard
+        // activation if there is a click handler for the target.
+        if (target.classList.contains("onboarding-tour-item")) {
+          this.handleClick(target);
+          target.focus();
+        }
+        break;
+      case "ArrowUp":
+        // Go to and focus on the previous tab if it's available.
+        targetIndex = this._tourItems.indexOf(target);
+        if (targetIndex > 0) {
+          let previous = this._tourItems[targetIndex - 1];
+          this.handleClick(previous);
+          previous.focus();
+        }
+        event.preventDefault();
+        break;
+      case "ArrowDown":
+        // Go to and focus on the next tab if it's available.
+        targetIndex = this._tourItems.indexOf(target);
+        if (targetIndex > -1 && targetIndex < this._tourItems.length - 1) {
+          let next = this._tourItems[targetIndex + 1];
+          this.handleClick(next);
+          next.focus();
+        }
+        event.preventDefault();
+        break;
+      default:
+        break;
+    }
+    event.stopPropagation();
+  }
+
+  handleEvent(evt) {
+    switch (evt.type) {
+      case "resize":
+        this._window.cancelIdleCallback(this._resizeTimerId);
+        this._resizeTimerId =
+          this._window.requestIdleCallback(() => this._resizeUI());
+        break;
+      case "keypress":
+        this.handleKeypress(evt);
+        break;
+      case "click":
+        this.handleClick(evt.target);
+        break;
+      default:
+        break;
+    }
+  }
+
   destroy() {
     if (!this.uiInitialized) {
       return;
     }
     this.uiInitialized = false;
 
     this._clearPrefObserver();
     this._overlayIcon.remove();
@@ -512,21 +576,23 @@ class Onboarding {
     for (let page of this._tourPages) {
       if (page.id === targetPageId) {
         page.style.display = "";
         page.dispatchEvent(new this._window.CustomEvent("beforeshow"));
       } else {
         page.style.display = "none";
       }
     }
-    for (let li of this._tourItems) {
-      if (li.id == tourId) {
-        li.classList.add("onboarding-active");
+    for (let tab of this._tourItems) {
+      if (tab.id == tourId) {
+        tab.classList.add("onboarding-active");
+        tab.setAttribute("aria-selected", true);
       } else {
-        li.classList.remove("onboarding-active");
+        tab.classList.remove("onboarding-active");
+        tab.setAttribute("aria-selected", false);
       }
     }
 
     switch (tourId) {
       // These tours should tagged completed instantly upon showing.
       case "onboarding-tour-default-browser":
       case "onboarding-tour-sync":
       case "onboarding-tour-performance":
@@ -551,19 +617,43 @@ class Onboarding {
     });
     if (params.length > 0) {
       sendMessageToChrome("set-prefs", params);
     }
   }
 
   markTourCompletionState(tourId) {
     // We are doing lazy load so there might be no items.
-    if (this._tourItems && this._tourItems.length > 0 && this.isTourCompleted(tourId)) {
-      let targetItem = this._tourItems.find(item => item.id == tourId);
+    if (!this._tourItems || this._tourItems.length === 0) {
+      return;
+    }
+
+    let completed = this.isTourCompleted(tourId);
+    let targetItem = this._tourItems.find(item => item.id == tourId);
+    let completedTextId = `onboarding-complete-${tourId}-text`;
+    // Accessibility: Text version of the auxiliary information about the tour
+    // item completion is provided via an invisible node with an aria-label that
+    // the tab is pointing to via aria-described by.
+    let completedText = targetItem.querySelector(`#${completedTextId}`);
+    if (completed) {
       targetItem.classList.add("onboarding-complete");
+      if (!completedText) {
+        completedText = this._window.document.createElement("span");
+        completedText.id = completedTextId;
+        completedText.setAttribute("aria-label",
+          this._bundle.GetStringFromName("onboarding.complete"));
+        targetItem.appendChild(completedText);
+        targetItem.setAttribute("aria-describedby", completedTextId);
+      }
+    } else {
+      targetItem.classList.remove("onboarding-complete");
+      targetItem.removeAttribute("aria-describedby");
+      if (completedText) {
+        completedText.remove();
+      }
     }
   }
 
   _muteNotificationOnFirstSession() {
     if (Services.prefs.prefHasUserValue("browser.onboarding.notification.tour-ids-queue")) {
       // There is a queue. We had prompted before, this must not be the 1st session.
       return false;
     }
@@ -776,17 +866,17 @@ class Onboarding {
     let div = this._window.document.createElement("div");
     div.id = "onboarding-overlay";
     // We use `innerHTML` for more friendly reading.
     // The security should be fine because this is not from an external input.
     div.innerHTML = `
       <div id="onboarding-overlay-dialog">
         <header id="onboarding-header"></header>
         <nav>
-          <ul id="onboarding-tour-list"></ul>
+          <ul id="onboarding-tour-list" role="tablist"></ul>
         </nav>
         <footer id="onboarding-footer">
           <input type="checkbox" id="onboarding-tour-hidden-checkbox" /><label for="onboarding-tour-hidden-checkbox"></label>
         </footer>
         <button id="onboarding-overlay-close-btn" class="onboarding-close-btn"></button>
       </div>
     `;
 
@@ -818,40 +908,57 @@ class Onboarding {
   }
 
   _loadTours(tours) {
     let itemsFrag = this._window.document.createDocumentFragment();
     let pagesFrag = this._window.document.createDocumentFragment();
     for (let tour of tours) {
       // Create tour navigation items dynamically
       let li = this._window.document.createElement("li");
-      li.textContent = this._bundle.GetStringFromName(tour.tourNameId);
-      li.id = tour.id;
-      li.className = "onboarding-tour-item";
+      // List item should have no semantics. It is just a container for an
+      // actual tab.
+      li.setAttribute("role", "presentation");
+      li.className = "onboarding-tour-item-container";
+      // Focusable but not tabbable.
+      li.tabIndex = -1;
+
+      let tab = this._window.document.createElement("span");
+      tab.id = tour.id;
+      tab.textContent = this._bundle.GetStringFromName(tour.tourNameId);
+      tab.className = "onboarding-tour-item";
+      tab.tabIndex = 0;
+      tab.setAttribute("role", "tab");
+
+      let tourPanelId = `${tour.id}-page`;
+      tab.setAttribute("aria-controls", tourPanelId);
+
+      li.appendChild(tab);
       itemsFrag.appendChild(li);
       // Dynamically create tour pages
       let div = tour.getPage(this._window, this._bundle);
 
       // Do a traverse for elements in the page that need to be localized.
       let l10nElements = div.querySelectorAll("[data-l10n-id]");
       for (let i = 0; i < l10nElements.length; i++) {
         let element = l10nElements[i];
         // We always put brand short name as the first argument for it's the
         // only and frequently used arguments in our l10n case. Rewrite it if
         // other arguments appears.
         element.textContent = this._bundle.formatStringFromName(
                                 element.dataset.l10nId, [BRAND_SHORT_NAME], 1);
       }
 
-      div.id = `${tour.id}-page`;
+      div.id = tourPanelId;
       div.classList.add("onboarding-tour-page");
+      div.setAttribute("role", "tabpanel");
+      div.setAttribute("aria-labelledby", tour.id);
       div.style.display = "none";
       pagesFrag.appendChild(div);
       // Cache elements in arrays for later use to avoid cost of querying elements
-      this._tourItems.push(li);
+      this._tourItems.push(tab);
       this._tourPages.push(div);
 
       this.markTourCompletionState(tour.id);
     }
 
     let dialog = this._window.document.getElementById("onboarding-overlay-dialog");
     let ul = this._window.document.getElementById("onboarding-tour-list");
     ul.appendChild(itemsFrag);
--- a/browser/extensions/onboarding/locales/en-US/onboarding.properties
+++ b/browser/extensions/onboarding/locales/en-US/onboarding.properties
@@ -18,16 +18,20 @@ onboarding.overlay-icon-tooltip2=New to %S?\nLet’s get started.
 # do appropriate line breaking.
 onboarding.overlay-icon-tooltip-updated2=%S is all new.\nSee what you can do!
 # LOCALIZATION NOTE(onboarding.overlay-close-button-tooltip): The overlay close button is an icon button. This tooltip would be shown when mousing hovering on the button.
 onboarding.overlay-close-button-tooltip=Close
 onboarding.notification-icon-tooltip-updated=See what’s new!
 # LOCALIZATION NOTE(onboarding.notification-close-button-tooltip): The notification close button is an icon button. This tooltip would be shown when mousing hovering on the button.
 onboarding.notification-close-button-tooltip=Dismiss
 
+# LOCALIZATION NOTE(onboarding.complete): This string is used to describe an
+# onboarding tour item that is complete.
+onboarding.complete=Complete
+
 onboarding.tour-private-browsing=Private Browsing
 onboarding.tour-private-browsing.title2=Browse by yourself.
 # LOCALIZATION NOTE(onboarding.tour-private-browsing.description3): This string will be used in the private-browsing tour description. %S is brandShortName.
 onboarding.tour-private-browsing.description3=Want to keep something to yourself? Use Private Browsing with Tracking Protection. %S will block online trackers while you browse and won’t remember your history after you’ve ended your session.
 onboarding.tour-private-browsing.button=Show Private Browsing in Menu
 onboarding.notification.onboarding-tour-private-browsing.title=Browse by yourself.
 onboarding.notification.onboarding-tour-private-browsing.message2=Want to keep something to yourself? Use Private Browsing with Tracking Protection.
 
--- a/browser/extensions/onboarding/test/browser/browser.ini
+++ b/browser/extensions/onboarding/test/browser/browser.ini
@@ -1,13 +1,15 @@
 [DEFAULT]
 support-files =
   head.js
 
 [browser_onboarding_accessibility.js]
 [browser_onboarding_hide_all.js]
+[browser_onboarding_keyboard.js]
+skip-if = debug || os == "mac" # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control in System Keyboard Preferences
 [browser_onboarding_notification.js]
 [browser_onboarding_notification_2.js]
 [browser_onboarding_notification_3.js]
 [browser_onboarding_notification_4.js]
 [browser_onboarding_select_default_tour.js]
 [browser_onboarding_tours.js]
 [browser_onboarding_tourset.js]
new file mode 100644
--- /dev/null
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_keyboard.js
@@ -0,0 +1,61 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+ "use strict";
+
+function assertTourList(browser, args) {
+  return ContentTask.spawn(browser, args, ({ tourId, focusedId }) => {
+    let doc = content.document;
+    let items = [...doc.querySelectorAll(".onboarding-tour-item")];
+    items.forEach(item => is(item.getAttribute("aria-selected"),
+      item.id === tourId ? "true" : "false",
+      "Active item should have aria-selected set to true and inactive to false"));
+    let focused = doc.getElementById(focusedId);
+    is(focused, doc.activeElement, `Focus should be set on ${focusedId}`);
+  });
+}
+
+const TEST_DATA = [
+  { key: "VK_DOWN", expected: { tourId: TOUR_IDs[1], focusedId: TOUR_IDs[1] }},
+  { key: "VK_DOWN", expected: { tourId: TOUR_IDs[2], focusedId: TOUR_IDs[2] }},
+  { key: "VK_DOWN", expected: { tourId: TOUR_IDs[3], focusedId: TOUR_IDs[3] }},
+  { key: "VK_DOWN", expected: { tourId: TOUR_IDs[4], focusedId: TOUR_IDs[4] }},
+  { key: "VK_UP", expected: { tourId: TOUR_IDs[3], focusedId: TOUR_IDs[3] }},
+  { key: "VK_UP", expected: { tourId: TOUR_IDs[2], focusedId: TOUR_IDs[2] }},
+  { key: "VK_TAB", expected: { tourId: TOUR_IDs[2], focusedId: TOUR_IDs[3] }},
+  { key: "VK_TAB", expected: { tourId: TOUR_IDs[2], focusedId: TOUR_IDs[4] }},
+  { key: "VK_RETURN", expected: { tourId: TOUR_IDs[4], focusedId: TOUR_IDs[4] }},
+  { key: "VK_TAB", options: { shiftKey: true }, expected: { tourId: TOUR_IDs[4], focusedId: TOUR_IDs[3] }},
+  { key: "VK_TAB", options: { shiftKey: true }, expected: { tourId: TOUR_IDs[4], focusedId: TOUR_IDs[2] }},
+  // VK_SPACE does not work well with EventUtils#synthesizeKey use " " instead
+  { key: " ", expected: { tourId: TOUR_IDs[2], focusedId: TOUR_IDs[2] }}
+];
+
+add_task(async function test_tour_list_keyboard_navigation() {
+  resetOnboardingDefaultState();
+
+  info("Display onboarding overlay on the home page");
+  let tab = await openTab(ABOUT_HOME_URL);
+  await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
+  await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button",
+                                                 {}, tab.linkedBrowser);
+  await promiseOnboardingOverlayOpened(tab.linkedBrowser);
+
+  info("Checking overall overlay tablist semantics");
+  await assertOverlaySemantics(tab.linkedBrowser);
+
+  info("Set initial focus on the currently active tab");
+  await ContentTask.spawn(tab.linkedBrowser, {}, () =>
+    content.document.querySelector(".onboarding-active").focus());
+  await assertTourList(tab.linkedBrowser,
+                       { tourId: TOUR_IDs[0], focusedId: TOUR_IDs[0] });
+
+  for (let { key, options = {}, expected } of TEST_DATA) {
+    info(`Pressing ${key} to select ${expected.tourId} and have focus on ${expected.focusedId}`);
+    await BrowserTestUtils.synthesizeKey(key, options, tab.linkedBrowser);
+    await assertTourList(tab.linkedBrowser, expected);
+  }
+
+  await BrowserTestUtils.removeTab(tab);
+});
--- a/browser/extensions/onboarding/test/browser/browser_onboarding_tours.js
+++ b/browser/extensions/onboarding/test/browser/browser_onboarding_tours.js
@@ -1,22 +1,31 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
  "use strict";
 
 requestLongerTimeout(2);
 
-function assertTourCompletedStyle(tourId, expectComplete, browser) {
+function assertTourCompleted(tourId, expectComplete, browser) {
   return ContentTask.spawn(browser, { tourId, expectComplete }, function(args) {
     let item = content.document.querySelector(`#${args.tourId}.onboarding-tour-item`);
+    let completedTextId = `onboarding-complete-${args.tourId}-text`;
+    let completedText = item.querySelector(`#${completedTextId}`);
     if (args.expectComplete) {
       ok(item.classList.contains("onboarding-complete"), `Should set the complete #${args.tourId} tour with the complete style`);
+      ok(completedText, "Text label should be present for a completed item");
+      is(completedText.id, completedTextId, "Text label node should have a unique id");
+      ok(completedText.getAttribute("aria-label"), "Text label node should have an aria-label attribute set");
+      is(item.getAttribute("aria-describedby"), completedTextId,
+        "Completed item should have aria-describedby attribute set to text label node's id");
     } else {
       ok(!item.classList.contains("onboarding-complete"), `Should not set the incomplete #${args.tourId} tour with the complete style`);
+      ok(!completedText, "Text label should not be present for an incomplete item");
+      ok(!item.hasAttribute("aria-describedby"), "Incomplete item should not have aria-describedby attribute set");
     }
   });
 }
 
 add_task(async function test_set_right_tour_completed_style_on_overlay() {
   resetOnboardingDefaultState();
 
   let tourIds = TOUR_IDs;
@@ -31,18 +40,19 @@ add_task(async function test_set_right_t
     await promiseOnboardingOverlayLoaded(tab.linkedBrowser);
     await BrowserTestUtils.synthesizeMouseAtCenter("#onboarding-overlay-button", {}, tab.linkedBrowser);
     await promiseOnboardingOverlayOpened(tab.linkedBrowser);
     tabs.push(tab);
   }
 
   for (let i = tabs.length - 1; i >= 0; --i) {
     let tab = tabs[i];
+    await assertOverlaySemantics(tab.linkedBrowser);
     for (let j = 0; j < tourIds.length; ++j) {
-      await assertTourCompletedStyle(tourIds[j], j % 2 == 0, tab.linkedBrowser);
+      await assertTourCompleted(tourIds[j], j % 2 == 0, tab.linkedBrowser);
     }
     await BrowserTestUtils.removeTab(tab);
   }
 });
 
 add_task(async function test_click_action_button_to_set_tour_completed() {
   resetOnboardingDefaultState();
   const CUSTOM_TOUR_IDs = [
@@ -66,14 +76,15 @@ add_task(async function test_click_actio
 
   let completedTourId = tourIds[0];
   let expectedPrefUpdate = promisePrefUpdated(`browser.onboarding.tour.${completedTourId}.completed`, true);
   await BrowserTestUtils.synthesizeMouseAtCenter(`#${completedTourId}-page .onboarding-tour-action-button`, {}, gBrowser.selectedBrowser);
   await expectedPrefUpdate;
 
   for (let i = tabs.length - 1; i >= 0; --i) {
     let tab = tabs[i];
+    await assertOverlaySemantics(tab.linkedBrowser);
     for (let id of tourIds) {
-      await assertTourCompletedStyle(id, id == completedTourId, tab.linkedBrowser);
+      await assertTourCompleted(id, id == completedTourId, tab.linkedBrowser);
     }
     await BrowserTestUtils.removeTab(tab);
   }
 });
--- a/browser/extensions/onboarding/test/browser/head.js
+++ b/browser/extensions/onboarding/test/browser/head.js
@@ -199,8 +199,38 @@ function waitUntilWindowIdle(browser) {
   return ContentTask.spawn(browser, {}, function() {
     return new Promise(resolve => content.requestIdleCallback(resolve));
   });
 }
 
 function skipMuteNotificationOnFirstSession() {
   Preferences.set("browser.onboarding.notification.mute-duration-on-first-session-ms", 0);
 }
+
+function assertOverlaySemantics(browser) {
+  return ContentTask.spawn(browser, {}, function() {
+    let doc = content.document;
+
+    info("Checking the tablist container");
+    is(doc.getElementById("onboarding-tour-list").getAttribute("role"), "tablist",
+      "Tour list should have a tablist role argument set");
+
+    info("Checking each tour item that represents the tab");
+    let items = [...doc.querySelectorAll(".onboarding-tour-item")];
+    items.forEach(item => {
+      is(item.parentNode.getAttribute("role"), "presentation",
+        "Parent should have no semantic value");
+      is(item.getAttribute("aria-selected"),
+         item.classList.contains("onboarding-active") ? "true" : "false",
+         "Active item should have aria-selected set to true and inactive to false");
+      is(item.tabIndex, "0", "Item tab index must be set for keyboard accessibility");
+      is(item.getAttribute("role"), "tab", "Item should have a tab role argument set");
+      let tourPanelId = `${item.id}-page`;
+      is(item.getAttribute("aria-controls"), tourPanelId,
+        "Item should have aria-controls attribute point to its tabpanel");
+      let panel = doc.getElementById(tourPanelId);
+      is(panel.getAttribute("role"), "tabpanel",
+        "Tour panel should have a tabpanel role argument set");
+      is(panel.getAttribute("aria-labelledby"), item.id,
+        "Tour panel should have aria-labelledby attribute point to its tab");
+    });
+  });
+}
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -250,19 +250,16 @@ menuitem.bookmark-item {
 
 .urlbar-textbox-container {
   -moz-appearance: none;
   -moz-box-align: stretch;
 }
 .urlbar-input-box {
   margin: 0;
 }
-.urlbar-history-dropmarker {
-  -moz-appearance: toolbarbutton-dropdown;
-}
 
 /* ::::: URL Bar Zoom Reset Button ::::: */
 @keyframes urlbar-zoom-reset-pulse {
   0% {
     transform: scale(0);
   }
   75% {
     transform: scale(1.5);
--- a/browser/themes/linux/compacttheme.css
+++ b/browser/themes/linux/compacttheme.css
@@ -1,36 +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/.
 
 %include ../shared/compacttheme.inc.css
 
-:root:-moz-lwtheme-darktext {
-  --urlbar-dropmarker-url: url("chrome://browser/skin/compacttheme/urlbar-history-dropmarker.svg");
-  --urlbar-dropmarker-region: rect(0px, 11px, 14px, 0px);
-  --urlbar-dropmarker-hover-region: rect(0, 22px, 14px, 11px);
-  --urlbar-dropmarker-active-region: rect(0px, 33px, 14px, 22px);
-  --urlbar-dropmarker-2x-url: url("chrome://browser/skin/compacttheme/urlbar-history-dropmarker.svg");
-  --urlbar-dropmarker-2x-region: rect(0px, 11px, 14px, 0px);
-  --urlbar-dropmarker-hover-2x-region: rect(0, 22px, 14px, 11px);
-  --urlbar-dropmarker-active-2x-region: rect(0px, 33px, 14px, 22px);
-}
-
 /* The menubar and tabs toolbar should match the devedition theme */
 #TabsToolbar,
 #toolbar-menubar {
   -moz-appearance: none !important;
 }
 #main-menubar {
   color: var(--chrome-color);
 }
 #main-menubar > menu:not([open]) {
   color: inherit;
 }
 
-.urlbar-history-dropmarker {
-  -moz-appearance: none;
-  padding: 0 3px;
-  list-style-image: var(--urlbar-dropmarker-url);
-  -moz-image-region: var(--urlbar-dropmarker-region);
-}
 
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -20,23 +20,16 @@
   --toolbar-non-lwt-textcolor: #0c0c0d;
   --toolbar-non-lwt-bgimage: none;
   --toolbar-bgcolor: var(--toolbar-non-lwt-bgcolor);
   --toolbar-bgimage: var(--toolbar-non-lwt-bgimage);
 
   --toolbarbutton-vertical-text-padding: calc(var(--toolbarbutton-inner-padding) + 1px);
   --toolbarbutton-border-radius: 4px;
 
-  --urlbar-dropmarker-url: url("chrome://browser/skin/urlbar-history-dropmarker.png");
-  --urlbar-dropmarker-region: rect(0, 11px, 14px, 0);
-  --urlbar-dropmarker-active-region: rect(0, 22px, 14px, 11px);
-  --urlbar-dropmarker-2x-url: url("chrome://browser/skin/urlbar-history-dropmarker@2x.png");
-  --urlbar-dropmarker-2x-region: rect(0, 22px, 28px, 0);
-  --urlbar-dropmarker-active-2x-region: rect(0, 44px, 28px, 22px);
-
   --panel-separator-color: hsla(210,4%,10%,.14);
   --arrowpanel-dimmed: hsla(210,4%,10%,.07);
   --arrowpanel-dimmed-further: hsla(210,4%,10%,.12);
   --arrowpanel-dimmed-even-further: hsla(210,4%,10%,.17);
 
   --urlbar-separator-color: hsla(0,0%,16%,.2);
 }
 
@@ -415,42 +408,16 @@ toolbarpaletteitem[place="palette"] > #p
   box-shadow: var(--focus-ring-box-shadow);
   border-inline-end-style: none;
   padding-inline-end: 5px;
 }
 .urlbar-input-box {
   margin: 0;
   padding: 3px 0 2px;
 }
-.urlbar-history-dropmarker {
-  padding: 0 3px;
-  list-style-image: var(--urlbar-dropmarker-url);
-  -moz-image-region: var(--urlbar-dropmarker-region);
-}
-
-.urlbar-history-dropmarker[open="true"],
-.urlbar-history-dropmarker:hover:active {
-  -moz-image-region: var(--urlbar-dropmarker-active-region);
-}
-
-@media (min-resolution: 2dppx) {
-  .urlbar-history-dropmarker {
-    list-style-image: var(--urlbar-dropmarker-2x-url);
-    -moz-image-region: var(--urlbar-dropmarker-2x-region);
-  }
-
-  .urlbar-history-dropmarker[open="true"],
-  .urlbar-history-dropmarker:hover:active {
-    -moz-image-region: var(--urlbar-dropmarker-active-2x-region);
-  }
-
-  .urlbar-history-dropmarker > .dropmarker-icon {
-    width: 11px;
-  }
-}
 
 /* ::::: URL Bar Zoom Reset Button ::::: */
 @keyframes urlbar-zoom-reset-pulse {
   0% {
     transform: scale(0);
   }
   75% {
     transform: scale(1.5);
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -33,18 +33,16 @@ browser.jar:
   skin/classic/browser/privatebrowsing-mask-short.png
   skin/classic/browser/privatebrowsing-mask-short@2x.png
   skin/classic/browser/reload-stop-go.png
   skin/classic/browser/reload-stop-go@2x.png
   skin/classic/browser/searchbar.css
   skin/classic/browser/slowStartup-16.png
   skin/classic/browser/toolbarbutton-dropmarker.png
   skin/classic/browser/toolbarbutton-dropmarker@2x.png
-  skin/classic/browser/urlbar-history-dropmarker.png
-  skin/classic/browser/urlbar-history-dropmarker@2x.png
   skin/classic/browser/urlbar-popup-blocked.png
   skin/classic/browser/urlbar-popup-blocked@2x.png
   skin/classic/browser/webRTC-sharingDevice-menubar.png
   skin/classic/browser/webRTC-sharingDevice-menubar@2x.png
   skin/classic/browser/webRTC-sharingMicrophone-menubar.png
   skin/classic/browser/webRTC-sharingMicrophone-menubar@2x.png
   skin/classic/browser/webRTC-sharingScreen-menubar.png
   skin/classic/browser/webRTC-sharingScreen-menubar@2x.png
deleted file mode 100644
index 53bd1cb0ca3a69eb5846f44d5ab2c7bacdc70e62..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index e7d4937b8d15d764dde4440cc0abd2f7152b9610..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/themes/shared/compacttheme.inc.css
+++ b/browser/themes/shared/compacttheme.inc.css
@@ -27,24 +27,16 @@
   --chrome-nav-buttons-hover-background: hsla(240, 5%, 5%, .15);
   --chrome-nav-bar-controls-border-color: hsla(240, 5%, 5%, .3);
   --chrome-selection-color: #fff;
   --chrome-selection-background-color: #5675B9;
 
   /* Url and search bars */
   --url-and-searchbar-background-color: rgb(71, 71, 73);
   --urlbar-separator-color: #5F6670;
-  --urlbar-dropmarker-url: url("chrome://browser/skin/compacttheme/urlbar-history-dropmarker.svg");
-  --urlbar-dropmarker-region: rect(0px, 11px, 14px, 0px);
-  --urlbar-dropmarker-hover-region: rect(0, 22px, 14px, 11px);
-  --urlbar-dropmarker-active-region: rect(0px, 33px, 14px, 22px);
-  --urlbar-dropmarker-2x-url: url("chrome://browser/skin/compacttheme/urlbar-history-dropmarker.svg");
-  --urlbar-dropmarker-2x-region: rect(0px, 11px, 14px, 0px);
-  --urlbar-dropmarker-hover-2x-region: rect(0, 22px, 14px, 11px);
-  --urlbar-dropmarker-active-2x-region: rect(0px, 33px, 14px, 22px);
 }
 
 :root:-moz-lwtheme-darktext {
   --url-and-searchbar-background-color: #fff;
 
   --chrome-background-color: #E3E4E6;
   --chrome-color: #18191a;
   --chrome-secondary-background-color: #f5f6f7;
deleted file mode 100644
--- a/browser/themes/shared/compacttheme/urlbar-history-dropmarker.svg
+++ /dev/null
@@ -1,22 +0,0 @@
-<!-- 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/. -->
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="33" height="14" viewBox="0 0 33 14">
-  <defs>
-    <polygon points="0,0 5.5,7 11,0" id="dropmarker-shape"/>
-  </defs>
-  <style>
-    use {
-      fill: #b6babf;
-    }
-    .hover {
-      fill: #61bdeb;
-    }
-    .active {
-      fill: #39ace6;
-    }
-  </style>
-  <use xlink:href="#dropmarker-shape" style="transform: translate(0, 4px)"/>
-  <use xlink:href="#dropmarker-shape" style="transform: translate(11px, 4px)" class="hover"/>
-  <use xlink:href="#dropmarker-shape" style="transform: translate(22px, 4px)" class="active"/>
-</svg>
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -252,10 +252,9 @@
   skin/classic/browser/places/unfiledBookmarks.svg             (../shared/places/unfiledBookmarks.svg)
   skin/classic/browser/privatebrowsing/aboutPrivateBrowsing.css (../shared/privatebrowsing/aboutPrivateBrowsing.css)
   skin/classic/browser/privatebrowsing/favicon.svg             (../shared/privatebrowsing/favicon.svg)
   skin/classic/browser/privatebrowsing/private-browsing.svg    (../shared/privatebrowsing/private-browsing.svg)
   skin/classic/browser/privatebrowsing/tracking-protection-off.svg (../shared/privatebrowsing/tracking-protection-off.svg)
   skin/classic/browser/privatebrowsing/tracking-protection.svg (../shared/privatebrowsing/tracking-protection.svg)
   skin/classic/browser/compacttheme/loading-inverted.png (../shared/compacttheme/loading-inverted.png)
   skin/classic/browser/compacttheme/loading-inverted@2x.png (../shared/compacttheme/loading-inverted@2x.png)
-  skin/classic/browser/compacttheme/urlbar-history-dropmarker.svg (../shared/compacttheme/urlbar-history-dropmarker.svg)
   skin/classic/browser/urlbar-tab.svg                          (../shared/urlbar-tab.svg)
--- a/browser/themes/shared/toolbarbuttons.inc.css
+++ b/browser/themes/shared/toolbarbuttons.inc.css
@@ -275,10 +275,10 @@ toolbarbutton.bookmark-item:not(.subview
 
 /* Force the display of the label for bookmarks */
 .bookmark-item > .toolbarbutton-text,
 #personal-bookmarks[cui-areatype="toolbar"] > #bookmarks-toolbar-placeholder > .toolbarbutton-text {
   display: -moz-box !important;
 }
 
 #PlacesToolbarItems > .bookmark-item > .toolbarbutton-icon[label]:not([label=""]) {
-  margin-inline-end: 5px;
+  margin-inline-end: 4px;
 }
--- a/browser/themes/shared/urlbar-searchbar.inc.css
+++ b/browser/themes/shared/urlbar-searchbar.inc.css
@@ -102,30 +102,34 @@
   list-style-image: url("chrome://browser/skin/device-desktop.svg");
 }
 
 #pageAction-panel-sendToDevice-fxa,
 #pageAction-urlbar-sendToDevice-fxa {
   list-style-image: url("chrome://browser/skin/sync.svg");
 }
 
+/* URL bar and page action buttons */
+
 .urlbar-history-dropmarker {
+  -moz-appearance: none;
+  list-style-image: url(chrome://browser/skin/arrow-dropdown-16.svg);
+  -moz-context-properties: fill;
+  fill: currentColor;
   transition: opacity 0.15s ease;
 }
 
 #urlbar[switchingtabs] > .urlbar-textbox-container > .urlbar-history-dropmarker {
   transition: none;
 }
 
 #navigator-toolbox:not(:hover) > #nav-bar:not([customizing="true"]) > #nav-bar-customization-target > #urlbar-container > #urlbar:not([focused]) > .urlbar-textbox-container > .urlbar-history-dropmarker {
   opacity: 0;
 }
 
-/* Page action urlbar buttons */
-
 #page-action-buttons {
   -moz-box-align: center;
   /* Add more space between the last icon and the urlbar's edge. */
   margin-inline-end: 3px;
 }
 
 .urlbar-icon {
   padding: 0 6px;
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -19,25 +19,16 @@
   --toolbar-non-lwt-textcolor: -moz-dialogtext;
   --toolbar-non-lwt-bgimage: linear-gradient(rgba(255,255,255,.15), rgba(255,255,255,.15));
   --toolbar-bgcolor: var(--toolbar-non-lwt-bgcolor);
   --toolbar-bgimage: var(--toolbar-non-lwt-bgimage);
 
   --toolbarbutton-vertical-text-padding: calc(var(--toolbarbutton-inner-padding) - 1px);
   --toolbarbutton-border-radius: 2px;
 
-  --urlbar-dropmarker-url: url("chrome://browser/skin/urlbar-history-dropmarker.png");
-  --urlbar-dropmarker-region: rect(0px, 11px, 14px, 0px);
-  --urlbar-dropmarker-hover-region: rect(0px, 22px, 14px, 11px);
-  --urlbar-dropmarker-active-region: rect(0px, 33px, 14px, 22px);
-  --urlbar-dropmarker-2x-url: url("chrome://browser/skin/urlbar-history-dropmarker@2x.png");
-  --urlbar-dropmarker-2x-region: rect(0, 22px, 28px, 0);
-  --urlbar-dropmarker-hover-2x-region: rect(0, 44px, 28px, 22px);
-  --urlbar-dropmarker-active-2x-region: rect(0, 66px, 28px, 44px);
-
   --panel-separator-color: ThreeDLightShadow;
   --arrowpanel-dimmed: hsla(0,0%,80%,.3);
   --arrowpanel-dimmed-further: hsla(0,0%,80%,.45);
   --arrowpanel-dimmed-even-further: hsla(0,0%,80%,.8);
 
   --urlbar-separator-color: ThreeDLightShadow;
 }
 
@@ -626,57 +617,16 @@ html|*.urlbar-input:-moz-lwtheme::placeh
 
 /* identity box */
 
 #identity-box:not(:active):-moz-focusring {
   outline: 1px dotted;
   outline-offset: -3px;
 }
 
-/* Location bar dropmarker */
-
-.urlbar-history-dropmarker {
-  -moz-appearance: none;
-  padding: 0 3px;
-  background-color: transparent;
-  border: none;
-  width: auto;
-  list-style-image: var(--urlbar-dropmarker-url);
-  -moz-image-region: var(--urlbar-dropmarker-region);
-}
-
-.urlbar-history-dropmarker:hover {
-  -moz-image-region: var(--urlbar-dropmarker-hover-region);
-}
-
-.urlbar-history-dropmarker:hover:active,
-.urlbar-history-dropmarker[open="true"] {
-  -moz-image-region: var(--urlbar-dropmarker-active-region);
-}
-
-@media (min-resolution: 1.1dppx) {
-  .urlbar-history-dropmarker {
-    list-style-image: var(--urlbar-dropmarker-2x-url);
-    -moz-image-region: var(--urlbar-dropmarker-2x-region);
-  }
-
-  .urlbar-history-dropmarker:hover {
-    -moz-image-region: var(--urlbar-dropmarker-hover-2x-region);
-  }
-
-  .urlbar-history-dropmarker[open="true"],
-  .urlbar-history-dropmarker:hover:active {
-    -moz-image-region: var(--urlbar-dropmarker-active-2x-region);
-  }
-
-  .urlbar-history-dropmarker > .dropmarker-icon {
-    width: 11px;
-  }
-}
-
 /* page proxy icon */
 
 %include ../shared/identity-block/identity-block.inc.css
 
 /* autocomplete */
 
 %include ../shared/autocomplete.inc.css
 
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -35,20 +35,16 @@ browser.jar:
   skin/classic/browser/reload-stop-go-win7@2x.png
   skin/classic/browser/searchbar.css
   skin/classic/browser/setDesktopBackground.css
   skin/classic/browser/slowStartup-16.png
   skin/classic/browser/sync-desktopIcon.svg  (../shared/sync-desktopIcon.svg)
   skin/classic/browser/sync-mobileIcon.svg  (../shared/sync-mobileIcon.svg)
   skin/classic/browser/toolbarbutton-dropdown-arrow-win7.png
   skin/classic/browser/urlbar-popup-blocked.png
-  skin/classic/browser/urlbar-history-dropmarker.png
-  skin/classic/browser/urlbar-history-dropmarker@2x.png
-  skin/classic/browser/urlbar-history-dropmarker-win7.png
-  skin/classic/browser/urlbar-history-dropmarker-win7@2x.png
   skin/classic/browser/webRTC-indicator.css  (../shared/webRTC-indicator.css)
 * skin/classic/browser/controlcenter/panel.css                 (controlcenter/panel.css)
   skin/classic/browser/customizableui/menu-arrow.svg           (customizableui/menu-arrow.svg)
 * skin/classic/browser/customizableui/panelUI.css       (customizableui/panelUI.css)
 * skin/classic/browser/downloads/allDownloadsViewOverlay.css   (downloads/allDownloadsViewOverlay.css)
   skin/classic/browser/downloads/download-glow-menuPanel.png   (downloads/download-glow-menuPanel.png)
   skin/classic/browser/downloads/download-glow-menuPanel-win7.png   (downloads/download-glow-menuPanel-win7.png)
 * skin/classic/browser/downloads/downloads.css                 (downloads/downloads.css)
@@ -107,18 +103,16 @@ browser.jar:
 % override chrome://browser/skin/feeds/videoFeedIcon.png              chrome://browser/skin/feeds/feedIcon.png
 % override chrome://browser/skin/feeds/videoFeedIcon16.png            chrome://browser/skin/feeds/feedIcon16.png
 
 % override chrome://browser/skin/privatebrowsing-mask-tabstrip.png    chrome://browser/skin/privatebrowsing-mask-tabstrip-win7.png  os=WINNT osversion<=6.1
 % override chrome://browser/skin/privatebrowsing-mask-titlebar.png    chrome://browser/skin/privatebrowsing-mask-titlebar-win7.png  os=WINNT osversion<=6.1
 % override chrome://browser/skin/reload-stop-go.png                   chrome://browser/skin/reload-stop-go-win7.png                 os=WINNT osversion<=6.1
 % override chrome://browser/skin/reload-stop-go@2x.png                chrome://browser/skin/reload-stop-go-win7@2x.png              os=WINNT osversion<=6.1
 % override chrome://browser/skin/toolbarbutton-dropdown-arrow.png     chrome://browser/skin/toolbarbutton-dropdown-arrow-win7.png   os=WINNT osversion<=6.1
-% override chrome://browser/skin/urlbar-history-dropmarker.png        chrome://browser/skin/urlbar-history-dropmarker-win7.png      os=WINNT osversion<=6.1
-% override chrome://browser/skin/urlbar-history-dropmarker@2x.png     chrome://browser/skin/urlbar-history-dropmarker-win7@2x.png   os=WINNT osversion<=6.1
 % override chrome://browser/skin/downloads/download-glow-menuPanel.png  chrome://browser/skin/downloads/download-glow-menuPanel-win7.png os=WINNT osversion<=6.1
 
 % override chrome://browser/skin/tabbrowser/tab-background-start.png     chrome://browser/skin/tabbrowser/tab-background-start-preWin10.png     os=WINNT osversion<=6.3
 % override chrome://browser/skin/tabbrowser/tab-background-start@2x.png  chrome://browser/skin/tabbrowser/tab-background-start-preWin10@2x.png  os=WINNT osversion<=6.3
 % override chrome://browser/skin/tabbrowser/tab-background-middle.png    chrome://browser/skin/tabbrowser/tab-background-middle-preWin10.png    os=WINNT osversion<=6.3
 % override chrome://browser/skin/tabbrowser/tab-background-middle@2x.png chrome://browser/skin/tabbrowser/tab-background-middle-preWin10@2x.png os=WINNT osversion<=6.3
 % override chrome://browser/skin/tabbrowser/tab-background-end.png       chrome://browser/skin/tabbrowser/tab-background-end-preWin10.png       os=WINNT osversion<=6.3
 % override chrome://browser/skin/tabbrowser/tab-background-end@2x.png    chrome://browser/skin/tabbrowser/tab-background-end-preWin10@2x.png    os=WINNT osversion<=6.3
deleted file mode 100644
index b03338822c37e8924e33ce1f09cd242374560b63..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index bff2997f85317e6a59ba98ddba282e6db722cdc9..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 01432f7e9bf185a810e9ae3d71729ffa9b618a82..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 6e710139a5082931f6d86b601901e9ae9e300238..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/chrome/nsChromeRegistry.cpp
+++ b/chrome/nsChromeRegistry.cpp
@@ -651,17 +651,17 @@ nsChromeRegistry::GetDirectionForLocale(
   // set, check the same preference but with just the first two characters of
   // the locale. If that isn't set, default to left-to-right.
   nsAutoCString prefString = NS_LITERAL_CSTRING("intl.uidirection.") + aLocale;
   nsCOMPtr<nsIPrefBranch> prefBranch (do_GetService(NS_PREFSERVICE_CONTRACTID));
   if (!prefBranch) {
     return false;
   }
 
-  nsXPIDLCString dir;
+  nsCString dir;
   prefBranch->GetCharPref(prefString.get(), getter_Copies(dir));
   if (dir.IsEmpty()) {
     int32_t hyphen = prefString.FindChar('-');
     if (hyphen >= 1) {
       nsAutoCString shortPref(Substring(prefString, 0, hyphen));
       prefBranch->GetCharPref(shortPref.get(), getter_Copies(dir));
     }
   }
--- a/chrome/nsChromeRegistryChrome.cpp
+++ b/chrome/nsChromeRegistryChrome.cpp
@@ -123,17 +123,17 @@ nsChromeRegistryChrome::Init()
     } else {
       prefs = do_QueryInterface(prefserv);
     }
   }
 
   if (!prefs) {
     NS_WARNING("Could not get pref service!");
   } else {
-    nsXPIDLCString provider;
+    nsCString provider;
     rv = prefs->GetCharPref(SELECTED_SKIN_PREF, getter_Copies(provider));
     if (NS_SUCCEEDED(rv))
       mSelectedSkin = provider;
 
     rv = prefs->AddObserver(SELECTED_SKIN_PREF, this, true);
   }
 
   nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
@@ -270,17 +270,17 @@ nsChromeRegistryChrome::Observe(nsISuppo
 
   if (!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic)) {
     nsCOMPtr<nsIPrefBranch> prefs (do_QueryInterface(aSubject));
     NS_ASSERTION(prefs, "Bad observer call!");
 
     NS_ConvertUTF16toUTF8 pref(someData);
 
     if (pref.EqualsLiteral(SELECTED_SKIN_PREF)) {
-      nsXPIDLCString provider;
+      nsCString provider;
       rv = prefs->GetCharPref(pref.get(), getter_Copies(provider));
       if (NS_FAILED(rv)) {
         NS_ERROR("Couldn't get new skin pref!");
         return rv;
       }
 
       mSelectedSkin = provider;
       RefreshSkins();
--- a/devtools/client/debugger/test/mochitest/browser2.ini
+++ b/devtools/client/debugger/test/mochitest/browser2.ini
@@ -303,25 +303,25 @@ skip-if = e10s && debug
 [browser_dbg_sources-webext-contentscript.js]
 [browser_dbg_split-console-paused-reload.js]
 skip-if = true # Bug 1288348 - previously e10s && debug
 [browser_dbg_stack-01.js]
 skip-if = e10s && debug
 [browser_dbg_stack-02.js]
 skip-if = e10s && debug
 [browser_dbg_stack-03.js]
-skip-if = e10s # TODO
+skip-if = e10s || (!e10s && os == "win") # TODO, win !e10s: Bug 1391369
 [browser_dbg_stack-04.js]
-skip-if = e10s && debug
+skip-if = e10s && debug || (!e10s && os == "win") # Bug 1391369
 [browser_dbg_stack-05.js]
-skip-if = e10s && (debug || asan) # timeouts
+skip-if = e10s && (debug || asan) || (!e10s && os == "win") # timeouts, Bug 1391369
 [browser_dbg_stack-06.js]
-skip-if = e10s && debug
+skip-if = e10s && debug || (!e10s && os == "win") # Bug 1391369
 [browser_dbg_stack-07.js]
-skip-if = e10s && debug
+skip-if = e10s && debug || (!e10s && os == "win") # Bug 1391369
 [browser_dbg_stack-contextmenu-01.js]
 skip-if = e10s && debug
 [browser_dbg_stack-contextmenu-02.js]
 subsuite = clipboard
 skip-if = (e10s && debug) || (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_dbg_step-out.js]
 skip-if = e10s && debug
 [browser_dbg_tabactor-01.js]
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -134,16 +134,17 @@ skip-if = (os == 'linux' && debug && bit
 [browser_net_pane-toggle.js]
 [browser_net_persistent_logs.js]
 [browser_net_post-data-01.js]
 [browser_net_post-data-02.js]
 [browser_net_post-data-03.js]
 [browser_net_post-data-04.js]
 [browser_net_prefs-and-l10n.js]
 [browser_net_prefs-reload.js]
+skip-if = os == 'win' # bug 1391264
 [browser_net_raw_headers.js]
 [browser_net_reload-button.js]
 [browser_net_reload-markers.js]
 [browser_net_req-resp-bodies.js]
 [browser_net_resend_cors.js]
 [browser_net_resend_headers.js]
 [browser_net_resend.js]
 [browser_net_security-details.js]
--- a/dom/base/nsContentAreaDragDrop.cpp
+++ b/dom/base/nsContentAreaDragDrop.cpp
@@ -575,17 +575,17 @@ DragDataProducer::Produce(DataTransfer* 
           imgRequest->GetURI(getter_AddRefs(imgUri));
 
           nsCOMPtr<nsIURL> imgUrl(do_QueryInterface(imgUri));
 
           if (imgUrl) {
             nsAutoCString extension;
             imgUrl->GetFileExtension(extension);
 
-            nsXPIDLCString mimeType;
+            nsCString mimeType;
             imgRequest->GetMimeType(getter_Copies(mimeType));
 
             nsCOMPtr<nsIMIMEInfo> mimeInfo;
             mimeService->GetFromTypeAndExtension(mimeType, EmptyCString(),
                                                  getter_AddRefs(mimeInfo));
 
             if (mimeInfo) {
               nsAutoCString spec;
--- a/dom/base/nsCopySupport.cpp
+++ b/dom/base/nsCopySupport.cpp
@@ -623,17 +623,17 @@ static nsresult AppendImagePromise(nsITr
 
   nsCOMPtr<nsIURL> imgUrl = do_QueryInterface(imgUri);
   NS_ENSURE_TRUE(imgUrl, NS_OK);
 
   nsAutoCString extension;
   rv = imgUrl->GetFileExtension(extension);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsXPIDLCString mimeType;
+  nsCString mimeType;
   rv = aImgRequest->GetMimeType(getter_Copies(mimeType));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIMIMEInfo> mimeInfo;
   mimeService->GetFromTypeAndExtension(mimeType, EmptyCString(),
                                        getter_AddRefs(mimeInfo));
   NS_ENSURE_TRUE(mimeInfo, NS_OK);
 
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -2997,17 +2997,17 @@ nsDOMWindowUtils::GetUnanimatedComputedS
     return NS_ERROR_INVALID_ARG;
   }
 
   nsIPresShell* shell = GetPresShell();
   if (!shell) {
     return NS_ERROR_FAILURE;
   }
 
-  nsIAtom* pseudo = nsCSSPseudoElements::GetPseudoAtom(aPseudoElement);
+  nsCOMPtr<nsIAtom> pseudo = nsCSSPseudoElements::GetPseudoAtom(aPseudoElement);
   RefPtr<nsStyleContext> styleContext =
     nsComputedDOMStyle::GetUnanimatedStyleContextNoFlush(element,
                                                          pseudo, shell);
 
   if (styleContext->IsServo()) {
     RefPtr<RawServoAnimationValue> value =
       Servo_ComputedValues_ExtractAnimationValue(styleContext->AsServo(),
                                                  propertyID).Consume();
--- a/dom/base/nsGlobalWindowCommands.cpp
+++ b/dom/base/nsGlobalWindowCommands.cpp
@@ -778,17 +778,17 @@ nsClipboardGetContentsCommand::IsClipboa
 
 nsresult
 nsClipboardGetContentsCommand::DoClipboardCommand(const char *aCommandName, nsIContentViewerEdit* aEdit, nsICommandParams* aParams)
 {
   NS_ENSURE_ARG(aParams);
 
   nsAutoCString mimeType("text/plain");
 
-  nsXPIDLCString format;    // nsICommandParams needs to use nsACString
+  nsCString format;    // nsICommandParams needs to use nsACString
   if (NS_SUCCEEDED(aParams->GetCStringValue("format", getter_Copies(format))))
     mimeType.Assign(format);
 
   bool selectionOnly = false;
   aParams->GetBooleanValue("selection_only", &selectionOnly);
 
   nsAutoString contents;
   nsresult rv = aEdit->GetContents(mimeType.get(), selectionOnly, contents);
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -2411,40 +2411,66 @@ static void
 SetGCParameter(JSGCParamKey aParam, uint32_t aValue)
 {
   AutoJSAPI jsapi;
   jsapi.Init();
   JS_SetGCParameter(jsapi.cx(), aParam, aValue);
 }
 
 static void
-SetMemoryHighWaterMarkPrefChangedCallback(const char* aPrefName, void* aClosure)
+ResetGCParameter(JSGCParamKey aParam)
 {
-  int32_t highwatermark = Preferences::GetInt(aPrefName, 128);
-  SetGCParameter(JSGC_MAX_MALLOC_BYTES,
-                 highwatermark * 1024L * 1024L);
+  AutoJSAPI jsapi;
+  jsapi.Init();
+  JS_ResetGCParameter(jsapi.cx(), aParam);
 }
 
 static void
-SetMemoryMaxPrefChangedCallback(const char* aPrefName, void* aClosure)
+SetMemoryPrefChangedCallbackMB(const char* aPrefName, void* aClosure)
 {
-  int32_t pref = Preferences::GetInt(aPrefName, -1);
+  int32_t prefMB = Preferences::GetInt(aPrefName, -1);
   // handle overflow and negative pref values
-  CheckedInt<uint32_t> max = CheckedInt<uint32_t>(pref) * 1024 * 1024;
-  SetGCParameter(JSGC_MAX_BYTES, max.isValid() ? max.value() : -1);
+  CheckedInt<int32_t> prefB = CheckedInt<int32_t>(prefMB) * 1024 * 1024;
+  if (prefB.isValid() && prefB.value() >= 0) {
+    SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, prefB.value());
+  } else {
+    ResetGCParameter((JSGCParamKey)(uintptr_t)aClosure);
+  }
 }
 
 static void
 SetMemoryNurseryMaxPrefChangedCallback(const char* aPrefName, void* aClosure)
 {
+  int32_t prefMB = Preferences::GetInt(aPrefName, -1);
+  // handle overflow and negative pref values
+  CheckedInt<int32_t> prefB = CheckedInt<int32_t>(prefMB) * 1024;
+  if (prefB.isValid() && prefB.value() >= 0) {
+    SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, prefB.value());
+  } else {
+    ResetGCParameter((JSGCParamKey)(uintptr_t)aClosure);
+  }
+}
+
+static void
+SetMemoryPrefChangedCallbackInt(const char* aPrefName, void* aClosure)
+{
   int32_t pref = Preferences::GetInt(aPrefName, -1);
   // handle overflow and negative pref values
-  CheckedInt<uint32_t> max = CheckedInt<uint32_t>(pref) * 1024;
-  SetGCParameter(JSGC_MAX_NURSERY_BYTES,
-    max.isValid() ? max.value() : JS::DefaultNurseryBytes);
+  if (pref >= 0 && pref < 10000) {
+    SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, pref);
+  } else {
+    ResetGCParameter((JSGCParamKey)(uintptr_t)aClosure);
+  }
+}
+
+static void
+SetMemoryPrefChangedCallbackBool(const char* aPrefName, void* aClosure)
+{
+  bool pref = Preferences::GetBool(aPrefName);
+  SetGCParameter((JSGCParamKey)(uintptr_t)aClosure, pref);
 }
 
 static void
 SetMemoryGCModePrefChangedCallback(const char* aPrefName, void* aClosure)
 {
   bool enableZoneGC = Preferences::GetBool("javascript.options.mem.gc_per_zone");
   bool enableIncrementalGC = Preferences::GetBool("javascript.options.mem.gc_incremental");
   JSGCMode mode;
@@ -2462,59 +2488,22 @@ SetMemoryGCModePrefChangedCallback(const
 static void
 SetMemoryGCSliceTimePrefChangedCallback(const char* aPrefName, void* aClosure)
 {
   int32_t pref = Preferences::GetInt(aPrefName, -1);
   // handle overflow and negative pref values
   if (pref > 0 && pref < 100000) {
     sActiveIntersliceGCBudget = pref;
     SetGCParameter(JSGC_SLICE_TIME_BUDGET, pref);
-  }
-}
-
-static void
-SetMemoryGCCompactingPrefChangedCallback(const char* aPrefName, void* aClosure)
-{
-  bool pref = Preferences::GetBool(aPrefName);
-  SetGCParameter(JSGC_COMPACTING_ENABLED, pref);
-}
-
-static void
-SetMemoryGCPrefChangedCallback(const char* aPrefName, void* aClosure)
-{
-  int32_t pref = Preferences::GetInt(aPrefName, -1);
-  // handle overflow and negative pref values
-  if (pref >= 0 && pref < 10000) {
-    SetGCParameter((JSGCParamKey)(intptr_t)aClosure, pref);
+  } else {
+    ResetGCParameter(JSGC_SLICE_TIME_BUDGET);
   }
 }
 
 static void
-SetMemoryGCDynamicHeapGrowthPrefChangedCallback(const char* aPrefName, void* aClosure)
-{
-  bool pref = Preferences::GetBool(aPrefName);
-  SetGCParameter(JSGC_DYNAMIC_HEAP_GROWTH, pref);
-}
-
-static void
-SetMemoryGCDynamicMarkSlicePrefChangedCallback(const char* aPrefName, void* aClosure)
-{
-  bool pref = Preferences::GetBool(aPrefName);
-  SetGCParameter(JSGC_DYNAMIC_MARK_SLICE, pref);
-}
-
-static void
-SetMemoryGCRefreshFrameSlicesEnabledPrefChangedCallback(const char* aPrefName, void* aClosure)
-{
-  bool pref = Preferences::GetBool(aPrefName);
-  SetGCParameter(JSGC_REFRESH_FRAME_SLICES_ENABLED, pref);
-}
-
-
-static void
 SetIncrementalCCPrefChangedCallback(const char* aPrefName, void* aClosure)
 {
   bool pref = Preferences::GetBool(aPrefName);
   sIncrementalCC = pref;
 }
 
 static bool
 AsmJSCacheOpenEntryForRead(JS::Handle<JSObject*> aGlobal,
@@ -2539,65 +2528,68 @@ AsmJSCacheOpenEntryForWrite(JS::Handle<J
                             intptr_t* aHandle)
 {
   nsIPrincipal* principal =
     nsJSPrincipals::get(JS_GetCompartmentPrincipals(js::GetObjectCompartment(aGlobal)));
   return asmjscache::OpenEntryForWrite(principal, aBegin, aEnd, aSize, aMemory,
                                        aHandle);
 }
 
-class AsyncTaskRunnable final : public Runnable
+class JSDispatchableRunnable final : public Runnable
 {
-  ~AsyncTaskRunnable()
+  ~JSDispatchableRunnable()
   {
-    MOZ_ASSERT(!mTask);
+    MOZ_ASSERT(!mDispatchable);
   }
 
 public:
-  explicit AsyncTaskRunnable(JS::AsyncTask* aTask)
-    : mozilla::Runnable("AsyncTaskRunnable")
-    , mTask(aTask)
+  explicit JSDispatchableRunnable(JS::Dispatchable* aDispatchable)
+    : mozilla::Runnable("JSDispatchableRunnable")
+    , mDispatchable(aDispatchable)
   {
-    MOZ_ASSERT(mTask);
+    MOZ_ASSERT(mDispatchable);
   }
 
 protected:
   NS_IMETHOD Run() override
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     AutoJSAPI jsapi;
     jsapi.Init();
-    mTask->finish(jsapi.cx());
-    mTask = nullptr;  // mTask may delete itself
+
+    JS::Dispatchable::MaybeShuttingDown maybeShuttingDown =
+      sShuttingDown ? JS::Dispatchable::ShuttingDown : JS::Dispatchable::NotShuttingDown;
+
+    mDispatchable->run(jsapi.cx(), maybeShuttingDown);
+    mDispatchable = nullptr;  // mDispatchable may delete itself
 
     return NS_OK;
   }
 
 private:
-  JS::AsyncTask* mTask;
+  JS::Dispatchable* mDispatchable;
 };
 
 static bool
-StartAsyncTaskCallback(JSContext* aCx, JS::AsyncTask* aTask)
+DispatchToEventLoop(void* closure, JS::Dispatchable* aDispatchable)
 {
-  return true;
-}
-
-static bool
-FinishAsyncTaskCallback(JS::AsyncTask* aTask)
-{
-  // AsyncTasks can finish during shutdown so cannot simply
-  // NS_DispatchToMainThread.
+  MOZ_ASSERT(!closure);
+
+  // This callback may execute either on the main thread or a random JS-internal
+  // helper thread. This callback can be called during shutdown so we cannot
+  // simply NS_DispatchToMainThread. Failure during shutdown is expected and
+  // properly handled by the JS engine.
+
   nsCOMPtr<nsIEventTarget> mainTarget = GetMainThreadEventTarget();
   if (!mainTarget) {
     return false;
   }
 
-  RefPtr<AsyncTaskRunnable> r = new AsyncTaskRunnable(aTask);
+  RefPtr<JSDispatchableRunnable> r = new JSDispatchableRunnable(aDispatchable);
   MOZ_ALWAYS_SUCCEEDS(mainTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL));
   return true;
 }
 
 void
 nsJSContext::EnsureStatics()
 {
   if (sIsInitialized) {
@@ -2619,84 +2611,91 @@ nsJSContext::EnsureStatics()
   static const JS::AsmJSCacheOps asmJSCacheOps = {
     AsmJSCacheOpenEntryForRead,
     asmjscache::CloseEntryForRead,
     AsmJSCacheOpenEntryForWrite,
     asmjscache::CloseEntryForWrite
   };
   JS::SetAsmJSCacheOps(jsapi.cx(), &asmJSCacheOps);
 
-  JS::SetAsyncTaskCallbacks(jsapi.cx(), StartAsyncTaskCallback, FinishAsyncTaskCallback);
+  JS::InitDispatchToEventLoop(jsapi.cx(), DispatchToEventLoop, nullptr);
 
   // Set these global xpconnect options...
-  Preferences::RegisterCallbackAndCall(SetMemoryHighWaterMarkPrefChangedCallback,
-                                       "javascript.options.mem.high_water_mark");
-
-  Preferences::RegisterCallbackAndCall(SetMemoryMaxPrefChangedCallback,
-                                       "javascript.options.mem.max");
+  Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackMB,
+                                       "javascript.options.mem.high_water_mark",
+                                       (void*)JSGC_MAX_MALLOC_BYTES);
+
+  Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackMB,
+                                       "javascript.options.mem.max",
+                                       (void*)JSGC_MAX_BYTES);
   Preferences::RegisterCallbackAndCall(SetMemoryNurseryMaxPrefChangedCallback,
-                                       "javascript.options.mem.nursery.max_kb");
+                                       "javascript.options.mem.nursery.max_kb",
+                                       (void*)JSGC_MAX_NURSERY_BYTES);
 
   Preferences::RegisterCallbackAndCall(SetMemoryGCModePrefChangedCallback,
                                        "javascript.options.mem.gc_per_zone");
 
   Preferences::RegisterCallbackAndCall(SetMemoryGCModePrefChangedCallback,
                                        "javascript.options.mem.gc_incremental");
 
   Preferences::RegisterCallbackAndCall(SetMemoryGCSliceTimePrefChangedCallback,
                                        "javascript.options.mem.gc_incremental_slice_ms");
 
-  Preferences::RegisterCallbackAndCall(SetMemoryGCCompactingPrefChangedCallback,
-                                       "javascript.options.mem.gc_compacting");
-
-  Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
+  Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackBool,
+                                       "javascript.options.mem.gc_compacting",
+                                       (void *)JSGC_COMPACTING_ENABLED);
+
+  Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackInt,
                                        "javascript.options.mem.gc_high_frequency_time_limit_ms",
                                        (void *)JSGC_HIGH_FREQUENCY_TIME_LIMIT);
 
-  Preferences::RegisterCallbackAndCall(SetMemoryGCDynamicMarkSlicePrefChangedCallback,
-                                       "javascript.options.mem.gc_dynamic_mark_slice");
-
-  Preferences::RegisterCallbackAndCall(SetMemoryGCRefreshFrameSlicesEnabledPrefChangedCallback,
-                                       "javascript.options.mem.gc_refresh_frame_slices_enabled");
-
-  Preferences::RegisterCallbackAndCall(SetMemoryGCDynamicHeapGrowthPrefChangedCallback,
-                                       "javascript.options.mem.gc_dynamic_heap_growth");
-
-  Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
+  Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackBool,
+                                       "javascript.options.mem.gc_dynamic_mark_slice",
+                                       (void *)JSGC_DYNAMIC_MARK_SLICE);
+
+  Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackBool,
+                                       "javascript.options.mem.gc_refresh_frame_slices_enabled",
+                                       (void *)JSGC_REFRESH_FRAME_SLICES_ENABLED);
+
+  Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackBool,
+                                       "javascript.options.mem.gc_dynamic_heap_growth",
+                                       (void *)JSGC_DYNAMIC_HEAP_GROWTH);
+
+  Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackInt,
                                        "javascript.options.mem.gc_low_frequency_heap_growth",
                                        (void *)JSGC_LOW_FREQUENCY_HEAP_GROWTH);
 
-  Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
+  Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackInt,
                                        "javascript.options.mem.gc_high_frequency_heap_growth_min",
                                        (void *)JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MIN);
 
-  Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
+  Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackInt,
                                        "javascript.options.mem.gc_high_frequency_heap_growth_max",
                                        (void *)JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MAX);
 
-  Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
+  Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackInt,
                                        "javascript.options.mem.gc_high_frequency_low_limit_mb",
                                        (void *)JSGC_HIGH_FREQUENCY_LOW_LIMIT);
 
-  Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
+  Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackInt,
                                        "javascript.options.mem.gc_high_frequency_high_limit_mb",
                                        (void *)JSGC_HIGH_FREQUENCY_HIGH_LIMIT);
 
-  Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
+  Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackInt,
                                        "javascript.options.mem.gc_allocation_threshold_mb",
                                        (void *)JSGC_ALLOCATION_THRESHOLD);
 
   Preferences::RegisterCallbackAndCall(SetIncrementalCCPrefChangedCallback,
                                        "dom.cycle_collector.incremental");
 
-  Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
+  Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackInt,
                                        "javascript.options.mem.gc_min_empty_chunk_count",
                                        (void *)JSGC_MIN_EMPTY_CHUNK_COUNT);
 
-  Preferences::RegisterCallbackAndCall(SetMemoryGCPrefChangedCallback,
+  Preferences::RegisterCallbackAndCall(SetMemoryPrefChangedCallbackInt,
                                        "javascript.options.mem.gc_max_empty_chunk_count",
                                        (void *)JSGC_MAX_EMPTY_CHUNK_COUNT);
 
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   if (!obs) {
     MOZ_CRASH();
   }
 
--- a/dom/base/nsOpenURIInFrameParams.cpp
+++ b/dom/base/nsOpenURIInFrameParams.cpp
@@ -3,20 +3,30 @@
 /* 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 "nsOpenURIInFrameParams.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/dom/ToJSValue.h"
 
-NS_IMPL_ISUPPORTS(nsOpenURIInFrameParams, nsIOpenURIInFrameParams)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsOpenURIInFrameParams)
+  NS_INTERFACE_MAP_ENTRY(nsIOpenURIInFrameParams)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION(nsOpenURIInFrameParams, mOpenerBrowser)
 
-nsOpenURIInFrameParams::nsOpenURIInFrameParams(const mozilla::OriginAttributes& aOriginAttributes)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsOpenURIInFrameParams)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsOpenURIInFrameParams)
+
+nsOpenURIInFrameParams::nsOpenURIInFrameParams(const mozilla::OriginAttributes& aOriginAttributes,
+                                               nsIFrameLoaderOwner* aOpener)
   : mOpenerOriginAttributes(aOriginAttributes)
+  , mOpenerBrowser(aOpener)
 {
 }
 
 nsOpenURIInFrameParams::~nsOpenURIInFrameParams() {
 }
 
 NS_IMETHODIMP
 nsOpenURIInFrameParams::GetReferrer(nsAString& aReferrer)
@@ -51,15 +61,23 @@ NS_IMETHODIMP
 nsOpenURIInFrameParams::SetTriggeringPrincipal(nsIPrincipal* aTriggeringPrincipal)
 {
   NS_ENSURE_TRUE(aTriggeringPrincipal, NS_ERROR_INVALID_ARG);
   mTriggeringPrincipal = aTriggeringPrincipal;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsOpenURIInFrameParams::GetOpenerBrowser(nsIFrameLoaderOwner** aOpenerBrowser)
+{
+  nsCOMPtr<nsIFrameLoaderOwner> owner = mOpenerBrowser;
+  owner.forget(aOpenerBrowser);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsOpenURIInFrameParams::GetOpenerOriginAttributes(JSContext* aCx,
                                                   JS::MutableHandle<JS::Value> aValue)
 {
   bool ok = ToJSValue(aCx, mOpenerOriginAttributes, aValue);
   NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
   return NS_OK;
 }
--- a/dom/base/nsOpenURIInFrameParams.h
+++ b/dom/base/nsOpenURIInFrameParams.h
@@ -1,29 +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 "mozilla/BasePrincipal.h"
+#include "nsCycleCollectionParticipant.h"
 #include "nsIBrowserDOMWindow.h"
+#include "nsIFrameLoader.h"
 #include "nsString.h"
 
 namespace mozilla {
 class OriginAttributes;
 }
 
 class nsOpenURIInFrameParams final : public nsIOpenURIInFrameParams
 {
 public:
-  NS_DECL_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS(nsOpenURIInFrameParams)
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_NSIOPENURIINFRAMEPARAMS
 
-  explicit nsOpenURIInFrameParams(const mozilla::OriginAttributes& aOriginAttributes);
+  explicit nsOpenURIInFrameParams(const mozilla::OriginAttributes& aOriginAttributes,
+                                  nsIFrameLoaderOwner* aOpener);
 
 private:
   ~nsOpenURIInFrameParams();
 
   mozilla::OriginAttributes mOpenerOriginAttributes;
+  nsCOMPtr<nsIFrameLoaderOwner> mOpenerBrowser;
   nsString mReferrer;
   nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
 };
--- a/dom/base/test/browser.ini
+++ b/dom/base/test/browser.ini
@@ -32,17 +32,17 @@ support-files =
 [browser_bug902350.js]
 tags = mcb
 [browser_bug1011748.js]
 [browser_bug1058164.js]
 [browser_force_process_selector.js]
 skip-if = !e10s # this only makes sense with e10s-multi
 [browser_messagemanager_loadprocessscript.js]
 [browser_aboutnewtab_process_selection.js]
-skip-if = os == 'linux' # Linux x64 JSDCov failure
+skip-if = os == 'linux' || !e10s # Bug 1391363
 [browser_messagemanager_targetframeloader.js]
 [browser_messagemanager_unload.js]
 [browser_pagehide_on_tab_close.js]
 skip-if = e10s # this tests non-e10s behavior. it's not expected to work in e10s.
 [browser_state_notifications.js]
 skip-if = true # Bug 1271028
 [browser_use_counters.js]
 [browser_timeout_throttling_with_audio_playback.js]
--- a/dom/browser-element/mochitest/mochitest-oop.ini
+++ b/dom/browser-element/mochitest/mochitest-oop.ini
@@ -89,13 +89,14 @@ disabled = Disabling some OOP tests for 
 #skip-if = asan # bug 1189592 - should be OK when ASAN mochitests are on Ubuntu 16.04
 disabled = Disabling some OOP tests for WebIDL scope changes (bug 1310706 for re-enabling)
 [test_browserElement_oop_XFrameOptionsDeny.html]
 disabled = Disabling some OOP tests for WebIDL scope changes (bug 1310706 for re-enabling)
 [test_browserElement_oop_XFrameOptionsSameOrigin.html]
 [test_browserElement_oop_ContextmenuEvents.html]
 [test_browserElement_oop_CloseFromOpener.html]
 [test_browserElement_oop_ExposableURI.html]
+skip-if = !e10s # Bug 1391349
 [test_browserElement_oop_GetContentDimensions.html]
 disabled = Disabling some OOP tests for WebIDL scope changes (bug 1310706 for re-enabling)
 [test_browserElement_oop_getWebManifest.html]
 disabled = Disabling some OOP tests for WebIDL scope changes (bug 1310706 for re-enabling)
 [test_browserElement_oop_OpenWindowEmpty.html]
--- a/dom/html/ImageDocument.cpp
+++ b/dom/html/ImageDocument.cpp
@@ -772,23 +772,23 @@ ImageDocument::UpdateTitleAndCharset()
   nsCOMPtr<imgIRequest> imageRequest;
   nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mImageContent);
   if (imageLoader) {
     imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
                             getter_AddRefs(imageRequest));
   }
 
   if (imageRequest) {
-    nsXPIDLCString mimeType;
+    nsCString mimeType;
     imageRequest->GetMimeType(getter_Copies(mimeType));
     ToUpperCase(mimeType);
-    nsXPIDLCString::const_iterator start, end;
+    nsCString::const_iterator start, end;
     mimeType.BeginReading(start);
     mimeType.EndReading(end);
-    nsXPIDLCString::const_iterator iter = end;
+    nsCString::const_iterator iter = end;
     if (FindInReadable(NS_LITERAL_CSTRING("IMAGE/"), start, iter) &&
         iter != end) {
       // strip out "X-" if any
       if (*iter == 'X') {
         ++iter;
         if (iter != end && *iter == '-') {
           ++iter;
           if (iter == end) {
--- a/dom/html/nsHTMLDocument.cpp
+++ b/dom/html/nsHTMLDocument.cpp
@@ -1366,17 +1366,17 @@ nsHTMLDocument::GetCookie(nsAString& aCo
     nsCOMPtr<nsIChannel> channel(mChannel);
     if (!channel) {
       channel = CreateDummyChannelForCookies(codebaseURI);
       if (!channel) {
         return;
       }
     }
 
-    nsXPIDLCString cookie;
+    nsCString cookie;
     service->GetCookieString(codebaseURI, channel, getter_Copies(cookie));
     // CopyUTF8toUTF16 doesn't handle error
     // because it assumes that the input is valid.
     UTF_8_ENCODING->DecodeWithoutBOMHandling(cookie, aCookie);
   }
 }
 
 NS_IMETHODIMP
@@ -3663,17 +3663,17 @@ nsHTMLDocument::QueryCommandValue(const 
   if (rv.Failed()) {
     return;
   }
 
   // If command does not have a state_attribute value, this call fails, and
   // aValue will wind up being the empty string.  This is fine -- we want to
   // return "" in that case anyway (bug 738385), so we just return NS_OK
   // regardless.
-  nsXPIDLCString cStringResult;
+  nsCString cStringResult;
   cmdParams->GetCStringValue("state_attribute",
                              getter_Copies(cStringResult));
   CopyUTF8toUTF16(cStringResult, aValue);
 }
 
 nsresult
 nsHTMLDocument::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult,
                       bool aPreallocateChildren) const
--- a/dom/html/test/mochitest.ini
+++ b/dom/html/test/mochitest.ini
@@ -598,9 +598,10 @@ skip-if = os == "android" # up/down arro
 [test_bug1310865.html]
 [test_bug1315146.html]
 [test_fakepath.html]
 [test_script_module.html]
 support-files =
   file_script_module.html
   file_script_nomodule.html
 [test_getElementsByName_after_mutation.html]
-[test_bug1279218.html]
\ No newline at end of file
+[test_bug1279218.html]
+[test_set_input_files.html]
new file mode 100644
--- /dev/null
+++ b/dom/html/test/test_set_input_files.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1384030
+-->
+<head>
+  <title>Test for Setting &lt;input type=file&gt;.files </title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1384030">Mozilla Bug 1384030</a>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Setting <input type=file>.files **/
+
+function runTest()
+{
+  const form = document.createElement("form");
+  const formInput = document.createElement("input");
+  formInput.type = "file";
+  formInput.name = "inputFile";
+  form.appendChild(formInput);
+
+  const input = document.createElement("input");
+  input.type = "file";
+  SpecialPowers.wrap(input).mozSetFileArray([
+    new File(["foo"], "foo"),
+    new File(["bar"], "bar")
+  ]);
+
+  formInput.files = input.files;
+
+  const inputFiles = (new FormData(form)).getAll("inputFile");
+  is(inputFiles.length, 2, "FormData should contain two input files");
+
+  is(inputFiles[0].name, "foo", "Input file name should be 'foo'");
+  is(inputFiles[1].name, "bar", "Input file name should be 'bar'");
+
+  is(inputFiles[0], input.files[0],
+     "Expect the same File object as input file 'foo'");
+  is(inputFiles[1], input.files[1],
+     "Expect the same File object as input file 'bar'");
+
+  SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+window.addEventListener('load', runTest);
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/interfaces/base/nsIBrowserDOMWindow.idl
+++ b/dom/interfaces/base/nsIBrowserDOMWindow.idl
@@ -13,16 +13,20 @@ interface nsIPrincipal;
 
 [scriptable, uuid(e774db14-79ac-4156-a7a3-aa3fd0a22c10)]
 interface nsIOpenURIInFrameParams : nsISupports
 {
   attribute DOMString referrer;
   readonly attribute boolean isPrivate;
   attribute nsIPrincipal triggeringPrincipal;
 
+  // The browser or frame element in the parent process which holds the
+  // opener window in the content process. May be null.
+  readonly attribute nsIFrameLoaderOwner openerBrowser;
+
   [implicit_jscontext]
   readonly attribute jsval openerOriginAttributes;
 };
 
 [scriptable, uuid(2a9bb880-5d73-40f3-8152-c60c8d137a14)]
 
 /**
  * The C++ source has access to the browser script source through
--- a/dom/interfaces/base/nsIServiceWorkerManager.idl
+++ b/dom/interfaces/base/nsIServiceWorkerManager.idl
@@ -60,20 +60,26 @@ interface nsIServiceWorkerInfo : nsISupp
 interface nsIServiceWorkerRegistrationInfoListener : nsISupports
 {
   void onChange();
 };
 
 [scriptable, builtinclass, uuid(ddbc1fd4-2f2e-4fca-a395-6e010bbedfe3)]
 interface nsIServiceWorkerRegistrationInfo : nsISupports
 {
+  // State values below should match the ServiceWorkerUpdateViaCache enumeration.
+  const unsigned short UPDATE_VIA_CACHE_IMPORTS = 0;
+  const unsigned short UPDATE_VIA_CACHE_ALL = 1;
+  const unsigned short UPDATE_VIA_CACHE_NONE = 2;
+
   readonly attribute nsIPrincipal principal;
 
   readonly attribute DOMString scope;
   readonly attribute DOMString scriptSpec;
+  readonly attribute unsigned short updateViaCache;
 
   readonly attribute PRTime lastUpdateTime;
 
   readonly attribute nsIServiceWorkerInfo installingWorker;
   readonly attribute nsIServiceWorkerInfo waitingWorker;
   readonly attribute nsIServiceWorkerInfo activeWorker;
 
   // Allows to get the related nsIServiceWorkerInfo for a given
@@ -105,17 +111,17 @@ interface nsIServiceWorkerManager : nsIS
    * the stack. This means you must call this from content code 'within'
    * a window.
    *
    * Returns a Promise.
    */
   nsISupports register(in mozIDOMWindow aWindow,
                        in nsIURI aScope,
                        in nsIURI aScriptURI,
-                       in nsLoadFlags aLoadFlags);
+                       in uint16_t aUpdateViaCache);
 
   /**
    * Unregister an existing ServiceWorker registration for `aScope`.
    * It keeps aCallback alive until the operation is concluded.
    */
   void unregister(in nsIPrincipal aPrincipal,
                   in nsIServiceWorkerUnregisterCallback aCallback,
                   in DOMString aScope);
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -427,17 +427,17 @@ ConsoleListener::Observe(nsIConsoleMessa
 {
   if (!mChild) {
     return NS_OK;
   }
 
   nsCOMPtr<nsIScriptError> scriptError = do_QueryInterface(aMessage);
   if (scriptError) {
     nsAutoString msg, sourceName, sourceLine;
-    nsXPIDLCString category;
+    nsCString category;
     uint32_t lineNum, colNum, flags;
 
     nsresult rv = scriptError->GetErrorMessage(msg);
     NS_ENSURE_SUCCESS(rv, rv);
     TruncateString(msg);
     rv = scriptError->GetSourceName(sourceName);
     NS_ENSURE_SUCCESS(rv, rv);
     TruncateString(sourceName);
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1107,16 +1107,22 @@ ContentParent::CreateBrowser(const TabCo
                              uint64_t aNextTabParentId)
 {
   AUTO_PROFILER_LABEL("ContentParent::CreateBrowser", OTHER);
 
   if (!sCanLaunchSubprocesses) {
     return nullptr;
   }
 
+  nsAutoString remoteType;
+  if (!aFrameElement->GetAttr(kNameSpaceID_None, nsGkAtoms::RemoteType,
+                              remoteType)) {
+    remoteType.AssignLiteral(DEFAULT_REMOTE_TYPE);
+  }
+
   if (aNextTabParentId) {
     if (TabParent* parent =
           sNextTabParents.GetAndRemove(aNextTabParentId).valueOr(nullptr)) {
       MOZ_ASSERT(!parent->GetOwnerElement(),
                  "Shouldn't have an owner elemnt before");
       parent->SetOwnerElement(aFrameElement);
       return parent;
     }
@@ -1127,22 +1133,16 @@ ContentParent::CreateBrowser(const TabCo
   TabId tabId(nsContentUtils::GenerateTabId());
 
   nsIDocShell* docShell = GetOpenerDocShellHelper(aFrameElement);
   TabId openerTabId;
   if (docShell) {
     openerTabId = TabParent::GetTabIdFrom(docShell);
   }
 
-  nsAutoString remoteType;
-  if (!aFrameElement->GetAttr(kNameSpaceID_None, nsGkAtoms::RemoteType,
-                              remoteType)) {
-    remoteType.AssignLiteral(DEFAULT_REMOTE_TYPE);
-  }
-
   bool isPreloadBrowser = false;
   nsAutoString isPreloadBrowserStr;
   if (aFrameElement->GetAttr(kNameSpaceID_None, nsGkAtoms::isPreloadBrowser,
                              isPreloadBrowserStr)) {
     isPreloadBrowser = isPreloadBrowserStr.EqualsLiteral("true");
   }
 
   RefPtr<nsIContentParent> constructorSender;
@@ -4590,18 +4590,20 @@ ContentParent::CommonCreateWindow(PBrows
   }
 
   if (openLocation == nsIBrowserDOMWindow::OPEN_NEWTAB) {
     if (NS_WARN_IF(!browserDOMWin)) {
       aResult = NS_ERROR_ABORT;
       return IPC_OK();
     }
 
+    nsCOMPtr<nsIFrameLoaderOwner> opener = do_QueryInterface(frame);
+
     nsCOMPtr<nsIOpenURIInFrameParams> params =
-      new nsOpenURIInFrameParams(openerOriginAttributes);
+      new nsOpenURIInFrameParams(openerOriginAttributes, opener);
     params->SetReferrer(NS_ConvertUTF8toUTF16(aBaseURI));
     MOZ_ASSERT(aTriggeringPrincipal, "need a valid triggeringPrincipal");
     params->SetTriggeringPrincipal(aTriggeringPrincipal);
 
     nsCOMPtr<nsIFrameLoaderOwner> frameLoaderOwner;
     aResult = browserDOMWin->OpenURIInFrame(aURIToLoad, params, openLocation,
                                             nsIBrowserDOMWindow::OPEN_NEW,
                                             aNextTabParentId, aName,
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -293,63 +293,81 @@ ContentListener::HandleEvent(nsIDOMEvent
   remoteEvent.mEvent = do_QueryInterface(aEvent);
   NS_ENSURE_STATE(remoteEvent.mEvent);
   mTabChild->SendEvent(remoteEvent);
   return NS_OK;
 }
 
 class TabChild::DelayedDeleteRunnable final
   : public Runnable
+  , public nsIRunnablePriority
 {
     RefPtr<TabChild> mTabChild;
 
+    // In order to ensure that this runnable runs after everything that could
+    // possibly touch this tab, we send it through the event queue twice. The
+    // first time it runs at normal priority and the second time it runs at
+    // input priority. This ensures that it runs after all events that were in
+    // either queue at the time it was first dispatched. mReadyToDelete starts
+    // out false (when it runs at normal priority) and is then set to true.
+    bool mReadyToDelete = false;
+
 public:
     explicit DelayedDeleteRunnable(TabChild* aTabChild)
       : Runnable("TabChild::DelayedDeleteRunnable")
       , mTabChild(aTabChild)
     {
         MOZ_ASSERT(NS_IsMainThread());
         MOZ_ASSERT(aTabChild);
     }
 
+    NS_DECL_ISUPPORTS_INHERITED
+
 private:
     ~DelayedDeleteRunnable()
     {
         MOZ_ASSERT(NS_IsMainThread());
         MOZ_ASSERT(!mTabChild);
     }
 
+    NS_IMETHOD GetPriority(uint32_t* aPriority) override
+    {
+      *aPriority = mReadyToDelete
+                 ? nsIRunnablePriority::PRIORITY_INPUT
+                 : nsIRunnablePriority::PRIORITY_NORMAL;
+      return NS_OK;
+    }
+
     NS_IMETHOD
     Run() override
     {
         MOZ_ASSERT(NS_IsMainThread());
         MOZ_ASSERT(mTabChild);
-        // When enabling input event prioritization, we reserve limited time
-        // to process input events. We may handle the rest in the next frame
-        // when running out of time of the current frame. In that case, input
-        // events may be dispatched after ActorDestroy. Delay
-        // DelayedDeleteRunnable to avoid it to happen.
-        nsThread* thread = nsThreadManager::get().GetCurrentThread();
-        MOZ_ASSERT(thread);
-        bool eventPrioritizationEnabled = false;
-        thread->IsEventPrioritizationEnabled(&eventPrioritizationEnabled);
-        if (eventPrioritizationEnabled && thread->HasPendingInputEvents()) {
+
+        if (!mReadyToDelete) {
+          // This time run this runnable at input priority.
+          mReadyToDelete = true;
           MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(this));
           return NS_OK;
         }
+
         // Check in case ActorDestroy was called after RecvDestroy message.
         if (mTabChild->IPCOpen()) {
-            Unused << PBrowserChild::Send__delete__(mTabChild);
+          Unused << PBrowserChild::Send__delete__(mTabChild);
         }
 
         mTabChild = nullptr;
         return NS_OK;
     }
 };
 
+NS_IMPL_ISUPPORTS_INHERITED(TabChild::DelayedDeleteRunnable,
+                            Runnable,
+                            nsIRunnablePriority)
+
 namespace {
 std::map<TabId, RefPtr<TabChild>>&
 NestedTabChildMap()
 {
   MOZ_ASSERT(NS_IsMainThread());
   static std::map<TabId, RefPtr<TabChild>> sNestedTabChildMap;
   return sNestedTabChildMap;
 }
--- a/dom/media/gmp/ChromiumCDMAdapter.cpp
+++ b/dom/media/gmp/ChromiumCDMAdapter.cpp
@@ -46,45 +46,49 @@ ChromiumCdmHost(int aHostInterfaceVersio
     return nullptr;
   }
   return static_cast<cdm::Host_8*>(aUserData);
 }
 
 #define STRINGIFY(s) _STRINGIFY(s)
 #define _STRINGIFY(s) #s
 
+#ifdef MOZILLA_OFFICIAL
 static cdm::HostFile
 TakeToCDMHostFile(HostFileData& aHostFileData)
 {
   return cdm::HostFile(aHostFileData.mBinary.Path().get(),
                        aHostFileData.mBinary.TakePlatformFile(),
                        aHostFileData.mSig.TakePlatformFile());
 }
+#endif
 
 GMPErr
 ChromiumCDMAdapter::GMPInit(const GMPPlatformAPI* aPlatformAPI)
 {
   CDM_LOG("ChromiumCDMAdapter::GMPInit");
   sPlatform = aPlatformAPI;
   if (!mLib) {
     return GMPGenericErr;
   }
 
+#ifdef MOZILLA_OFFICIAL
   // Note: we must call the VerifyCdmHost_0 function if it's present before
   // we call the initialize function.
   auto verify = reinterpret_cast<decltype(::VerifyCdmHost_0)*>(
     PR_FindFunctionSymbol(mLib, STRINGIFY(VerifyCdmHost_0)));
   if (verify) {
     nsTArray<cdm::HostFile> files;
     for (HostFileData& hostFile : mHostFiles) {
       files.AppendElement(TakeToCDMHostFile(hostFile));
     }
     bool result = verify(files.Elements(), files.Length());
     GMP_LOG("%s VerifyCdmHost_0 returned %d", __func__, result);
   }
+#endif
 
   auto init = reinterpret_cast<decltype(::INITIALIZE_CDM_MODULE)*>(
     PR_FindFunctionSymbol(mLib, STRINGIFY(INITIALIZE_CDM_MODULE)));
   if (!init) {
     return GMPGenericErr;
   }
 
   CDM_LOG(STRINGIFY(INITIALIZE_CDM_MODULE)"()");
--- a/dom/promise/tests/test_webassembly_compile.html
+++ b/dom/promise/tests/test_webassembly_compile.html
@@ -7,20 +7,24 @@
   <title>WebAssembly.compile Test</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 <script>
 const wasmTextToBinary = SpecialPowers.unwrap(SpecialPowers.Cu.getJSTestingFunctions().wasmTextToBinary);
 const wasmIsSupported = SpecialPowers.Cu.getJSTestingFunctions().wasmIsSupported
-const fooModuleCode = wasmTextToBinary(`(module
-  (func $foo (result i32) (i32.const 42))
-  (export "foo" $foo)
-)`, 'new-format');
+
+var moduleStr = '(module\n';
+for (var i = 0; i < 50; i++)
+    moduleStr += ' (func (param i32 i32) (result i32) (i32.add (get_local 0) (get_local 1)))\n';
+moduleStr += ' (func (export "foo") (result i32) (i32.const 42))\n';
+moduleStr += ')';
+
+const fooModuleCode = wasmTextToBinary(moduleStr);
 
 function checkFooModule(m) {
   ok(m instanceof WebAssembly.Module, "got a module");
   var i = new WebAssembly.Instance(m);
   ok(i instanceof WebAssembly.Instance, "got an instance");
   ok(i.exports.foo() === 42, "got 42");
 }
 
--- a/dom/svg/SVGDocument.cpp
+++ b/dom/svg/SVGDocument.cpp
@@ -110,17 +110,17 @@ SVGDocument::EnsureNonSVGUserAgentStyleS
 
           nsCOMPtr<nsISupportsCString> icStr = do_QueryInterface(sheet);
           MOZ_ASSERT(icStr,
                      "category manager entries must be nsISupportsCStrings");
 
           nsAutoCString name;
           icStr->GetData(name);
 
-          nsXPIDLCString spec;
+          nsCString spec;
           catMan->GetCategoryEntry("agent-style-sheets", name.get(),
                                    getter_Copies(spec));
 
           mozilla::css::Loader* cssLoader = CSSLoader();
           if (cssLoader->GetEnabled()) {
             nsCOMPtr<nsIURI> uri;
             NS_NewURI(getter_AddRefs(uri), spec);
             if (uri) {
--- a/dom/webidl/ServiceWorkerContainer.webidl
+++ b/dom/webidl/ServiceWorkerContainer.webidl
@@ -37,9 +37,10 @@ interface ServiceWorkerContainer : Event
 // Testing only.
 partial interface ServiceWorkerContainer {
   [Throws,Pref="dom.serviceWorkers.testing.enabled"]
   DOMString getScopeForUrl(DOMString url);
 };
 
 dictionary RegistrationOptions {
   USVString scope;
+  ServiceWorkerUpdateViaCache updateViaCache = "imports";
 };
--- a/dom/webidl/ServiceWorkerRegistration.webidl
+++ b/dom/webidl/ServiceWorkerRegistration.webidl
@@ -12,27 +12,34 @@
 [Func="mozilla::dom::ServiceWorkerRegistration::Visible",
  Exposed=(Window,Worker)]
 interface ServiceWorkerRegistration : EventTarget {
   [Unforgeable] readonly attribute ServiceWorker? installing;
   [Unforgeable] readonly attribute ServiceWorker? waiting;
   [Unforgeable] readonly attribute ServiceWorker? active;
 
   readonly attribute USVString scope;
+  readonly attribute ServiceWorkerUpdateViaCache updateViaCache;
 
   [Throws, NewObject]
   Promise<void> update();
 
   [Throws, NewObject]
   Promise<boolean> unregister();
 
   // event
   attribute EventHandler onupdatefound;
 };
 
+enum ServiceWorkerUpdateViaCache {
+  "imports",
+  "all",
+  "none"
+};
+
 // https://w3c.github.io/push-api/
 partial interface ServiceWorkerRegistration {
   [Throws, Exposed=(Window,Worker), Func="nsContentUtils::PushEnabled"]
   readonly attribute PushManager pushManager;
 };
 
 // https://notifications.spec.whatwg.org/
 partial interface ServiceWorkerRegistration {
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -713,193 +713,108 @@ AsmJSCacheOpenEntryForWrite(JS::Handle<J
   if (!principal) {
     return JS::AsmJSCache_InternalError;
   }
 
   return asmjscache::OpenEntryForWrite(principal, aBegin, aEnd, aSize, aMemory,
                                        aHandle);
 }
 
-class AsyncTaskWorkerHolder final : public WorkerHolder
+// JSDispatchableRunnables are WorkerRunnables used to dispatch JS::Dispatchable
+// back to their worker thread. A WorkerRunnable is used for two reasons:
+//
+// 1. The JS::Dispatchable::run() callback may run JS so we cannot use a control
+// runnable since they use async interrupts and break JS run-to-completion.
+//
+// 2. The DispatchToEventLoopCallback interface is *required* to fail during
+// shutdown (see jsapi.h) which is exactly what WorkerRunnable::Dispatch() will
+// do. Moreover, JS_DestroyContext() does *not* block on JS::Dispatchable::run
+// being called, DispatchToEventLoopCallback failure is expected to happen
+// during shutdown.
+class JSDispatchableRunnable final : public WorkerRunnable
 {
-  bool Notify(Status aStatus) override
+  JS::Dispatchable* mDispatchable;
+
+  ~JSDispatchableRunnable()
   {
-    // The async task must complete in bounded time and there is not (currently)
-    // a clean way to cancel it. Async tasks do not run arbitrary content.
-    return true;
+    MOZ_ASSERT(!mDispatchable);
   }
 
-public:
-  WorkerPrivate* Worker() const
-  {
-    return mWorkerPrivate;
-  }
-};
-
-template <class RunnableBase>
-class AsyncTaskBase : public RunnableBase
-{
-  UniquePtr<AsyncTaskWorkerHolder> mHolder;
-
   // Disable the usual pre/post-dispatch thread assertions since we are
   // dispatching from some random JS engine internal thread:
 
   bool PreDispatch(WorkerPrivate* aWorkerPrivate) override
   {
     return true;
   }
 
   void PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
-  { }
-
-protected:
-  explicit AsyncTaskBase(UniquePtr<AsyncTaskWorkerHolder> aHolder)
-    : RunnableBase(aHolder->Worker(),
-                   WorkerRunnable::WorkerThreadUnchangedBusyCount)
-    , mHolder(Move(aHolder))
-  {
-    MOZ_ASSERT(mHolder);
-  }
-
-  ~AsyncTaskBase()
-  {
-    MOZ_ASSERT(!mHolder);
-  }
-
-  void DestroyHolder()
-  {
-    MOZ_ASSERT(mHolder);
-    mHolder.reset();
-  }
-
-public:
-  UniquePtr<AsyncTaskWorkerHolder> StealHolder()
-  {
-    return Move(mHolder);
-  }
-};
-
-class AsyncTaskRunnable final : public AsyncTaskBase<WorkerRunnable>
-{
-  JS::AsyncTask* mTask;
-
-  ~AsyncTaskRunnable()
-  {
-    MOZ_ASSERT(!mTask);
-  }
-
-  void PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
   {
     // For the benefit of the destructor assert.
     if (!aDispatchResult) {
-      mTask = nullptr;
+      mDispatchable = nullptr;
     }
   }
 
 public:
-  AsyncTaskRunnable(UniquePtr<AsyncTaskWorkerHolder> aHolder,
-                    JS::AsyncTask* aTask)
-    : AsyncTaskBase<WorkerRunnable>(Move(aHolder))
-    , mTask(aTask)
+  JSDispatchableRunnable(WorkerPrivate* aWorkerPrivate,
+                         JS::Dispatchable* aDispatchable)
+    : WorkerRunnable(aWorkerPrivate,
+                     WorkerRunnable::WorkerThreadUnchangedBusyCount)
+    , mDispatchable(aDispatchable)
   {
-    MOZ_ASSERT(mTask);
+    MOZ_ASSERT(mDispatchable);
   }
 
   bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     MOZ_ASSERT(aWorkerPrivate == mWorkerPrivate);
     MOZ_ASSERT(aCx == mWorkerPrivate->GetJSContext());
-    MOZ_ASSERT(mTask);
+    MOZ_ASSERT(mDispatchable);
 
     AutoJSAPI jsapi;
     jsapi.Init();
 
-    mTask->finish(mWorkerPrivate->GetJSContext());
-    mTask = nullptr;  // mTask may delete itself
-
-    DestroyHolder();
+    mDispatchable->run(mWorkerPrivate->GetJSContext(),
+                       JS::Dispatchable::NotShuttingDown);
+    mDispatchable = nullptr;  // mDispatchable may delete itself
 
     return true;
   }
 
   nsresult Cancel() override
   {
-    MOZ_ASSERT(mTask);
+    MOZ_ASSERT(mDispatchable);
 
     AutoJSAPI jsapi;
     jsapi.Init();
 
-    mTask->cancel(mWorkerPrivate->GetJSContext());
-    mTask = nullptr;  // mTask may delete itself
-
-    DestroyHolder();
+    mDispatchable->run(mWorkerPrivate->GetJSContext(),
+                       JS::Dispatchable::ShuttingDown);
+    mDispatchable = nullptr;  // mDispatchable may delete itself
 
     return WorkerRunnable::Cancel();
   }
 };
 
-class AsyncTaskControlRunnable final
-  : public AsyncTaskBase<WorkerControlRunnable>
-{
-public:
-  explicit AsyncTaskControlRunnable(UniquePtr<AsyncTaskWorkerHolder> aHolder)
-    : AsyncTaskBase<WorkerControlRunnable>(Move(aHolder))
-  { }
-
-  bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
-  {
-    // See comment in FinishAsyncTaskCallback.
-    DestroyHolder();
-    return true;
-  }
-};
-
 static bool
-StartAsyncTaskCallback(JSContext* aCx, JS::AsyncTask* aTask)
-{
-  WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
-  worker->AssertIsOnWorkerThread();
-
-  auto holder = MakeUnique<AsyncTaskWorkerHolder>();
-  if (!holder->HoldWorker(worker, Status::Closing)) {
-    return false;
-  }
-
-  // Matched by a UniquePtr in FinishAsyncTaskCallback which, by
-  // interface contract, must be called in the future.
-  aTask->user = holder.release();
-  return true;
-}
-
-static bool
-FinishAsyncTaskCallback(JS::AsyncTask* aTask)
+DispatchToEventLoop(void* aClosure, JS::Dispatchable* aDispatchable)
 {
-  // May execute either on the worker thread or a random JS-internal helper
-  // thread.
-
-  // Match the release() in StartAsyncTaskCallback.
-  UniquePtr<AsyncTaskWorkerHolder> holder(
-    static_cast<AsyncTaskWorkerHolder*>(aTask->user));
-
-  RefPtr<AsyncTaskRunnable> r = new AsyncTaskRunnable(Move(holder), aTask);
-
-  // WorkerRunnable::Dispatch() can fail during worker shutdown. In that case,
-  // report failure back to the JS engine but make sure to release the
-  // WorkerHolder on the worker thread using a control runnable. Control
-  // runables aren't suitable for calling AsyncTask::finish() since they are run
-  // via the interrupt callback which breaks JS run-to-completion.
-  if (!r->Dispatch()) {
-    RefPtr<AsyncTaskControlRunnable> cr =
-      new AsyncTaskControlRunnable(r->StealHolder());
-
-    MOZ_ALWAYS_TRUE(cr->Dispatch());
-    return false;
-  }
-
-  return true;
+  // This callback may execute either on the worker thread or a random
+  // JS-internal helper thread.
+
+  // See comment at JS::InitDispatchToEventLoop() below for how we know the
+  // WorkerPrivate is alive.
+  WorkerPrivate* workerPrivate = reinterpret_cast<WorkerPrivate*>(aClosure);
+
+  // Dispatch is expected to fail during shutdown for the reasons outlined in
+  // the JSDispatchableRunnable comment above.
+  RefPtr<JSDispatchableRunnable> r =
+    new JSDispatchableRunnable(workerPrivate, aDispatchable);
+  return r->Dispatch();
 }
 
 class WorkerJSContext;
 
 class WorkerThreadContextPrivate : private PerThreadAtomCache
 {
   friend class WorkerJSContext;
 
@@ -987,17 +902,19 @@ InitJSContextForWorker(WorkerPrivate* aW
   static const JS::AsmJSCacheOps asmJSCacheOps = {
     AsmJSCacheOpenEntryForRead,
     asmjscache::CloseEntryForRead,
     AsmJSCacheOpenEntryForWrite,
     asmjscache::CloseEntryForWrite
   };
   JS::SetAsmJSCacheOps(aWorkerCx, &asmJSCacheOps);
 
-  JS::SetAsyncTaskCallbacks(aWorkerCx, StartAsyncTaskCallback, FinishAsyncTaskCallback);
+  // A WorkerPrivate lives strictly longer than its JSRuntime so we can safely
+  // store a raw pointer as the callback's closure argument on the JSRuntime.
+  JS::InitDispatchToEventLoop(aWorkerCx, DispatchToEventLoop, (void*)aWorkerPrivate);
 
   if (!JS::InitSelfHostedCode(aWorkerCx)) {
     NS_WARNING("Could not init self-hosted code!");
     return false;
   }
 
   JS_AddInterruptCallback(aWorkerCx, InterruptCallback);
 
--- a/dom/workers/ServiceWorkerContainer.cpp
+++ b/dom/workers/ServiceWorkerContainer.cpp
@@ -194,23 +194,20 @@ ServiceWorkerContainer::Register(const n
     }
 
     aRv = CheckForSlashEscapedCharsInPath(scopeURI);
     if (NS_WARN_IF(aRv.Failed())) {
       return nullptr;
     }
   }
 
-  // This is a quick fix for temporarily turning off script loading setting when
-  // registering a service worker. This should be removed in Bug 1353636.
-  nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL;
-
   // The spec says that the "client" passed to Register() must be the global
   // where the ServiceWorkerContainer was retrieved from.
-  aRv = swm->Register(GetOwner(), scopeURI, scriptURI, loadFlags,
+  aRv = swm->Register(GetOwner(), scopeURI, scriptURI,
+                      static_cast<uint16_t>(aOptions.mUpdateViaCache),
                       getter_AddRefs(promise));
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   RefPtr<Promise> ret = static_cast<Promise*>(promise.get());
   MOZ_ASSERT(ret);
   return ret.forget();
--- a/dom/workers/ServiceWorkerInfo.cpp
+++ b/dom/workers/ServiceWorkerInfo.cpp
@@ -204,39 +204,44 @@ ServiceWorkerInfo::UpdateState(ServiceWo
     serviceWorkerScriptCache::PurgeCache(mPrincipal, mCacheName);
   }
 }
 
 ServiceWorkerInfo::ServiceWorkerInfo(nsIPrincipal* aPrincipal,
                                      const nsACString& aScope,
                                      const nsACString& aScriptSpec,
                                      const nsAString& aCacheName,
-                                     nsLoadFlags aLoadFlags)
+                                     nsLoadFlags aImportsLoadFlags)
   : mPrincipal(aPrincipal)
   , mScope(aScope)
   , mScriptSpec(aScriptSpec)
   , mCacheName(aCacheName)
-  , mLoadFlags(aLoadFlags)
   , mState(ServiceWorkerState::EndGuard_)
+  , mImportsLoadFlags(aImportsLoadFlags)
   , mServiceWorkerID(GetNextID())
   , mCreationTime(PR_Now())
   , mCreationTimeStamp(TimeStamp::Now())
   , mInstalledTime(0)
   , mActivatedTime(0)
   , mRedundantTime(0)
   , mServiceWorkerPrivate(new ServiceWorkerPrivate(this))
   , mSkipWaitingFlag(false)
   , mHandlesFetch(Unknown)
 {
   MOZ_ASSERT(mPrincipal);
   // cache origin attributes so we can use them off main thread
   mOriginAttributes = mPrincipal->OriginAttributesRef();
   MOZ_ASSERT(!mScope.IsEmpty());
   MOZ_ASSERT(!mScriptSpec.IsEmpty());
   MOZ_ASSERT(!mCacheName.IsEmpty());
+
+  // Scripts of a service worker should always be loaded bypass service workers.
+  // Otherwise, we might not be able to update a service worker correctly, if
+  // there is a service worker generating the script.
+  MOZ_DIAGNOSTIC_ASSERT(mImportsLoadFlags & nsIChannel::LOAD_BYPASS_SERVICE_WORKER);
 }
 
 ServiceWorkerInfo::~ServiceWorkerInfo()
 {
   MOZ_ASSERT(mServiceWorkerPrivate);
   mServiceWorkerPrivate->NoteDeadServiceWorkerInfo();
 }
 
--- a/dom/workers/ServiceWorkerInfo.h
+++ b/dom/workers/ServiceWorkerInfo.h
@@ -26,20 +26,29 @@ class ServiceWorkerPrivate;
  */
 class ServiceWorkerInfo final : public nsIServiceWorkerInfo
 {
 private:
   nsCOMPtr<nsIPrincipal> mPrincipal;
   const nsCString mScope;
   const nsCString mScriptSpec;
   const nsString mCacheName;
-  const nsLoadFlags mLoadFlags;
   ServiceWorkerState mState;
   OriginAttributes mOriginAttributes;
 
+  // This LoadFlags is only applied to imported scripts, since the main script
+  // has already been downloaded when performing the bytecheck. This LoadFlag is
+  // composed of three parts:
+  //   1. nsIChannel::LOAD_BYPASS_SERVICE_WORKER
+  //   2. (Optional) nsIRequest::VALIDATE_ALWAYS
+  //      depends on ServiceWorkerUpdateViaCache of its registration.
+  //   3. (optional) nsIRequest::LOAD_BYPASS_CACHE
+  //      depends on whether the update timer is expired.
+  const nsLoadFlags mImportsLoadFlags;
+
   // This id is shared with WorkerPrivate to match requests issued by service
   // workers to their corresponding serviceWorkerInfo.
   uint64_t mServiceWorkerID;
 
   // Timestamp to track SW's state
   PRTime mCreationTime;
   TimeStamp mCreationTimeStamp;
 
@@ -132,19 +141,19 @@ public:
 
   const nsString&
   CacheName() const
   {
     return mCacheName;
   }
 
   nsLoadFlags
-  GetLoadFlags() const
+  GetImportsLoadFlags() const
   {
-    return mLoadFlags;
+    return mImportsLoadFlags;
   }
 
   uint64_t
   ID() const
   {
     return mServiceWorkerID;
   }
 
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -128,16 +128,29 @@ static_assert(nsIHttpChannelInternal::FE
              "RequestCache enumeration value should match Necko Cache mode value.");
 static_assert(nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE == static_cast<uint32_t>(RequestCache::Force_cache),
              "RequestCache enumeration value should match Necko Cache mode value.");
 static_assert(nsIHttpChannelInternal::FETCH_CACHE_MODE_ONLY_IF_CACHED == static_cast<uint32_t>(RequestCache::Only_if_cached),
              "RequestCache enumeration value should match Necko Cache mode value.");
 static_assert(6 == static_cast<uint32_t>(RequestCache::EndGuard_),
              "RequestCache enumeration value should match Necko Cache mode value.");
 
+static_assert(static_cast<uint16_t>(ServiceWorkerUpdateViaCache::Imports) ==
+              nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+              "nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_*"
+              " should match ServiceWorkerUpdateViaCache enumeration.");
+static_assert(static_cast<uint16_t>(ServiceWorkerUpdateViaCache::All) ==
+              nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_ALL,
+              "nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_*"
+              " should match ServiceWorkerUpdateViaCache enumeration.");
+static_assert(static_cast<uint16_t>(ServiceWorkerUpdateViaCache::None) ==
+              nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_NONE,
+              "nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_*"
+              " should match ServiceWorkerUpdateViaCache enumeration.");
+
 static StaticRefPtr<ServiceWorkerManager> gInstance;
 
 struct ServiceWorkerManager::RegistrationDataPerPrincipal final
 {
   // Ordered list of scopes for glob matching.
   // Each entry is an absolute URL representing the scope.
   // Each value of the hash table is an array of an absolute URLs representing
   // the scopes.
@@ -191,17 +204,18 @@ PopulateRegistrationData(nsIPrincipal* a
     aData.currentWorkerHandlesFetch() = aRegistration->GetActive()->HandlesFetch();
 
     aData.currentWorkerInstalledTime() =
       aRegistration->GetActive()->GetInstalledTime();
     aData.currentWorkerActivatedTime() =
       aRegistration->GetActive()->GetActivatedTime();
   }
 
-  aData.loadFlags() = aRegistration->GetLoadFlags();
+  aData.updateViaCache() =
+    static_cast<uint32_t>(aRegistration->GetUpdateViaCache());
 
   aData.lastUpdateTime() = aRegistration->GetLastUpdateTime();
 
   return NS_OK;
 }
 
 class TeardownRunnable final : public Runnable
 {
@@ -799,17 +813,17 @@ IsFromAuthenticatedOrigin(nsIDocument* a
 }
 
 // If we return an error code here, the ServiceWorkerContainer will
 // automatically reject the Promise.
 NS_IMETHODIMP
 ServiceWorkerManager::Register(mozIDOMWindow* aWindow,
                                nsIURI* aScopeURI,
                                nsIURI* aScriptURI,
-                               nsLoadFlags aLoadFlags,
+                               uint16_t aUpdateViaCache,
                                nsISupports** aPromise)
 {
   AssertIsOnMainThread();
 
   if (NS_WARN_IF(!aWindow)) {
     return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
@@ -924,19 +938,21 @@ ServiceWorkerManager::Register(mozIDOMWi
   ir->MaybeAddTabChild(docLoadGroup);
 
   // Create a load group that is separate from, yet related to, the document's load group.
   // This allows checks for interfaces like nsILoadContext to yield the values used by the
   // the document, yet will not cancel the update job if the document's load group is cancelled.
   nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
   MOZ_ALWAYS_SUCCEEDS(loadGroup->SetNotificationCallbacks(ir));
 
-  RefPtr<ServiceWorkerRegisterJob> job =
-    new ServiceWorkerRegisterJob(documentPrincipal, cleanedScope, spec,
-                                 loadGroup, aLoadFlags);
+  RefPtr<ServiceWorkerRegisterJob> job = new ServiceWorkerRegisterJob(
+    documentPrincipal, cleanedScope, spec, loadGroup,
+    static_cast<ServiceWorkerUpdateViaCache>(aUpdateViaCache)
+  );
+
   job->AppendResultCallback(cb);
   queue->ScheduleJob(job);
 
   AssertIsOnMainThread();
   Telemetry::Accumulate(Telemetry::SERVICE_WORKER_REGISTRATIONS, 1);
 
   ContentChild* contentChild = ContentChild::GetSingleton();
   if (contentChild &&
@@ -2031,41 +2047,51 @@ ServiceWorkerManager::LoadRegistration(
     PrincipalInfoToPrincipal(aRegistration.principal());
   if (!principal) {
     return;
   }
 
   RefPtr<ServiceWorkerRegistrationInfo> registration =
     GetRegistration(principal, aRegistration.scope());
   if (!registration) {
-    registration = CreateNewRegistration(aRegistration.scope(), principal,
-                                         aRegistration.loadFlags());
+    registration =
+      CreateNewRegistration(
+        aRegistration.scope(),
+        principal,
+        static_cast<ServiceWorkerUpdateViaCache>(aRegistration.updateViaCache())
+      );
   } else {
     // If active worker script matches our expectations for a "current worker",
     // then we are done. Since scripts with the same URL might have different
     // contents such as updated scripts or scripts with different LoadFlags, we
     // use the CacheName to judje whether the two scripts are identical, where
     // the CacheName is an UUID generated when a new script is found.
     if (registration->GetActive() &&
         registration->GetActive()->CacheName() == aRegistration.cacheName()) {
       // No needs for updates.
       return;
     }
   }
 
   registration->SetLastUpdateTime(aRegistration.lastUpdateTime());
 
+  nsLoadFlags importsLoadFlags = nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
+  importsLoadFlags |=
+    aRegistration.updateViaCache() == static_cast<uint16_t>(ServiceWorkerUpdateViaCache::None)
+      ? nsIRequest::LOAD_NORMAL
+      : nsIRequest::VALIDATE_ALWAYS;
+
   const nsCString& currentWorkerURL = aRegistration.currentWorkerURL();
   if (!currentWorkerURL.IsEmpty()) {
     registration->SetActive(
       new ServiceWorkerInfo(registration->mPrincipal,
                             registration->mScope,
                             currentWorkerURL,
                             aRegistration.cacheName(),
-                            registration->GetLoadFlags()));
+                            importsLoadFlags));
     registration->GetActive()->SetHandlesFetch(aRegistration.currentWorkerHandlesFetch());
     registration->GetActive()->SetInstalledTime(aRegistration.currentWorkerInstalledTime());
     registration->GetActive()->SetActivatedTime(aRegistration.currentWorkerActivatedTime());
   }
 }
 
 void
 ServiceWorkerManager::LoadRegistrations(
@@ -3115,17 +3141,17 @@ ServiceWorkerManager::SoftUpdateInternal
   // TODO(catalinb): We don't implement the force bypass cache flag.
   // See: https://github.com/slightlyoff/ServiceWorker/issues/759
   RefPtr<ServiceWorkerJobQueue> queue = GetOrCreateJobQueue(scopeKey,
                                                             aScope);
 
   RefPtr<ServiceWorkerUpdateJob> job =
     new ServiceWorkerUpdateJob(principal, registration->mScope,
                                newest->ScriptSpec(), nullptr,
-                               registration->GetLoadFlags());
+                               registration->GetUpdateViaCache());
 
   RefPtr<UpdateJobCallback> cb = new UpdateJobCallback(aCallback);
   job->AppendResultCallback(cb);
 
   queue->ScheduleJob(job);
 }
 
 void
@@ -3198,17 +3224,17 @@ ServiceWorkerManager::UpdateInternal(nsI
 
   RefPtr<ServiceWorkerJobQueue> queue = GetOrCreateJobQueue(scopeKey, aScope);
 
   // "Invoke Update algorithm, or its equivalent, with client, registration as
   // its argument."
   RefPtr<ServiceWorkerUpdateJob> job =
     new ServiceWorkerUpdateJob(aPrincipal, registration->mScope,
                                newest->ScriptSpec(), nullptr,
-                               registration->GetLoadFlags());
+                               registration->GetUpdateViaCache());
 
   RefPtr<UpdateJobCallback> cb = new UpdateJobCallback(aCallback);
   job->AppendResultCallback(cb);
 
   queue->ScheduleJob(job);
 }
 
 namespace {
@@ -3543,33 +3569,35 @@ ServiceWorkerManager::GetRegistration(co
     return reg.forget();
   }
 
   data->mInfos.Get(aScope, getter_AddRefs(reg));
   return reg.forget();
 }
 
 already_AddRefed<ServiceWorkerRegistrationInfo>
-ServiceWorkerManager::CreateNewRegistration(const nsCString& aScope,
-                                            nsIPrincipal* aPrincipal,
-                                            nsLoadFlags aLoadFlags)
+ServiceWorkerManager::CreateNewRegistration(
+    const nsCString& aScope,
+    nsIPrincipal* aPrincipal,
+    ServiceWorkerUpdateViaCache aUpdateViaCache)
 {
 #ifdef DEBUG
   AssertIsOnMainThread();
   nsCOMPtr<nsIURI> scopeURI;
   nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope, nullptr, nullptr);
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 
   RefPtr<ServiceWorkerRegistrationInfo> tmp =
     GetRegistration(aPrincipal, aScope);
   MOZ_ASSERT(!tmp);
 #endif
 
   RefPtr<ServiceWorkerRegistrationInfo> registration =
-    new ServiceWorkerRegistrationInfo(aScope, aPrincipal, aLoadFlags);
+    new ServiceWorkerRegistrationInfo(aScope, aPrincipal, aUpdateViaCache);
+
   // From now on ownership of registration is with
   // mServiceWorkerRegistrationInfos.
   AddScopeAndRegistration(aScope, registration);
   return registration.forget();
 }
 
 void
 ServiceWorkerManager::MaybeRemoveRegistration(ServiceWorkerRegistrationInfo* aRegistration)
--- a/dom/workers/ServiceWorkerManager.h
+++ b/dom/workers/ServiceWorkerManager.h
@@ -196,17 +196,17 @@ public:
   RemoveAll();
 
   already_AddRefed<ServiceWorkerRegistrationInfo>
   GetRegistration(nsIPrincipal* aPrincipal, const nsACString& aScope) const;
 
   already_AddRefed<ServiceWorkerRegistrationInfo>
   CreateNewRegistration(const nsCString& aScope,
                         nsIPrincipal* aPrincipal,
-                        nsLoadFlags aLoadFlags);
+                        ServiceWorkerUpdateViaCache aUpdateViaCache);
 
   void
   RemoveRegistration(ServiceWorkerRegistrationInfo* aRegistration);
 
   void StoreRegistration(nsIPrincipal* aPrincipal,
                          ServiceWorkerRegistrationInfo* aRegistration);
 
   void
--- a/dom/workers/ServiceWorkerPrivate.cpp
+++ b/dom/workers/ServiceWorkerPrivate.cpp
@@ -1801,17 +1801,17 @@ ServiceWorkerPrivate::SpawnWorkerIfNeede
   info.mServiceWorkerCacheName = mInfo->CacheName();
   info.mServiceWorkerID = mInfo->ID();
   info.mLoadGroup = aLoadGroup;
   info.mLoadFailedAsyncRunnable = aLoadFailedRunnable;
 
   // If we are loading a script for a ServiceWorker then we must not
   // try to intercept it.  If the interception matches the current
   // ServiceWorker's scope then we could deadlock the load.
-  info.mLoadFlags = mInfo->GetLoadFlags() |
+  info.mLoadFlags = mInfo->GetImportsLoadFlags() |
                     nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
 
   rv = info.mBaseURI->GetHost(info.mDomain);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsCOMPtr<nsIURI> uri;
--- a/dom/workers/ServiceWorkerRegisterJob.cpp
+++ b/dom/workers/ServiceWorkerRegisterJob.cpp
@@ -7,23 +7,24 @@
 #include "ServiceWorkerRegisterJob.h"
 
 #include "Workers.h"
 
 namespace mozilla {
 namespace dom {
 namespace workers {
 
-ServiceWorkerRegisterJob::ServiceWorkerRegisterJob(nsIPrincipal* aPrincipal,
-                                                   const nsACString& aScope,
-                                                   const nsACString& aScriptSpec,
-                                                   nsILoadGroup* aLoadGroup,
-                                                   nsLoadFlags aLoadFlags)
+ServiceWorkerRegisterJob::ServiceWorkerRegisterJob(
+    nsIPrincipal* aPrincipal,
+    const nsACString& aScope,
+    const nsACString& aScriptSpec,
+    nsILoadGroup* aLoadGroup,
+    ServiceWorkerUpdateViaCache aUpdateViaCache)
   : ServiceWorkerUpdateJob(Type::Register, aPrincipal, aScope, aScriptSpec,
-                           aLoadGroup, aLoadFlags)
+                           aLoadGroup, aUpdateViaCache)
 {
 }
 
 void
 ServiceWorkerRegisterJob::AsyncExecute()
 {
   AssertIsOnMainThread();
 
@@ -32,36 +33,36 @@ ServiceWorkerRegisterJob::AsyncExecute()
     FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
     return;
   }
 
   RefPtr<ServiceWorkerRegistrationInfo> registration =
     swm->GetRegistration(mPrincipal, mScope);
 
   if (registration) {
-    bool isSameLoadFlags = registration->GetLoadFlags() == GetLoadFlags();
-    registration->SetLoadFlags(GetLoadFlags());
+    bool sameUVC = GetUpdateViaCache() == registration->GetUpdateViaCache();
+    registration->SetUpdateViaCache(GetUpdateViaCache());
 
     // If we are resurrecting an uninstalling registration, then persist
     // it to disk again.  We preemptively removed it earlier during
     // unregister so that closing the window by shutting down the browser
     // results in the registration being gone on restart.
     if (registration->mPendingUninstall) {
       swm->StoreRegistration(mPrincipal, registration);
     }
     registration->mPendingUninstall = false;
     RefPtr<ServiceWorkerInfo> newest = registration->Newest();
-    if (newest && mScriptSpec.Equals(newest->ScriptSpec()) && isSameLoadFlags) {
+    if (newest && mScriptSpec.Equals(newest->ScriptSpec()) && sameUVC) {
       SetRegistration(registration);
       Finish(NS_OK);
       return;
     }
   } else {
     registration = swm->CreateNewRegistration(mScope, mPrincipal,
-                                              GetLoadFlags());
+                                              GetUpdateViaCache());
     if (!registration) {
       FailUpdateJob(NS_ERROR_DOM_ABORT_ERR);
       return;
     }
   }
 
   SetRegistration(registration);
   Update();
--- a/dom/workers/ServiceWorkerRegisterJob.h
+++ b/dom/workers/ServiceWorkerRegisterJob.h
@@ -18,17 +18,17 @@ namespace workers {
 // spec algorithms.
 class ServiceWorkerRegisterJob final : public ServiceWorkerUpdateJob
 {
 public:
   ServiceWorkerRegisterJob(nsIPrincipal* aPrincipal,
                            const nsACString& aScope,
                            const nsACString& aScriptSpec,
                            nsILoadGroup* aLoadGroup,
-                           nsLoadFlags aLoadFlags);
+                           ServiceWorkerUpdateViaCache aUpdateViaCache);
 
 private:
   // Implement the Register algorithm steps and then call the parent class
   // Update() to complete the job execution.
   virtual void
   AsyncExecute() override;
 
   virtual ~ServiceWorkerRegisterJob();
--- a/dom/workers/ServiceWorkerRegistrar.cpp
+++ b/dom/workers/ServiceWorkerRegistrar.cpp
@@ -36,16 +36,17 @@ using namespace mozilla::ipc;
 
 namespace mozilla {
 namespace dom {
 
 namespace {
 
 static const char* gSupportedRegistrarVersions[] = {
   SERVICEWORKERREGISTRAR_VERSION,
+  "7",
   "6",
   "5",
   "4",
   "3",
   "2"
 };
 
 StaticRefPtr<ServiceWorkerRegistrar> gServiceWorkerRegistrar;
@@ -367,24 +368,86 @@ ServiceWorkerRegistrar::ReadData()
       }
       entry->currentWorkerHandlesFetch() =
         fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE);
 
       nsAutoCString cacheName;
       GET_LINE(cacheName);
       CopyUTF8toUTF16(cacheName, entry->cacheName());
 
+      nsAutoCString updateViaCache;
+      GET_LINE(updateViaCache);
+      entry->updateViaCache() = updateViaCache.ToInteger(&rv, 16);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      } else if (entry->updateViaCache() > nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_NONE) {
+        return NS_ERROR_INVALID_ARG;
+      }
+
+      nsAutoCString installedTimeStr;
+      GET_LINE(installedTimeStr);
+      int64_t installedTime = installedTimeStr.ToInteger64(&rv);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+      entry->currentWorkerInstalledTime() = installedTime;
+
+      nsAutoCString activatedTimeStr;
+      GET_LINE(activatedTimeStr);
+      int64_t activatedTime = activatedTimeStr.ToInteger64(&rv);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+      entry->currentWorkerActivatedTime() = activatedTime;
+
+      nsAutoCString lastUpdateTimeStr;
+      GET_LINE(lastUpdateTimeStr);
+      int64_t lastUpdateTime = lastUpdateTimeStr.ToInteger64(&rv);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+      entry->lastUpdateTime() = lastUpdateTime;
+    } else if (version.EqualsLiteral("7")) {
+      nsAutoCString suffix;
+      GET_LINE(suffix);
+
+      OriginAttributes attrs;
+      if (!attrs.PopulateFromSuffix(suffix)) {
+        return NS_ERROR_INVALID_ARG;
+      }
+
+      GET_LINE(entry->scope());
+
+      entry->principal() =
+        mozilla::ipc::ContentPrincipalInfo(attrs, void_t(), entry->scope());
+
+      GET_LINE(entry->currentWorkerURL());
+
+      nsAutoCString fetchFlag;
+      GET_LINE(fetchFlag);
+      if (!fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE) &&
+          !fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_FALSE)) {
+        return NS_ERROR_INVALID_ARG;
+      }
+      entry->currentWorkerHandlesFetch() =
+        fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE);
+
+      nsAutoCString cacheName;
+      GET_LINE(cacheName);
+      CopyUTF8toUTF16(cacheName, entry->cacheName());
+
       nsAutoCString loadFlags;
       GET_LINE(loadFlags);
-      entry->loadFlags() = loadFlags.ToInteger(&rv, 16);
+      entry->updateViaCache() =
+        loadFlags.ToInteger(&rv, 16) == nsIRequest::LOAD_NORMAL
+          ? nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_ALL
+          : nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS;
+
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
-      } else if (entry->loadFlags() != nsIRequest::LOAD_NORMAL &&
-                 entry->loadFlags() != nsIRequest::VALIDATE_ALWAYS) {
-        return NS_ERROR_INVALID_ARG;
       }
 
       nsAutoCString installedTimeStr;
       GET_LINE(installedTimeStr);
       int64_t installedTime = installedTimeStr.ToInteger64(&rv);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
@@ -431,22 +494,23 @@ ServiceWorkerRegistrar::ReadData()
         fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE);
 
       nsAutoCString cacheName;
       GET_LINE(cacheName);
       CopyUTF8toUTF16(cacheName, entry->cacheName());
 
       nsAutoCString loadFlags;
       GET_LINE(loadFlags);
-      entry->loadFlags() = loadFlags.ToInteger(&rv, 16);
+      entry->updateViaCache() =
+        loadFlags.ToInteger(&rv, 16) == nsIRequest::LOAD_NORMAL
+          ? nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_ALL
+          : nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS;
+
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
-      } else if (entry->loadFlags() != nsIRequest::LOAD_NORMAL &&
-                 entry->loadFlags() != nsIRequest::VALIDATE_ALWAYS) {
-        return NS_ERROR_INVALID_ARG;
       }
 
       entry->currentWorkerInstalledTime() = 0;
       entry->currentWorkerActivatedTime() = 0;
       entry->lastUpdateTime() = 0;
     } else if (version.EqualsLiteral("5")) {
       overwrite = true;
       dedupe = true;
@@ -474,17 +538,18 @@ ServiceWorkerRegistrar::ReadData()
       }
       entry->currentWorkerHandlesFetch() =
         fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE);
 
       nsAutoCString cacheName;
       GET_LINE(cacheName);
       CopyUTF8toUTF16(cacheName, entry->cacheName());
 
-      entry->loadFlags() = nsIRequest::VALIDATE_ALWAYS;
+      entry->updateViaCache() =
+        nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS;
 
       entry->currentWorkerInstalledTime() = 0;
       entry->currentWorkerActivatedTime() = 0;
       entry->lastUpdateTime() = 0;
     } else if (version.EqualsLiteral("4")) {
       overwrite = true;
       dedupe = true;
 
@@ -505,17 +570,18 @@ ServiceWorkerRegistrar::ReadData()
 
       // default handlesFetch flag to Enabled
       entry->currentWorkerHandlesFetch() = true;
 
       nsAutoCString cacheName;
       GET_LINE(cacheName);
       CopyUTF8toUTF16(cacheName, entry->cacheName());
 
-      entry->loadFlags() = nsIRequest::VALIDATE_ALWAYS;
+      entry->updateViaCache() =
+        nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS;
 
       entry->currentWorkerInstalledTime() = 0;
       entry->currentWorkerActivatedTime() = 0;
       entry->lastUpdateTime() = 0;
     } else if (version.EqualsLiteral("3")) {
       overwrite = true;
       dedupe = true;
 
@@ -539,17 +605,18 @@ ServiceWorkerRegistrar::ReadData()
 
       // default handlesFetch flag to Enabled
       entry->currentWorkerHandlesFetch() = true;
 
       nsAutoCString cacheName;
       GET_LINE(cacheName);
       CopyUTF8toUTF16(cacheName, entry->cacheName());
 
-      entry->loadFlags() = nsIRequest::VALIDATE_ALWAYS;
+      entry->updateViaCache() =
+        nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS;
 
       entry->currentWorkerInstalledTime() = 0;
       entry->currentWorkerActivatedTime() = 0;
       entry->lastUpdateTime() = 0;
     } else if (version.EqualsLiteral("2")) {
       overwrite = true;
       dedupe = true;
 
@@ -579,17 +646,18 @@ ServiceWorkerRegistrar::ReadData()
 
       nsAutoCString cacheName;
       GET_LINE(cacheName);
       CopyUTF8toUTF16(cacheName, entry->cacheName());
 
       // waitingCacheName is no more used in latest version.
       GET_LINE(unused);
 
-      entry->loadFlags() = nsIRequest::VALIDATE_ALWAYS;
+      entry->updateViaCache() =
+        nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS;
 
       entry->currentWorkerInstalledTime() = 0;
       entry->currentWorkerActivatedTime() = 0;
       entry->lastUpdateTime() = 0;
     } else {
       MOZ_ASSERT_UNREACHABLE("Should never get here!");
     }
 
@@ -894,20 +962,23 @@ ServiceWorkerRegistrar::WriteData()
 
     buffer.Append(data[i].currentWorkerHandlesFetch() ?
                     SERVICEWORKERREGISTRAR_TRUE : SERVICEWORKERREGISTRAR_FALSE);
     buffer.Append('\n');
 
     buffer.Append(NS_ConvertUTF16toUTF8(data[i].cacheName()));
     buffer.Append('\n');
 
-    buffer.AppendInt(data[i].loadFlags(), 16);
+    buffer.AppendInt(data[i].updateViaCache(), 16);
     buffer.Append('\n');
-    MOZ_DIAGNOSTIC_ASSERT(data[i].loadFlags() == nsIRequest::LOAD_NORMAL ||
-                          data[i].loadFlags() == nsIRequest::VALIDATE_ALWAYS);
+    MOZ_DIAGNOSTIC_ASSERT(
+      data[i].updateViaCache() == nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS ||
+      data[i].updateViaCache() == nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_ALL ||
+      data[i].updateViaCache() == nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_NONE
+    );
 
     static_assert(nsIRequest::LOAD_NORMAL == 0,
                   "LOAD_NORMAL matches serialized value.");
     static_assert(nsIRequest::VALIDATE_ALWAYS == (1 << 11),
                   "VALIDATE_ALWAYS matches serialized value");
 
     buffer.AppendInt(data[i].currentWorkerInstalledTime());
     buffer.Append('\n');
--- a/dom/workers/ServiceWorkerRegistrar.h
+++ b/dom/workers/ServiceWorkerRegistrar.h
@@ -11,17 +11,17 @@
 #include "mozilla/Telemetry.h"
 #include "nsClassHashtable.h"
 #include "nsIObserver.h"
 #include "nsCOMPtr.h"
 #include "nsString.h"
 #include "nsTArray.h"
 
 #define SERVICEWORKERREGISTRAR_FILE "serviceworker.txt"
-#define SERVICEWORKERREGISTRAR_VERSION "7"
+#define SERVICEWORKERREGISTRAR_VERSION "8"
 #define SERVICEWORKERREGISTRAR_TERMINATOR "#"
 #define SERVICEWORKERREGISTRAR_TRUE "true"
 #define SERVICEWORKERREGISTRAR_FALSE "false"
 
 class nsIFile;
 
 namespace mozilla {
 
--- a/dom/workers/ServiceWorkerRegistrarTypes.ipdlh
+++ b/dom/workers/ServiceWorkerRegistrarTypes.ipdlh
@@ -14,17 +14,17 @@ struct ServiceWorkerRegistrationData
   nsCString scope;
   nsCString currentWorkerURL;
   bool currentWorkerHandlesFetch;
 
   nsString cacheName;
 
   PrincipalInfo principal;
 
-  uint32_t loadFlags;
+  uint16_t updateViaCache;
 
   int64_t currentWorkerInstalledTime;
   int64_t currentWorkerActivatedTime;
   int64_t lastUpdateTime;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/workers/ServiceWorkerRegistration.cpp
+++ b/dom/workers/ServiceWorkerRegistration.cpp
@@ -134,16 +134,43 @@ public:
   RegistrationRemoved() override;
 
   void
   GetScope(nsAString& aScope) const override
   {
     aScope = mScope;
   }
 
+  ServiceWorkerUpdateViaCache
+  UpdateViaCache() const override
+  {
+    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+    MOZ_ASSERT(swm);
+
+    nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
+    MOZ_ASSERT(window);
+
+    nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
+    MOZ_ASSERT(doc);
+
+    nsCOMPtr<nsIServiceWorkerRegistrationInfo> registration;
+    nsresult rv = swm->GetRegistrationByPrincipal(doc->NodePrincipal(), mScope,
+                                                  getter_AddRefs(registration));
+    MOZ_ASSERT(NS_SUCCEEDED(rv) && registration);
+
+    uint16_t updateViaCache;
+    rv = registration->GetUpdateViaCache(&updateViaCache);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+    // Silence possible compiler warnings.
+    Unused << rv;
+
+    return static_cast<ServiceWorkerUpdateViaCache>(updateViaCache);
+  }
+
 private:
   ~ServiceWorkerRegistrationMainThread();
 
   already_AddRefed<ServiceWorker>
   GetWorkerReference(WhichServiceWorker aWhichOne);
 
   void
   StartListeningForEvents();
@@ -911,16 +938,23 @@ public:
   GetActive() override;
 
   void
   GetScope(nsAString& aScope) const override
   {
     aScope = mScope;
   }
 
+  ServiceWorkerUpdateViaCache
+  UpdateViaCache() const override
+  {
+    // FIXME(hopang): Will be implemented after Bug 1113522.
+    return ServiceWorkerUpdateViaCache::Imports;
+  }
+
   bool
   Notify(Status aStatus) override;
 
   already_AddRefed<PushManager>
   GetPushManager(JSContext* aCx, ErrorResult& aRv) override;
 
 private:
   ~ServiceWorkerRegistrationWorkerThread();
--- a/dom/workers/ServiceWorkerRegistration.h
+++ b/dom/workers/ServiceWorkerRegistration.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_ServiceWorkerRegistration_h
 #define mozilla_dom_ServiceWorkerRegistration_h
 
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/dom/ServiceWorkerBinding.h"
 #include "mozilla/dom/ServiceWorkerCommon.h"
+#include "mozilla/dom/ServiceWorkerRegistrationBinding.h"
 #include "mozilla/dom/workers/bindings/WorkerHolder.h"
 #include "nsContentUtils.h" // Required for nsContentUtils::PushEnabled
 
 // Support for Notification API extension.
 #include "mozilla/dom/NotificationBinding.h"
 
 class nsPIDOMWindowInner;
 
@@ -85,16 +86,19 @@ public:
   GetWaiting() = 0;
 
   virtual already_AddRefed<workers::ServiceWorker>
   GetActive() = 0;
 
   virtual void
   GetScope(nsAString& aScope) const = 0;
 
+  virtual ServiceWorkerUpdateViaCache
+  UpdateViaCache() const = 0;
+
   virtual already_AddRefed<Promise>
   Update(ErrorResult& aRv) = 0;
 
   virtual already_AddRefed<Promise>
   Unregister(ErrorResult& aRv) = 0;
 
   virtual already_AddRefed<PushManager>
   GetPushManager(JSContext* aCx, ErrorResult& aRv) = 0;
--- a/dom/workers/ServiceWorkerRegistrationInfo.cpp
+++ b/dom/workers/ServiceWorkerRegistrationInfo.cpp
@@ -72,25 +72,26 @@ ServiceWorkerRegistrationInfo::Clear()
     mActiveWorker->UpdateRedundantTime();
     mActiveWorker->WorkerPrivate()->NoteDeadServiceWorkerInfo();
     mActiveWorker = nullptr;
   }
 
   NotifyChromeRegistrationListeners();
 }
 
-ServiceWorkerRegistrationInfo::ServiceWorkerRegistrationInfo(const nsACString& aScope,
-                                                             nsIPrincipal* aPrincipal,
-                                                             nsLoadFlags aLoadFlags)
+ServiceWorkerRegistrationInfo::ServiceWorkerRegistrationInfo(
+    const nsACString& aScope,
+    nsIPrincipal* aPrincipal,
+    ServiceWorkerUpdateViaCache aUpdateViaCache)
   : mControlledDocumentsCounter(0)
   , mUpdateState(NoUpdate)
   , mCreationTime(PR_Now())
   , mCreationTimeStamp(TimeStamp::Now())
   , mLastUpdateTime(0)
-  , mLoadFlags(aLoadFlags)
+  , mUpdateViaCache(aUpdateViaCache)
   , mScope(aScope)
   , mPrincipal(aPrincipal)
   , mPendingUninstall(false)
 {}
 
 ServiceWorkerRegistrationInfo::~ServiceWorkerRegistrationInfo()
 {
   if (IsControllingDocuments()) {
@@ -123,16 +124,23 @@ ServiceWorkerRegistrationInfo::GetScript
   RefPtr<ServiceWorkerInfo> newest = Newest();
   if (newest) {
     CopyUTF8toUTF16(newest->ScriptSpec(), aScriptSpec);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
+ServiceWorkerRegistrationInfo::GetUpdateViaCache(uint16_t* aUpdateViaCache)
+{
+    *aUpdateViaCache = static_cast<uint16_t>(GetUpdateViaCache());
+    return NS_OK;
+}
+
+NS_IMETHODIMP
 ServiceWorkerRegistrationInfo::GetLastUpdateTime(PRTime* _retval)
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(_retval);
   *_retval = mLastUpdateTime;
   return NS_OK;
 }
 
@@ -632,26 +640,27 @@ ServiceWorkerRegistrationInfo::Transitio
 }
 
 bool
 ServiceWorkerRegistrationInfo::IsIdle() const
 {
   return !mActiveWorker || mActiveWorker->WorkerPrivate()->IsIdle();
 }
 
-nsLoadFlags
-ServiceWorkerRegistrationInfo::GetLoadFlags() const
+ServiceWorkerUpdateViaCache
+ServiceWorkerRegistrationInfo::GetUpdateViaCache() const
 {
-  return mLoadFlags;
+  return mUpdateViaCache;
 }
 
 void
-ServiceWorkerRegistrationInfo::SetLoadFlags(nsLoadFlags aLoadFlags)
+ServiceWorkerRegistrationInfo::SetUpdateViaCache(
+    ServiceWorkerUpdateViaCache aUpdateViaCache)
 {
-  mLoadFlags = aLoadFlags;
+  mUpdateViaCache = aUpdateViaCache;
 }
 
 int64_t
 ServiceWorkerRegistrationInfo::GetLastUpdateTime() const
 {
   return mLastUpdateTime;
 }
 
--- a/dom/workers/ServiceWorkerRegistrationInfo.h
+++ b/dom/workers/ServiceWorkerRegistrationInfo.h
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_workers_serviceworkerregistrationinfo_h
 #define mozilla_dom_workers_serviceworkerregistrationinfo_h
 
 #include "mozilla/dom/workers/ServiceWorkerInfo.h"
+#include "mozilla/dom/ServiceWorkerRegistrationBinding.h"
 
 namespace mozilla {
 namespace dom {
 namespace workers {
 
 class ServiceWorkerRegistrationInfo final
   : public nsIServiceWorkerRegistrationInfo
 {
@@ -26,17 +27,17 @@ class ServiceWorkerRegistrationInfo fina
   } mUpdateState;
 
   // Timestamp to track SWR's last update time
   PRTime mCreationTime;
   TimeStamp mCreationTimeStamp;
   // The time of update is 0, if SWR've never been updated yet.
   PRTime mLastUpdateTime;
 
-  nsLoadFlags mLoadFlags;
+  ServiceWorkerUpdateViaCache mUpdateViaCache;
 
   RefPtr<ServiceWorkerInfo> mEvaluatingWorker;
   RefPtr<ServiceWorkerInfo> mActiveWorker;
   RefPtr<ServiceWorkerInfo> mWaitingWorker;
   RefPtr<ServiceWorkerInfo> mInstallingWorker;
 
   virtual ~ServiceWorkerRegistrationInfo();
 
@@ -52,17 +53,17 @@ public:
 
   // When unregister() is called on a registration, it is not immediately
   // removed since documents may be controlled. It is marked as
   // pendingUninstall and when all controlling documents go away, removed.
   bool mPendingUninstall;
 
   ServiceWorkerRegistrationInfo(const nsACString& aScope,
                                 nsIPrincipal* aPrincipal,
-                                nsLoadFlags aLoadFlags);
+                                ServiceWorkerUpdateViaCache aUpdateViaCache);
 
   already_AddRefed<ServiceWorkerInfo>
   Newest() const
   {
     RefPtr<ServiceWorkerInfo> newest;
     if (mInstallingWorker) {
       newest = mInstallingWorker;
     } else if (mWaitingWorker) {
@@ -178,21 +179,21 @@ public:
   // worker is updated to the Activating state.
   void
   TransitionWaitingToActive();
 
   // Determine if the registration is actively performing work.
   bool
   IsIdle() const;
 
-  nsLoadFlags
-  GetLoadFlags() const;
+  ServiceWorkerUpdateViaCache
+  GetUpdateViaCache() const;
 
   void
-  SetLoadFlags(nsLoadFlags aLoadFlags);
+  SetUpdateViaCache(ServiceWorkerUpdateViaCache aUpdateViaCache);
 
   int64_t
   GetLastUpdateTime() const;
 
   void
   SetLastUpdateTime(const int64_t aTime);
 
 private:
--- a/dom/workers/ServiceWorkerScriptCache.cpp
+++ b/dom/workers/ServiceWorkerScriptCache.cpp
@@ -672,17 +672,24 @@ CompareNetwork::Initialize(nsIPrincipal*
 
   nsCOMPtr<nsILoadGroup> loadGroup;
   rv = NS_NewLoadGroup(getter_AddRefs(loadGroup), aPrincipal);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // Update LoadFlags for propagating to ServiceWorkerInfo.
-  mLoadFlags |= mRegistration->GetLoadFlags();
+  mLoadFlags = nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
+
+  ServiceWorkerUpdateViaCache uvc = mRegistration->GetUpdateViaCache();
+  if (uvc == ServiceWorkerUpdateViaCache::None ||
+      (uvc == ServiceWorkerUpdateViaCache::Imports && mIsMainScript)) {
+    mLoadFlags |= nsIRequest::VALIDATE_ALWAYS;
+  }
+
   if (mRegistration->IsLastUpdateCheckTimeOverOneDay()) {
     mLoadFlags |= nsIRequest::LOAD_BYPASS_CACHE;
   }
 
   // Different settings are needed for fetching imported scripts, since they
   // might be cross-origin scripts.
   uint32_t secFlags =
       mIsMainScript ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
--- a/dom/workers/ServiceWorkerUpdateJob.cpp
+++ b/dom/workers/ServiceWorkerUpdateJob.cpp
@@ -164,44 +164,46 @@ public:
   {
     AssertIsOnMainThread();
     mJob->ContinueAfterInstallEvent(mSuccess);
     mJob = nullptr;
     return NS_OK;
   }
 };
 
-ServiceWorkerUpdateJob::ServiceWorkerUpdateJob(nsIPrincipal* aPrincipal,
-                                               const nsACString& aScope,
-                                               const nsACString& aScriptSpec,
-                                               nsILoadGroup* aLoadGroup,
-                                               nsLoadFlags aLoadFlags)
+ServiceWorkerUpdateJob::ServiceWorkerUpdateJob(
+    nsIPrincipal* aPrincipal,
+    const nsACString& aScope,
+    const nsACString& aScriptSpec,
+    nsILoadGroup* aLoadGroup,
+    ServiceWorkerUpdateViaCache aUpdateViaCache)
   : ServiceWorkerJob(Type::Update, aPrincipal, aScope, aScriptSpec)
   , mLoadGroup(aLoadGroup)
-  , mLoadFlags(aLoadFlags)
+  , mUpdateViaCache(aUpdateViaCache)
 {
 }
 
 already_AddRefed<ServiceWorkerRegistrationInfo>
 ServiceWorkerUpdateJob::GetRegistration() const
 {
   AssertIsOnMainThread();
   RefPtr<ServiceWorkerRegistrationInfo> ref = mRegistration;
   return ref.forget();
 }
 
-ServiceWorkerUpdateJob::ServiceWorkerUpdateJob(Type aType,
-                                               nsIPrincipal* aPrincipal,
-                                               const nsACString& aScope,
-                                               const nsACString& aScriptSpec,
-                                               nsILoadGroup* aLoadGroup,
-                                               nsLoadFlags aLoadFlags)
+ServiceWorkerUpdateJob::ServiceWorkerUpdateJob(
+    Type aType,
+    nsIPrincipal* aPrincipal,
+    const nsACString& aScope,
+    const nsACString& aScriptSpec,
+    nsILoadGroup* aLoadGroup,
+    ServiceWorkerUpdateViaCache aUpdateViaCache)
   : ServiceWorkerJob(aType, aPrincipal, aScope, aScriptSpec)
   , mLoadGroup(aLoadGroup)
-  , mLoadFlags(aLoadFlags)
+  , mUpdateViaCache(aUpdateViaCache)
 {
 }
 
 ServiceWorkerUpdateJob::~ServiceWorkerUpdateJob()
 {
 }
 
 void
@@ -322,20 +324,20 @@ ServiceWorkerUpdateJob::Update()
                                       NS_ConvertUTF8toUTF16(mScriptSpec),
                                       callback, mLoadGroup);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     FailUpdateJob(rv);
     return;
   }
 }
 
-nsLoadFlags
-ServiceWorkerUpdateJob::GetLoadFlags() const
+ServiceWorkerUpdateViaCache
+ServiceWorkerUpdateJob::GetUpdateViaCache() const
 {
-  return mLoadFlags;
+  return mUpdateViaCache;
 }
 
 void
 ServiceWorkerUpdateJob::ComparisonResult(nsresult aStatus,
                                          bool aInCacheAndEqual,
                                          const nsAString& aNewCacheName,
                                          const nsACString& aMaxScope,
                                          nsLoadFlags aLoadFlags)
@@ -376,18 +378,16 @@ ServiceWorkerUpdateJob::ComparisonResult
     rv = NS_NewURI(getter_AddRefs(maxScopeURI), aMaxScope,
                    nullptr, scriptURI);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       FailUpdateJob(NS_ERROR_DOM_SECURITY_ERR);
       return;
     }
   }
 
-  mLoadFlags = aLoadFlags;
-
   nsAutoCString defaultAllowedPrefix;
   rv = GetRequiredScopeStringPrefix(scriptURI, defaultAllowedPrefix,
                                     eUseDirectory);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     FailUpdateJob(NS_ERROR_DOM_SECURITY_ERR);
     return;
   }
 
@@ -424,23 +424,27 @@ ServiceWorkerUpdateJob::ComparisonResult
   if (aInCacheAndEqual) {
     Finish(NS_OK);
     return;
   }
 
   Telemetry::Accumulate(Telemetry::SERVICE_WORKER_UPDATED, 1);
 
   // Begin step 7 of the Update algorithm to evaluate the new script.
+  nsLoadFlags flags = aLoadFlags;
+  if (GetUpdateViaCache() == ServiceWorkerUpdateViaCache::None) {
+    flags |= nsIRequest::VALIDATE_ALWAYS;
+  }
 
   RefPtr<ServiceWorkerInfo> sw =
     new ServiceWorkerInfo(mRegistration->mPrincipal,
                           mRegistration->mScope,
                           mScriptSpec,
                           aNewCacheName,
-                          mLoadFlags);
+                          flags);
 
   mRegistration->SetEvaluating(sw);
 
   nsMainThreadPtrHandle<ServiceWorkerUpdateJob> handle(
       new nsMainThreadPtrHolder<ServiceWorkerUpdateJob>(
         "ServiceWorkerUpdateJob", this));
   RefPtr<LifeCycleEventCallback> callback = new ContinueUpdateRunnable(handle);
 
--- a/dom/workers/ServiceWorkerUpdateJob.h
+++ b/dom/workers/ServiceWorkerUpdateJob.h
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_workers_serviceworkerupdatejob_h
 #define mozilla_dom_workers_serviceworkerupdatejob_h
 
 #include "ServiceWorkerJob.h"
+#include "ServiceWorkerRegistration.h"
 
 namespace mozilla {
 namespace dom {
 namespace workers {
 
 class ServiceWorkerManager;
 
 // A job class that performs the Update and Install algorithms from the
@@ -23,29 +24,29 @@ class ServiceWorkerManager;
 class ServiceWorkerUpdateJob : public ServiceWorkerJob
 {
 public:
   // Construct an update job to be used only for updates.
   ServiceWorkerUpdateJob(nsIPrincipal* aPrincipal,
                          const nsACString& aScope,
                          const nsACString& aScriptSpec,
                          nsILoadGroup* aLoadGroup,
-                         nsLoadFlags aLoadFlags);
+                         ServiceWorkerUpdateViaCache aUpdateViaCache);
 
   already_AddRefed<ServiceWorkerRegistrationInfo>
   GetRegistration() const;
 
 protected:
   // Construct an update job that is overriden as another job type.
   ServiceWorkerUpdateJob(Type aType,
                          nsIPrincipal* aPrincipal,
                          const nsACString& aScope,
                          const nsACString& aScriptSpec,
                          nsILoadGroup* aLoadGroup,
-                         nsLoadFlags aLoadFlags);
+                         ServiceWorkerUpdateViaCache aUpdateViaCache);
 
   virtual ~ServiceWorkerUpdateJob();
 
   // FailUpdateJob() must be called if an update job needs Finish() with
   // an error.
   void
   FailUpdateJob(ErrorResult& aRv);
 
@@ -65,18 +66,18 @@ protected:
 
   // Execute the bulk of the update job logic using the registration defined
   // by a previous SetRegistration() call.  This can be called by the overriden
   // AsyncExecute() to complete the job.  The Update() method will always call
   // Finish().  This method corresponds to the spec Update algorithm.
   void
   Update();
 
-  nsLoadFlags
-  GetLoadFlags() const;
+  ServiceWorkerUpdateViaCache
+  GetUpdateViaCache() const;
 
 private:
   class CompareCallback;
   class ContinueUpdateRunnable;
   class ContinueInstallRunnable;
 
   // Utility method called after a script is loaded and compared to
   // our current cached script.
@@ -95,17 +96,17 @@ private:
   void
   Install(ServiceWorkerManager* aSWM);
 
   // Utility method called after the install event is handled.
   void
   ContinueAfterInstallEvent(bool aInstallEventSuccess);
 
   nsCOMPtr<nsILoadGroup> mLoadGroup;
-  nsLoadFlags mLoadFlags;
+  ServiceWorkerUpdateViaCache mUpdateViaCache;
 
   RefPtr<ServiceWorkerRegistrationInfo> mRegistration;
 };
 
 } // namespace workers
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -68,16 +68,17 @@
 #include "mozilla/dom/SimpleGlobalObject.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/StructuredCloneHolder.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/WorkerBinding.h"
 #include "mozilla/dom/WorkerDebuggerGlobalScopeBinding.h"
 #include "mozilla/dom/WorkerGlobalScopeBinding.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/ThreadEventQueue.h"
 #include "mozilla/ThrottledEventQueue.h"
 #include "mozilla/TimelineConsumers.h"
 #include "mozilla/WorkerTimelineMarker.h"
 #include "nsAlgorithm.h"
 #include "nsContentUtils.h"
 #include "nsCycleCollector.h"
 #include "nsError.h"
 #include "nsDOMJSUtils.h"
@@ -5831,21 +5832,19 @@ WorkerPrivate::CreateNewSyncLoop(Status 
   {
     MutexAutoLock lock(mMutex);
 
     if (mStatus >= aFailStatus) {
       return nullptr;
     }
   }
 
-  nsCOMPtr<nsIThreadInternal> thread = do_QueryInterface(NS_GetCurrentThread());
-  MOZ_ASSERT(thread);
-
-  nsCOMPtr<nsIEventTarget> realEventTarget;
-  MOZ_ALWAYS_SUCCEEDS(thread->PushEventQueue(getter_AddRefs(realEventTarget)));
+  auto queue = static_cast<ThreadEventQueue<EventQueue>*>(mThread->EventQueue());
+  nsCOMPtr<nsISerialEventTarget> realEventTarget = queue->PushEventQueue();
+  MOZ_ASSERT(realEventTarget);
 
   RefPtr<EventTarget> workerEventTarget =
     new EventTarget(this, realEventTarget);
 
   {
     // Modifications must be protected by mMutex in DEBUG builds, see comment
     // about mSyncLoopStack in WorkerPrivate.h.
 #ifdef DEBUG
@@ -5966,17 +5965,18 @@ WorkerPrivate::DestroySyncLoop(uint32_t 
 #ifdef DEBUG
     MutexAutoLock lock(mMutex);
 #endif
 
     // This will delete |loopInfo|!
     mSyncLoopStack.RemoveElementAt(aLoopIndex);
   }
 
-  MOZ_ALWAYS_SUCCEEDS(aThread->PopEventQueue(nestedEventTarget));
+  auto queue = static_cast<ThreadEventQueue<EventQueue>*>(mThread->EventQueue());
+  queue->PopEventQueue(nestedEventTarget);
 
   if (mSyncLoopStack.IsEmpty() && mPendingEventQueueClearing) {
     mPendingEventQueueClearing = false;
     ClearMainEventQueue(WorkerRan);
   }
 
   return result;
 }
--- a/dom/workers/WorkerThread.cpp
+++ b/dom/workers/WorkerThread.cpp
@@ -3,16 +3,18 @@
 /* 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 "WorkerThread.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/ipc/BackgroundChild.h"
+#include "EventQueue.h"
+#include "mozilla/ThreadEventQueue.h"
 #include "nsIThreadInternal.h"
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
 
 #ifdef DEBUG
 #include "nsThreadManager.h"
 #endif
 
@@ -60,17 +62,20 @@ private:
   {
     mWorkerPrivate->AssertIsOnWorkerThread();
   }
 
   NS_DECL_NSITHREADOBSERVER
 };
 
 WorkerThread::WorkerThread()
-  : nsThread(nsThread::NOT_MAIN_THREAD, kWorkerStackSize)
+  : nsThread(WrapNotNull(new ThreadEventQueue<mozilla::EventQueue>(
+                           MakeUnique<mozilla::EventQueue>())),
+             nsThread::NOT_MAIN_THREAD,
+             kWorkerStackSize)
   , mLock("WorkerThread::mLock")
   , mWorkerPrivateCondVar(mLock, "WorkerThread::mWorkerPrivateCondVar")
   , mWorkerPrivate(nullptr)
   , mOtherThreadsDispatchingViaEventTarget(0)
 #ifdef DEBUG
   , mAcceptingNonWorkerRunnables(true)
 #endif
 {
--- a/dom/workers/WorkerThread.h
+++ b/dom/workers/WorkerThread.h
@@ -73,24 +73,16 @@ public:
 
   nsresult
   DispatchAnyThread(const WorkerThreadFriendKey& aKey,
            already_AddRefed<WorkerRunnable> aWorkerRunnable);
 
   uint32_t
   RecursionDepth(const WorkerThreadFriendKey& aKey) const;
 
-  // Required for MinGW build #1336527 to handle compiler bug:
-  // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=79582
-  NS_IMETHOD
-  RegisterIdlePeriod(already_AddRefed<nsIIdlePeriod> aIdlePeriod) override
-  {
-    return nsThread::RegisterIdlePeriod(already_AddRefed<nsIIdlePeriod>(aIdlePeriod.take()));
-  }
-
   NS_DECL_ISUPPORTS_INHERITED
 
 private:
   WorkerThread();
   ~WorkerThread();
 
   // This should only be called by consumers that have an
   // nsIEventTarget/nsIThread pointer.
--- a/dom/workers/test/gtest/TestReadWrite.cpp
+++ b/dom/workers/test/gtest/TestReadWrite.cpp
@@ -10,16 +10,17 @@
 #include "mozilla/dom/ServiceWorkerRegistrarTypes.h"
 #include "mozilla/ipc/PBackgroundSharedTypes.h"
 
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsIFile.h"
 #include "nsIOutputStream.h"
 #include "nsNetUtil.h"
 #include "nsPrintfCString.h"
+#include "nsIServiceWorkerManager.h"
 
 #include "prtime.h"
 
 using namespace mozilla::dom;
 using namespace mozilla::ipc;
 
 class ServiceWorkerRegistrarTest : public ServiceWorkerRegistrar
 {
@@ -152,31 +153,31 @@ TEST(ServiceWorkerRegistrar, TestWrongVe
 TEST(ServiceWorkerRegistrar, TestReadData)
 {
   nsAutoCString buffer(SERVICEWORKERREGISTRAR_VERSION "\n");
 
   buffer.Append("^appId=123&inBrowser=1\n");
   buffer.Append("scope 0\ncurrentWorkerURL 0\n");
   buffer.Append(SERVICEWORKERREGISTRAR_TRUE "\n");
   buffer.Append("cacheName 0\n");
-  buffer.AppendInt(nsIRequest::LOAD_NORMAL, 16);
+  buffer.AppendInt(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS, 16);
   buffer.Append("\n");
   buffer.AppendInt(0);
   buffer.Append("\n");
   buffer.AppendInt(0);
   buffer.Append("\n");
   buffer.AppendInt(0);
   buffer.Append("\n");
   buffer.Append(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
 
   buffer.Append("\n");
   buffer.Append("scope 1\ncurrentWorkerURL 1\n");
   buffer.Append(SERVICEWORKERREGISTRAR_FALSE "\n");
   buffer.Append("cacheName 1\n");
-  buffer.AppendInt(nsIRequest::VALIDATE_ALWAYS, 16);
+  buffer.AppendInt(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_ALL, 16);
   buffer.Append("\n");
   PRTime ts = PR_Now();
   buffer.AppendInt(ts);
   buffer.Append("\n");
   buffer.AppendInt(ts);
   buffer.Append("\n");
   buffer.AppendInt(ts);
   buffer.Append("\n");
@@ -200,17 +201,18 @@ TEST(ServiceWorkerRegistrar, TestReadDat
   cInfo0.attrs().CreateSuffix(suffix0);
 
   ASSERT_STREQ("^appId=123&inBrowser=1", suffix0.get());
   ASSERT_STREQ("scope 0", cInfo0.spec().get());
   ASSERT_STREQ("scope 0", data[0].scope().get());
   ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
   ASSERT_TRUE(data[0].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
-  ASSERT_EQ(nsIRequest::LOAD_NORMAL, data[0].loadFlags());
+  ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+            data[0].updateViaCache());
   ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
   ASSERT_EQ((int64_t)0, data[0].currentWorkerActivatedTime());
   ASSERT_EQ((int64_t)0, data[0].lastUpdateTime());
 
   const mozilla::ipc::PrincipalInfo& info1 = data[1].principal();
   ASSERT_EQ(info1.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) << "First principal must be content";
   const mozilla::ipc::ContentPrincipalInfo& cInfo1 = data[1].principal();
 
@@ -218,17 +220,18 @@ TEST(ServiceWorkerRegistrar, TestReadDat
   cInfo1.attrs().CreateSuffix(suffix1);
 
   ASSERT_STREQ("", suffix1.get());
   ASSERT_STREQ("scope 1", cInfo1.spec().get());
   ASSERT_STREQ("scope 1", data[1].scope().get());
   ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
   ASSERT_FALSE(data[1].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 1", NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
-  ASSERT_EQ(nsIRequest::VALIDATE_ALWAYS, data[1].loadFlags());
+  ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_ALL,
+            data[1].updateViaCache());
   ASSERT_EQ((int64_t)ts, data[1].currentWorkerInstalledTime());
   ASSERT_EQ((int64_t)ts, data[1].currentWorkerActivatedTime());
   ASSERT_EQ((int64_t)ts, data[1].lastUpdateTime());
 }
 
 TEST(ServiceWorkerRegistrar, TestDeleteData)
 {
   ASSERT_TRUE(CreateFile(NS_LITERAL_CSTRING("Foobar"))) << "CreateFile should not fail";
@@ -254,17 +257,18 @@ TEST(ServiceWorkerRegistrar, TestWriteDa
     for (int i = 0; i < 10; ++i) {
       ServiceWorkerRegistrationData reg;
 
       reg.scope() = nsPrintfCString("scope write %d", i);
       reg.currentWorkerURL() = nsPrintfCString("currentWorkerURL write %d", i);
       reg.currentWorkerHandlesFetch() = true;
       reg.cacheName() =
         NS_ConvertUTF8toUTF16(nsPrintfCString("cacheName write %d", i));
-      reg.loadFlags() = nsIRequest::VALIDATE_ALWAYS;
+      reg.updateViaCache() =
+        nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS;
 
       reg.currentWorkerInstalledTime() = PR_Now();
       reg.currentWorkerActivatedTime() = PR_Now();
       reg.lastUpdateTime() = PR_Now();
 
       nsAutoCString spec;
       spec.AppendPrintf("spec write %d", i);
       reg.principal() =
@@ -311,17 +315,18 @@ TEST(ServiceWorkerRegistrar, TestWriteDa
     ASSERT_STREQ(test.get(), data[i].currentWorkerURL().get());
 
     ASSERT_EQ(true, data[i].currentWorkerHandlesFetch());
 
     test.Truncate();
     test.AppendPrintf("cacheName write %d", i);
     ASSERT_STREQ(test.get(), NS_ConvertUTF16toUTF8(data[i].cacheName()).get());
 
-    ASSERT_EQ(nsIRequest::VALIDATE_ALWAYS, data[i].loadFlags());
+    ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+              data[i].updateViaCache());
 
     ASSERT_NE((int64_t)0, data[i].currentWorkerInstalledTime());
     ASSERT_NE((int64_t)0, data[i].currentWorkerActivatedTime());
     ASSERT_NE((int64_t)0, data[i].lastUpdateTime());
   }
 }
 
 TEST(ServiceWorkerRegistrar, TestVersion2Migration)
@@ -354,17 +359,18 @@ TEST(ServiceWorkerRegistrar, TestVersion
   cInfo0.attrs().CreateSuffix(suffix0);
 
   ASSERT_STREQ("^appId=123&inBrowser=1", suffix0.get());
   ASSERT_STREQ("scope 0", cInfo0.spec().get());
   ASSERT_STREQ("scope 0", data[0].scope().get());
   ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
   ASSERT_EQ(true, data[0].currentWorkerHandlesFetch());
   ASSERT_STREQ("activeCache 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
-  ASSERT_EQ(nsIRequest::VALIDATE_ALWAYS, data[0].loadFlags());
+  ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+            data[0].updateViaCache());
   ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
   ASSERT_EQ((int64_t)0, data[0].currentWorkerActivatedTime());
   ASSERT_EQ((int64_t)0, data[0].lastUpdateTime());
 
   const mozilla::ipc::PrincipalInfo& info1 = data[1].principal();
   ASSERT_EQ(info1.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) << "First principal must be content";
   const mozilla::ipc::ContentPrincipalInfo& cInfo1 = data[1].principal();
 
@@ -372,17 +378,18 @@ TEST(ServiceWorkerRegistrar, TestVersion
   cInfo1.attrs().CreateSuffix(suffix1);
 
   ASSERT_STREQ("", suffix1.get());
   ASSERT_STREQ("scope 1", cInfo1.spec().get());
   ASSERT_STREQ("scope 1", data[1].scope().get());
   ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
   ASSERT_EQ(true, data[1].currentWorkerHandlesFetch());
   ASSERT_STREQ("activeCache 1", NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
-  ASSERT_EQ(nsIRequest::VALIDATE_ALWAYS, data[1].loadFlags());
+  ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+            data[1].updateViaCache());
   ASSERT_EQ((int64_t)0, data[1].currentWorkerInstalledTime());
   ASSERT_EQ((int64_t)0, data[1].currentWorkerActivatedTime());
   ASSERT_EQ((int64_t)0, data[1].lastUpdateTime());
 }
 
 TEST(ServiceWorkerRegistrar, TestVersion3Migration)
 {
   nsAutoCString buffer("3" "\n");
@@ -413,17 +420,18 @@ TEST(ServiceWorkerRegistrar, TestVersion
   cInfo0.attrs().CreateSuffix(suffix0);
 
   ASSERT_STREQ("^appId=123&inBrowser=1", suffix0.get());
   ASSERT_STREQ("scope 0", cInfo0.spec().get());
   ASSERT_STREQ("scope 0", data[0].scope().get());
   ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
   ASSERT_EQ(true, data[0].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
-  ASSERT_EQ(nsIRequest::VALIDATE_ALWAYS, data[0].loadFlags());
+  ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+            data[0].updateViaCache());
   ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
   ASSERT_EQ((int64_t)0, data[0].currentWorkerActivatedTime());
   ASSERT_EQ((int64_t)0, data[0].lastUpdateTime());
 
   const mozilla::ipc::PrincipalInfo& info1 = data[1].principal();
   ASSERT_EQ(info1.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) << "First principal must be content";
   const mozilla::ipc::ContentPrincipalInfo& cInfo1 = data[1].principal();
 
@@ -431,17 +439,18 @@ TEST(ServiceWorkerRegistrar, TestVersion
   cInfo1.attrs().CreateSuffix(suffix1);
 
   ASSERT_STREQ("", suffix1.get());
   ASSERT_STREQ("scope 1", cInfo1.spec().get());
   ASSERT_STREQ("scope 1", data[1].scope().get());
   ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
   ASSERT_EQ(true, data[1].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 1", NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
-  ASSERT_EQ(nsIRequest::VALIDATE_ALWAYS, data[1].loadFlags());
+  ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+            data[1].updateViaCache());
   ASSERT_EQ((int64_t)0, data[1].currentWorkerInstalledTime());
   ASSERT_EQ((int64_t)0, data[1].currentWorkerActivatedTime());
   ASSERT_EQ((int64_t)0, data[1].lastUpdateTime());
 }
 
 TEST(ServiceWorkerRegistrar, TestVersion4Migration)
 {
   nsAutoCString buffer("4" "\n");
@@ -473,17 +482,18 @@ TEST(ServiceWorkerRegistrar, TestVersion
 
   ASSERT_STREQ("^appId=123&inBrowser=1", suffix0.get());
   ASSERT_STREQ("scope 0", cInfo0.spec().get());
   ASSERT_STREQ("scope 0", data[0].scope().get());
   ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
   // default is true
   ASSERT_EQ(true, data[0].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
-  ASSERT_EQ(nsIRequest::VALIDATE_ALWAYS, data[0].loadFlags());
+  ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+            data[0].updateViaCache());
   ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
   ASSERT_EQ((int64_t)0, data[0].currentWorkerActivatedTime());
   ASSERT_EQ((int64_t)0, data[0].lastUpdateTime());
 
   const mozilla::ipc::PrincipalInfo& info1 = data[1].principal();
   ASSERT_EQ(info1.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) << "First principal must be content";
   const mozilla::ipc::ContentPrincipalInfo& cInfo1 = data[1].principal();
 
@@ -492,17 +502,18 @@ TEST(ServiceWorkerRegistrar, TestVersion
 
   ASSERT_STREQ("", suffix1.get());
   ASSERT_STREQ("scope 1", cInfo1.spec().get());
   ASSERT_STREQ("scope 1", data[1].scope().get());
   ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
   // default is true
   ASSERT_EQ(true, data[1].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 1", NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
-  ASSERT_EQ(nsIRequest::VALIDATE_ALWAYS, data[1].loadFlags());
+  ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+            data[1].updateViaCache());
   ASSERT_EQ((int64_t)0, data[1].currentWorkerInstalledTime());
   ASSERT_EQ((int64_t)0, data[1].currentWorkerActivatedTime());
   ASSERT_EQ((int64_t)0, data[1].lastUpdateTime());
 }
 
 TEST(ServiceWorkerRegistrar, TestVersion5Migration)
 {
   nsAutoCString buffer("5" "\n");
@@ -537,17 +548,18 @@ TEST(ServiceWorkerRegistrar, TestVersion
   cInfo0.attrs().CreateSuffix(suffix0);
 
   ASSERT_STREQ("^appId=123&inBrowser=1", suffix0.get());
   ASSERT_STREQ("scope 0", cInfo0.spec().get());
   ASSERT_STREQ("scope 0", data[0].scope().get());
   ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
   ASSERT_TRUE(data[0].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
-  ASSERT_EQ(nsIRequest::VALIDATE_ALWAYS, data[0].loadFlags());
+  ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+            data[0].updateViaCache());
   ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
   ASSERT_EQ((int64_t)0, data[0].currentWorkerActivatedTime());
   ASSERT_EQ((int64_t)0, data[0].lastUpdateTime());
 
   const mozilla::ipc::PrincipalInfo& info1 = data[1].principal();
   ASSERT_EQ(info1.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) << "First principal must be content";
   const mozilla::ipc::ContentPrincipalInfo& cInfo1 = data[1].principal();
 
@@ -555,17 +567,18 @@ TEST(ServiceWorkerRegistrar, TestVersion
   cInfo1.attrs().CreateSuffix(suffix1);
 
   ASSERT_STREQ("", suffix1.get());
   ASSERT_STREQ("scope 1", cInfo1.spec().get());
   ASSERT_STREQ("scope 1", data[1].scope().get());
   ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
   ASSERT_FALSE(data[1].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 1", NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
-  ASSERT_EQ(nsIRequest::VALIDATE_ALWAYS, data[1].loadFlags());
+  ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+            data[1].updateViaCache());
   ASSERT_EQ((int64_t)0, data[1].currentWorkerInstalledTime());
   ASSERT_EQ((int64_t)0, data[1].currentWorkerActivatedTime());
   ASSERT_EQ((int64_t)0, data[1].lastUpdateTime());
 }
 
 TEST(ServiceWorkerRegistrar, TestVersion6Migration)
 {
   nsAutoCString buffer("6" "\n");
@@ -604,17 +617,18 @@ TEST(ServiceWorkerRegistrar, TestVersion
   cInfo0.attrs().CreateSuffix(suffix0);
 
   ASSERT_STREQ("^appId=123&inBrowser=1", suffix0.get());
   ASSERT_STREQ("scope 0", cInfo0.spec().get());
   ASSERT_STREQ("scope 0", data[0].scope().get());
   ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
   ASSERT_TRUE(data[0].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
-  ASSERT_EQ(nsIRequest::LOAD_NORMAL, data[0].loadFlags());
+  ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_ALL,
+            data[0].updateViaCache());
   ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
   ASSERT_EQ((int64_t)0, data[0].currentWorkerActivatedTime());
   ASSERT_EQ((int64_t)0, data[0].lastUpdateTime());
 
   const mozilla::ipc::PrincipalInfo& info1 = data[1].principal();
   ASSERT_EQ(info1.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) << "First principal must be content";
   const mozilla::ipc::ContentPrincipalInfo& cInfo1 = data[1].principal();
 
@@ -622,22 +636,105 @@ TEST(ServiceWorkerRegistrar, TestVersion
   cInfo1.attrs().CreateSuffix(suffix1);
 
   ASSERT_STREQ("", suffix1.get());
   ASSERT_STREQ("scope 1", cInfo1.spec().get());
   ASSERT_STREQ("scope 1", data[1].scope().get());
   ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
   ASSERT_FALSE(data[1].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 1", NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
-  ASSERT_EQ(nsIRequest::VALIDATE_ALWAYS, data[1].loadFlags());
+  ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+            data[1].updateViaCache());
   ASSERT_EQ((int64_t)0, data[1].currentWorkerInstalledTime());
   ASSERT_EQ((int64_t)0, data[1].currentWorkerActivatedTime());
   ASSERT_EQ((int64_t)0, data[1].lastUpdateTime());
 }
 
+TEST(ServiceWorkerRegistrar, TestVersion7Migration)
+{
+  nsAutoCString buffer("7" "\n");
+
+  buffer.Append("^appId=123&inBrowser=1\n");
+  buffer.Append("scope 0\ncurrentWorkerURL 0\n");
+  buffer.Append(SERVICEWORKERREGISTRAR_TRUE "\n");
+  buffer.Append("cacheName 0\n");
+  buffer.AppendInt(nsIRequest::LOAD_NORMAL, 16);
+  buffer.Append("\n");
+  buffer.AppendInt(0);
+  buffer.Append("\n");
+  buffer.AppendInt(0);
+  buffer.Append("\n");
+  buffer.AppendInt(0);
+  buffer.Append("\n");
+  buffer.Append(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
+
+  buffer.Append("\n");
+  buffer.Append("scope 1\ncurrentWorkerURL 1\n");
+  buffer.Append(SERVICEWORKERREGISTRAR_FALSE "\n");
+  buffer.Append("cacheName 1\n");
+  buffer.AppendInt(nsIRequest::VALIDATE_ALWAYS, 16);
+  buffer.Append("\n");
+  PRTime ts = PR_Now();
+  buffer.AppendInt(ts);
+  buffer.Append("\n");
+  buffer.AppendInt(ts);
+  buffer.Append("\n");
+  buffer.AppendInt(ts);
+  buffer.Append("\n");
+  buffer.Append(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
+
+  ASSERT_TRUE(CreateFile(buffer)) << "CreateFile should not fail";
+
+  RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
+
+  nsresult rv = swr->TestReadData();
+  ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail";
+
+  const nsTArray<ServiceWorkerRegistrationData>& data = swr->TestGetData();
+  ASSERT_EQ((uint32_t)2, data.Length()) << "2 entries should be found";
+
+  const mozilla::ipc::PrincipalInfo& info0 = data[0].principal();
+  ASSERT_EQ(info0.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) << "First principal must be content";
+  const mozilla::ipc::ContentPrincipalInfo& cInfo0 = data[0].principal();
+
+  nsAutoCString suffix0;
+  cInfo0.attrs().CreateSuffix(suffix0);
+
+  ASSERT_STREQ("^appId=123&inBrowser=1", suffix0.get());
+  ASSERT_STREQ("scope 0", cInfo0.spec().get());
+  ASSERT_STREQ("scope 0", data[0].scope().get());
+  ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
+  ASSERT_TRUE(data[0].currentWorkerHandlesFetch());
+  ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
+  ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_ALL,
+            data[0].updateViaCache());
+  ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
+  ASSERT_EQ((int64_t)0, data[0].currentWorkerActivatedTime());
+  ASSERT_EQ((int64_t)0, data[0].lastUpdateTime());
+
+  const mozilla::ipc::PrincipalInfo& info1 = data[1].principal();
+  ASSERT_EQ(info1.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) << "First principal must be content";
+  const mozilla::ipc::ContentPrincipalInfo& cInfo1 = data[1].principal();
+
+  nsAutoCString suffix1;
+  cInfo1.attrs().CreateSuffix(suffix1);
+
+  ASSERT_STREQ("", suffix1.get());
+  ASSERT_STREQ("scope 1", cInfo1.spec().get());
+  ASSERT_STREQ("scope 1", data[1].scope().get());
+  ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
+  ASSERT_FALSE(data[1].currentWorkerHandlesFetch());
+  ASSERT_STREQ("cacheName 1", NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
+  ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+            data[1].updateViaCache());
+  ASSERT_EQ((int64_t)ts, data[1].currentWorkerInstalledTime());
+  ASSERT_EQ((int64_t)ts, data[1].currentWorkerActivatedTime());
+  ASSERT_EQ((int64_t)ts, data[1].lastUpdateTime());
+}
+
 TEST(ServiceWorkerRegistrar, TestDedupeRead)
 {
   nsAutoCString buffer("3" "\n");
 
   // unique entries
   buffer.Append("^appId=123&inBrowser=1\n");
   buffer.Append("spec 0\nscope 0\ncurrentWorkerURL 0\ncacheName 0\n");
   buffer.Append(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
@@ -677,17 +774,18 @@ TEST(ServiceWorkerRegistrar, TestDedupeR
   cInfo0.attrs().CreateSuffix(suffix0);
 
   ASSERT_STREQ("^appId=123&inBrowser=1", suffix0.get());
   ASSERT_STREQ("scope 0", cInfo0.spec().get());
   ASSERT_STREQ("scope 0", data[0].scope().get());
   ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
   ASSERT_EQ(true, data[0].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
-  ASSERT_EQ(nsIRequest::VALIDATE_ALWAYS, data[0].loadFlags());
+  ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+            data[0].updateViaCache());
   ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
   ASSERT_EQ((int64_t)0, data[0].currentWorkerActivatedTime());
   ASSERT_EQ((int64_t)0, data[0].lastUpdateTime());
 
   const mozilla::ipc::PrincipalInfo& info1 = data[1].principal();
   ASSERT_EQ(info1.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) << "First principal must be content";
   const mozilla::ipc::ContentPrincipalInfo& cInfo1 = data[1].principal();
 
@@ -695,17 +793,18 @@ TEST(ServiceWorkerRegistrar, TestDedupeR
   cInfo1.attrs().CreateSuffix(suffix1);
 
   ASSERT_STREQ("", suffix1.get());
   ASSERT_STREQ("scope 1", cInfo1.spec().get());
   ASSERT_STREQ("scope 1", data[1].scope().get());
   ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
   ASSERT_EQ(true, data[1].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 1", NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
-  ASSERT_EQ(nsIRequest::VALIDATE_ALWAYS, data[1].loadFlags());
+  ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+            data[1].updateViaCache());
   ASSERT_EQ((int64_t)0, data[1].currentWorkerInstalledTime());
   ASSERT_EQ((int64_t)0, data[1].currentWorkerActivatedTime());
   ASSERT_EQ((int64_t)0, data[1].lastUpdateTime());
 }
 
 TEST(ServiceWorkerRegistrar, TestDedupeWrite)
 {
   {
@@ -714,17 +813,18 @@ TEST(ServiceWorkerRegistrar, TestDedupeW
     for (int i = 0; i < 10; ++i) {
       ServiceWorkerRegistrationData reg;
 
       reg.scope() = NS_LITERAL_CSTRING("scope write dedupe");
       reg.currentWorkerURL() = nsPrintfCString("currentWorkerURL write %d", i);
       reg.currentWorkerHandlesFetch() = true;
       reg.cacheName() =
         NS_ConvertUTF8toUTF16(nsPrintfCString("cacheName write %d", i));
-      reg.loadFlags() = nsIRequest::VALIDATE_ALWAYS;
+      reg.updateViaCache() =
+        nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS;
 
       nsAutoCString spec;
       spec.AppendPrintf("spec write dedupe/%d", i);
       reg.principal() =
         mozilla::ipc::ContentPrincipalInfo(mozilla::OriginAttributes(0, false),
                                            mozilla::void_t(), spec);
 
       swr->TestRegisterServiceWorker(reg);
@@ -755,17 +855,18 @@ TEST(ServiceWorkerRegistrar, TestDedupeW
   // previous values.  So expect "9" in values here.
   ASSERT_STREQ(expectSuffix.get(), suffix.get());
   ASSERT_STREQ("scope write dedupe", cInfo.spec().get());
   ASSERT_STREQ("scope write dedupe", data[0].scope().get());
   ASSERT_STREQ("currentWorkerURL write 9", data[0].currentWorkerURL().get());
   ASSERT_EQ(true, data[0].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName write 9",
                NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
-  ASSERT_EQ(nsIRequest::VALIDATE_ALWAYS, data[0].loadFlags());
+  ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
+            data[0].updateViaCache());
   ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
   ASSERT_EQ((int64_t)0, data[0].currentWorkerActivatedTime());
   ASSERT_EQ((int64_t)0, data[0].lastUpdateTime());
 }
 
 int main(int argc, char** argv) {
   ::testing::InitGoogleTest(&argc, argv);
 
--- a/dom/xslt/xslt/txStylesheetCompiler.cpp
+++ b/dom/xslt/xslt/txStylesheetCompiler.cpp
@@ -1037,17 +1037,17 @@ findFunction(nsIAtom* aName, int32_t aNa
         nsCOMPtr<nsICategoryManager> catman =
             do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv);
         NS_ENSURE_SUCCESS(rv, rv);
 
         nsAutoString namespaceURI;
         rv = txNamespaceManager::getNamespaceURI(aNamespaceID, namespaceURI);
         NS_ENSURE_SUCCESS(rv, rv);
 
-        nsXPIDLCString contractID;
+        nsCString contractID;
         rv = catman->GetCategoryEntry("XSLT-extension-functions",
                                       NS_ConvertUTF16toUTF8(namespaceURI).get(),
                                       getter_Copies(contractID));
         if (rv == NS_ERROR_NOT_AVAILABLE) {
             return NS_ERROR_XPATH_UNKNOWN_FUNCTION;
         }
         NS_ENSURE_SUCCESS(rv, rv);
 
--- a/editor/composer/nsComposerCommands.cpp
+++ b/editor/composer/nsComposerCommands.cpp
@@ -622,17 +622,17 @@ nsMultiStateCommand::DoCommandParams(con
   }
   mozilla::HTMLEditor* htmlEditor = editor->AsHTMLEditor();
   if (NS_WARN_IF(!htmlEditor)) {
     return NS_ERROR_FAILURE;
   }
 
   nsAutoString tString;
   if (aParams) {
-    nsXPIDLCString s;
+    nsCString s;
     nsresult rv = aParams->GetCStringValue(STATE_ATTRIBUTE, getter_Copies(s));
     if (NS_SUCCEEDED(rv))
       CopyASCIItoUTF16(s, tString);
     else
       aParams->GetStringValue(STATE_ATTRIBUTE, tString);
   }
   return SetState(htmlEditor, tString);
 }
@@ -1503,17 +1503,17 @@ nsInsertTagCommand::DoCommandParams(cons
     return NS_ERROR_FAILURE;
   }
   mozilla::HTMLEditor* htmlEditor = editor->AsHTMLEditor();
   if (NS_WARN_IF(!htmlEditor)) {
     return NS_ERROR_FAILURE;
   }
 
   // do we have an href to use for creating link?
-  nsXPIDLCString s;
+  nsCString s;
   nsresult rv = aParams->GetCStringValue(STATE_ATTRIBUTE, getter_Copies(s));
   NS_ENSURE_SUCCESS(rv, rv);
   nsAutoString attrib;
   CopyASCIItoUTF16(s, attrib);
 
   if (attrib.IsEmpty())
     return NS_ERROR_INVALID_ARG;
 
--- a/editor/composer/nsComposerDocumentCommands.cpp
+++ b/editor/composer/nsComposerDocumentCommands.cpp
@@ -270,17 +270,17 @@ nsSetDocumentStateCommand::DoCommandPara
     if (NS_WARN_IF(!aParams)) {
       return NS_ERROR_NULL_POINTER;
     }
     HTMLEditor* htmlEditor = textEditor->AsHTMLEditor();
     if (NS_WARN_IF(!htmlEditor)) {
       return NS_ERROR_INVALID_ARG;
     }
 
-    nsXPIDLCString newValue;
+    nsCString newValue;
     nsresult rv = aParams->GetCStringValue(STATE_ATTRIBUTE,
                                            getter_Copies(newValue));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     if (newValue.LowerCaseEqualsLiteral("div")) {
       htmlEditor->SetDefaultParagraphSeparator(ParagraphSeparator::div);
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -4887,16 +4887,35 @@ EditorBase::InitializeSelection(nsIDOMEv
                                               mIMETextLength,
                                               mComposition->GetRanges());
     }
   }
 
   return NS_OK;
 }
 
+class RepaintSelectionRunner final : public Runnable {
+public:
+  explicit RepaintSelectionRunner(nsISelectionController* aSelectionController)
+    : Runnable("RepaintSelectionRunner")
+    , mSelectionController(aSelectionController)
+  {
+  }
+
+  NS_IMETHOD Run() override
+  {
+    mSelectionController->RepaintSelection(
+                            nsISelectionController::SELECTION_NORMAL);
+    return NS_OK;
+  }
+
+private:
+  nsCOMPtr<nsISelectionController> mSelectionController;
+};
+
 NS_IMETHODIMP
 EditorBase::FinalizeSelection()
 {
   nsCOMPtr<nsISelectionController> selectionController =
     GetSelectionController();
   if (NS_WARN_IF(!selectionController)) {
     return NS_ERROR_FAILURE;
   }
@@ -4941,18 +4960,20 @@ EditorBase::FinalizeSelection()
                            nsISelectionController::SELECTION_HIDDEN);
   } else {
     // Otherwise, although we're not sure how this case happens, the
     // independent selection should be marked as disabled.
     selectionController->SetDisplaySelection(
                            nsISelectionController::SELECTION_DISABLED);
   }
 
-  selectionController->RepaintSelection(
-                         nsISelectionController::SELECTION_NORMAL);
+  // FinalizeSelection might be called from ContentRemoved even if selection
+  // isn't updated.  So we need to call RepaintSelection after updated it.
+  nsContentUtils::AddScriptRunner(
+                    new RepaintSelectionRunner(selectionController));
   return NS_OK;
 }
 
 Element*
 EditorBase::GetRoot()
 {
   if (!mRootElement) {
     // Let GetRootElement() do the work
--- a/extensions/auth/nsAuthGSSAPI.cpp
+++ b/extensions/auth/nsAuthGSSAPI.cpp
@@ -97,17 +97,17 @@ static PRLibrary* gssLibrary = nullptr;
 static PRFuncPtr KLCacheHasValidTicketsPtr;
 #define KLCacheHasValidTickets_ptr \
         ((KLCacheHasValidTickets_type)*KLCacheHasValidTicketsPtr)
 #endif
 
 static nsresult
 gssInit()
 {
-    nsXPIDLCString libPath;
+    nsCString libPath;
     nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
     if (prefs) {
         prefs->GetCharPref(kNegotiateAuthGssLib, getter_Copies(libPath));
         prefs->GetBoolPref(kNegotiateAuthNativeImp, &gssNativeImp);
     }
 
     PRLibrary *lib = nullptr;
 
--- a/extensions/pref/autoconfig/src/nsAutoConfig.cpp
+++ b/extensions/pref/autoconfig/src/nsAutoConfig.cpp
@@ -203,17 +203,16 @@ NS_IMETHODIMP nsAutoConfig::Observe(nsIS
 
     return rv;
 }
 
 nsresult nsAutoConfig::downloadAutoConfig()
 {
     nsresult rv;
     nsAutoCString emailAddr;
-    nsXPIDLCString urlName;
     static bool firstTime = true;
 
     if (mConfigURL.IsEmpty()) {
         MOZ_LOG(MCD, LogLevel::Debug, ("global config url is empty - did you set autoadmin.global_config_url?\n"));
         NS_WARNING("AutoConfig called without global_config_url");
         return NS_OK;
     }
 
@@ -458,17 +457,17 @@ nsresult nsAutoConfig::writeFailoverFile
     outStr->Close();
     return rv;
 }
 
 nsresult nsAutoConfig::getEmailAddr(nsACString & emailAddr)
 {
 
     nsresult rv;
-    nsXPIDLCString prefValue;
+    nsCString prefValue;
 
     /* Getting an email address through set of three preferences:
        First getting a default account with
        "mail.accountmanager.defaultaccount"
        second getting an associated id with the default account
        Third getting an email address with id
     */
 
--- a/extensions/spellcheck/locales/en-US/hunspell/en-US.dic
+++ b/extensions/spellcheck/locales/en-US/hunspell/en-US.dic
@@ -1,9 +1,9 @@
-52359
+52361
 0/nm
 0th/pt
 1/n1
 1st/p
 1th/tc
 2/nm
 2nd/p
 2th/tc
@@ -15826,16 +15826,18 @@ baldness/M
 baldric/SM
 baldy/S
 bale/DRSMZG
 baleen/M
 baleful/PY
 balefulness/M
 baler/M
 balk/SGMD
+balkanization
+balkanize/SDG
 balky/RT
 ball/SGMD
 ballad/SM
 balladeer/MS
 balladry/M
 ballast/GSMD
 ballcock/MS
 ballerina/SM
--- a/gfx/gl/GLBlitHelper.cpp
+++ b/gfx/gl/GLBlitHelper.cpp
@@ -812,17 +812,21 @@ GLBlitHelper::BlitImageToFramebuffer(lay
     ScopedGLDrawState autoStates(mGL);
 
     BlitType type;
     OriginPos srcOrigin;
 
     switch (srcImage->GetFormat()) {
     case ImageFormat::PLANAR_YCBCR:
         type = ConvertPlanarYCbCr;
+#if defined(MOZ_WIDGET_ANDROID)
+        srcOrigin = OriginPos::TopLeft;
+#else
         srcOrigin = OriginPos::BottomLeft;
+#endif // defined(MOZ_WIDGET_ANDROID)
         break;
 
 #ifdef MOZ_WIDGET_ANDROID
     case ImageFormat::SURFACE_TEXTURE:
         type = ConvertSurfaceTexture;
         srcOrigin = srcImage->AsSurfaceTextureImage()->GetOriginPos();
         break;
     case ImageFormat::EGLIMAGE:
--- a/gfx/gl/GLContextProviderEGL.cpp
+++ b/gfx/gl/GLContextProviderEGL.cpp
@@ -237,17 +237,16 @@ GLContextEGL::~GLContextEGL()
         return;
     }
 
 #ifdef DEBUG
     printf_stderr("Destroying context %p surface %p on display %p\n", mContext, mSurface, EGL_DISPLAY());
 #endif
 
     sEGLLibrary.fDestroyContext(EGL_DISPLAY(), mContext);
-    sEGLLibrary.UnsetCachedCurrentContext();
 
     mozilla::gl::DestroySurface(mSurface);
 }
 
 bool
 GLContextEGL::Init()
 {
 #if defined(ANDROID)
@@ -341,28 +340,18 @@ GLContextEGL::SetEGLSurfaceOverride(EGLS
 
 bool
 GLContextEGL::MakeCurrentImpl(bool aForce) {
     bool succeeded = true;
 
     // Assume that EGL has the same problem as WGL does,
     // where MakeCurrent with an already-current context is
     // still expensive.
-    bool hasDifferentContext = false;
-    if (sEGLLibrary.CachedCurrentContext() != mContext) {
-        // even if the cached context doesn't match the current one
-        // might still
-        if (sEGLLibrary.fGetCurrentContext() != mContext) {
-            hasDifferentContext = true;
-        } else {
-            sEGLLibrary.SetCachedCurrentContext(mContext);
-        }
-    }
-
-    if (aForce || hasDifferentContext) {
+    bool needsMakeCurrent = (aForce || sEGLLibrary.fGetCurrentContext() != mContext);
+    if (needsMakeCurrent) {
         EGLSurface surface = mSurfaceOverride != EGL_NO_SURFACE
                               ? mSurfaceOverride
                               : mSurface;
         if (surface == EGL_NO_SURFACE) {
             return false;
         }
         succeeded = sEGLLibrary.fMakeCurrent(EGL_DISPLAY(),
                                               surface, surface,
@@ -373,21 +362,17 @@ GLContextEGL::MakeCurrentImpl(bool aForc
                 mContextLost = true;
                 NS_WARNING("EGL context has been lost.");
             } else {
                 NS_WARNING("Failed to make GL context current!");
 #ifdef DEBUG
                 printf_stderr("EGL Error: 0x%04x\n", eglError);
 #endif
             }
-        } else {
-            sEGLLibrary.SetCachedCurrentContext(mContext);
         }
-    } else {
-        MOZ_ASSERT(sEGLLibrary.CachedCurrentContextMatches());
     }
 
     return succeeded;
 }
 
 bool
 GLContextEGL::IsCurrent() {
     return sEGLLibrary.fGetCurrentContext() == mContext;
--- a/gfx/gl/GLLibraryEGL.h
+++ b/gfx/gl/GLLibraryEGL.h
@@ -480,24 +480,16 @@ private:
         EGLBoolean (GLAPIENTRY * fStreamPostD3DTextureNV12ANGLE)(EGLDisplay dpy,
                                                                  EGLStreamKHR stream,
                                                                  void* texture,
                                                                  const EGLAttrib* attrib_list);
         void       (GLAPIENTRY * fANGLEPlatformInitialize)(angle::Platform* platform);
         void       (GLAPIENTRY * fANGLEPlatformShutdown)();
     } mSymbols;
 
-public:
-    EGLContext CachedCurrentContext() {
-        return nullptr;
-    }
-    void UnsetCachedCurrentContext() {}
-    void SetCachedCurrentContext(EGLContext aCtx) { }
-    bool CachedCurrentContextMatches() { return true; }
-
 private:
     bool mInitialized;
     PRLibrary* mEGLLibrary;
     EGLDisplay mEGLDisplay;
     RefPtr<GLContext> mReadbackGL;
 
     bool mIsANGLE;
     bool mIsWARP;
--- a/gfx/layers/PaintThread.cpp
+++ b/gfx/layers/PaintThread.cpp
@@ -61,21 +61,25 @@ PaintThread::Release()
 {
 }
 
 void
 PaintThread::AddRef()
 {
 }
 
-void
-PaintThread::InitOnPaintThread()
+/* static */ void
+PaintThread::Start()
 {
-  MOZ_ASSERT(!NS_IsMainThread());
-  sThreadId = PlatformThread::CurrentId();
+  PaintThread::sSingleton = new PaintThread();
+
+  if (!PaintThread::sSingleton->Init()) {
+    gfxCriticalNote << "Unable to start paint thread";
+    PaintThread::sSingleton = nullptr;
+  }
 }
 
 bool
 PaintThread::Init()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   RefPtr<nsIThread> thread;
@@ -87,32 +91,21 @@ PaintThread::Init()
 
   nsCOMPtr<nsIRunnable> paintInitTask =
     NewRunnableMethod("PaintThread::InitOnPaintThread",
                       this, &PaintThread::InitOnPaintThread);
   SyncRunnable::DispatchToThread(sThread, paintInitTask);
   return true;
 }
 
-/* static */ void
-PaintThread::Start()
+void
+PaintThread::InitOnPaintThread()
 {
-  PaintThread::sSingleton = new PaintThread();
-
-  if (!PaintThread::sSingleton->Init()) {
-    gfxCriticalNote << "Unable to start paint thread";
-    PaintThread::sSingleton = nullptr;
-  }
-}
-
-/* static */ PaintThread*
-PaintThread::Get()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  return PaintThread::sSingleton.get();
+  MOZ_ASSERT(!NS_IsMainThread());
+  sThreadId = PlatformThread::CurrentId();
 }
 
 void
 DestroyPaintThread(UniquePtr<PaintThread>&& pt)
 {
   MOZ_ASSERT(PaintThread::IsOnPaintThread());
   pt->ShutdownOnPaintThread();
 }
@@ -133,120 +126,30 @@ PaintThread::Shutdown()
 }
 
 void
 PaintThread::ShutdownOnPaintThread()
 {
   MOZ_ASSERT(IsOnPaintThread());
 }
 
+/* static */ PaintThread*
+PaintThread::Get()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return PaintThread::sSingleton.get();
+}
+
 /* static */ bool
 PaintThread::IsOnPaintThread()
 {
   return sThreadId == PlatformThread::CurrentId();
 }
 
 void
-PaintThread::PaintContentsAsync(CompositorBridgeChild* aBridge,
-                                CapturedPaintState* aState,
-                                PrepDrawTargetForPaintingCallback aCallback)
-{
-  MOZ_ASSERT(IsOnPaintThread());
-  MOZ_ASSERT(aState);
-
-  DrawTarget* target = aState->mTarget;
-  DrawTargetCapture* capture = aState->mCapture;
-
-  AutoCapturedPaintSetup setup(aState, aBridge);
-
-  if (!aCallback(aState)) {
-    return;
-  }
-
-  // Draw all the things into the actual dest target.
-  target->DrawCapturedDT(capture, Matrix());
-  if (!mDrawTargetsToFlush.Contains(target)) {
-    mDrawTargetsToFlush.AppendElement(target);
-  }
-}
-
-void
-PaintThread::FinishedLayerBatch()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  RefPtr<PaintThread> self = this;
-  RefPtr<Runnable> task = NS_NewRunnableFunction("PaintThread::EndAsyncPaintingLayer",
-  [self]() -> void
-  {
-    self->EndAsyncPaintingLayer();
-  });
-
-  if (!gfxPrefs::LayersOMTPForceSync()) {
-    sThread->Dispatch(task.forget());
-  } else {
-    SyncRunnable::DispatchToThread(sThread, task);
-  }
-}
-
-void
-PaintThread::EndAsyncPaintingLayer()
-{
-  MOZ_ASSERT(IsOnPaintThread());
-  // Textureclient forces a flush once we "end paint", so
-  // users of this texture expect all the drawing to be complete.
-  // Force a flush now.
-  for (size_t i = 0; i < mDrawTargetsToFlush.Length(); i++) {
-    mDrawTargetsToFlush[i]->Flush();
-  }
-
-  mDrawTargetsToFlush.Clear();
-}
-
-void
-PaintThread::SynchronizePaintTextures(SyncObjectClient* aSyncObject)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aSyncObject);
-
-  RefPtr<CompositorBridgeChild> cbc;
-  if (!gfxPrefs::LayersOMTPForceSync()) {
-    cbc = CompositorBridgeChild::Get();
-  }
-
-  RefPtr<SyncObjectClient> syncObject(aSyncObject);
-  RefPtr<PaintThread> self = this;
-  RefPtr<Runnable> task = NS_NewRunnableFunction("PaintThread::SyncTextureData",
-    [self, cbc, syncObject]() -> void
-  {
-    self->SyncTextureData(cbc, syncObject);
-  });
-
-  if (cbc) {
-    sThread->Dispatch(task.forget());
-  } else {
-    SyncRunnable::DispatchToThread(sThread, task);
-  }
-}
-
-void
-PaintThread::SyncTextureData(CompositorBridgeChild* aBridge,
-                             SyncObjectClient* aSyncObject)
-{
-  MOZ_ASSERT(IsOnPaintThread());
-  MOZ_ASSERT(aSyncObject);
-
-  aSyncObject->Synchronize();
-
-  if (aBridge) {
-    aBridge->NotifyFinishedAsyncPaintTransaction();
-  }
-}
-
-void
 PaintThread::PaintContents(CapturedPaintState* aState,
                            PrepDrawTargetForPaintingCallback aCallback)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aState);
 
   // If painting asynchronously, we need to acquire the compositor bridge which
   // owns the underlying MessageChannel. Otherwise we leave it null and use
@@ -258,22 +161,120 @@ PaintThread::PaintContents(CapturedPaint
   }
   RefPtr<CapturedPaintState> state(aState);
   RefPtr<DrawTargetCapture> capture(aState->mCapture);
 
   RefPtr<PaintThread> self = this;
   RefPtr<Runnable> task = NS_NewRunnableFunction("PaintThread::PaintContents",
     [self, cbc, capture, state, aCallback]() -> void
   {
-    self->PaintContentsAsync(cbc,
+    self->AsyncPaintContents(cbc,
                              state,
                              aCallback);
   });
 
   if (cbc) {
     sThread->Dispatch(task.forget());
   } else {
     SyncRunnable::DispatchToThread(sThread, task);
   }
 }
 
+void
+PaintThread::AsyncPaintContents(CompositorBridgeChild* aBridge,
+                                CapturedPaintState* aState,
+                                PrepDrawTargetForPaintingCallback aCallback)
+{
+  MOZ_ASSERT(IsOnPaintThread());
+  MOZ_ASSERT(aState);
+
+  DrawTarget* target = aState->mTarget;
+  DrawTargetCapture* capture = aState->mCapture;
+
+  AutoCapturedPaintSetup setup(aState, aBridge);
+
+  if (!aCallback(aState)) {
+    return;
+  }
+
+  // Draw all the things into the actual dest target.
+  target->DrawCapturedDT(capture, Matrix());
+  if (!mDrawTargetsToFlush.Contains(target)) {
+    mDrawTargetsToFlush.AppendElement(target);
+  }
+}
+
+void
+PaintThread::EndLayer()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  RefPtr<PaintThread> self = this;
+  RefPtr<Runnable> task = NS_NewRunnableFunction("PaintThread::AsyncEndLayer",
+  [self]() -> void
+  {
+    self->AsyncEndLayer();
+  });
+
+  if (!gfxPrefs::LayersOMTPForceSync()) {
+    sThread->Dispatch(task.forget());
+  } else {
+    SyncRunnable::DispatchToThread(sThread, task);
+  }
+}
+
+void
+PaintThread::AsyncEndLayer()
+{
+  MOZ_ASSERT(IsOnPaintThread());
+  // Textureclient forces a flush once we "end paint", so
+  // users of this texture expect all the drawing to be complete.
+  // Force a flush now.
+  for (size_t i = 0; i < mDrawTargetsToFlush.Length(); i++) {
+    mDrawTargetsToFlush[i]->Flush();
+  }
+
+  mDrawTargetsToFlush.Clear();
+}
+
+void
+PaintThread::EndLayerTransaction(SyncObjectClient* aSyncObject)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  RefPtr<CompositorBridgeChild> cbc;
+  if (!gfxPrefs::LayersOMTPForceSync()) {
+    cbc = CompositorBridgeChild::Get();
+    cbc->NotifyBeginAsyncEndLayerTransaction();
+  }
+
+  RefPtr<SyncObjectClient> syncObject(aSyncObject);
+  RefPtr<PaintThread> self = this;
+  RefPtr<Runnable> task = NS_NewRunnableFunction("PaintThread::AsyncEndLayerTransaction",
+    [self, cbc, syncObject]() -> void
+  {
+    self->AsyncEndLayerTransaction(cbc, syncObject);
+  });
+
+  if (cbc) {
+    sThread->Dispatch(task.forget());
+  } else {
+    SyncRunnable::DispatchToThread(sThread, task);
+  }
+}
+
+void
+PaintThread::AsyncEndLayerTransaction(CompositorBridgeChild* aBridge,
+                                      SyncObjectClient* aSyncObject)
+{
+  MOZ_ASSERT(IsOnPaintThread());
+
+  if (aSyncObject) {
+    aSyncObject->Synchronize();
+  }
+
+  if (aBridge) {
+    aBridge->NotifyFinishedAsyncEndLayerTransaction();
+  }
+}
+
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/PaintThread.h
+++ b/gfx/layers/PaintThread.h
@@ -62,52 +62,54 @@ class CompositorBridgeChild;
 class PaintThread final
 {
   friend void DestroyPaintThread(UniquePtr<PaintThread>&& aPaintThread);
 
 public:
   static void Start();
   static void Shutdown();
   static PaintThread* Get();
+
+  // Helper for asserts.
+  static bool IsOnPaintThread();
+
   void PaintContents(CapturedPaintState* aState,
                      PrepDrawTargetForPaintingCallback aCallback);
 
-  // To be called on the main thread. Signifies that the current
+  // Must be called on the main thread. Signifies that the current
   // batch of CapturedPaintStates* for PaintContents have been recorded
   // and the main thread is finished recording this layer.
-  void FinishedLayerBatch();
+  void EndLayer();
 
-  // Must be called on the main thread. Tells the paint thread
-  // to schedule a sync textures after all async paints are done.
-  // NOTE: The other paint thread functions are on a per PAINT
-  // or per paint layer basis. This MUST be called at the end
-  // of a layer transaction as multiple paints can occur
-  // with multiple layers. We only have to do this once per transaction.
-  void SynchronizePaintTextures(SyncObjectClient* aSyncObject);
+  // Must be called on the main thread. Signifies that the current
+  // layer tree transaction has been finished and any async paints
+  // for it have been queued on the paint thread. This MUST be called
+  // at the end of a layer transaction as it will be used to do an optional
+  // texture sync and then unblock the main thread if it is waiting to paint
+  // a new frame.
+  void EndLayerTransaction(SyncObjectClient* aSyncObject);
 
   // Sync Runnables need threads to be ref counted,
   // But this thread lives through the whole process.
   // We're only temporarily using sync runnables so
   // Override release/addref but don't do anything.
   void Release();
   void AddRef();
 
-  // Helper for asserts.
-  static bool IsOnPaintThread();
-
 private:
   bool Init();
   void ShutdownOnPaintThread();
   void InitOnPaintThread();
-  void PaintContentsAsync(CompositorBridgeChild* aBridge,
+
+  void AsyncPaintContents(CompositorBridgeChild* aBridge,
                           CapturedPaintState* aState,
                           PrepDrawTargetForPaintingCallback aCallback);
-  void EndAsyncPaintingLayer();
-  void SyncTextureData(CompositorBridgeChild* aBridge,
-                       SyncObjectClient* aSyncObject);
+  void AsyncEndLayer();
+  void AsyncEndLayerTransaction(CompositorBridgeChild* aBridge,
+                                SyncObjectClient* aSyncObject);
 
   static StaticAutoPtr<PaintThread> sSingleton;
   static StaticRefPtr<nsIThread> sThread;
   static PlatformThreadId sThreadId;
 
   // This shouldn't be very many elements, so a list should be fine.
   // Should only be accessed on the paint thread.
   nsTArray<RefPtr<gfx::DrawTarget>> mDrawTargetsToFlush;
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -724,28 +724,35 @@ void
 ClientLayerManager::ForwardTransaction(bool aScheduleComposite)
 {
   AutoProfilerTracing tracing("Paint", "ForwardTransaction");
   TimeStamp start = TimeStamp::Now();
 
   // Skip the synchronization for buffer since we also skip the painting during
   // device-reset status. With OMTP, we have to wait for async paints
   // before we synchronize and it's done on the paint thread.
+  RefPtr<SyncObjectClient> syncObject = nullptr;
   if (!gfxPlatform::GetPlatform()->DidRenderingDeviceReset()) {
     if (mForwarder->GetSyncObject() &&
         mForwarder->GetSyncObject()->IsSyncObjectValid()) {
-      if (mTextureSyncOnPaintThread) {
-        // We have to wait for all async paints to finish to do this
-        PaintThread::Get()->SynchronizePaintTextures(mForwarder->GetSyncObject());
-      } else {
-        mForwarder->GetSyncObject()->Synchronize();
-      }
+      syncObject = mForwarder->GetSyncObject();
     }
   }
 
+  // If there were async paints queued, then we need to notify the paint thread
+  // that we finished queuing async paints so it can schedule a runnable after
+  // all async painting is finished to do a texture sync and unblock the main
+  // thread if it is waiting before doing a new layer transaction.
+  if (mTextureSyncOnPaintThread) {
+    MOZ_ASSERT(PaintThread::Get());
+    PaintThread::Get()->EndLayerTransaction(syncObject);
+  } else if (syncObject) {
+    syncObject->Synchronize();
+  }
+
   mPhase = PHASE_FORWARD;
 
   mLatestTransactionId = mTransactionIdAllocator->GetTransactionId(!mIsRepeatTransaction);
   TimeStamp transactionStart;
   if (!mTransactionIdAllocator->GetTransactionStart().IsNull()) {
     transactionStart = mTransactionIdAllocator->GetTransactionStart();
   } else {
     transactionStart = mTransactionStart;
--- a/gfx/layers/client/ClientPaintedLayer.cpp
+++ b/gfx/layers/client/ClientPaintedLayer.cpp
@@ -258,17 +258,17 @@ ClientPaintedLayer::PaintOffMainThread()
     PaintThread::Get()->PaintContents(captureState,
                                       RotatedContentBuffer::PrepareDrawTargetForPainting);
 
     mContentClient->ReturnDrawTargetToBuffer(target);
 
     didUpdate = true;
   }
 
-  PaintThread::Get()->FinishedLayerBatch();
+  PaintThread::Get()->EndLayer();
   mContentClient->EndPaint(nullptr);
 
   if (didUpdate) {
     UpdateContentClient(state);
     ClientManager()->SetNeedTextureSyncOnPaintThread();
   }
   return true;
 }
--- a/gfx/layers/ipc/CompositorBridgeChild.cpp
+++ b/gfx/layers/ipc/CompositorBridgeChild.cpp
@@ -87,16 +87,17 @@ CompositorBridgeChild::CompositorBridgeC
   , mActorDestroyed(false)
   , mFwdTransactionId(0)
   , mDeviceResetSequenceNumber(0)
   , mMessageLoop(MessageLoop::current())
   , mProcessToken(0)
   , mSectionAllocator(nullptr)
   , mPaintLock("CompositorBridgeChild.mPaintLock")
   , mOutstandingAsyncPaints(0)
+  , mOutstandingAsyncEndTransaction(false)
   , mIsWaitingForPaint(false)
 {
   MOZ_ASSERT(NS_IsMainThread());
 }
 
 CompositorBridgeChild::~CompositorBridgeChild()
 {
   if (mCanSend) {
@@ -1129,16 +1130,30 @@ CompositorBridgeChild::GetNextExternalIm
 
 wr::PipelineId
 CompositorBridgeChild::GetNextPipelineId()
 {
   return wr::AsPipelineId(GetNextResourceId());
 }
 
 void
+CompositorBridgeChild::FlushAsyncPaints()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  MonitorAutoLock lock(mPaintLock);
+  while (mIsWaitingForPaint) {
+    lock.Wait();
+  }
+
+  // It's now safe to free any TextureClients that were used during painting.
+  mTextureClientsForAsyncPaint.Clear();
+}
+
+void
 CompositorBridgeChild::NotifyBeginAsyncPaint(CapturedPaintState* aState)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   MonitorAutoLock lock(mPaintLock);
 
   // We must not be waiting for paints to complete yet. This would imply we
   // started a new paint without waiting for a previous one, which could lead to
@@ -1184,52 +1199,51 @@ CompositorBridgeChild::NotifyFinishedAsy
   aState->mTextureClient = nullptr;
   if (aState->mTextureClientOnWhite) {
     aState->mTextureClientOnWhite->DropPaintThreadRef();
     aState->mTextureClientOnWhite = nullptr;
   }
 }
 
 void
-CompositorBridgeChild::NotifyFinishedAsyncPaintTransaction()
+CompositorBridgeChild::NotifyBeginAsyncEndLayerTransaction()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MonitorAutoLock lock(mPaintLock);
+
+  MOZ_ASSERT(!mOutstandingAsyncEndTransaction);
+  mOutstandingAsyncEndTransaction = true;
+}
+
+void
+CompositorBridgeChild::NotifyFinishedAsyncEndLayerTransaction()
 {
   MOZ_ASSERT(PaintThread::IsOnPaintThread());
   MonitorAutoLock lock(mPaintLock);
+
   // Since this should happen after ALL paints are done and
   // at the end of a transaction, this should always be true.
   MOZ_RELEASE_ASSERT(mOutstandingAsyncPaints == 0);
+  MOZ_ASSERT(mOutstandingAsyncEndTransaction);
+
+  mOutstandingAsyncEndTransaction = false;
 
   // It's possible that we painted so fast that the main thread never reached
   // the code that starts delaying messages. If so, mIsWaitingForPaint will be
   // false, and we can safely return.
   if (mIsWaitingForPaint) {
     ResumeIPCAfterAsyncPaint();
 
     // Notify the main thread in case it's blocking. We do this unconditionally
     // to avoid deadlocking.
     lock.Notify();
   }
 }
 
 void
-CompositorBridgeChild::PostponeMessagesIfAsyncPainting()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  MonitorAutoLock lock(mPaintLock);
-
-  MOZ_ASSERT(!mIsWaitingForPaint);
-
-  if (mOutstandingAsyncPaints > 0) {
-    mIsWaitingForPaint = true;
-    GetIPCChannel()->BeginPostponingSends();
-  }
-}
-
-void
 CompositorBridgeChild::ResumeIPCAfterAsyncPaint()
 {
   // Note: the caller is responsible for holding the lock.
   mPaintLock.AssertCurrentThreadOwns();
   MOZ_ASSERT(PaintThread::IsOnPaintThread());
   MOZ_ASSERT(mOutstandingAsyncPaints == 0);
   MOZ_ASSERT(mIsWaitingForPaint);
 
@@ -1239,23 +1253,26 @@ CompositorBridgeChild::ResumeIPCAfterAsy
   if (!mCanSend || mActorDestroyed) {
     return;
   }
 
   GetIPCChannel()->StopPostponingSends();
 }
 
 void
-CompositorBridgeChild::FlushAsyncPaints()
+CompositorBridgeChild::PostponeMessagesIfAsyncPainting()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   MonitorAutoLock lock(mPaintLock);
-  while (mIsWaitingForPaint) {
-    lock.Wait();
+
+  MOZ_ASSERT(!mIsWaitingForPaint);
+
+  // We need to wait for async paints and the async end transaction as
+  // it will do texture synchronization
+  if (mOutstandingAsyncPaints > 0 || mOutstandingAsyncEndTransaction) {
+    mIsWaitingForPaint = true;
+    GetIPCChannel()->BeginPostponingSends();
   }
-
-  // It's now safe to free any TextureClients that were used during painting.
-  mTextureClientsForAsyncPaint.Clear();
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/ipc/CompositorBridgeChild.h
+++ b/gfx/layers/ipc/CompositorBridgeChild.h
@@ -217,40 +217,45 @@ public:
   uint64_t DeviceResetSequenceNumber() const {
     return mDeviceResetSequenceNumber;
   }
 
   wr::MaybeExternalImageId GetNextExternalImageId() override;
 
   wr::PipelineId GetNextPipelineId();
 
+  // Must only be called from the main thread. Ensures that any paints from
+  // previous frames have been flushed. The main thread blocks until the
+  // operation completes.
+  void FlushAsyncPaints();
+
   // Must only be called from the main thread. Notifies the CompositorBridge
   // that the paint thread is going to begin painting asynchronously.
   void NotifyBeginAsyncPaint(CapturedPaintState* aState);
 
+  // Must only be called from the paint thread. Notifies the CompositorBridge
+  // that the paint thread has finished an asynchronous paint request.
+  void NotifyFinishedAsyncPaint(CapturedPaintState* aState);
+
+  // Must only be called from the main thread. Notifies the CompositorBridge
+  // that the paint thread is going to perform texture synchronization at the
+  // end of async painting, and should postpone messages if needed until
+  // finished.
+  void NotifyBeginAsyncEndLayerTransaction();
+
+  // Must only be called from the paint thread. Notifies the CompositorBridge
+  // that the paint thread has finished all async paints and texture syncs from
+  // a given transaction and may resume sending messages.
+  void NotifyFinishedAsyncEndLayerTransaction();
+
   // Must only be called from the main thread. Notifies the CompoistorBridge
   // that a transaction is about to be sent, and if the paint thread is
   // currently painting, to begin delaying IPC messages.
   void PostponeMessagesIfAsyncPainting();
 
-  // Must only be called from the main thread. Ensures that any paints from
-  // previous frames have been flushed. The main thread blocks until the
-  // operation completes.
-  void FlushAsyncPaints();
-
-  // Must only be called from the paint thread. Notifies the CompositorBridge
-  // that the paint thread has finished an asynchronous paint request.
-  void NotifyFinishedAsyncPaint(CapturedPaintState* aState);
-
-  // Must only be called from the paint thread. Notifies the CompositorBridge
-  // that the paint thread has finished ALL async requests from a given
-  // transaction. We can resume IPC transactions after ALL
-  // async paints are done.
-  void NotifyFinishedAsyncPaintTransaction();
-
 private:
   // Private destructor, to discourage deletion outside of Release():
   virtual ~CompositorBridgeChild();
 
   // Must only be called from the paint thread. If the main thread is delaying
   // IPC messages, this forwards all such delayed IPC messages to the I/O thread
   // and resumes IPC.
   void ResumeIPCAfterAsyncPaint();
@@ -368,16 +373,19 @@ private:
   // state below.
   Monitor mPaintLock;
 
   // Contains the number of outstanding asynchronous paints tied to a
   // PLayerTransaction on this bridge. This is R/W on both the main and paint
   // threads, and must be accessed within the paint lock.
   size_t mOutstandingAsyncPaints;
 
+  // Whether we are waiting for an async paint end transaction
+  bool mOutstandingAsyncEndTransaction;
+
   // True if this CompositorBridge is currently delaying its messages until the
   // paint thread completes. This is R/W on both the main and paint threads, and
   // must be accessed within the paint lock.
   bool mIsWaitingForPaint;
 };
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/ipc/CompositorManagerParent.cpp
+++ b/gfx/layers/ipc/CompositorManagerParent.cpp
@@ -133,19 +133,30 @@ CompositorManagerParent::ActorDestroy(Ac
   if (sInstance == this) {
     sInstance = nullptr;
   }
 }
 
 void
 CompositorManagerParent::DeallocPCompositorManagerParent()
 {
+  MessageLoop::current()->PostTask(
+          NewRunnableMethod("layers::CompositorManagerParent::DeferredDestroy",
+                            this,
+                            &CompositorManagerParent::DeferredDestroy));
+
   Release();
 }
 
+void
+CompositorManagerParent::DeferredDestroy()
+{
+  mCompositorThreadHolder = nullptr;
+}
+
 PCompositorBridgeParent*
 CompositorManagerParent::AllocPCompositorBridgeParent(const CompositorBridgeOptions& aOpt)
 {
   switch (aOpt.type()) {
     case CompositorBridgeOptions::TContentCompositorOptions: {
       CrossProcessCompositorBridgeParent* bridge =
         new CrossProcessCompositorBridgeParent(this);
       bridge->AddRef();
--- a/gfx/layers/ipc/CompositorManagerParent.h
+++ b/gfx/layers/ipc/CompositorManagerParent.h
@@ -45,16 +45,18 @@ private:
 
   CompositorManagerParent();
   ~CompositorManagerParent() override;
 
   void Bind(Endpoint<PCompositorManagerParent>&& aEndpoint);
 
   void DeallocPCompositorManagerParent() override;
 
+  void DeferredDestroy();
+
   RefPtr<CompositorThreadHolder> mCompositorThreadHolder;
 
   AutoTArray<RefPtr<CompositorBridgeParent>, 1> mPendingCompositorBridges;
 };
 
 } // namespace layers
 } // namespace mozilla
 
--- a/image/ImageFactory.cpp
+++ b/image/ImageFactory.cpp
@@ -69,19 +69,16 @@ ComputeImageFlags(ImageURL* uri, const n
     imageFlags |= Image::INIT_FLAG_DISCARDABLE;
   }
   if (doDecodeImmediately) {
     imageFlags |= Image::INIT_FLAG_DECODE_IMMEDIATELY;
   }
   if (isMultiPart) {
     imageFlags |= Image::INIT_FLAG_TRANSIENT;
   }
-  imageFlags |= Image::INIT_FLAG_SYNC_LOAD;
-  // Always synchronously decode metadata (including size) so as to avoid
-  // unnecessary reflows.
 
   return imageFlags;
 }
 
 /* static */ already_AddRefed<Image>
 ImageFactory::CreateImage(nsIRequest* aRequest,
                           ProgressTracker* aProgressTracker,
                           const nsCString& aMimeType,
--- a/image/test/mochitest/mochitest.ini
+++ b/image/test/mochitest/mochitest.ini
@@ -120,17 +120,16 @@ skip-if = true # disabled - See bug 5791
 [test_bug767779.html]
 [test_bug865919.html]
 [test_bug89419-1.html]
 [test_bug89419-2.html]
 [test_bug1132427.html]
 skip-if = os == 'android'
 [test_bug1180105.html]
 [test_bug1217571.html]
-[test_bug1325080.html]
 [test_bullet_animation.html]
 skip-if = os == 'android'
 [test_changeOfSource.html]
 skip-if = os == 'android'
 [test_changeOfSource2.html]
 skip-if = os == 'android'
 [test_discardAnimatedImage.html]
 [test_drawDiscardedImage.html]
deleted file mode 100644
--- a/image/test/mochitest/test_bug1325080.html
+++ /dev/null
@@ -1,37 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=1325080
--->
-<head>
-  <title>Test for Bug 1325080</title>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1325080">Mozilla Bug 1325080</a>
-<pre id="test">
-<script type="application/javascript">
-/** Test for Bug 1325080 **/
-
-SimpleTest.waitForExplicitFinish();
-
-function createImage() {
-  // This function's code comes from the Acid3 test #72
-  document.open();
-  document.write('<!DOCTYPE html><head><style>img { height: 10px; }</style></head><body><img src="data:image/gif;base64,R0lGODlhAQABAID%2FAMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw%3D%3D" alt="alt-text"></body>');
-  document.close();
-}
-
-window.onload = function() {
-  createImage();
-  SimpleTest.executeSoon(() => {
-    ok(document.images[0].height == 10, "Style should set height of image.");
-    SimpleTest.finish();
-  });
-}
-
-</script>
-</pre>
-</body>
-</html>
--- a/intl/hyphenation/README.mozilla
+++ b/intl/hyphenation/README.mozilla
@@ -1,13 +1,13 @@
 About the hyphenation code in this directory
 ============================================
 
 The hyphen directory comes from the Hyphen library, part of the hunspell project.
-  http://sourceforge.net/projects/hunspell/files/Hyphen/.
+  https://github.com/hunspell/hyphen
 
 This code is distributed under the GPL 2.0/LGPL 2.1/MPL 1.1 tri-license, as
 detailed in the associated README and COPYING files.
 
 Note that we do not include other tools and resources found in the complete
 Hyphen package from upstream, so the original README.* files may refer to
 additional files that are not present in the Mozilla source tree.
 
new file mode 100644
--- /dev/null
+++ b/intl/hyphenation/hyphen/AUTHORS
@@ -0,0 +1,17 @@
+Libhnj was written by Raph Levien <raph at acm dot org>.
+
+Original Libhnj source with OOo's patches are managed by Rene Engelhard and
+Chris Halls at Debian: http://packages.debian.org/stable/libdevel/libhnj-dev
+and http://packages.debian.org/unstable/source/libhnj
+
+This subset of Libhnj was extended by
+Peter Novodvorsky <nidd at alt-linux dot org> (OOo integration),
+László Németh <nemeth at numbertext dot org> (non-standard and compound
+hyphenation with Unicode support),
+Nanning Buitenhuis <nanning at elvenkind dot com> (substrings.c)
+
+Write bug reports to László Németh or in the bug tracker of hunspell.sf.net.
+
+---
+Please contact Raph Levien for information about licensing for
+proprietary applications.
--- a/intl/hyphenation/hyphen/README
+++ b/intl/hyphenation/hyphen/README
@@ -43,16 +43,17 @@ tbhyphext.tex: hyphenation exception log
   Generated with the hyphenex script
   (http://www.ctan.org/tex-archive/info/digests/tugboat/hyphenex.sh)
 
   sh hyphenex.sh <tb0hyf.tex >tbhyphext.tex
 
 
 INSTALLATION
 
+autoreconf -fvi
 ./configure
 make
 make install
 
 UNIT TESTS (WITH VALGRIND DEBUGGER)
 
 make check
 VALGRIND=memcheck make check
@@ -65,16 +66,22 @@ or (under Linux)
 
 echo example | ./example hyph_en_US.dic /dev/stdin
 
 NOTE: In the case of Unicode encoded input, convert your words
 to lowercase before hyphenation (under UTF-8 console environment):
 
 cat mywords.txt | awk '{print tolower($0)}' >mywordslow.txt
 
+BUILD DLL USING CROSS-COMPILATION
+
+./configure --host i586-mingw32 --prefix=/tmp/hyphen-dll
+make
+make install
+
 DEVELOPMENT
 
 See README.hyphen for hyphenation algorithm, README.nonstandard
 and doc/tb87nemeth.pdf for non-standard hyphenation,
 README.compound for compound word hyphenation, and tests/*.
 
 Description of the dictionary format:
 
--- a/intl/hyphenation/hyphen/hyphen.c
+++ b/intl/hyphenation/hyphen/hyphen.c
@@ -34,16 +34,17 @@
  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the MPL
  * for the specific language governing rights and limitations under the
  * MPL.
  *
  */
 #include <stdlib.h> /* for NULL, malloc */
 #include <stdio.h>  /* for fprintf */
 #include <string.h> /* for strdup */
+#include <limits.h> /* for INT_MAX */
 
 #ifdef UNX
 #include <unistd.h> /* for exit */
 #endif
 
 #define noVERBOSE
 
 /* calculate hyphenmin values with long ligature length (2 or 3 characters
@@ -300,17 +301,17 @@ void hnj_hyphen_load_line(char * buf, Hy
                 }
             } else {
                 hnj_strchomp(repl + 1);
                 replindex = 0;
                 replcut = (signed char) strlen(buf);
             }
             repl = hnj_strdup(repl + 1);
           }
-	  for (i = 0; ((buf[i] > ' ') || (buf[i] < 0)); i++)
+	  for (i = 0; (unsigned char)buf[i] > (unsigned char)' '; i++)
 	    {
 	      if (buf[i] >= '0' && buf[i] <= '9')
 		pattern[j] = buf[i];
 	      else
 		{
 		  word[j] = buf[i];
 		  pattern[++j] = '0';
 		}
@@ -323,17 +324,17 @@ void hnj_hyphen_load_line(char * buf, Hy
 	    /* Optimize away leading zeroes */
             for (; pattern[i] == '0'; i++);
           } else {
             if (*word == '.') i++;
             /* convert UTF-8 char. positions of discretionary hyph. replacements to 8-bit */
             if (dict->utf8) {
                 int pu = -1;        /* unicode character position */
                 int ps = -1;        /* unicode start position (original replindex) */
-                int pc = (*word == '.') ? 1: 0; /* 8-bit character position */
+                size_t pc = (*word == '.') ? 1: 0; /* 8-bit character position */
                 for (; pc < (strlen(word) + 1); pc++) {
                 /* beginning of an UTF-8 character (not '10' start bits) */
                     if ((((unsigned char) word[pc]) >> 6) != 2) pu++;
                     if ((ps < 0) && (replindex == pu)) {
                         ps = replindex;
                         replindex = (signed char) pc;
                     }
                     if ((ps >= 0) && ((pu - ps) == replcut)) {
@@ -391,17 +392,17 @@ hnj_hyphen_load_file (FILE *f)
 {
   HyphenDict *dict[2];
   HashTab *hashtab;
   char buf[MAX_CHARS];
   int nextlevel = 0;
   int i, j, k;
   HashEntry *e;
   int state_num = 0;
-// loading one or two dictionaries (separated by NEXTLEVEL keyword)
+/* loading one or two dictionaries (separated by NEXTLEVEL keyword) */
 for (k = 0; k < 2; k++) { 
   hashtab = hnj_hash_new ();
 #ifdef VERBOSE
   global[k] = hashtab;
 #endif
   hnj_hash_insert (hashtab, "", 0);
   dict[k] = (HyphenDict *) hnj_malloc (sizeof(HyphenDict));
   dict[k]->num_states = 1;
@@ -442,17 +443,17 @@ for (k = 0; k < 2; k++) {
 	nextlevel = 1;
 	break;
       } else if (buf[0] != '%') hnj_hyphen_load_line(buf, dict[k], hashtab);
     }
   } else if (k == 1) {
     /* default first level: hyphen and ASCII apostrophe */
     if (!dict[0]->utf8) hnj_hyphen_load_line("NOHYPHEN ',-\n", dict[k], hashtab);
     else hnj_hyphen_load_line("NOHYPHEN ',\xe2\x80\x93,\xe2\x80\x99,-\n", dict[k], hashtab);
-    strncpy(buf, "1-1\n", MAX_CHARS-1); // buf rewritten by hnj_hyphen_load here
+    strncpy(buf, "1-1\n", MAX_CHARS-1); /* buf rewritten by hnj_hyphen_load here */
     buf[MAX_CHARS-1] = '\0';
     hnj_hyphen_load_line(buf, dict[k], hashtab); /* remove hyphen */
     hnj_hyphen_load_line("1'1\n", dict[k], hashtab); /* ASCII apostrophe */
     if (dict[0]->utf8) {
       hnj_hyphen_load_line("1\xe2\x80\x93" "1\n", dict[k], hashtab); /* endash */
       hnj_hyphen_load_line("1\xe2\x80\x99" "1\n", dict[k], hashtab); /* apostrophe */
     }
   }
@@ -689,89 +690,89 @@ int hnj_ligature(unsigned char c) {
 
 /* character length of the first n byte of the input word */
 int hnj_hyphen_strnlen(const char * word, int n, int utf8)
 {
     int i = 0;
     int j = 0;
     while (j < n && word[j] != '\0') {
       i++;
-      // Unicode ligature support
+      /* Unicode ligature support */
       if (utf8 && ((unsigned char) word[j] == 0xEF) && ((unsigned char) word[j + 1] == 0xAC))  {
         i += hnj_ligature(word[j + 2]);
       }
       for (j++; utf8 && (word[j] & 0xc0) == 0x80; j++);
     }
     return i;
 }
 
 int hnj_hyphen_lhmin(int utf8, const char *word, int word_size, char * hyphens,
 	char *** rep, int ** pos, int ** cut, int lhmin)
 {
     int i = 1, j;
 
-    // Unicode ligature support
+    /* Unicode ligature support */
     if (utf8 && ((unsigned char) word[0] == 0xEF) && ((unsigned char) word[1] == 0xAC))  {
       i += hnj_ligature(word[2]);
     }
 
-    // ignore numbers
+    /* ignore numbers */
     for (j = 0; word[j] <= '9' && word[j] >= '0'; j++) i--;
 
     for (j = 0; i < lhmin && word[j] != '\0'; i++) do {
-      // check length of the non-standard part
+      /* check length of the non-standard part */
       if (*rep && *pos && *cut && (*rep)[j]) {
         char * rh = strchr((*rep)[j], '=');
         if (rh && (hnj_hyphen_strnlen(word, j - (*pos)[j] + 1, utf8) +
           hnj_hyphen_strnlen((*rep)[j], rh - (*rep)[j], utf8)) < lhmin) {
             free((*rep)[j]);
             (*rep)[j] = NULL;
             hyphens[j] = '0';
           }
        } else {
          hyphens[j] = '0';
        }
        j++;
 
-       // Unicode ligature support
+       /* Unicode ligature support */
        if (utf8 && ((unsigned char) word[j] == 0xEF) && ((unsigned char) word[j + 1] == 0xAC))  {
          i += hnj_ligature(word[j + 2]);
        }
     } while (utf8 && (word[j] & 0xc0) == 0x80);
     return 0;
 }
 
 int hnj_hyphen_rhmin(int utf8, const char *word, int word_size, char * hyphens,
 	char *** rep, int ** pos, int ** cut, int rhmin)
 {
     int i = 0;
     int j;
 
-    // ignore numbers
+    /* ignore numbers */
     for (j = word_size - 1; j > 0 && word[j] <= '9' && word[j] >= '0'; j--) i--;
 
     for (j = word_size - 1; i < rhmin && j > 0; j--) {
-      // check length of the non-standard part
+      /* check length of the non-standard part */
       if (*rep && *pos && *cut && (*rep)[j]) {
         char * rh = strchr((*rep)[j], '=');
         if (rh && (hnj_hyphen_strnlen(word + j - (*pos)[j] + (*cut)[j] + 1, 100, utf8) +
           hnj_hyphen_strnlen(rh + 1, strlen(rh + 1), utf8)) < rhmin) {
             free((*rep)[j]);
             (*rep)[j] = NULL;
             hyphens[j] = '0';
           }
        } else {
          hyphens[j] = '0';
        }
        if (!utf8 || (word[j] & 0xc0) == 0xc0 || (word[j] & 0x80) != 0x80) i++;
     }
     return 0;
 }
 
-// recursive function for compound level hyphenation
+/* recursive function for compound level hyphenation */
 int hnj_hyphen_hyph_(HyphenDict *dict, const char *word, int word_size,
     char * hyphens, char *** rep, int ** pos, int ** cut,
     int clhmin, int crhmin, int lend, int rend)
 {
   char *prep_word;
   int i, j, k;
   int state;
   char ch;
@@ -943,49 +944,49 @@ int hnj_hyphen_hyph_(HyphenDict *dict, c
                 i += matchlen[i] - 1;
           }
        }
 
   hnj_free (matchrepl);
   hnj_free (matchlen);
   hnj_free (matchindex);
 
-  // recursive hyphenation of the first (compound) level segments
+  /* recursive hyphenation of the first (compound) level segments */
   if (dict->nextlevel) {
      char ** rep2;
      int * pos2;
      int * cut2;
      char * hyphens2;
      int begin = 0;
 
      rep2 = (char**) hnj_malloc (word_size * sizeof(char *));
      pos2 = (int*) hnj_malloc (word_size * sizeof(int));
      cut2 = (int*) hnj_malloc (word_size * sizeof(int));
      hyphens2 = (char*) hnj_malloc (word_size + 3);
      for (i = 0; i < word_size; i++) rep2[i] = NULL;
      for (i = 0; i < word_size; i++) if 
         (hyphens[i]&1 || (begin > 0 && i + 1 == word_size)) {
-        if (i - begin > 1) {
+        if (i - begin > 0) {
             int hyph = 0;
             prep_word[i + 2] = '\0';
             /* non-standard hyphenation at compound boundary (Schiffahrt) */
             if (rep && *rep && *pos && *cut && (*rep)[i]) {
                 char * l = strchr((*rep)[i], '=');
                 size_t offset = 2 + i - (*pos)[i];
                 strncpy(prep_word + offset, (*rep)[i], prep_word_size - offset - 1);
                 prep_word[prep_word_size - 1] = '\0';
                 if (l) {
                     hyph = (l - (*rep)[i]) - (*pos)[i];
                     prep_word[2 + i + hyph] = '\0';
                 }
             }
             hnj_hyphen_hyph_(dict, prep_word + begin + 1, i - begin + 1 + hyph,
                 hyphens2, &rep2, &pos2, &cut2, clhmin,
                 crhmin, (begin > 0 ? 0 : lend), (hyphens[i]&1 ? 0 : rend));
-            for (j = 0; j < i - begin - 1; j++) {
+            for (j = 0; j < i - begin; j++) {
                 hyphens[begin + j] = hyphens2[j];
                 if (rep2[j] && rep && pos && cut) {
                     if (!*rep && !*pos && !*cut) {
                         int k;
                         *rep = (char **) malloc(sizeof(char *) * word_size);
                         *pos = (int *) malloc(sizeof(int) * word_size);
                         *cut = (int *) malloc(sizeof(int) * word_size);
                         for (k = 0; k < word_size; k++) {
@@ -1005,17 +1006,17 @@ int hnj_hyphen_hyph_(HyphenDict *dict, c
                 strncpy(prep_word + offset, word, prep_word_size - offset - 1);
                 prep_word[prep_word_size - 1] = '\0';
             }
         }
         begin = i + 1;
         for (j = 0; j < word_size; j++) rep2[j] = NULL;
      }
      
-     // non-compound
+     /* non-compound */
      if (begin == 0) {
         hnj_hyphen_hyph_(dict->nextlevel, word, word_size,
             hyphens, rep, pos, cut, clhmin, crhmin, lend, rend);
         if (!lend) hnj_hyphen_lhmin(dict->utf8, word, word_size, hyphens,
             rep, pos, cut, clhmin);
         if (!rend) hnj_hyphen_rhmin(dict->utf8, word, word_size, hyphens,
             rep, pos, cut, crhmin);
      }
@@ -1068,33 +1069,51 @@ int hnj_hyphen_norm(const char *word, in
   hyphens[j + 1] = '\0';
 #ifdef VERBOSE
   printf ("nums: %s\n", hyphens);
 #endif
   return 0;
 }
 
 /* get the word with all possible hyphenations (output: hyphword) */
-void hnj_hyphen_hyphword(const char * word, int l, const char * hyphens, 
+void hnj_hyphen_hyphword(const char * word, int word_size, const char * hyphens,
     char * hyphword, char *** rep, int ** pos, int ** cut)
 {
-  int hyphenslen = l + 5;
+  
+  if (word_size <= 0 || word_size > INT_MAX / 2) {
+    hyphword[0] = '\0';
+    return;
+  }
+  
+  /* hyphword buffer size must be at least 2 * l */
+  int hyphword_size = 2 * word_size - 1;
+
+  int nonstandard = 0;
+  if (*rep && *pos && *cut) {
+    nonstandard = 1;
+  }
 
-  int i, j;
-  for (i = 0, j = 0; i < l; i++, j++) {
-    if (hyphens[i]&1) {
-      hyphword[j] = word[i];
-      if (*rep && *pos && *cut && (*rep)[i]) {
-        size_t offset = j - (*pos)[i] + 1;
-        strncpy(hyphword + offset, (*rep)[i], hyphenslen - offset - 1);
-        hyphword[hyphenslen-1] = '\0';
-        j += strlen((*rep)[i]) - (*pos)[i];
+  int i;
+  int j = 0;
+  for (i = 0; i < word_size && j < hyphword_size; i++) {
+    hyphword[j++] = word[i];
+    if (hyphens[i]&1 && j < hyphword_size) {
+      if (nonstandard && (*rep)[i] && j >= (*pos)[i]) {
+        /* non-standard */
+        j -= (*pos)[i];
+        char *s = (*rep)[i];
+        while (*s && j < hyphword_size) {
+          hyphword[j++] = *s++;
+        }
         i += (*cut)[i] - (*pos)[i];
-      } else hyphword[++j] = '=';
-    } else hyphword[j] = word[i];
+      } else {
+        /* standard */
+        hyphword[j++] = '=';
+      }
+    }
   }
   hyphword[j] = '\0';
 }
 
 
 /* main api function with default hyphenmin parameters */
 int hnj_hyphen_hyphenate2 (HyphenDict *dict,
 			   const char *word, int word_size, char * hyphens,
--- a/intl/strres/nsStringBundle.cpp
+++ b/intl/strres/nsStringBundle.cpp
@@ -638,17 +638,16 @@ nsStringBundleService::CreateExtensibleB
 #define GLOBAL_PROPERTIES "chrome://global/locale/global-strres.properties"
 
 nsresult
 nsStringBundleService::FormatWithBundle(nsIStringBundle* bundle, nsresult aStatus,
                                         uint32_t argCount, char16_t** argArray,
                                         nsAString& result)
 {
   nsresult rv;
-  nsXPIDLCString key;
 
   // try looking up the error message with the int key:
   uint16_t code = NS_ERROR_GET_CODE(aStatus);
   rv = bundle->FormatStringFromID(code, (const char16_t**)argArray, argCount, result);
 
   // If the int key fails, try looking up the default error message. E.g. print:
   //   An unknown error has occurred (0x804B0003).
   if (NS_FAILED(rv)) {
--- a/ipc/mscom/COMPtrHolder.h
+++ b/ipc/mscom/COMPtrHolder.h
@@ -11,16 +11,19 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Move.h"
 #include "mozilla/mscom/ProxyStream.h"
 #include "mozilla/mscom/Ptr.h"
 #if defined(MOZ_CONTENT_SANDBOX)
 #include "mozilla/SandboxSettings.h"
 #endif // defined(MOZ_CONTENT_SANDBOX)
+#if defined(MOZ_CRASHREPORTER)
+#include "nsExceptionHandler.h"
+#endif // defined(MOZ_CRASHREPORTER)
 
 namespace mozilla {
 namespace mscom {
 
 template<typename Interface, const IID& _IID>
 class COMPtrHolder
 {
 public:
@@ -168,16 +171,20 @@ struct ParamTraits<mozilla::mscom::COMPt
       buf = mozilla::MakeUnique<BYTE[]>(length);
       if (!aMsg->ReadBytesInto(aIter, buf.get(), length)) {
         return false;
       }
     }
 
     mozilla::mscom::ProxyStream proxyStream(_IID, buf.get(), length);
     if (!proxyStream.IsValid()) {
+#if defined(MOZ_CRASHREPORTER)
+      CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("ProxyStreamValid"),
+                                         NS_LITERAL_CSTRING("false"));
+#endif // defined(MOZ_CRASHREPORTER)
       return false;
     }
 
     typename paramType::COMPtrType ptr;
     if (!proxyStream.GetInterface(mozilla::mscom::getter_AddRefs(ptr))) {
       return false;
     }
 
--- a/ipc/mscom/ProxyStream.cpp
+++ b/ipc/mscom/ProxyStream.cpp
@@ -34,25 +34,37 @@ ProxyStream::ProxyStream()
 // reconstructing the stream from a buffer anyway.
 ProxyStream::ProxyStream(REFIID aIID, const BYTE* aInitBuf,
                          const int aInitBufSize)
   : mStream(InitStream(aInitBuf, static_cast<const UINT>(aInitBufSize)))
   , mGlobalLockedBuf(nullptr)
   , mHGlobal(nullptr)
   , mBufSize(aInitBufSize)
 {
+#if defined(MOZ_CRASHREPORTER)
+  NS_NAMED_LITERAL_CSTRING(kCrashReportKey, "ProxyStreamUnmarshalStatus");
+#endif
+
   if (!aInitBufSize) {
+#if defined(MOZ_CRASHREPORTER)
+    CrashReporter::AnnotateCrashReport(kCrashReportKey,
+                                       NS_LITERAL_CSTRING("!aInitBufSize"));
+#endif // defined(MOZ_CRASHREPORTER)
     // We marshaled a nullptr. Nothing else to do here.
     return;
   }
   // NB: We can't check for a null mStream until after we have checked for
   // the zero aInitBufSize above. This is because InitStream will also fail
   // in that case, even though marshaling a nullptr is allowable.
   MOZ_ASSERT(mStream);
   if (!mStream) {
+#if defined(MOZ_CRASHREPORTER)
+    CrashReporter::AnnotateCrashReport(kCrashReportKey,
+                                       NS_LITERAL_CSTRING("!mStream"));
+#endif // defined(MOZ_CRASHREPORTER)
     return;
   }
 
   HRESULT unmarshalResult = S_OK;
 
   // We need to convert to an interface here otherwise we mess up const
   // correctness with IPDL. We'll request an IUnknown and then QI the
   // actual interface later.
@@ -143,30 +155,43 @@ ProxyStream::InitStream(const BYTE* aIni
   if (FAILED(hr)) {
     return nullptr;
   }
 
   return stream.forget();
 }
 
 ProxyStream::ProxyStream(ProxyStream&& aOther)
+  : mGlobalLockedBuf(nullptr)
+  , mHGlobal(nullptr)
+  , mBufSize(0)
 {
   *this = mozilla::Move(aOther);
 }
 
 ProxyStream&
 ProxyStream::operator=(ProxyStream&& aOther)
 {
+  if (mHGlobal && mGlobalLockedBuf) {
+    DebugOnly<BOOL> result = ::GlobalUnlock(mHGlobal);
+    MOZ_ASSERT(!result && ::GetLastError() == NO_ERROR);
+  }
+
   mStream = Move(aOther.mStream);
+
   mGlobalLockedBuf = aOther.mGlobalLockedBuf;
   aOther.mGlobalLockedBuf = nullptr;
+
+  // ::GlobalFree() was called implicitly when mStream was replaced.
   mHGlobal = aOther.mHGlobal;
   aOther.mHGlobal = nullptr;
+
   mBufSize = aOther.mBufSize;
   aOther.mBufSize = 0;
+
   mUnmarshaledProxy = Move(aOther.mUnmarshaledProxy);
   return *this;
 }
 
 ProxyStream::~ProxyStream()
 {
   if (mHGlobal && mGlobalLockedBuf) {
     DebugOnly<BOOL> result = ::GlobalUnlock(mHGlobal);
@@ -215,16 +240,23 @@ ProxyStream::GetInterface(void** aOutInt
   // We should not have a locked buffer on this side
   MOZ_ASSERT(!mGlobalLockedBuf);
   MOZ_ASSERT(aOutInterface);
 
   if (!aOutInterface) {
     return false;
   }
 
+#if defined(MOZ_CRASHREPORTER)
+  if (!mUnmarshaledProxy) {
+    CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("ProxyStreamUnmarshalStatus"),
+                                       NS_LITERAL_CSTRING("!mUnmarshaledProxy"));
+  }
+#endif // defined(MOZ_CRASHREPORTER)
+
   *aOutInterface = mUnmarshaledProxy.release();
   return true;
 }
 
 ProxyStream::ProxyStream(REFIID aIID, IUnknown* aObject)
   : mGlobalLockedBuf(nullptr)
   , mHGlobal(nullptr)
   , mBufSize(0)
@@ -232,71 +264,117 @@ ProxyStream::ProxyStream(REFIID aIID, IU
   if (!aObject) {
     return;
   }
 
   RefPtr<IStream> stream;
   HGLOBAL hglobal = NULL;
   int streamSize = 0;
 
+  HRESULT createStreamResult = S_OK;
   HRESULT marshalResult = S_OK;
+  HRESULT statResult = S_OK;
+  HRESULT getHGlobalResult = S_OK;
 
   auto marshalFn = [&]() -> void
   {
-    HRESULT hr = ::CreateStreamOnHGlobal(nullptr, TRUE, getter_AddRefs(stream));
-    if (FAILED(hr)) {
+    createStreamResult = ::CreateStreamOnHGlobal(nullptr, TRUE, getter_AddRefs(stream));
+    if (FAILED(createStreamResult)) {
       return;
     }
 
-    hr = ::CoMarshalInterface(stream, aIID, aObject, MSHCTX_LOCAL, nullptr,
-                              MSHLFLAGS_NORMAL);
-    if (FAILED(hr)) {
-      marshalResult = hr;
+    marshalResult = ::CoMarshalInterface(stream, aIID, aObject, MSHCTX_LOCAL,
+                                         nullptr, MSHLFLAGS_NORMAL);
+    if (FAILED(marshalResult)) {
       return;
     }
 
     STATSTG statstg;
-    hr = stream->Stat(&statstg, STATFLAG_NONAME);
-    if (SUCCEEDED(hr)) {
+    statResult = stream->Stat(&statstg, STATFLAG_NONAME);
+    if (SUCCEEDED(statResult)) {
       streamSize = static_cast<int>(statstg.cbSize.LowPart);
+    } else {
+      return;
     }
 
-    hr = ::GetHGlobalFromStream(stream, &hglobal);
-    MOZ_ASSERT(SUCCEEDED(hr));
+    getHGlobalResult = ::GetHGlobalFromStream(stream, &hglobal);
+    MOZ_ASSERT(SUCCEEDED(getHGlobalResult));
   };
 
   if (XRE_IsParentProcess()) {
     // We'll marshal this stuff directly using the current thread, therefore its
     // stub will reside in the same apartment as the current thread.
     marshalFn();
   } else {
     // When marshaling in child processes, we want to force the MTA.
     EnsureMTA mta(marshalFn);
   }
 
 #if defined(MOZ_CRASHREPORTER)
+  if (FAILED(createStreamResult)) {
+    nsPrintfCString hrAsStr("0x%08X", createStreamResult);
+    CrashReporter::AnnotateCrashReport(
+        NS_LITERAL_CSTRING("CreateStreamOnHGlobalFailure"),
+        hrAsStr);
+  }
+
   if (FAILED(marshalResult)) {
     AnnotateInterfaceRegistration(aIID);
     nsPrintfCString hrAsStr("0x%08X", marshalResult);
     CrashReporter::AnnotateCrashReport(
         NS_LITERAL_CSTRING("CoMarshalInterfaceFailure"), hrAsStr);
   }
+
+  if (FAILED(statResult)) {
+    nsPrintfCString hrAsStr("0x%08X", statResult);
+    CrashReporter::AnnotateCrashReport(
+        NS_LITERAL_CSTRING("StatFailure"),
+        hrAsStr);
+  }
+
+  if (FAILED(getHGlobalResult)) {
+    nsPrintfCString hrAsStr("0x%08X", getHGlobalResult);
+    CrashReporter::AnnotateCrashReport(
+        NS_LITERAL_CSTRING("GetHGlobalFromStreamFailure"),
+        hrAsStr);
+  }
 #endif // defined(MOZ_CRASHREPORTER)
 
   mStream = mozilla::Move(stream);
-  mBufSize = streamSize;
 
-  if (hglobal) {
-    mGlobalLockedBuf = reinterpret_cast<BYTE*>(::GlobalLock(hglobal));
-    mHGlobal = hglobal;
+  if (streamSize) {
+#if defined(MOZ_CRASHREPORTER)
+    CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("ProxyStreamSizeFrom"),
+                                       NS_LITERAL_CSTRING("IStream::Stat"));
+#endif // defined(MOZ_CRASHREPORTER)
+    mBufSize = streamSize;
+  }
+
+  if (!hglobal) {
+    return;
+  }
+
+  mGlobalLockedBuf = reinterpret_cast<BYTE*>(::GlobalLock(hglobal));
+  mHGlobal = hglobal;
 
-    // If we couldn't get the stream size directly from mStream, we may use
-    // the size of the memory block allocated by the HGLOBAL, though it might
-    // be larger than the actual stream size.
-    if (!streamSize) {
-      mBufSize = static_cast<int>(::GlobalSize(hglobal));
-    }
+  // If we couldn't get the stream size directly from mStream, we may use
+  // the size of the memory block allocated by the HGLOBAL, though it might
+  // be larger than the actual stream size.
+  if (!streamSize) {
+#if defined(MOZ_CRASHREPORTER)
+    CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("ProxyStreamSizeFrom"),
+                                       NS_LITERAL_CSTRING("GlobalSize"));
+#endif // defined(MOZ_CRASHREPORTER)
+    mBufSize = static_cast<int>(::GlobalSize(hglobal));
   }
+
+#if defined(MOZ_CRASHREPORTER)
+  nsAutoCString strBufSize;
+  strBufSize.AppendInt(mBufSize);
+
+  CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("ProxyStreamSize"),
+                                     strBufSize);
+#endif // defined(MOZ_CRASHREPORTER)
 }
 
 } // namespace mscom
 } // namespace mozilla
 
--- a/ipc/mscom/ProxyStream.h
+++ b/ipc/mscom/ProxyStream.h
@@ -29,17 +29,17 @@ public:
   ProxyStream(const ProxyStream& aOther) = delete;
   ProxyStream& operator=(const ProxyStream& aOther) = delete;
 
   ProxyStream(ProxyStream&& aOther);
   ProxyStream& operator=(ProxyStream&& aOther);
 
   inline bool IsValid() const
   {
-    return !(mStream && mUnmarshaledProxy);
+    return !(mUnmarshaledProxy && mStream);
   }
 
   bool GetInterface(void** aOutInterface);
   const BYTE* GetBuffer(int& aReturnedBufSize) const;
   RefPtr<IStream> GetStream() const;
 
   bool operator==(const ProxyStream& aOther) const
   {
@@ -51,15 +51,14 @@ private:
                                               const UINT aInitBufSize);
 
 private:
   RefPtr<IStream> mStream;
   BYTE*           mGlobalLockedBuf;
   HGLOBAL         mHGlobal;
   int             mBufSize;
   ProxyUniquePtr<IUnknown> mUnmarshaledProxy;
-  HRESULT         mUnmarshalResult;
 };
 
 } // namespace mscom
 } // namespace mozilla
 
 #endif // mozilla_mscom_ProxyStream_h
--- a/js/src/builtin/Array.js
+++ b/js/src/builtin/Array.js
@@ -180,27 +180,34 @@ function ArrayStaticSome(list, callbackf
     var T = arguments.length > 2 ? arguments[2] : void 0;
     return callFunction(ArraySome, list, callbackfn, T);
 }
 
 // ES2018 draft rev 3bbc87cd1b9d3bf64c3e68ca2fe9c5a3f2c304c0
 // 22.1.3.25 Array.prototype.sort ( comparefn )
 function ArraySort(comparefn) {
     // Step 1.
-    assert(typeof comparefn === "function", "Only called when a comparator is present");
+    if (comparefn !== undefined) {
+        if (!IsCallable(comparefn))
+            ThrowTypeError(JSMSG_BAD_SORT_ARG);
+    }
 
     // Step 2.
-    assert(IsObject(this), "|this| should be an object");
-    var O = this;
+    var O = ToObject(this);
+
+    // First try to sort the array in native code, if that fails, indicated by
+    // returning |false| from ArrayNativeSort, sort it in self-hosted code.
+    if (callFunction(ArrayNativeSort, O, comparefn))
+        return O;
 
     // Step 3.
     var len = ToLength(O.length);
 
     if (len <= 1)
-      return this;
+      return O;
 
     /* 22.1.3.25.1 Runtime Semantics: SortCompare( x, y ) */
     var wrappedCompareFn = comparefn;
     comparefn = function(x, y) {
         /* Steps 1-3. */
         if (x === undefined) {
             if (y === undefined)
                 return 0;
@@ -1114,17 +1121,17 @@ function ArrayStaticReverse(arr) {
     if (arguments.length < 1)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, "Array.reverse");
     return callFunction(std_Array_reverse, arr);
 }
 
 function ArrayStaticSort(arr, comparefn) {
     if (arguments.length < 1)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, "Array.sort");
-    return callFunction(std_Array_sort, arr, comparefn);
+    return callFunction(ArraySort, arr, comparefn);
 }
 
 function ArrayStaticPush(arr, arg1) {
     if (arguments.length < 1)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, "Array.push");
     var args = callFunction(std_Array_slice, arguments, 1);
     return callFunction(std_Function_apply, std_Array_push, arr, args);
 }
--- a/js/src/builtin/Intl.js
+++ b/js/src/builtin/Intl.js
@@ -450,17 +450,17 @@ function CanonicalizeLanguageTag(locale)
     while (i < subtags.length && subtags[i] !== "x") {
         var extensionStart = i;
         i++;
         while (i < subtags.length && subtags[i].length > 1)
             i++;
         var extension = ArrayJoinRange(subtags, "-", extensionStart, i);
         _DefineDataProperty(extensions, extensions.length, extension);
     }
-    callFunction(std_Array_sort, extensions);
+    callFunction(ArraySort, extensions);
 
     // Private use sequences are left as is. "x-private"
     var privateUse = "";
     if (i < subtags.length)
         privateUse = ArrayJoinRange(subtags, "-", i);
 
     // Put everything back together.
     var canonical = normal;
--- a/js/src/builtin/Module.js
+++ b/js/src/builtin/Module.js
@@ -166,17 +166,17 @@ function GetModuleNamespace(module)
 
     // Step 4
     return namespace;
 }
 
 // 9.4.6.13 ModuleNamespaceCreate(module, exports)
 function ModuleNamespaceCreate(module, exports)
 {
-    callFunction(std_Array_sort, exports);
+    callFunction(ArraySort, exports);
 
     let ns = NewModuleNamespace(module, exports);
 
     // Pre-compute all bindings now rather than calling ResolveExport() on every
     // access.
     for (let i = 0; i < exports.length; i++) {
         let name = exports[i];
         let binding = callFunction(module.resolveExport, module, name);
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -3186,54 +3186,266 @@ PromiseObject::onSettled(JSContext* cx, 
     promise->setFixedSlot(PromiseSlot_ResolutionTime, DoubleValue(MillisecondsSinceStartup()));
 
     if (promise->state() == JS::PromiseState::Rejected && promise->isUnhandled())
         cx->runtime()->addUnhandledRejectedPromise(cx, promise);
 
     JS::dbg::onPromiseSettled(cx, promise);
 }
 
-PromiseTask::PromiseTask(JSContext* cx, Handle<PromiseObject*> promise)
+OffThreadPromiseTask::OffThreadPromiseTask(JSContext* cx, Handle<PromiseObject*> promise)
   : runtime_(cx->runtime()),
-    promise_(cx, promise)
-{}
-
-PromiseTask::~PromiseTask()
+    promise_(cx, promise),
+    registered_(false)
+{
+    MOZ_ASSERT(runtime_ == promise->zone()->runtimeFromActiveCooperatingThread());
+    MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
+    MOZ_ASSERT(cx->runtime()->offThreadPromiseState.ref().initialized());
+}
+
+OffThreadPromiseTask::~OffThreadPromiseTask()
 {
-    MOZ_ASSERT(CurrentThreadCanAccessZone(promise_->zone()));
+    MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
+
+    OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref();
+    MOZ_ASSERT(state.initialized());
+
+    if (registered_) {
+        LockGuard<Mutex> lock(state.mutex_);
+        state.live_.remove(this);
+    }
+}
+
+bool
+OffThreadPromiseTask::init(JSContext* cx)
+{
+    MOZ_ASSERT(cx->runtime() == runtime_);
+    MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
+
+    OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref();
+    MOZ_ASSERT(state.initialized());
+
+    LockGuard<Mutex> lock(state.mutex_);
+
+    if (!state.live_.putNew(this)) {
+        ReportOutOfMemory(cx);
+        return false;
+    }
+
+    registered_ = true;
+    return true;
 }
 
 void
-PromiseTask::finish(JSContext* cx)
+OffThreadPromiseTask::run(JSContext* cx, MaybeShuttingDown maybeShuttingDown)
 {
     MOZ_ASSERT(cx->runtime() == runtime_);
-    {
+    MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
+    MOZ_ASSERT(registered_);
+    MOZ_ASSERT(runtime_->offThreadPromiseState.ref().initialized());
+
+    if (maybeShuttingDown == JS::Dispatchable::NotShuttingDown) {
         // We can't leave a pending exception when returning to the caller so do
         // the same thing as Gecko, which is to ignore the error. This should
         // only happen due to OOM or interruption.
         AutoCompartment ac(cx, promise_);
-        if (!finishPromise(cx, promise_))
+        if (!resolve(cx, promise_))
             cx->clearPendingException();
     }
+
     js_delete(this);
 }
 
 void
-PromiseTask::cancel(JSContext* cx)
+OffThreadPromiseTask::dispatchResolve()
+{
+    MOZ_ASSERT(registered_);
+
+    OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref();
+    MOZ_ASSERT(state.initialized());
+    MOZ_ASSERT((LockGuard<Mutex>(state.mutex_), state.live_.has(this)));
+
+    // If the dispatch succeeds, then we are guaranteed that run() will be
+    // called on an active JSContext of runtime_.
+    if (state.dispatchToEventLoopCallback_(state.dispatchToEventLoopClosure_, this))
+        return;
+
+    // We assume, by interface contract, that if the dispatch fails, it's
+    // because the embedding is in the process of shutting down the JSRuntime.
+    // Since JSRuntime destruction calls shutdown(), we can rely on shutdown()
+    // to delete the task on its active JSContext thread. shutdown() waits for
+    // numCanceled_ == live_.length, so we notify when this condition is
+    // reached.
+    LockGuard<Mutex> lock(state.mutex_);
+    state.numCanceled_++;
+    if (state.numCanceled_ == state.live_.count())
+        state.allCanceled_.notify_one();
+}
+
+OffThreadPromiseRuntimeState::OffThreadPromiseRuntimeState()
+  : dispatchToEventLoopCallback_(nullptr),
+    dispatchToEventLoopClosure_(nullptr),
+    mutex_(mutexid::OffThreadPromiseState),
+    numCanceled_(0),
+    internalDispatchQueueClosed_(false)
+{
+    AutoEnterOOMUnsafeRegion noOOM;
+    if (!live_.init())
+        noOOM.crash("OffThreadPromiseRuntimeState");
+}
+
+OffThreadPromiseRuntimeState::~OffThreadPromiseRuntimeState()
 {
-    MOZ_ASSERT(cx->runtime() == runtime_);
-    js_delete(this);
+    MOZ_ASSERT(live_.empty());
+    MOZ_ASSERT(numCanceled_ == 0);
+    MOZ_ASSERT(internalDispatchQueue_.empty());
+    MOZ_ASSERT(!initialized());
+}
+
+void
+OffThreadPromiseRuntimeState::init(JS::DispatchToEventLoopCallback callback, void* closure)
+{
+    MOZ_ASSERT(!initialized());
+
+    dispatchToEventLoopCallback_ = callback;
+    dispatchToEventLoopClosure_ = closure;
+
+    MOZ_ASSERT(initialized());
+}
+
+/* static */ bool
+OffThreadPromiseRuntimeState::internalDispatchToEventLoop(void* closure, JS::Dispatchable* d)
+{
+    OffThreadPromiseRuntimeState& state = *reinterpret_cast<OffThreadPromiseRuntimeState*>(closure);
+    MOZ_ASSERT(state.usingInternalDispatchQueue());
+
+    LockGuard<Mutex> lock(state.mutex_);
+
+    if (state.internalDispatchQueueClosed_)
+        return false;
+
+    // The JS API contract is that 'false' means shutdown, so be infallible
+    // here (like Gecko).
+    AutoEnterOOMUnsafeRegion noOOM;
+    if (!state.internalDispatchQueue_.append(d))
+        noOOM.crash("internalDispatchToEventLoop");
+
+    // Wake up internalDrain() if it is waiting for a job to finish.
+    state.internalDispatchQueueAppended_.notify_one();
+    return true;
+}
+
+bool
+OffThreadPromiseRuntimeState::usingInternalDispatchQueue() const
+{
+    return dispatchToEventLoopCallback_ == internalDispatchToEventLoop;
+}
+
+void
+OffThreadPromiseRuntimeState::initInternalDispatchQueue()
+{
+    init(internalDispatchToEventLoop, this);
+    MOZ_ASSERT(usingInternalDispatchQueue());
 }
 
 bool
-PromiseTask::executeAndFinish(JSContext* cx)
+OffThreadPromiseRuntimeState::initialized() const
+{
+    return !!dispatchToEventLoopCallback_;
+}
+
+void
+OffThreadPromiseRuntimeState::internalDrain(JSContext* cx)
+{
+    MOZ_ASSERT(usingInternalDispatchQueue());
+    MOZ_ASSERT(!internalDispatchQueueClosed_);
+
+    while (true) {
+        DispatchableVector dispatchQueue;
+        {
+            LockGuard<Mutex> lock(mutex_);
+
+            MOZ_ASSERT_IF(!internalDispatchQueue_.empty(), !live_.empty());
+            if (live_.empty())
+                return;
+
+            while (internalDispatchQueue_.empty())
+                internalDispatchQueueAppended_.wait(lock);
+
+            Swap(dispatchQueue, internalDispatchQueue_);
+            MOZ_ASSERT(internalDispatchQueue_.empty());
+        }
+
+        // Don't call run() with mutex_ held to avoid deadlock.
+        for (JS::Dispatchable* d : dispatchQueue)
+            d->run(cx, JS::Dispatchable::NotShuttingDown);
+    }
+}
+
+bool
+OffThreadPromiseRuntimeState::internalHasPending()
+{
+    MOZ_ASSERT(usingInternalDispatchQueue());
+    MOZ_ASSERT(!internalDispatchQueueClosed_);
+
+    LockGuard<Mutex> lock(mutex_);
+    MOZ_ASSERT_IF(!internalDispatchQueue_.empty(), !live_.empty());
+    return !live_.empty();
+}
+
+void
+OffThreadPromiseRuntimeState::shutdown(JSContext* cx)
 {
-    MOZ_ASSERT(!CanUseExtraThreads());
-    execute();
-    return finishPromise(cx, promise_);
+    if (!initialized())
+        return;
+
+    // When the shell is using the internal event loop, we must simulate our
+    // requirement of the embedding that, before shutdown, all successfully-
+    // dispatched-to-event-loop tasks have been run.
+    if (usingInternalDispatchQueue()) {
+        DispatchableVector dispatchQueue;
+        {
+            LockGuard<Mutex> lock(mutex_);
+            Swap(dispatchQueue, internalDispatchQueue_);
+            MOZ_ASSERT(internalDispatchQueue_.empty());
+            internalDispatchQueueClosed_ = true;
+        }
+
+        // Don't call run() with mutex_ held to avoid deadlock.
+        for (JS::Dispatchable* d : dispatchQueue)
+            d->run(cx, JS::Dispatchable::ShuttingDown);
+    }
+
+    {
+        // Wait until all live OffThreadPromiseRuntimeState have been confirmed
+        // canceled by OffThreadPromiseTask::dispatchResolve().
+        LockGuard<Mutex> lock(mutex_);
+        while (live_.count() != numCanceled_) {
+            MOZ_ASSERT(numCanceled_ < live_.count());
+            allCanceled_.wait(lock);
+        }
+    }
+
+    // Now that all the tasks have stopped concurrent execution, we can just
+    // delete everything. We don't want each OffThreadPromiseTask to unregister
+    // itself (which would mutate live_ while we are iterating over it) so reset
+    // the tasks' internal registered_ flag.
+    for (OffThreadPromiseTaskSet::Range r = live_.all(); !r.empty(); r.popFront()) {
+        OffThreadPromiseTask* task = r.front();
+        MOZ_ASSERT(task->registered_);
+        task->registered_ = false;
+        js_delete(task);
+    }
+    live_.clear();
+    numCanceled_ = 0;
+
+    // After shutdown, there should be no OffThreadPromiseTask activity in this
+    // JSRuntime. Revert to the !initialized() state to catch bugs.
+    dispatchToEventLoopCallback_ = nullptr;
+    MOZ_ASSERT(!initialized());
 }
 
 static JSObject*
 CreatePromisePrototype(JSContext* cx, JSProtoKey key)
 {
     return GlobalObject::createBlankPrototype(cx, cx->global(), &PromiseObject::protoClass_);
 }
 
--- a/js/src/builtin/Promise.h
+++ b/js/src/builtin/Promise.h
@@ -3,16 +3,18 @@
  * 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 builtin_Promise_h
 #define builtin_Promise_h
 
 #include "builtin/SelfHostingDefines.h"
+#include "threading/ConditionVariable.h"
+#include "threading/Mutex.h"
 #include "vm/NativeObject.h"
 
 namespace js {
 
 enum PromiseSlots {
     PromiseSlot_Flags = 0,
     PromiseSlot_ReactionsOrResult,
     PromiseSlot_RejectFunction,
@@ -158,54 +160,100 @@ AsyncGeneratorReject(JSContext* cx, Hand
 
 MOZ_MUST_USE bool
 AsyncGeneratorEnqueue(JSContext* cx, HandleValue asyncGenVal, CompletionKind completionKind,
                       HandleValue completionValue, MutableHandleValue result);
 
 bool
 AsyncFromSyncIteratorMethod(JSContext* cx, CallArgs& args, CompletionKind completionKind);
 
-/**
- * A PromiseTask represents a task that can be dispatched to a helper thread
- * (via StartPromiseTask), executed (by implementing PromiseTask::execute()),
- * and then resolved back on the original JSContext owner thread.
- * Because it contains a PersistentRooted, a PromiseTask will only be destroyed
- * on the JSContext's owner thread.
- */
-class PromiseTask : public JS::AsyncTask
+// An OffThreadPromiseTask holds a rooted Promise JSObject while executing an
+// off-thread task (defined by the subclass) that needs to resolve the Promise
+// on completion. Because OffThreadPromiseTask contains a PersistentRooted, it
+// must be destroyed on an active JSContext thread of the Promise's JSRuntime.
+// OffThreadPromiseTasks may be run off-thread in various ways (e.g., see
+// PromiseHelperTask). At any time, the task can be dispatched to an active
+// JSContext of the Promise's JSRuntime by calling dispatchResolve().
+
+class OffThreadPromiseTask : public JS::Dispatchable
 {
-    JSRuntime* runtime_;
-    PersistentRooted<PromiseObject*> promise_;
+    friend class OffThreadPromiseRuntimeState;
 
-    // PromiseTask implements JS::AsyncTask and prevents derived classes from
-    // overriding; derived classes should implement the new pure virtual
-    // functions introduced below. Both of these methods 'delete this'.
-    void finish(JSContext* cx) override final;
-    void cancel(JSContext* cx) override final;
+    JSRuntime*                       runtime_;
+    PersistentRooted<PromiseObject*> promise_;
+    bool                             registered_;
+
+    void operator=(const OffThreadPromiseTask&) = delete;
+    OffThreadPromiseTask(const OffThreadPromiseTask&) = delete;
 
   protected:
-    // Called by PromiseTask on the JSContext's owner thread after execute()
-    // completes on the helper thread, assuming JS::FinishAsyncTaskCallback
-    // succeeds. After this method returns, the task will be deleted.
-    virtual bool finishPromise(JSContext* cx, Handle<PromiseObject*> promise) = 0;
+    OffThreadPromiseTask(JSContext* cx, Handle<PromiseObject*> promise);
+
+    // To be called by OffThreadPromiseTask and implemented by the derived class.
+    virtual bool resolve(JSContext* cx, Handle<PromiseObject*> promise) = 0;
+
+    // JS::Dispatchable implementation. Ends with 'delete this'.
+    void run(JSContext* cx, MaybeShuttingDown maybeShuttingDown) override final;
 
   public:
-    PromiseTask(JSContext* cx, Handle<PromiseObject*> promise);
-    ~PromiseTask();
-    JSRuntime* runtime() const { return runtime_; }
+    ~OffThreadPromiseTask() override;
+    bool init(JSContext* cx);
+
+    // An initialized OffThreadPromiseTask can be dispatched to an active
+    // JSContext of its Promise's JSRuntime from any thread. Normally, this will
+    // lead to resolve() being called on JSContext thread, given the Promise.
+    // However, if shutdown interrupts, resolve() may not be called, though the
+    // OffThreadPromiseTask will be destroyed on a JSContext thread.
+    void dispatchResolve();
+};
+
+using OffThreadPromiseTaskSet = HashSet<OffThreadPromiseTask*,
+                                        DefaultHasher<OffThreadPromiseTask*>,
+                                        SystemAllocPolicy>;
+
+using DispatchableVector = Vector<JS::Dispatchable*, 0, SystemAllocPolicy>;
+
+class OffThreadPromiseRuntimeState
+{
+    friend class OffThreadPromiseTask;
+
+    // These fields are initialized once before any off-thread usage and thus do
+    // not require a lock.
+    JS::DispatchToEventLoopCallback dispatchToEventLoopCallback_;
+    void*                           dispatchToEventLoopClosure_;
 
-    // Called on a helper thread after StartAsyncTask. After execute()
-    // completes, the JS::FinishAsyncTaskCallback will be called. If this fails
-    // the task will be enqueued for deletion at some future point without ever
-    // calling finishPromise().
-    virtual void execute() = 0;
+    // These fields are mutated by any thread and are guarded by mutex_.
+    Mutex                           mutex_;
+    ConditionVariable               allCanceled_;
+    OffThreadPromiseTaskSet         live_;
+    size_t                          numCanceled_;
+    DispatchableVector              internalDispatchQueue_;
+    ConditionVariable               internalDispatchQueueAppended_;
+    bool                            internalDispatchQueueClosed_;
+
+    static bool internalDispatchToEventLoop(void*, JS::Dispatchable*);
+    bool usingInternalDispatchQueue() const;
+
+    void operator=(const OffThreadPromiseRuntimeState&) = delete;
+    OffThreadPromiseRuntimeState(const OffThreadPromiseRuntimeState&) = delete;
 
-    // May be called in the absence of helper threads to synchronously execute
-    // and finish a PromiseTask.
-    bool executeAndFinish(JSContext* cx);
+  public:
+    OffThreadPromiseRuntimeState();
+    ~OffThreadPromiseRuntimeState();
+    void init(JS::DispatchToEventLoopCallback callback, void* closure);
+    void initInternalDispatchQueue();
+    bool initialized() const;
+
+    // If initInternalDispatchQueue() was called, internalDrain() can be
+    // called to periodically drain the dispatch queue before shutdown.
+    void internalDrain(JSContext* cx);
+    bool internalHasPending();
+
+    // shutdown() must be called by the JSRuntime while the JSRuntime is valid.
+    void shutdown(JSContext* cx);
 };
 
 bool
 Promise_static_resolve(JSContext* cx, unsigned argc, Value* vp);
 bool
 Promise_reject(JSContext* cx, unsigned argc, Value* vp);
 bool
 Promise_then(JSContext* cx, unsigned argc, Value* vp);
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -26,17 +26,16 @@
 #include "builtin/SelfHostingDefines.h"
 #ifdef DEBUG
 #include "frontend/TokenStream.h"
 #include "irregexp/RegExpAST.h"
 #include "irregexp/RegExpEngine.h"
 #include "irregexp/RegExpParser.h"
 #endif
 #include "jit/InlinableNatives.h"
-#include "jit/JitFrameIterator.h"
 #include "js/Debug.h"
 #include "js/HashTable.h"
 #include "js/StructuredClone.h"
 #include "js/UbiNode.h"
 #include "js/UbiNodeBreadthFirst.h"
 #include "js/UbiNodeShortestPaths.h"
 #include "js/UniquePtr.h"
 #include "js/Vector.h"
@@ -2144,17 +2143,17 @@ testingFunc_inIon(JSContext* cx, unsigne
 
         args.rval().setString(error);
         return true;
     }
 
     ScriptFrameIter iter(cx);
     if (!iter.done() && iter.isIon()) {
         // Reset the counter of the IonScript's script.
-        jit::JitFrameIterator jitIter(cx);
+        jit::JSJitFrameIter jitIter(cx);
         ++jitIter;
         jitIter.script()->resetWarmUpResetCounter();
     } else {
         // Check if we missed multiple attempts at compiling the innermost script.
         JSScript* script = cx->currentScript();
         if (script && script->getWarmUpResetCount() >= 20) {
             JSString* error = JS_NewStringCopyZ(cx, "Compilation is being repeatedly prevented. Giving up.");
             if (!error)
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -135,131 +135,141 @@ class BackgroundDecommitTask : public GC
 
 /*
  * Encapsulates all of the GC tunables. These are effectively constant and
  * should only be modified by setParameter.
  */
 class GCSchedulingTunables
 {
     /*
-     * Soft limit on the number of bytes we are allowed to allocate in the GC
-     * heap. Attempts to allocate gcthings over this limit will return null and
-     * subsequently invoke the standard OOM machinery, independent of available
-     * physical memory.
+     * JSGC_MAX_BYTES
+     *
+     * Maximum nominal heap before last ditch GC.
      */
     UnprotectedData<size_t> gcMaxBytes_;
 
     /*
+     * JSGC_MAX_NURSERY_BYTES
+     *
      * Maximum nursery size for each zone group.
-     * Initially DefaultNurseryBytes and can be set by
-     * javascript.options.mem.nursery.max_kb
      */
     ActiveThreadData<size_t> gcMaxNurseryBytes_;
 
     /*
-     * The base value used to compute zone->trigger.gcBytes(). When
-     * usage.gcBytes() surpasses threshold.gcBytes() for a zone, the zone may
-     * be scheduled for a GC, depending on the exact circumstances.
+     * JSGC_ALLOCATION_THRESHOLD
+     *
+     * The base value used to compute zone->threshold.gcTriggerBytes(). When
+     * usage.gcBytes() surpasses threshold.gcTriggerBytes() for a zone, the
+     * zone may be scheduled for a GC, depending on the exact circumstances.
      */
     ActiveThreadOrGCTaskData<size_t> gcZoneAllocThresholdBase_;
 
     /* Fraction of threshold.gcBytes() which triggers an incremental GC. */
-    UnprotectedData<double> zoneAllocThresholdFactor_;
+    UnprotectedData<float> zoneAllocThresholdFactor_;
     /* The same except when doing so would interrupt an already running GC. */
-    UnprotectedData<double> zoneAllocThresholdFactorAvoidInterrupt_;
+    UnprotectedData<float> zoneAllocThresholdFactorAvoidInterrupt_;
 
     /*
      * Number of bytes to allocate between incremental slices in GCs triggered
      * by the zone allocation threshold.
+     *
+     * This value does not have a JSGCParamKey parameter yet.
      */
     UnprotectedData<size_t> zoneAllocDelayBytes_;
 
     /*
+     * JSGC_DYNAMIC_HEAP_GROWTH
+     *
      * Totally disables |highFrequencyGC|, the HeapGrowthFactor, and other
      * tunables that make GC non-deterministic.
      */
     ActiveThreadData<bool> dynamicHeapGrowthEnabled_;
 
     /*
+     * JSGC_HIGH_FREQUENCY_TIME_LIMIT
+     *
      * We enter high-frequency mode if we GC a twice within this many
      * microseconds. This value is stored directly in microseconds.
      */
     ActiveThreadData<uint64_t> highFrequencyThresholdUsec_;
 
     /*
+     * JSGC_HIGH_FREQUENCY_LOW_LIMIT
+     * JSGC_HIGH_FREQUENCY_HIGH_LIMIT
+     * JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MAX
+     * JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MIN
+     *
      * When in the |highFrequencyGC| mode, these parameterize the per-zone
      * "HeapGrowthFactor" computation.
      */
     ActiveThreadData<uint64_t> highFrequencyLowLimitBytes_;
     ActiveThreadData<uint64_t> highFrequencyHighLimitBytes_;
     ActiveThreadData<double> highFrequencyHeapGrowthMax_;
     ActiveThreadData<double> highFrequencyHeapGrowthMin_;
 
     /*
+     * JSGC_LOW_FREQUENCY_HEAP_GROWTH
+     *
      * When not in |highFrequencyGC| mode, this is the global (stored per-zone)
      * "HeapGrowthFactor".
      */
     ActiveThreadData<double> lowFrequencyHeapGrowth_;
 
     /*
+     * JSGC_DYNAMIC_MARK_SLICE
+     *
      * Doubles the length of IGC slices when in the |highFrequencyGC| mode.
      */
     ActiveThreadData<bool> dynamicMarkSliceEnabled_;
 
     /*
+     * JSGC_REFRESH_FRAME_SLICES_ENABLED
+     *
      * Controls whether painting can trigger IGC slices.
      */
     ActiveThreadData<bool> refreshFrameSlicesEnabled_;
 
     /*
+     * JSGC_MIN_EMPTY_CHUNK_COUNT
+     * JSGC_MAX_EMPTY_CHUNK_COUNT
+     *
      * Controls the number of empty chunks reserved for future allocation.
      */
     UnprotectedData<uint32_t> minEmptyChunkCount_;
     UnprotectedData<uint32_t> maxEmptyChunkCount_;
 
   public:
-    GCSchedulingTunables()
-      : gcMaxBytes_(0),
-        gcMaxNurseryBytes_(0),
-        gcZoneAllocThresholdBase_(30 * 1024 * 1024),
-        zoneAllocThresholdFactor_(0.9),
-        zoneAllocThresholdFactorAvoidInterrupt_(0.9),
-        zoneAllocDelayBytes_(1024 * 1024),
-        dynamicHeapGrowthEnabled_(false),
-        highFrequencyThresholdUsec_(1000 * 1000),
-        highFrequencyLowLimitBytes_(100 * 1024 * 1024),
-        highFrequencyHighLimitBytes_(500 * 1024 * 1024),
-        highFrequencyHeapGrowthMax_(3.0),
-        highFrequencyHeapGrowthMin_(1.5),
-        lowFrequencyHeapGrowth_(1.5),
-        dynamicMarkSliceEnabled_(false),
-        refreshFrameSlicesEnabled_(true),
-        minEmptyChunkCount_(1),
-        maxEmptyChunkCount_(30)
-    {}
+    GCSchedulingTunables();
 
     size_t gcMaxBytes() const { return gcMaxBytes_; }
     size_t gcMaxNurseryBytes() const { return gcMaxNurseryBytes_; }
     size_t gcZoneAllocThresholdBase() const { return gcZoneAllocThresholdBase_; }
-    double zoneAllocThresholdFactor() const { return zoneAllocThresholdFactor_; }
-    double zoneAllocThresholdFactorAvoidInterrupt() const { return zoneAllocThresholdFactorAvoidInterrupt_; }
+    float zoneAllocThresholdFactor() const { return zoneAllocThresholdFactor_; }
+    float zoneAllocThresholdFactorAvoidInterrupt() const { return zoneAllocThresholdFactorAvoidInterrupt_; }
     size_t zoneAllocDelayBytes() const { return zoneAllocDelayBytes_; }
     bool isDynamicHeapGrowthEnabled() const { return dynamicHeapGrowthEnabled_; }
     uint64_t highFrequencyThresholdUsec() const { return highFrequencyThresholdUsec_; }
     uint64_t highFrequencyLowLimitBytes() const { return highFrequencyLowLimitBytes_; }
     uint64_t highFrequencyHighLimitBytes() const { return highFrequencyHighLimitBytes_; }
     double highFrequencyHeapGrowthMax() const { return highFrequencyHeapGrowthMax_; }
     double highFrequencyHeapGrowthMin() const { return highFrequencyHeapGrowthMin_; }
     double lowFrequencyHeapGrowth() const { return lowFrequencyHeapGrowth_; }
     bool isDynamicMarkSliceEnabled() const { return dynamicMarkSliceEnabled_; }
     bool areRefreshFrameSlicesEnabled() const { return refreshFrameSlicesEnabled_; }
     unsigned minEmptyChunkCount(const AutoLockGC&) const { return minEmptyChunkCount_; }
     unsigned maxEmptyChunkCount() const { return maxEmptyChunkCount_; }
 
     MOZ_MUST_USE bool setParameter(JSGCParamKey key, uint32_t value, const AutoLockGC& lock);
+    void resetParameter(JSGCParamKey key, const AutoLockGC& lock);
+
+private:
+    void setHighFrequencyLowLimit(uint64_t value);
+    void setHighFrequencyHighLimit(uint64_t value);
+    void setMinEmptyChunkCount(uint32_t value);
+    void setMaxEmptyChunkCount(uint32_t value);
 };
 
 /*
  * GC Scheduling Overview
  * ======================
  *
  * Scheduling GC's in SpiderMonkey/Firefox is tremendously complicated because
  * of the large number of subtle, cross-cutting, and widely dispersed factors
@@ -695,16 +705,17 @@ class GCRuntime
     inline bool upcomingZealousGC();
     inline bool needZealousGC();
 
     MOZ_MUST_USE bool addRoot(Value* vp, const char* name);
     void removeRoot(Value* vp);
     void setMarkStackLimit(size_t limit, AutoLockGC& lock);
 
     MOZ_MUST_USE bool setParameter(JSGCParamKey key, uint32_t value, AutoLockGC& lock);
+    void resetParameter(JSGCParamKey key, AutoLockGC& lock);
     uint32_t getParameter(JSGCParamKey key, const AutoLockGC& lock);
 
     MOZ_MUST_USE bool triggerGC(JS::gcreason::Reason reason);
     void maybeAllocTriggerZoneGC(Zone* zone, const AutoLockGC& lock);
     // The return value indicates if we were able to do the GC.
     bool triggerZoneGC(Zone* zone, JS::gcreason::Reason reason,
                        size_t usedBytes, size_t thresholdBytes);
     void maybeGC(Zone* zone);
@@ -1156,16 +1167,21 @@ class GCRuntime
      */
     mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> numArenasFreeCommitted;
     ActiveThreadData<VerifyPreTracer*> verifyPreData;
 
   private:
     UnprotectedData<bool> chunkAllocationSinceLastGC;
     ActiveThreadData<int64_t> lastGCTime;
 
+    /*
+     * JSGC_MODE
+     * prefs: javascript.options.mem.gc_per_zone and
+     *   javascript.options.mem.gc_incremental.
+     */
     ActiveThreadData<JSGCMode> mode;
 
     mozilla::Atomic<size_t, mozilla::ReleaseAcquire> numActiveZoneIters;
 
     /* During shutdown, the GC needs to clean up every possible object. */
     ActiveThreadData<bool> cleanUpEverything;
 
     // Gray marking must be done after all black marking is complete. However,
@@ -1290,27 +1306,35 @@ class GCRuntime
 
     /*
      * Indicates that a GC slice has taken place in the middle of an animation
      * frame, rather than at the beginning. In this case, the next slice will be
      * delayed so that we don't get back-to-back slices.
      */
     ActiveThreadData<bool> interFrameGC;
 
-    /* Default budget for incremental GC slice. See js/SliceBudget.h. */
+    /*
+     * Default budget for incremental GC slice. See js/SliceBudget.h.
+     *
+     * JSGC_SLICE_TIME_BUDGET
+     * pref: javascript.options.mem.gc_incremental_slice_ms,
+     */
     ActiveThreadData<int64_t> defaultTimeBudget_;
 
     /*
      * We disable incremental GC if we encounter a Class with a trace hook
      * that does not implement write barriers.
      */
     ActiveThreadData<bool> incrementalAllowed;
 
     /*
      * Whether compacting GC can is enabled globally.
+     *
+     * JSGC_COMPACTING_ENABLED
+     * pref: javascript.options.mem.gc_compacting
      */
     ActiveThreadData<bool> compactingEnabled;
 
     ActiveThreadData<bool> rootsRemoved;
 
     /*
      * These options control the zealousness of the GC. At every allocation,
      * nextScheduled is decremented. When it reaches zero we do a full GC.
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -2357,17 +2357,17 @@ MarkStackIter::saveValueArray(NativeObje
 /*
  * ExpandWeakMaps: the GC is recomputing the liveness of WeakMap entries by
  * expanding each live WeakMap into its constituent key->value edges, a table
  * of which will be consulted in a later phase whenever marking a potential
  * key.
  */
 GCMarker::GCMarker(JSRuntime* rt)
   : JSTracer(rt, JSTracer::TracerKindTag::Marking, ExpandWeakMaps),
-    stack(size_t(-1)),
+    stack(),
     color(MarkColor::Black),
     unmarkedArenaStackTop(nullptr)
 #ifdef DEBUG
   , markLaterArenas(0)
   , started(false)
   , strictCompartmentChecking(false)
 #endif
 {
--- a/js/src/gc/Marking.h
+++ b/js/src/gc/Marking.h
@@ -109,19 +109,21 @@ class MarkStack
     {
         SavedValueArray(JSObject* obj, size_t index, HeapSlot::Kind kind);
 
         uintptr_t kind;
         uintptr_t index;
         TaggedPtr ptr;
     };
 
-    explicit MarkStack(size_t maxCapacity);
+    explicit MarkStack(size_t maxCapacity = DefaultCapacity);
     ~MarkStack();
 
+    static const size_t DefaultCapacity = SIZE_MAX;
+
     size_t capacity() { return end_ - stack_; }
 
     size_t position() const {
         auto result = tos_ - stack_;
         MOZ_ASSERT(result >= 0);
         return size_t(result);
     }
 
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -324,17 +324,16 @@ js::gc::GCRuntime::traceRuntimeCommon(JS
     {
         gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_STACK);
 
         JSContext* cx = TlsContext.get();
         for (const CooperatingContext& target : rt->cooperatingContexts()) {
             // Trace active interpreter and JIT stack roots.
             TraceInterpreterActivations(cx, target, trc);
             jit::TraceJitActivations(cx, target, trc);
-            wasm::TraceActivations(cx, target, trc);
 
             // Trace legacy C stack roots.
             AutoGCRooter::traceAll(target, trc);
 
             // Trace C stack roots.
             TraceExactStackRoots(target, trc);
         }
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/worker-kill.js
@@ -0,0 +1,11 @@
+if (helperThreadCount() == 0)
+  quit();
+
+evalInWorker(`
+    var code = "(module ";
+    for (var i = 0; i < 100; i++)
+        code += "(func (param i32) (result i32) (i32.add (i32.const 1) (get_local 0))) ";
+    code += ")";
+    var buf = wasmTextToBinary(code);
+    WebAssembly.compile(buf);
+`);
--- a/js/src/jit/Bailouts.cpp
+++ b/js/src/jit/Bailouts.cpp
@@ -12,17 +12,17 @@
 
 #include "jit/BaselineJIT.h"
 #include "jit/Ion.h"
 #include "jit/JitCompartment.h"
 #include "jit/JitSpewer.h"
 #include "jit/Snapshots.h"
 #include "vm/TraceLogging.h"
 
-#include "jit/JitFrameIterator-inl.h"
+#include "jit/JSJitFrameIter-inl.h"
 #include "vm/Probes-inl.h"
 #include "vm/Stack-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
 using mozilla::IsInRange;
 
@@ -36,51 +36,51 @@ jit::Bailout(BailoutStack* sp, BaselineB
     MOZ_ASSERT(IsInRange(FAKE_EXITFP_FOR_BAILOUT, 0, 0x1000) &&
                IsInRange(FAKE_EXITFP_FOR_BAILOUT + sizeof(CommonFrameLayout), 0, 0x1000),
                "Fake exitfp pointer should be within the first page.");
 
     cx->activation()->asJit()->setExitFP(FAKE_EXITFP_FOR_BAILOUT);
 
     JitActivationIterator jitActivations(cx);
     BailoutFrameInfo bailoutData(jitActivations, sp);
-    JitFrameIterator iter(jitActivations);
-    MOZ_ASSERT(!iter.ionScript()->invalidated());
-    CommonFrameLayout* currentFramePtr = iter.current();
+    JSJitFrameIter frame(jitActivations->asJit());
+    MOZ_ASSERT(!frame.ionScript()->invalidated());
+    CommonFrameLayout* currentFramePtr = frame.current();
 
     TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx);
     TraceLogTimestamp(logger, TraceLogger_Bailout);
 
-    JitSpew(JitSpew_IonBailouts, "Took bailout! Snapshot offset: %d", iter.snapshotOffset());
+    JitSpew(JitSpew_IonBailouts, "Took bailout! Snapshot offset: %d", frame.snapshotOffset());
 
     MOZ_ASSERT(IsBaselineEnabled(cx));
 
     *bailoutInfo = nullptr;
-    uint32_t retval = BailoutIonToBaseline(cx, bailoutData.activation(), iter, false, bailoutInfo,
+    uint32_t retval = BailoutIonToBaseline(cx, bailoutData.activation(), frame, false, bailoutInfo,
                                            /* excInfo = */ nullptr);
     MOZ_ASSERT(retval == BAILOUT_RETURN_OK ||
                retval == BAILOUT_RETURN_FATAL_ERROR ||
                retval == BAILOUT_RETURN_OVERRECURSED);
     MOZ_ASSERT_IF(retval == BAILOUT_RETURN_OK, *bailoutInfo != nullptr);
 
     if (retval != BAILOUT_RETURN_OK) {
-        JSScript* script = iter.script();
+        JSScript* script = frame.script();
         probes::ExitScript(cx, script, script->functionNonDelazifying(),
                            /* popProfilerFrame = */ false);
     }
 
     // This condition was wrong when we entered this bailout function, but it
     // might be true now. A GC might have reclaimed all the Jit code and
     // invalidated all frames which are currently on the stack. As we are
     // already in a bailout, we could not switch to an invalidation
     // bailout. When the code of an IonScript which is on the stack is
     // invalidated (see InvalidateActivation), we remove references to it and
     // increment the reference counter for each activation that appear on the
     // stack. As the bailed frame is one of them, we have to decrement it now.
-    if (iter.ionScript()->invalidated())
-        iter.ionScript()->decrementInvalidationCount(cx->runtime()->defaultFreeOp());
+    if (frame.ionScript()->invalidated())
+        frame.ionScript()->decrementInvalidationCount(cx->runtime()->defaultFreeOp());
 
     // NB: Commentary on how |lastProfilingFrame| is set from bailouts.
     //
     // Once we return to jitcode, any following frames might get clobbered,
     // but the current frame will not (as it will be clobbered "in-place"
     // with a baseline frame that will share the same frame prefix).
     // However, there may be multiple baseline frames unpacked from this
     // single Ion frame, which means we will need to once again reset
@@ -108,31 +108,32 @@ jit::InvalidationBailout(InvalidationBai
 
     JSContext* cx = TlsContext.get();
 
     // We don't have an exit frame.
     cx->activation()->asJit()->setExitFP(FAKE_EXITFP_FOR_BAILOUT);
 
     JitActivationIterator jitActivations(cx);
     BailoutFrameInfo bailoutData(jitActivations, sp);
-    JitFrameIterator iter(jitActivations);
-    CommonFrameLayout* currentFramePtr = iter.current();
+    JSJitFrameIter frame(jitActivations->asJit());
+    CommonFrameLayout* currentFramePtr = frame.current();
 
     TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx);
     TraceLogTimestamp(logger, TraceLogger_Invalidation);
 
-    JitSpew(JitSpew_IonBailouts, "Took invalidation bailout! Snapshot offset: %d", iter.snapshotOffset());
+    JitSpew(JitSpew_IonBailouts, "Took invalidation bailout! Snapshot offset: %d",
+            frame.snapshotOffset());
 
     // Note: the frame size must be computed before we return from this function.
-    *frameSizeOut = iter.frameSize();
+    *frameSizeOut = frame.frameSize();
 
     MOZ_ASSERT(IsBaselineEnabled(cx));
 
     *bailoutInfo = nullptr;
-    uint32_t retval = BailoutIonToBaseline(cx, bailoutData.activation(), iter, true, bailoutInfo,
+    uint32_t retval = BailoutIonToBaseline(cx, bailoutData.activation(), frame, true, bailoutInfo,
                                            /* excInfo = */ nullptr);
     MOZ_ASSERT(retval == BAILOUT_RETURN_OK ||
                retval == BAILOUT_RETURN_FATAL_ERROR ||
                retval == BAILOUT_RETURN_OVERRECURSED);
     MOZ_ASSERT_IF(retval == BAILOUT_RETURN_OK, *bailoutInfo != nullptr);
 
     if (retval != BAILOUT_RETURN_OK) {
         // If the bailout failed, then bailout trampoline will pop the
@@ -141,41 +142,41 @@ jit::InvalidationBailout(InvalidationBai
         // frame will be silently forgotten.
         //
         // We call ExitScript here to ensure that if the ionScript had Gecko
         // Profiler instrumentation, then the entry for it is popped.
         //
         // However, if the bailout was during argument check, then a
         // pseudostack frame would not have been pushed in the first
         // place, so don't pop anything in that case.
-        JSScript* script = iter.script();
+        JSScript* script = frame.script();
         probes::ExitScript(cx, script, script->functionNonDelazifying(),
                            /* popProfilerFrame = */ false);
 
 #ifdef JS_JITSPEW
-        JitFrameLayout* frame = iter.jsFrame();
+        JitFrameLayout* layout = frame.jsFrame();
         JitSpew(JitSpew_IonInvalidate, "Bailout failed (%s)",
                 (retval == BAILOUT_RETURN_FATAL_ERROR) ? "Fatal Error" : "Over Recursion");
-        JitSpew(JitSpew_IonInvalidate, "   calleeToken %p", (void*) frame->calleeToken());
-        JitSpew(JitSpew_IonInvalidate, "   frameSize %u", unsigned(frame->prevFrameLocalSize()));
-        JitSpew(JitSpew_IonInvalidate, "   ra %p", (void*) frame->returnAddress());
+        JitSpew(JitSpew_IonInvalidate, "   calleeToken %p", (void*) layout->calleeToken());
+        JitSpew(JitSpew_IonInvalidate, "   frameSize %u", unsigned(layout->prevFrameLocalSize()));
+        JitSpew(JitSpew_IonInvalidate, "   ra %p", (void*) layout->returnAddress());
 #endif
     }
 
-    iter.ionScript()->decrementInvalidationCount(cx->runtime()->defaultFreeOp());
+    frame.ionScript()->decrementInvalidationCount(cx->runtime()->defaultFreeOp());
 
     // Make the frame being bailed out the top profiled frame.
     if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(cx->runtime()))
         cx->jitActivation->setLastProfilingFrame(currentFramePtr);
 
     return retval;
 }
 
 BailoutFrameInfo::BailoutFrameInfo(const JitActivationIterator& activations,
-                                   const JitFrameIterator& frame)
+                                   const JSJitFrameIter& frame)
   : machine_(frame.machineState())
 {
     framePointer_ = (uint8_t*) frame.fp();
     topFrameSize_ = frame.frameSize();
     topIonScript_ = frame.ionScript();
     attachOnJitActivation(activations);
 
     const OsiIndex* osiIndex = frame.osiIndex();
@@ -197,28 +198,28 @@ jit::ExceptionHandlerBailout(JSContext* 
     uint8_t* prevExitFP = act->exitFP();
     auto restoreExitFP = mozilla::MakeScopeExit([&]() { act->setExitFP(prevExitFP); });
     act->setExitFP(FAKE_EXITFP_FOR_BAILOUT);
 
     gc::AutoSuppressGC suppress(cx);
 
     JitActivationIterator jitActivations(cx);
     BailoutFrameInfo bailoutData(jitActivations, frame.frame());
-    JitFrameIterator iter(jitActivations);
-    CommonFrameLayout* currentFramePtr = iter.current();
+    JSJitFrameIter frameView(jitActivations->asJit());
+    CommonFrameLayout* currentFramePtr = frameView.current();
 
     BaselineBailoutInfo* bailoutInfo = nullptr;
     uint32_t retval;
 
     {
         // Currently we do not tolerate OOM here so as not to complicate the
         // exception handling code further.
         AutoEnterOOMUnsafeRegion oomUnsafe;
 
-        retval = BailoutIonToBaseline(cx, bailoutData.activation(), iter, true,
+        retval = BailoutIonToBaseline(cx, bailoutData.activation(), frameView, true,
                                       &bailoutInfo, &excInfo);
         if (retval == BAILOUT_RETURN_FATAL_ERROR && cx->isThrowingOutOfMemory())
             oomUnsafe.crash("ExceptionHandlerBailout");
     }
 
     if (retval == BAILOUT_RETURN_OK) {
         MOZ_ASSERT(bailoutInfo);
 
--- a/js/src/jit/Bailouts.h
+++ b/js/src/jit/Bailouts.h
@@ -4,18 +4,18 @@
  * 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 jit_Bailouts_h
 #define jit_Bailouts_h
 
 #include "jstypes.h"
 
-#include "jit/JitFrameIterator.h"
 #include "jit/JitFrames.h"
+#include "jit/JSJitFrameIter.h"
 #include "vm/Stack.h"
 
 namespace js {
 namespace jit {
 
 // A "bailout" is a condition in which we need to recover an interpreter frame
 // from an IonFrame. Bailouts can happen for the following reasons:
 //   (1) A deoptimization guard, for example, an add overflows or a type check
@@ -108,32 +108,32 @@ static uint8_t* const FAKE_EXITFP_FOR_BA
 // bailout handler.
 class BailoutStack;
 class InvalidationBailoutStack;
 
 // Must be implemented by each architecture.
 
 // This structure is constructed before recovering the baseline frames for a
 // bailout. It records all information extracted from the stack, and which are
-// needed for the JitFrameIterator.
+// needed for the JSJitFrameIter.
 class BailoutFrameInfo
 {
     MachineState machine_;
     uint8_t* framePointer_;
     size_t topFrameSize_;
     IonScript* topIonScript_;
     uint32_t snapshotOffset_;
     JitActivation* activation_;
 
     void attachOnJitActivation(const JitActivationIterator& activations);
 
   public:
     BailoutFrameInfo(const JitActivationIterator& activations, BailoutStack* sp);
     BailoutFrameInfo(const JitActivationIterator& activations, InvalidationBailoutStack* sp);
-    BailoutFrameInfo(const JitActivationIterator& activations, const JitFrameIterator& frame);
+    BailoutFrameInfo(const JitActivationIterator& activations, const JSJitFrameIter& frame);
     ~BailoutFrameInfo();
 
     uint8_t* fp() const {
         return framePointer_;
     }
     SnapshotOffset snapshotOffset() const {
         return snapshotOffset_;
     }
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -80,31 +80,31 @@ class BufferPointer
  * needs to be enlarged to accommodate new data.  Similarly to the C stack, the
  * data that's written to the reconstructed stack grows from high to low in memory.
  *
  * The lowest region of the allocated memory contains a BaselineBailoutInfo structure that
  * points to the start and end of the written data.
  */
 struct BaselineStackBuilder
 {
-    JitFrameIterator& iter_;
+    const JSJitFrameIter& iter_;
     JitFrameLayout* frame_;
 
     static size_t HeaderSize() {
         return AlignBytes(sizeof(BaselineBailoutInfo), sizeof(void*));
     }
     size_t bufferTotal_;
     size_t bufferAvail_;
     size_t bufferUsed_;
     uint8_t* buffer_;
     BaselineBailoutInfo* header_;
 
     size_t framePushed_;
 
-    BaselineStackBuilder(JitFrameIterator& iter, size_t initialSize)
+    BaselineStackBuilder(const JSJitFrameIter& iter, size_t initialSize)
       : iter_(iter),
         frame_(static_cast<JitFrameLayout*>(iter.current())),
         bufferTotal_(initialSize),
         bufferAvail_(0),
         bufferUsed_(0),
         buffer_(nullptr),
         header_(nullptr),
         framePushed_(0)
@@ -423,20 +423,20 @@ struct BaselineStackBuilder
 };
 
 // Ensure that all value locations are readable from the SnapshotIterator.
 // Remove RInstructionResults from the JitActivation if the frame got recovered
 // ahead of the bailout.
 class SnapshotIteratorForBailout : public SnapshotIterator
 {
     JitActivation* activation_;
-    JitFrameIterator& iter_;
+    const JSJitFrameIter& iter_;
 
   public:
-    SnapshotIteratorForBailout(JitActivation* activation, JitFrameIterator& iter)
+    SnapshotIteratorForBailout(JitActivation* activation, const JSJitFrameIter& iter)
       : SnapshotIterator(iter, activation->bailoutData()->machineState()),
         activation_(activation),
         iter_(iter)
     {
         MOZ_ASSERT(iter.isBailoutJS());
     }
 
     ~SnapshotIteratorForBailout() {
@@ -1488,18 +1488,19 @@ InitFromBailout(JSContext* cx, HandleScr
     if (!builder.writePtr(rectReturnAddr, "ReturnAddr"))
         return false;
     MOZ_ASSERT(builder.framePushed() % JitStackAlignment == 0);
 
     return true;
 }
 
 uint32_t
-jit::BailoutIonToBaseline(JSContext* cx, JitActivation* activation, JitFrameIterator& iter,
-                          bool invalidate, BaselineBailoutInfo** bailoutInfo,
+jit::BailoutIonToBaseline(JSContext* cx, JitActivation* activation,
+                          const JSJitFrameIter& iter, bool invalidate,
+                          BaselineBailoutInfo** bailoutInfo,
                           const ExceptionBailoutInfo* excInfo)
 {
     MOZ_ASSERT(bailoutInfo != nullptr);
     MOZ_ASSERT(*bailoutInfo == nullptr);
 
     TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx);
     TraceLogStopEvent(logger, TraceLogger_IonMonkey);
     TraceLogStartEvent(logger, TraceLogger_Baseline);
@@ -1874,17 +1875,17 @@ jit::FinishBailoutToBaseline(BaselineBai
     }
 
     // Create arguments objects for bailed out frames, to maintain the invariant
     // that script->needsArgsObj() implies frame->hasArgsObj().
     RootedScript innerScript(cx, nullptr);
     RootedScript outerScript(cx, nullptr);
 
     MOZ_ASSERT(cx->currentlyRunningInJit());
-    JitFrameIterator iter(cx);
+    JSJitFrameIter iter(cx);
     uint8_t* outerFp = nullptr;
 
     // Iter currently points at the exit frame.  Get the previous frame
     // (which must be a baseline frame), and set it as the last profiling
     // frame.
     if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(cx->runtime()))
         cx->jitActivation->setLastProfilingFrame(iter.prevFp());
 
@@ -1936,17 +1937,17 @@ jit::FinishBailoutToBaseline(BaselineBai
     MOZ_ASSERT(outerFp);
 
     // If we rematerialized Ion frames due to debug mode toggling, copy their
     // values into the baseline frame. We need to do this even when debug mode
     // is off, as we should respect the mutations made while debug mode was
     // on.
     JitActivation* act = cx->activation()->asJit();
     if (act->hasRematerializedFrame(outerFp)) {
-        JitFrameIterator iter(cx);
+        JSJitFrameIter iter(cx);
         size_t inlineDepth = numFrames;
         bool ok = true;
         while (inlineDepth > 0) {
             if (iter.isBaselineJS()) {
                 // We must attempt to copy all rematerialized frames over,
                 // even if earlier ones failed, to invoke the proper frame
                 // cleanup in the Debugger.
                 ok = CopyFromRematerializedFrame(cx, act, outerFp, --inlineDepth,
--- a/js/src/jit/BaselineDebugModeOSR.cpp
+++ b/js/src/jit/BaselineDebugModeOSR.cpp
@@ -183,47 +183,48 @@ class UniqueScriptOSREntryIter
 };
 
 static bool
 CollectJitStackScripts(JSContext* cx, const Debugger::ExecutionObservableSet& obs,
                        const ActivationIterator& activation, DebugModeOSREntryVector& entries)
 {
     ICStub* prevFrameStubPtr = nullptr;
     bool needsRecompileHandler = false;
-    for (JitFrameIterator iter(activation); !iter.done(); ++iter) {
-        switch (iter.type()) {
+    for (OnlyJSJitFrameIter iter(activation); !iter.done(); ++iter) {
+        const JSJitFrameIter& frame = iter.frame();
+        switch (frame.type()) {
           case JitFrame_BaselineJS: {
-            JSScript* script = iter.script();
+            JSScript* script = frame.script();
 
             if (!obs.shouldRecompileOrInvalidate(script)) {
                 prevFrameStubPtr = nullptr;
                 break;
             }
 
-            BaselineFrame* frame = iter.baselineFrame();
+            BaselineFrame* baselineFrame = frame.baselineFrame();
 
-            if (BaselineDebugModeOSRInfo* info = frame->getDebugModeOSRInfo()) {
+            if (BaselineDebugModeOSRInfo* info = baselineFrame->getDebugModeOSRInfo()) {
                 // If patching a previously patched yet unpopped frame, we can
                 // use the BaselineDebugModeOSRInfo on the frame directly to
-                // patch. Indeed, we cannot use iter.returnAddressToFp(), as
+                // patch. Indeed, we cannot use frame.returnAddressToFp(), as
                 // it points into the debug mode OSR handler and cannot be
                 // used to look up a corresponding ICEntry.
                 //
                 // See cases F and G in PatchBaselineFramesForDebugMode.
                 if (!entries.append(DebugModeOSREntry(script, info)))
                     return false;
-            } else if (frame->isHandlingException()) {
+            } else if (baselineFrame->isHandlingException()) {
                 // We are in the middle of handling an exception and the frame
                 // must have an override pc.
-                uint32_t offset = script->pcToOffset(frame->overridePc());
+                uint32_t offset = script->pcToOffset(baselineFrame->overridePc());
                 if (!entries.append(DebugModeOSREntry(script, offset)))
                     return false;
             } else {
                 // The frame must be settled on a pc with an ICEntry.
-                uint8_t* retAddr = iter.returnAddressToFp();
+                uint8_t* retAddr = frame.returnAddressToFp();
                 BaselineICEntry& icEntry = script->baselineScript()->icEntryFromReturnAddress(retAddr);
                 if (!entries.append(DebugModeOSREntry(script, icEntry)))
                     return false;
             }
 
             if (entries.back().needsRecompileInfo()) {
                 if (!entries.back().allocateRecompileInfo(cx))
                     return false;
@@ -232,21 +233,21 @@ CollectJitStackScripts(JSContext* cx, co
             }
             entries.back().oldStub = prevFrameStubPtr;
             prevFrameStubPtr = nullptr;
             break;
           }
 
           case JitFrame_BaselineStub:
             prevFrameStubPtr =
-                reinterpret_cast<BaselineStubFrameLayout*>(iter.fp())->maybeStubPtr();
+                reinterpret_cast<BaselineStubFrameLayout*>(frame.fp())->maybeStubPtr();
             break;
 
           case JitFrame_IonJS: {
-            InlineFrameIterator inlineIter(cx, &iter);
+            InlineFrameIterator inlineIter(cx, &frame);
             while (true) {
                 if (obs.shouldRecompileOrInvalidate(inlineIter.script())) {
                     if (!entries.append(DebugModeOSREntry(inlineIter.script())))
                         return false;
                 }
                 if (!inlineIter.more())
                     break;
                 ++inlineIter;
@@ -378,36 +379,37 @@ PatchBaselineFramesForDebugMode(JSContex
     // In general, we patch the return address from the VM call to return to a
     // "continuation fixer" to fix up machine state (registers and stack
     // state). Specifics on what needs to be done are documented below.
     //
 
     CommonFrameLayout* prev = nullptr;
     size_t entryIndex = *start;
 
-    for (JitFrameIterator iter(activation); !iter.done(); ++iter) {
-        switch (iter.type()) {
+    for (OnlyJSJitFrameIter iter(activation); !iter.done(); ++iter) {
+        const JSJitFrameIter& frame = iter.frame();
+        switch (frame.type()) {
           case JitFrame_BaselineJS: {
             // If the script wasn't recompiled or is not observed, there's
             // nothing to patch.
-            if (!obs.shouldRecompileOrInvalidate(iter.script()))
+            if (!obs.shouldRecompileOrInvalidate(frame.script()))
                 break;
 
             DebugModeOSREntry& entry = entries[entryIndex];
 
             if (!entry.recompiled()) {
                 entryIndex++;
                 break;
             }
 
             JSScript* script = entry.script;
             uint32_t pcOffset = entry.pcOffset;
             jsbytecode* pc = script->offsetToPC(pcOffset);
 
-            MOZ_ASSERT(script == iter.script());
+            MOZ_ASSERT(script == frame.script());
             MOZ_ASSERT(pcOffset < script->length());
 
             BaselineScript* bl = script->baselineScript();
             ICEntry::Kind kind = entry.frameKind;
 
             if (kind == ICEntry::Kind_Op) {
                 // Case A above.
                 //
@@ -415,17 +417,17 @@ PatchBaselineFramesForDebugMode(JSContex
                 // the baseline frame. The stub frame is patched below. For
                 // the baseline frame here, we resume right after the IC
                 // returns.
                 //
                 // Since we're using the same IC stub code, we can resume
                 // directly to the IC resume address.
                 uint8_t* retAddr = bl->returnAddressForIC(bl->icEntryFromPCOffset(pcOffset));
                 SpewPatchBaselineFrame(prev->returnAddress(), retAddr, script, kind, pc);
-                DebugModeOSRVolatileJitFrameIterator::forwardLiveIterators(
+                DebugModeOSRVolatileJitFrameIter::forwardLiveIterators(
                     target, prev->returnAddress(), retAddr);
                 prev->setReturnAddress(retAddr);
                 entryIndex++;
                 break;
             }
 
             if (kind == ICEntry::Kind_Invalid) {
                 // Case H above.
@@ -435,52 +437,52 @@ PatchBaselineFramesForDebugMode(JSContex
                 // invocation, on a pc without an ICEntry. This means the
                 // frame must have an override pc.
                 //
                 // If profiling is off, patch the resume address to nullptr,
                 // to ensure the old address is not used anywhere.
                 //
                 // If profiling is on, JitProfilingFrameIterator requires a
                 // valid return address.
-                MOZ_ASSERT(iter.baselineFrame()->isHandlingException());
-                MOZ_ASSERT(iter.baselineFrame()->overridePc() == pc);
+                MOZ_ASSERT(frame.baselineFrame()->isHandlingException());
+                MOZ_ASSERT(frame.baselineFrame()->overridePc() == pc);
                 uint8_t* retAddr;
                 if (cx->runtime()->geckoProfiler().enabled())
                     retAddr = bl->nativeCodeForPC(script, pc);
                 else
                     retAddr = nullptr;
                 SpewPatchBaselineFrameFromExceptionHandler(prev->returnAddress(), retAddr,
                                                            script, pc);
-                DebugModeOSRVolatileJitFrameIterator::forwardLiveIterators(
+                DebugModeOSRVolatileJitFrameIter::forwardLiveIterators(
                     target, prev->returnAddress(), retAddr);
                 prev->setReturnAddress(retAddr);
                 entryIndex++;
                 break;
             }
 
             // Case F above.
             //
             // We undo a previous recompile by handling cases B, C, D, E, I or J
             // like normal, except that we retrieve the pc information via
             // the previous OSR debug info stashed on the frame.
-            BaselineDebugModeOSRInfo* info = iter.baselineFrame()->getDebugModeOSRInfo();
+            BaselineDebugModeOSRInfo* info = frame.baselineFrame()->getDebugModeOSRInfo();
             if (info) {
                 MOZ_ASSERT(info->pc == pc);
                 MOZ_ASSERT(info->frameKind == kind);
                 MOZ_ASSERT(kind == ICEntry::Kind_CallVM ||
                            kind == ICEntry::Kind_WarmupCounter ||
                            kind == ICEntry::Kind_StackCheck ||
                            kind == ICEntry::Kind_EarlyStackCheck ||
                            kind == ICEntry::Kind_DebugTrap ||
                            kind == ICEntry::Kind_DebugPrologue ||
                            kind == ICEntry::Kind_DebugEpilogue);
 
                 // We will have allocated a new recompile info, so delete the
                 // existing one.
-                iter.baselineFrame()->deleteDebugModeOSRInfo();
+                frame.baselineFrame()->deleteDebugModeOSRInfo();
             }
 
             // The RecompileInfo must already be allocated so that this
             // function may be infallible.
             BaselineDebugModeOSRInfo* recompInfo = entry.takeRecompInfo();
 
             bool popFrameReg;
             switch (kind) {
@@ -563,38 +565,38 @@ PatchBaselineFramesForDebugMode(JSContex
 
             // The recompile handler must already be created so that this
             // function may be infallible.
             JitRuntime* rt = cx->runtime()->jitRuntime();
             void* handlerAddr = rt->getBaselineDebugModeOSRHandlerAddress(cx, popFrameReg);
             MOZ_ASSERT(handlerAddr);
 
             prev->setReturnAddress(reinterpret_cast<uint8_t*>(handlerAddr));
-            iter.baselineFrame()->setDebugModeOSRInfo(recompInfo);
-            iter.baselineFrame()->setOverridePc(recompInfo->pc);
+            frame.baselineFrame()->setDebugModeOSRInfo(recompInfo);
+            frame.baselineFrame()->setOverridePc(recompInfo->pc);
 
             entryIndex++;
             break;
           }
 
           case JitFrame_BaselineStub: {
-            JitFrameIterator prev(iter);
+            JSJitFrameIter prev(iter.frame());
             ++prev;
             BaselineFrame* prevFrame = prev.baselineFrame();
             if (!obs.shouldRecompileOrInvalidate(prevFrame->script()))
                 break;
 
             DebugModeOSREntry& entry = entries[entryIndex];
 
             // If the script wasn't recompiled, there's nothing to patch.
             if (!entry.recompiled())
                 break;
 
             BaselineStubFrameLayout* layout =
-                reinterpret_cast<BaselineStubFrameLayout*>(iter.fp());
+                reinterpret_cast<BaselineStubFrameLayout*>(frame.fp());
             MOZ_ASSERT(layout->maybeStubPtr() == entry.oldStub);
 
             // Patch baseline stub frames for case A above.
             //
             // We need to patch the stub frame to point to an ICStub belonging
             // to the recompiled baseline script. These stubs are allocated up
             // front in CloneOldBaselineStub. They share the same JitCode as
             // the old baseline script's stubs, so we don't need to patch the
@@ -615,31 +617,31 @@ PatchBaselineFramesForDebugMode(JSContex
                 layout->setStubPtr(entry.newStub);
             }
 
             break;
           }
 
           case JitFrame_IonJS: {
             // Nothing to patch.
-            InlineFrameIterator inlineIter(cx, &iter);
+            InlineFrameIterator inlineIter(cx, &frame);
             while (true) {
                 if (obs.shouldRecompileOrInvalidate(inlineIter.script()))
                     entryIndex++;
                 if (!inlineIter.more())
                     break;
                 ++inlineIter;
             }
             break;
           }
 
           default:;
         }
 
-        prev = iter.current();
+        prev = frame.current();
     }
 
     *start = entryIndex;
 }
 
 static void
 SkipInterpreterFrameEntries(const Debugger::ExecutionObservableSet& obs,
                             const ActivationIterator& activation,
@@ -1168,17 +1170,15 @@ JitRuntime::generateBaselineDebugModeOSR
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "BaselineDebugModeOSRHandler");
 #endif
 
     return code;
 }
 
 /* static */ void
-DebugModeOSRVolatileJitFrameIterator::forwardLiveIterators(const CooperatingContext& cx,
-                                                           uint8_t* oldAddr, uint8_t* newAddr)
+DebugModeOSRVolatileJitFrameIter::forwardLiveIterators(const CooperatingContext& cx,
+                                                         uint8_t* oldAddr, uint8_t* newAddr)
 {
-    DebugModeOSRVolatileJitFrameIterator* iter;
-    for (iter = cx.context()->liveVolatileJitFrameIterators_; iter; iter = iter->prev) {
-        if (iter->returnAddressToFp_ == oldAddr)
-            iter->returnAddressToFp_ = newAddr;
-    }
+    DebugModeOSRVolatileJitFrameIter* iter;
+    for (iter = cx.context()->liveVolatileJitFrameIter_; iter; iter = iter->prev)
+        iter->asJSJit().exchangeReturnAddressIfMatch(oldAddr, newAddr);
 }
--- a/js/src/jit/BaselineDebugModeOSR.h
+++ b/js/src/jit/BaselineDebugModeOSR.h
@@ -5,17 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef jit_BaselineDebugModeOSR_h
 #define jit_BaselineDebugModeOSR_h
 
 #include "jit/BaselineFrame.h"
 #include "jit/BaselineIC.h"
 #include "jit/BaselineJIT.h"
-#include "jit/JitFrameIterator.h"
+#include "jit/JSJitFrameIter.h"
 
 #include "vm/Debugger.h"
 
 namespace js {
 namespace jit {
 
 // Note that this file and the corresponding .cpp implement debug mode
 // on-stack recompilation. This is to be distinguished from ordinary
@@ -77,34 +77,35 @@ class DebugModeOSRVolatileStub
     T& get() { MOZ_ASSERT(!invalid()); return stub_; }
     const T& get() const { MOZ_ASSERT(!invalid()); return stub_; }
 
     bool operator!=(const T& other) const { MOZ_ASSERT(!invalid()); return stub_ != other; }
     bool operator==(const T& other) const { MOZ_ASSERT(!invalid()); return stub_ == other; }
 };
 
 //
-// A JitFrameIterator that updates itself in case of recompilation of an
-// on-stack baseline script.
+// A JitFrameIter that updates internal JSJitFrameIter in case of
+// recompilation of an on-stack baseline script.
 //
-class DebugModeOSRVolatileJitFrameIterator : public JitFrameIterator
+
+class DebugModeOSRVolatileJitFrameIter : public JitFrameIter
 {
-    DebugModeOSRVolatileJitFrameIterator** stack;
-    DebugModeOSRVolatileJitFrameIterator* prev;
+    DebugModeOSRVolatileJitFrameIter** stack;
+    DebugModeOSRVolatileJitFrameIter* prev;
 
   public:
-    explicit DebugModeOSRVolatileJitFrameIterator(JSContext* cx)
-      : JitFrameIterator(cx)
+    explicit DebugModeOSRVolatileJitFrameIter(JSContext* cx)
+      : JitFrameIter(cx->activation())
     {
-        stack = &cx->liveVolatileJitFrameIterators_.ref();
+        stack = &cx->liveVolatileJitFrameIter_.ref();
         prev = *stack;
         *stack = this;
     }
 
-    ~DebugModeOSRVolatileJitFrameIterator() {
+    ~DebugModeOSRVolatileJitFrameIter() {
         MOZ_ASSERT(*stack == this);
         *stack = prev;
     }
 
     static void forwardLiveIterators(const CooperatingContext& target,
                                      uint8_t* oldAddr, uint8_t* newAddr);
 };
 
--- a/js/src/jit/BaselineFrame.cpp
+++ b/js/src/jit/BaselineFrame.cpp
@@ -23,17 +23,17 @@ TraceLocals(BaselineFrame* frame, JSTrac
     if (start < end) {
         // Stack grows down.
         Value* last = frame->valueSlot(end - 1);
         TraceRootRange(trc, end - start, last, "baseline-stack");
     }
 }
 
 void
-BaselineFrame::trace(JSTracer* trc, JitFrameIterator& frameIterator)
+BaselineFrame::trace(JSTracer* trc, const JSJitFrameIter& frameIterator)
 {
     replaceCalleeToken(TraceCalleeToken(trc, calleeToken()));
 
     // Trace |this|, actual and formal args.
     if (isFunctionFrame()) {
         TraceRoot(trc, &thisArgument(), "baseline-this");
 
         unsigned numArgs = js::Max(numActualArgs(), numFormalArgs());
@@ -137,20 +137,20 @@ BaselineFrame::initForOsr(InterpreterFra
 
         // For debuggee frames, update any Debugger.Frame objects for the
         // InterpreterFrame to point to the BaselineFrame.
 
         // The caller pushed a fake return address. ScriptFrameIter, used by the
         // debugger, wants a valid return address, but it's okay to just pick one.
         // In debug mode there's always at least 1 ICEntry (since there are always
         // debug prologue/epilogue calls).
-        JitFrameIterator iter(cx);
-        MOZ_ASSERT(iter.returnAddress() == nullptr);
+        JSJitFrameIter frame(cx);
+        MOZ_ASSERT(frame.returnAddress() == nullptr);
         BaselineScript* baseline = fp->script()->baselineScript();
-        iter.current()->setReturnAddress(baseline->returnAddressForIC(baseline->icEntry(0)));
+        frame.current()->setReturnAddress(baseline->returnAddressForIC(baseline->icEntry(0)));
 
         if (!Debugger::handleBaselineOsr(cx, fp, this))
             return false;
 
         setIsDebuggee();
     }
 
     return true;
--- a/js/src/jit/BaselineFrame.h
+++ b/js/src/jit/BaselineFrame.h
@@ -365,17 +365,17 @@ class BaselineFrame
         flags_ |= HAS_OVERRIDE_PC;
         overrideOffset_ = script()->pcToOffset(pc);
     }
 
     void clearOverridePc() {
         flags_ &= ~HAS_OVERRIDE_PC;
     }
 
-    void trace(JSTracer* trc, JitFrameIterator& frame);
+    void trace(JSTracer* trc, const JSJitFrameIter& frame);
 
     bool isGlobalFrame() const {
         return script()->isGlobalCode();
     }
     bool isModuleFrame() const {
         return script()->module();
     }
     bool isEvalFrame() const {
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -1224,33 +1224,34 @@ jit::ToggleBaselineTraceLoggerEngine(JSR
         }
     }
 }
 #endif
 
 static void
 MarkActiveBaselineScripts(JSContext* cx, const JitActivationIterator& activation)
 {
-    for (jit::JitFrameIterator iter(activation); !iter.done(); ++iter) {
-        switch (iter.type()) {
+    for (OnlyJSJitFrameIter iter(activation); !iter.done(); ++iter) {
+        const JSJitFrameIter& frame = iter.frame();
+        switch (frame.type()) {
           case JitFrame_BaselineJS:
-            iter.script()->baselineScript()->setActive();
+            frame.script()->baselineScript()->setActive();
             break;
           case JitFrame_Exit:
-            if (iter.exitFrame()->is<LazyLinkExitFrameLayout>()) {
-                LazyLinkExitFrameLayout* ll = iter.exitFrame()->as<LazyLinkExitFrameLayout>();
+            if (frame.exitFrame()->is<LazyLinkExitFrameLayout>()) {
+                LazyLinkExitFrameLayout* ll = frame.exitFrame()->as<LazyLinkExitFrameLayout>();
                 ScriptFromCalleeToken(ll->jsFrame()->calleeToken())->baselineScript()->setActive();
             }
             break;
           case JitFrame_Bailout:
           case JitFrame_IonJS: {
             // Keep the baseline script around, since bailouts from the ion
             // jitcode might need to re-enter into the baseline jitcode.
-            iter.script()->baselineScript()->setActive();
-            for (InlineFrameIterator inlineIter(cx, &iter); inlineIter.more(); ++inlineIter)
+            frame.script()->baselineScript()->setActive();
+            for (InlineFrameIterator inlineIter(cx, &frame); inlineIter.more(); ++inlineIter)
                 inlineIter.script()->baselineScript()->setActive();
             break;
           }
           default:;
         }
     }
 }
 
--- a/js/src/jit/BaselineJIT.h
+++ b/js/src/jit/BaselineJIT.h
@@ -625,17 +625,17 @@ struct BaselineBailoutInfo
     // needed to perform the check.
     bool checkGlobalDeclarationConflicts;
 
     // The bailout kind.
     BailoutKind bailoutKind;
 };
 
 uint32_t
-BailoutIonToBaseline(JSContext* cx, JitActivation* activation, JitFrameIterator& iter,
+BailoutIonToBaseline(JSContext* cx, JitActivation* activation, const JSJitFrameIter& iter,
                      bool invalidate, BaselineBailoutInfo** bailoutInfo,
                      const ExceptionBailoutInfo* exceptionInfo);
 
 // Mark baseline scripts on the stack as active, so that they are not discarded
 // during GC.
 void
 MarkActiveBaselineScripts(Zone* zone);
 
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -586,18 +586,18 @@ jit::LinkIonScript(JSContext* cx, Handle
     }
 }
 
 uint8_t*
 jit::LazyLinkTopActivation()
 {
     // First frame should be an exit frame.
     JSContext* cx = TlsContext.get();
-    JitFrameIterator it(cx);
-    LazyLinkExitFrameLayout* ll = it.exitFrame()->as<LazyLinkExitFrameLayout>();
+    JSJitFrameIter frame(cx);
+    LazyLinkExitFrameLayout* ll = frame.exitFrame()->as<LazyLinkExitFrameLayout>();
     RootedScript calleeScript(cx, ScriptFromCalleeToken(ll->jsFrame()->calleeToken()));
 
     LinkIonScript(cx, calleeScript);
 
     MOZ_ASSERT(calleeScript->hasBaselineScript());
     MOZ_ASSERT(calleeScript->baselineOrIonRawPointer());
 
     return calleeScript->baselineOrIonRawPointer();
@@ -2966,74 +2966,75 @@ InvalidateActivation(FreeOp* fop, const 
 
 #ifdef CHECK_OSIPOINT_REGISTERS
     if (JitOptions.checkOsiPointRegisters)
         activations->asJit()->setCheckRegs(false);
 #endif
 
     size_t frameno = 1;
 
-    for (JitFrameIterator it(activations); !it.done(); ++it, ++frameno) {
-        MOZ_ASSERT_IF(frameno == 1, it.isExitFrame() || it.type() == JitFrame_Bailout);
+    for (OnlyJSJitFrameIter iter(activations); !iter.done(); ++iter, ++frameno) {
+        const JSJitFrameIter& frame = iter.frame();
+        MOZ_ASSERT_IF(frameno == 1, frame.isExitFrame() || frame.type() == JitFrame_Bailout);
 
 #ifdef JS_JITSPEW
-        switch (it.type()) {
+        switch (frame.type()) {
           case JitFrame_Exit:
-            JitSpew(JitSpew_IonInvalidate, "#%zu exit frame @ %p", frameno, it.fp());
+            JitSpew(JitSpew_IonInvalidate, "#%zu exit frame @ %p", frameno, frame.fp());
             break;
           case JitFrame_BaselineJS:
           case JitFrame_IonJS:
           case JitFrame_Bailout:
           {
-            MOZ_ASSERT(it.isScripted());
+            MOZ_ASSERT(frame.isScripted());
             const char* type = "Unknown";
-            if (it.isIonJS())
+            if (frame.isIonJS())
                 type = "Optimized";
-            else if (it.isBaselineJS())
+            else if (frame.isBaselineJS())
                 type = "Baseline";
-            else if (it.isBailoutJS())
+            else if (frame.isBailoutJS())
                 type = "Bailing";
             JitSpew(JitSpew_IonInvalidate,
                     "#%zu %s JS frame @ %p, %s:%zu (fun: %p, script: %p, pc %p)",
-                    frameno, type, it.fp(), it.script()->maybeForwardedFilename(),
-                    it.script()->lineno(), it.maybeCallee(), (JSScript*)it.script(),
-                    it.returnAddressToFp());
+                    frameno, type, frame.fp(), frame.script()->maybeForwardedFilename(),
+                    frame.script()->lineno(), frame.maybeCallee(), (JSScript*)frame.script(),
+                    frame.returnAddressToFp());
             break;
           }
           case JitFrame_BaselineStub:
-            JitSpew(JitSpew_IonInvalidate, "#%zu baseline stub frame @ %p", frameno, it.fp());
+            JitSpew(JitSpew_IonInvalidate, "#%zu baseline stub frame @ %p", frameno, frame.fp());
             break;
           case JitFrame_Rectifier:
-            JitSpew(JitSpew_IonInvalidate, "#%zu rectifier frame @ %p", frameno, it.fp());
+            JitSpew(JitSpew_IonInvalidate, "#%zu rectifier frame @ %p", frameno, frame.fp());
             break;
           case JitFrame_IonICCall:
-            JitSpew(JitSpew_IonInvalidate, "#%zu ion IC call frame @ %p", frameno, it.fp());
+            JitSpew(JitSpew_IonInvalidate, "#%zu ion IC call frame @ %p", frameno, frame.fp());
             break;
           case JitFrame_Entry:
-            JitSpew(JitSpew_IonInvalidate, "#%zu entry frame @ %p", frameno, it.fp());
+            JitSpew(JitSpew_IonInvalidate, "#%zu entry frame @ %p", frameno, frame.fp());
             break;
         }
 #endif // JS_JITSPEW
 
-        if (!it.isIonScripted())
+        if (!frame.isIonScripted())
             continue;
 
         bool calledFromLinkStub = false;
         JitCode* lazyLinkStub = fop->runtime()->jitRuntime()->lazyLinkStub();
-        if (it.returnAddressToFp() >= lazyLinkStub->raw() &&
-            it.returnAddressToFp() < lazyLinkStub->rawEnd())
+        if (frame.returnAddressToFp() >= lazyLinkStub->raw() &&
+            frame.returnAddressToFp() < lazyLinkStub->rawEnd())
         {
             calledFromLinkStub = true;
         }
 
         // See if the frame has already been invalidated.
-        if (!calledFromLinkStub && it.checkInvalidation())
+        if (!calledFromLinkStub && frame.checkInvalidation())
             continue;
 
-        JSScript* script = it.script();
+        JSScript* script = frame.script();
         if (!script->hasIonScript())
             continue;
 
         if (!invalidateAll && !script->ionScript()->invalidated())
             continue;
 
         IonScript* ionScript = script->ionScript();
 
@@ -3079,30 +3080,30 @@ InvalidateActivation(FreeOp* fop, const 
             // JitCode for the incremental GC, as it must know about
             // those edges.
             ionCode->traceChildren(zone->barrierTracer());
         }
         ionCode->setInvalidated();
 
         // Don't adjust OSI points in the linkStub (which don't exist), or in a
         // bailout path.
-        if (calledFromLinkStub || it.isBailoutJS())
+        if (calledFromLinkStub || frame.isBailoutJS())
             continue;
 
         // Write the delta (from the return address offset to the
         // IonScript pointer embedded into the invalidation epilogue)
         // where the safepointed call instruction used to be. We rely on
         // the call sequence causing the safepoint being >= the size of
         // a uint32, which is checked during safepoint index
         // construction.
         AutoWritableJitCode awjc(ionCode);
-        const SafepointIndex* si = ionScript->getSafepointIndex(it.returnAddressToFp());
-        CodeLocationLabel dataLabelToMunge(it.returnAddressToFp());
+        const SafepointIndex* si = ionScript->getSafepointIndex(frame.returnAddressToFp());
+        CodeLocationLabel dataLabelToMunge(frame.returnAddressToFp());
         ptrdiff_t delta = ionScript->invalidateEpilogueDataOffset() -
-                          (it.returnAddressToFp() - ionCode->raw());
+                          (frame.returnAddressToFp() - ionCode->raw());
         Assembler::PatchWrite_Imm32(dataLabelToMunge, Imm32(delta));
 
         CodeLocationLabel osiPatchPoint = SafepointReader::InvalidationPatchPoint(ionScript, si);
         CodeLocationLabel invalidateEpilogue(ionCode, CodeOffset(ionScript->invalidateEpilogueOffset()));
 
         JitSpew(JitSpew_IonInvalidate, "   ! Invalidate ionScript %p (inv count %zu) -> patching osipoint %p",
                 ionScript, ionScript->invalidationCount(), (void*) osiPatchPoint.raw());
         Assembler::PatchWrite_NearCall(osiPatchPoint, invalidateEpilogue);
--- a/js/src/jit/IonCaches.cpp
+++ b/js/src/jit/IonCaches.cpp
@@ -84,24 +84,24 @@ CodeOffsetJump::fixup(MacroAssembler* ma
 #ifdef JS_SMALL_BRANCH
      jumpTableIndex_ = masm->actualIndex(jumpTableIndex_);
 #endif
 }
 
 void*
 jit::GetReturnAddressToIonCode(JSContext* cx)
 {
-    JitFrameIterator iter(cx);
-    MOZ_ASSERT(iter.type() == JitFrame_Exit,
+    JSJitFrameIter frame(cx);
+    MOZ_ASSERT(frame.type() == JitFrame_Exit,
                "An exit frame is expected as update functions are called with a VMFunction.");
 
-    void* returnAddr = iter.returnAddress();
+    void* returnAddr = frame.returnAddress();
 #ifdef DEBUG
-    ++iter;
-    MOZ_ASSERT(iter.isIonJS());
+    ++frame;
+    MOZ_ASSERT(frame.isIonJS());
 #endif
     return returnAddr;
 }
 
 void
 jit::EmitIonStoreDenseElement(MacroAssembler& masm, const ConstantOrRegister& value,
                               Register elements, BaseObjectElementIndex target)
 {
rename from js/src/jit/JitFrameIterator-inl.h
rename to js/src/jit/JSJitFrameIter-inl.h
--- a/js/src/jit/JitFrameIterator-inl.h
+++ b/js/src/jit/JSJitFrameIter-inl.h
@@ -1,49 +1,49 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#ifndef jit_JitFrameIterator_inl_h
-#define jit_JitFrameIterator_inl_h
+#ifndef jit_JSJitFrameIter_inl_h
+#define jit_JSJitFrameIter_inl_h
 
-#include "jit/JitFrameIterator.h"
+#include "jit/JSJitFrameIter.h"
 
 #include "jit/Bailouts.h"
 #include "jit/BaselineFrame.h"
 #include "jit/JitFrames.h"
 
 namespace js {
 namespace jit {
 
 inline uint8_t*
-JitFrameIterator::returnAddress() const
+JSJitFrameIter::returnAddress() const
 {
     CommonFrameLayout* current = (CommonFrameLayout*) current_;
     return current->returnAddress();
 }
 
 inline FrameType
-JitFrameIterator::prevType() const
+JSJitFrameIter::prevType() const
 {
     CommonFrameLayout* current = (CommonFrameLayout*) current_;
     return current->prevType();
 }
 
 inline ExitFrameLayout*
-JitFrameIterator::exitFrame() const
+JSJitFrameIter::exitFrame() const
 {
     MOZ_ASSERT(isExitFrame());
     return (ExitFrameLayout*) fp();
 }
 
 inline size_t
-JitFrameIterator::prevFrameLocalSize() const
+JSJitFrameIter::prevFrameLocalSize() const
 {
     CommonFrameLayout* current = (CommonFrameLayout*) current_;
     return current->prevFrameLocalSize();
 }
 
 inline JitFrameLayout*
 JitProfilingFrameIterator::framePtr()
 {
@@ -53,27 +53,27 @@ JitProfilingFrameIterator::framePtr()
 
 inline JSScript*
 JitProfilingFrameIterator::frameScript()
 {
     return ScriptFromCalleeToken(framePtr()->calleeToken());
 }
 
 inline BaselineFrame*
-JitFrameIterator::baselineFrame() const
+JSJitFrameIter::baselineFrame() const
 {
     MOZ_ASSERT(isBaselineJS());
     return (BaselineFrame*)(fp() - BaselineFrame::FramePointerOffset - BaselineFrame::Size());
 }
 
 template <typename T>
 bool
-JitFrameIterator::isExitFrameLayout() const
+JSJitFrameIter::isExitFrameLayout() const
 {
     if (!isExitFrame())
         return false;
     return exitFrame()->is<T>();
 }
 
 } // namespace jit
 } // namespace js
 
-#endif /* jit_JitFrameIterator_inl_h */
+#endif /* jit_JSJitFrameIter_inl_h */
rename from js/src/jit/JitFrameIterator.cpp
rename to js/src/jit/JSJitFrameIter.cpp
--- a/js/src/jit/JitFrameIterator.cpp
+++ b/js/src/jit/JSJitFrameIter.cpp
@@ -1,68 +1,52 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "jit/JitFrameIterator-inl.h"
-
+#include "jit/JSJitFrameIter-inl.h"
 
 #include "jit/BaselineIC.h"
 #include "jit/JitcodeMap.h"
 #include "jit/JitFrames.h"
 
 using namespace js;
 using namespace js::jit;
 
-JitFrameIterator::JitFrameIterator()
-  : current_(nullptr),
-    type_(JitFrame_Exit),
-    returnAddressToFp_(nullptr),
-    frameSize_(0),
-    cachedSafepointIndex_(nullptr),
-    activation_(nullptr)
-{
-}
-
-JitFrameIterator::JitFrameIterator(const JitActivation* activation)
+JSJitFrameIter::JSJitFrameIter(const JitActivation* activation)
   : current_(activation->exitFP()),
     type_(JitFrame_Exit),
     returnAddressToFp_(nullptr),
     frameSize_(0),
     cachedSafepointIndex_(nullptr),
     activation_(activation)
 {
     if (activation_->bailoutData()) {
         current_ = activation_->bailoutData()->fp();
         frameSize_ = activation_->bailoutData()->topFrameSize();
         type_ = JitFrame_Bailout;
     }
 }
 
-JitFrameIterator::JitFrameIterator(JSContext* cx)
-  : JitFrameIterator(cx->activation()->asJit())
-{
-}
-
-JitFrameIterator::JitFrameIterator(const ActivationIterator& activations)
-  : JitFrameIterator(activations->asJit())
+JSJitFrameIter::JSJitFrameIter(JSContext* cx)
+  : JSJitFrameIter(cx->activation()->asJit())
 {
 }
 
 bool
-JitFrameIterator::checkInvalidation() const
+JSJitFrameIter::checkInvalidation() const
 {
     IonScript* dummy;
     return checkInvalidation(&dummy);
 }
 
 bool
-JitFrameIterator::checkInvalidation(IonScript** ionScriptOut) const
+JSJitFrameIter::checkInvalidation(IonScript** ionScriptOut) const
 {
     JSScript* script = this->script();
     if (isBailoutJS()) {
         *ionScriptOut = activation_->bailoutData()->ionScript();
         return !script->hasIonScript() || script->ionScript() != *ionScriptOut;
     }
 
     uint8_t* returnAddr = returnAddressToFp();
@@ -77,64 +61,64 @@ JitFrameIterator::checkInvalidation(IonS
     uint8_t* ionScriptDataOffset = returnAddr + invalidationDataOffset;
     IonScript* ionScript = (IonScript*) Assembler::GetPointer(ionScriptDataOffset);
     MOZ_ASSERT(ionScript->containsReturnAddress(returnAddr));
     *ionScriptOut = ionScript;
     return true;
 }
 
 CalleeToken
-JitFrameIterator::calleeToken() const
+JSJitFrameIter::calleeToken() const
 {
     return ((JitFrameLayout*) current_)->calleeToken();
 }
 
 JSFunction*
-JitFrameIterator::callee() const
+JSJitFrameIter::callee() const
 {
     MOZ_ASSERT(isScripted());
     MOZ_ASSERT(isFunctionFrame());
     return CalleeTokenToFunction(calleeToken());
 }
 
 JSFunction*
-JitFrameIterator::maybeCallee() const
+JSJitFrameIter::maybeCallee() const
 {
     if (isScripted() && (isFunctionFrame()))
         return callee();
     return nullptr;
 }
 
 bool
-JitFrameIterator::isBareExit() const
+JSJitFrameIter::isBareExit() const
 {
     if (type_ != JitFrame_Exit)
         return false;
     return exitFrame()->isBareExit();
 }
 
 bool
-JitFrameIterator::isFunctionFrame() const
+JSJitFrameIter::isFunctionFrame() const
 {
     return CalleeTokenIsFunction(calleeToken());
 }
 
 JSScript*
-JitFrameIterator::script() const
+JSJitFrameIter::script() const
 {
     MOZ_ASSERT(isScripted());
     if (isBaselineJS())
         return baselineFrame()->script();
     JSScript* script = ScriptFromCalleeToken(calleeToken());
     MOZ_ASSERT(script);
     return script;
 }
 
 void
-JitFrameIterator::baselineScriptAndPc(JSScript** scriptRes, jsbytecode** pcRes) const
+JSJitFrameIter::baselineScriptAndPc(JSScript** scriptRes, jsbytecode** pcRes) const
 {
     MOZ_ASSERT(isBaselineJS());
     JSScript* script = this->script();
     if (scriptRes)
         *scriptRes = script;
 
     MOZ_ASSERT(pcRes);
 
@@ -148,63 +132,61 @@ JitFrameIterator::baselineScriptAndPc(JS
 
     // Else, there must be an ICEntry for the current return address.
     uint8_t* retAddr = returnAddressToFp();
     ICEntry& icEntry = script->baselineScript()->icEntryFromReturnAddress(retAddr);
     *pcRes = icEntry.pc(script);
 }
 
 Value*
-JitFrameIterator::actualArgs() const
+JSJitFrameIter::actualArgs() const
 {
     return jsFrame()->argv() + 1;
 }
 
 uint8_t*
-JitFrameIterator::prevFp() const
+JSJitFrameIter::prevFp() const
 {
     return current_ + current()->prevFrameLocalSize() + current()->headerSize();
 }
 
-JitFrameIterator&
-JitFrameIterator::operator++()
+void
+JSJitFrameIter::operator++()
 {
     MOZ_ASSERT(type_ != JitFrame_Entry);
 
     frameSize_ = prevFrameLocalSize();
     cachedSafepointIndex_ = nullptr;
 
     // If the next frame is the entry frame, just exit. Don't update current_,
     // since the entry and first frames overlap.
     if (current()->prevType() == JitFrame_Entry) {
         type_ = JitFrame_Entry;
-        return *this;
+        return;
     }
 
     type_ = current()->prevType();
     returnAddressToFp_ = current()->returnAddress();
     current_ = prevFp();
-
-    return *this;
 }
 
 uintptr_t*
-JitFrameIterator::spillBase() const
+JSJitFrameIter::spillBase() const
 {
     MOZ_ASSERT(isIonJS());
 
     // Get the base address to where safepoint registers are spilled.
     // Out-of-line calls do not unwind the extra padding space used to
     // aggregate bailout tables, so we use frameSize instead of frameLocals,
     // which would only account for local stack slots.
     return reinterpret_cast<uintptr_t*>(fp() - ionScript()->frameSize());
 }
 
 MachineState
-JitFrameIterator::machineState() const
+JSJitFrameIter::machineState() const
 {
     MOZ_ASSERT(isIonScripted());
 
     // The MachineState is used by GCs for tracing call-sites.
     if (MOZ_UNLIKELY(isBailoutJS()))
         return *activation_->bailoutData()->machineState();
 
     SafepointReader reader(ionScript(), safepoint());
@@ -229,90 +211,90 @@ JitFrameIterator::machineState() const
             machine.setRegisterLocation(ftmp, (double*)floatSpill);
         }
     }
 
     return machine;
 }
 
 JitFrameLayout*
-JitFrameIterator::jsFrame() const
+JSJitFrameIter::jsFrame() const
 {
     MOZ_ASSERT(isScripted());
     if (isBailoutJS())
         return (JitFrameLayout*) activation_->bailoutData()->fp();
 
     return (JitFrameLayout*) fp();
 }
 
 IonScript*
-JitFrameIterator::ionScript() const
+JSJitFrameIter::ionScript() const
 {
     MOZ_ASSERT(isIonScripted());
     if (isBailoutJS())
         return activation_->bailoutData()->ionScript();
 
     IonScript* ionScript = nullptr;
     if (checkInvalidation(&ionScript))
         return ionScript;
     return ionScriptFromCalleeToken();
 }
 
 IonScript*
-JitFrameIterator::ionScriptFromCalleeToken() const
+JSJitFrameIter::ionScriptFromCalleeToken() const
 {
     MOZ_ASSERT(isIonJS());
     MOZ_ASSERT(!checkInvalidation());
     return script()->ionScript();
 }
 
 const SafepointIndex*
-JitFrameIterator::safepoint() const
+JSJitFrameIter::safepoint() const
 {
     MOZ_ASSERT(isIonJS());
     if (!cachedSafepointIndex_)
         cachedSafepointIndex_ = ionScript()->getSafepointIndex(returnAddressToFp());
     return cachedSafepointIndex_;
 }
 
 SnapshotOffset
-JitFrameIterator::snapshotOffset() const
+JSJitFrameIter::snapshotOffset() const
 {
     MOZ_ASSERT(isIonScripted());
     if (isBailoutJS())
         return activation_->bailoutData()->snapshotOffset();
     return osiIndex()->snapshotOffset();
 }
 
 const OsiIndex*
-JitFrameIterator::osiIndex() const
+JSJitFrameIter::osiIndex() const
 {
     MOZ_ASSERT(isIonJS());
     SafepointReader reader(ionScript(), safepoint());
     return ionScript()->getOsiIndex(reader.osiReturnPointOffset());
 }
 
 bool
-JitFrameIterator::isConstructing() const
+JSJitFrameIter::isConstructing() const
 {
     return CalleeTokenIsConstructing(calleeToken());
 }
 
 unsigned
-JitFrameIterator::numActualArgs() const
+JSJitFrameIter::numActualArgs() const
 {
     if (isScripted())
         return jsFrame()->numActualArgs();
 
     MOZ_ASSERT(isExitFrameLayout<NativeExitFrameLayout>());
     return exitFrame()->as<NativeExitFrameLayout>()->argc();
 }
 
 void
-JitFrameIterator::dumpBaseline() const
+JSJitFrameIter::dumpBaseline() const
 {
     MOZ_ASSERT(isBaselineJS());
 
     fprintf(stderr, " JS Baseline frame\n");
     if (isFunctionFrame()) {
         fprintf(stderr, "  callee fun: ");
 #ifdef DEBUG
         DumpObject(callee());
@@ -345,17 +327,17 @@ JitFrameIterator::dumpBaseline() const
         DumpValue(*v);
 #else
         fprintf(stderr, "?\n");
 #endif
     }
 }
 
 void
-JitFrameIterator::dump() const
+JSJitFrameIter::dump() const
 {
     switch (type_) {
       case JitFrame_Entry:
         fprintf(stderr, " Entry frame\n");
         fprintf(stderr, "  Frame size: %u\n", unsigned(current()->prevFrameLocalSize()));
         break;
       case JitFrame_BaselineJS:
         dumpBaseline();
@@ -388,17 +370,17 @@ JitFrameIterator::dump() const
         fprintf(stderr, " Exit frame\n");
         break;
     };
     fputc('\n', stderr);
 }
 
 #ifdef DEBUG
 bool
-JitFrameIterator::verifyReturnAddressUsingNativeToBytecodeMap()
+JSJitFrameIter::verifyReturnAddressUsingNativeToBytecodeMap()
 {
     MOZ_ASSERT(returnAddressToFp_ != nullptr);
 
     // Only handle Ion frames for now.
     if (type_ != JitFrame_IonJS && type_ != JitFrame_BaselineJS)
         return true;
 
     JSRuntime* rt = TlsContext.get()->runtime();
rename from js/src/jit/JitFrameIterator.h
rename to js/src/jit/JSJitFrameIter.h
--- a/js/src/jit/JitFrameIterator.h
+++ b/js/src/jit/JSJitFrameIter.h
@@ -1,31 +1,27 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#ifndef jit_JitFrameIterator_h
-#define jit_JitFrameIterator_h
+#ifndef jit_JSJitFrameIter_h
+#define jit_JSJitFrameIter_h
 
 #include "jsfun.h"
 #include "jsscript.h"
 #include "jstypes.h"
 
 #include "jit/IonCode.h"
 #include "jit/Snapshots.h"
 
 #include "js/ProfilingFrameIterator.h"
 
 namespace js {
-    class ActivationIterator;
-} // namespace js
-
-namespace js {
 namespace jit {
 
 typedef void * CalleeToken;
 
 enum FrameType
 {
     // A JS frame is analogous to a js::InterpreterFrame, representing one scripted
     // function activation. IonJS frames are used by the optimizing compiler.
@@ -78,38 +74,51 @@ class ExitFrameLayout;
 
 class BaselineFrame;
 
 class JitActivation;
 
 // Iterate over the JIT stack to assert that all invariants are respected.
 //  - Check that all entry frames are aligned on JitStackAlignment.
 //  - Check that all rectifier frames keep the JitStackAlignment.
+
 void AssertJitStackInvariants(JSContext* cx);
 
-class JitFrameIterator
+// A JSJitFrameIter can iterate over a linear frame group of JS jit frames
+// only. It will stop at the first frame that is not of the same kind, or at
+// the end of an activation.
+//
+// If you want to handle every kind of frames (including wasm frames), use
+// JitFrameIter. If you want to skip interleaved frames of other kinds, use
+// OnlyJSJitFrameIter.
+
+class JSJitFrameIter
 {
   protected:
     uint8_t* current_;
     FrameType type_;
     uint8_t* returnAddressToFp_;
     size_t frameSize_;
 
   private:
     mutable const SafepointIndex* cachedSafepointIndex_;
     const JitActivation* activation_;
 
     void dumpBaseline() const;
 
-    explicit JitFrameIterator(const JitActivation* activation);
+  public:
+    // See comment above the class.
+    explicit JSJitFrameIter(const JitActivation* activation);
+    explicit JSJitFrameIter(JSContext* cx);
 
-  public:
-    explicit JitFrameIterator();
-    explicit JitFrameIterator(JSContext* cx);
-    explicit JitFrameIterator(const ActivationIterator& activations);
+    // Used only by DebugModeOSRVolatileJitFrameIter.
+    void exchangeReturnAddressIfMatch(uint8_t* oldAddr, uint8_t* newAddr) {
+        if (returnAddressToFp_ == oldAddr)
+            returnAddressToFp_ = newAddr;
+    }
 
     // Current frame information.
     FrameType type() const {
         return type_;
     }
     uint8_t* fp() const {
         return current_;
     }
@@ -194,20 +203,20 @@ class JitFrameIterator
     // not include the size of its fixed header.
     size_t frameSize() const {
         MOZ_ASSERT(!isExitFrame());
         return frameSize_;
     }
 
     // Functions used to iterate on frames. When prevType is JitFrame_Entry,
     // the current frame is the last frame.
-    inline bool done() const {
+    bool done() const {
         return type_ == JitFrame_Entry;
     }
-    JitFrameIterator& operator++();
+    void operator++();
 
     // Returns the IonScript associated with this JS frame.
     IonScript* ionScript() const;
 
     // Returns the IonScript associated with this JS frame; the frame must
     // not be invalidated.
     IonScript* ionScriptFromCalleeToken() const;
 
@@ -255,17 +264,17 @@ class JitFrameIterator
 
     inline BaselineFrame* baselineFrame() const;
 
     // This function isn't used, but we keep it here (debug-only) because it is
     // helpful when chasing issues with the jitcode map.
 #ifdef DEBUG
     bool verifyReturnAddressUsingNativeToBytecodeMap();
 #else
-    inline bool verifyReturnAddressUsingNativeToBytecodeMap() { return true; }
+    bool verifyReturnAddressUsingNativeToBytecodeMap() { return true; }
 #endif
 };
 
 class JitcodeGlobalTable;
 
 class JitProfilingFrameIterator
 {
     uint8_t* fp_;
@@ -340,30 +349,30 @@ struct MaybeReadFallback
 
     enum FallbackConsequence {
         Fallback_Invalidate,
         Fallback_DoNothing
     };
 
     JSContext* maybeCx;
     JitActivation* activation;
-    const JitFrameIterator* frame;
+    const JSJitFrameIter* frame;
     const NoGCValue unreadablePlaceholder_;
     const FallbackConsequence consequence;
 
     explicit MaybeReadFallback(const Value& placeholder = UndefinedValue())
       : maybeCx(nullptr),
         activation(nullptr),
         frame(nullptr),
         unreadablePlaceholder_(noGCPlaceholder(placeholder)),
         consequence(Fallback_Invalidate)
     {
     }
 
-    MaybeReadFallback(JSContext* cx, JitActivation* activation, const JitFrameIterator* frame,
+    MaybeReadFallback(JSContext* cx, JitActivation* activation, const JSJitFrameIter* frame,
                       FallbackConsequence consequence = Fallback_Invalidate)
       : maybeCx(cx),
         activation(activation),
         frame(frame),
         unreadablePlaceholder_(NoGC_UndefinedValue),
         consequence(consequence)
     {
     }
@@ -530,17 +539,17 @@ class SnapshotIterator
         // there is more instruction there is necesseray more frames.
         return moreInstructions();
     }
 
   public:
     // Connect all informations about the current script in order to recover the
     // content of baseline frames.
 
-    SnapshotIterator(const JitFrameIterator& iter, const MachineState* machineState);
+    SnapshotIterator(const JSJitFrameIter& iter, const MachineState* machineState);
     SnapshotIterator();
 
     Value read() {
         return allocationValue(readAllocation());
     }
 
     // Read the |Normal| value unless it is not available and that the snapshot
     // provides a |Default| value. This is useful to avoid invalidations of the
@@ -610,17 +619,17 @@ class SnapshotIterator
     }
 #endif
 };
 
 // Reads frame information in callstack order (that is, innermost frame to
 // outermost frame).
 class InlineFrameIterator
 {
-    const JitFrameIterator* frame_;
+    const JSJitFrameIter* frame_;
     SnapshotIterator start_;
     SnapshotIterator si_;
     uint32_t framesRead_;
 
     // When the inline-frame-iterator is created, this variable is defined to
     // UINT32_MAX. Then the first iteration of findNextFrame, which settle on
     // the innermost frame, is used to update this counter to the number of
     // frames contained in the recover buffer.
@@ -648,17 +657,17 @@ class InlineFrameIterator
     };
 
   private:
     void findNextFrame();
     JSObject* computeEnvironmentChain(const Value& envChainValue, MaybeReadFallback& fallback,
                                       bool* hasInitialEnv = nullptr) const;
 
   public:
-    InlineFrameIterator(JSContext* cx, const JitFrameIterator* iter);
+    InlineFrameIterator(JSContext* cx, const JSJitFrameIter* iter);
     InlineFrameIterator(JSContext* cx, const InlineFrameIterator* iter);
 
     bool more() const {
         return frame_ && framesRead_ < frameCount_;
     }
 
     // Due to optimizations, we are not always capable of reading the callee of
     // inlined frames without invalidating the IonCode. This function might
@@ -831,19 +840,19 @@ class InlineFrameIterator
 
     InlineFrameIterator& operator++() {
         findNextFrame();
         return *this;
     }
 
     void dump() const;
 
-    void resetOn(const JitFrameIterator* iter);
+    void resetOn(const JSJitFrameIter* iter);
 
-    const JitFrameIterator& frame() const {
+    const JSJitFrameIter& frame() const {
         return *frame_;
     }
 
     // Inline frame number, 0 for the outermost (non-inlined) frame.
     size_t frameNo() const {
         return frameCount() - framesRead_;
     }
     size_t frameCount() const {
@@ -854,9 +863,9 @@ class InlineFrameIterator
   private:
     InlineFrameIterator() = delete;
     InlineFrameIterator(const InlineFrameIterator& iter) = delete;
 };
 
 } // namespace jit
 } // namespace js
 
-#endif /* jit_JitFrameIterator_h */
+#endif /* jit_JSJitFrameIter_h */
--- a/js/src/jit/JitFrames-inl.h
+++ b/js/src/jit/JitFrames-inl.h
@@ -6,17 +6,17 @@
 
 #ifndef jit_JitFrames_inl_h
 #define jit_JitFrames_inl_h
 
 #include "jit/JitFrames.h"
 
 #include "jit/LIR.h"
 
-#include "jit/JitFrameIterator-inl.h"
+#include "jit/JSJitFrameIter-inl.h"
 
 namespace js {
 namespace jit {
 
 inline void
 SafepointIndex::resolve()
 {
     MOZ_ASSERT(!resolved);
@@ -24,21 +24,21 @@ SafepointIndex::resolve()
 #ifdef DEBUG
     resolved = true;
 #endif
 }
 
 inline BaselineFrame*
 GetTopBaselineFrame(JSContext* cx)
 {
-    JitFrameIterator iter(cx);
-    MOZ_ASSERT(iter.type() == JitFrame_Exit);
-    ++iter;
-    if (iter.isBaselineStub())
-        ++iter;
-    MOZ_ASSERT(iter.isBaselineJS());
-    return iter.baselineFrame();
+    JSJitFrameIter frame(cx);
+    MOZ_ASSERT(frame.type() == JitFrame_Exit);
+    ++frame;
+    if (frame.isBaselineStub())
+        ++frame;
+    MOZ_ASSERT(frame.isBaselineJS());
+    return frame.baselineFrame();
 }
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_JitFrames_inl_h */
--- a/js/src/jit/JitFrames.cpp
+++ b/js/src/jit/JitFrames.cpp
@@ -31,17 +31,17 @@
 #include "vm/Debugger.h"
 #include "vm/GeckoProfiler.h"
 #include "vm/Interpreter.h"
 #include "vm/TraceLogging.h"
 #include "vm/TypeInference.h"
 
 #include "jsscriptinlines.h"
 #include "gc/Nursery-inl.h"
-#include "jit/JitFrameIterator-inl.h"
+#include "jit/JSJitFrameIter-inl.h"
 #include "vm/Debugger-inl.h"
 #include "vm/Probes-inl.h"
 #include "vm/TypeInference-inl.h"
 
 namespace js {
 namespace jit {
 
 // Given a slot index, returns the offset, in bytes, of that slot from an
@@ -268,46 +268,46 @@ HandleExceptionIon(JSContext* cx, const 
 
           default:
             MOZ_CRASH("Unexpected try note");
         }
     }
 }
 
 static void
-OnLeaveBaselineFrame(JSContext* cx, const JitFrameIterator& frame, jsbytecode* pc,
+OnLeaveBaselineFrame(JSContext* cx, const JSJitFrameIter& frame, jsbytecode* pc,
                      ResumeFromException* rfe, bool frameOk)
 {
     BaselineFrame* baselineFrame = frame.baselineFrame();
     if (jit::DebugEpilogue(cx, baselineFrame, pc, frameOk)) {
         rfe->kind = ResumeFromException::RESUME_FORCED_RETURN;
         rfe->framePointer = frame.fp() - BaselineFrame::FramePointerOffset;
         rfe->stackPointer = reinterpret_cast<uint8_t*>(baselineFrame);
     }
 }
 
 static inline void
-ForcedReturn(JSContext* cx, const JitFrameIterator& frame, jsbytecode* pc,
+ForcedReturn(JSContext* cx, const JSJitFrameIter& frame, jsbytecode* pc,
              ResumeFromException* rfe)
 {
     OnLeaveBaselineFrame(cx, frame, pc, rfe, true);
 }
 
 static inline void
-BaselineFrameAndStackPointersFromTryNote(JSTryNote* tn, const JitFrameIterator& frame,
+BaselineFrameAndStackPointersFromTryNote(JSTryNote* tn, const JSJitFrameIter& frame,
                                          uint8_t** framePointer, uint8_t** stackPointer)
 {
     JSScript* script = frame.baselineFrame()->script();
     *framePointer = frame.fp() - BaselineFrame::FramePointerOffset;
     *stackPointer = *framePointer - BaselineFrame::Size() -
                     (script->nfixed() + tn->stackDepth) * sizeof(Value);
 }
 
 static void
-SettleOnTryNote(JSContext* cx, JSTryNote* tn, const JitFrameIterator& frame,
+SettleOnTryNote(JSContext* cx, JSTryNote* tn, const JSJitFrameIter& frame,
                 EnvironmentIter& ei, ResumeFromException* rfe, jsbytecode** pc)
 {
     RootedScript script(cx, frame.baselineFrame()->script());
 
     // Unwind environment chain (pop block objects).
     if (cx->isExceptionPending())
         UnwindEnvironment(cx, ei, UnwindEnvironmentToTryPc(script, tn));
 
@@ -352,17 +352,17 @@ class TryNoteIterBaseline : public TryNo
     TryNoteIterBaseline(JSContext* cx, BaselineFrame* frame, jsbytecode* pc)
       : TryNoteIter(cx, frame->script(), pc, BaselineFrameStackDepthOp(frame))
     { }
 };
 
 // Close all live iterators on a BaselineFrame due to exception unwinding. The
 // pc parameter is updated to where the envs have been unwound to.
 static void
-CloseLiveIteratorsBaselineForUncatchableException(JSContext* cx, const JitFrameIterator& frame,
+CloseLiveIteratorsBaselineForUncatchableException(JSContext* cx, const JSJitFrameIter& frame,
                                                   jsbytecode* pc)
 {
     for (TryNoteIterBaseline tni(cx, frame.baselineFrame(), pc); !tni.done(); ++tni) {
         JSTryNote* tn = *tni;
 
         if (tn->kind == JSTRY_FOR_IN) {
             uint8_t* framePointer;
             uint8_t* stackPointer;
@@ -370,17 +370,17 @@ CloseLiveIteratorsBaselineForUncatchable
             Value iterValue(*(Value*) stackPointer);
             RootedObject iterObject(cx, &iterValue.toObject());
             UnwindIteratorForUncatchableException(cx, iterObject);
         }
     }
 }
 
 static bool
-ProcessTryNotesBaseline(JSContext* cx, const JitFrameIterator& frame, EnvironmentIter& ei,
+ProcessTryNotesBaseline(JSContext* cx, const JSJitFrameIter& frame, EnvironmentIter& ei,
                         ResumeFromException* rfe, jsbytecode** pc)
 {
     RootedScript script(cx, frame.baselineFrame()->script());
     bool inForOfIterClose = false;
 
     for (TryNoteIterBaseline tni(cx, frame.baselineFrame(), *pc); !tni.done(); ++tni) {
         JSTryNote* tn = *tni;
 
@@ -471,17 +471,17 @@ ProcessTryNotesBaseline(JSContext* cx, c
           default:
             MOZ_CRASH("Invalid try note");
         }
     }
     return true;
 }
 
 static void
-HandleExceptionBaseline(JSContext* cx, const JitFrameIterator& frame, ResumeFromException* rfe,
+HandleExceptionBaseline(JSContext* cx, const JSJitFrameIter& frame, ResumeFromException* rfe,
                         jsbytecode* pc)
 {
     MOZ_ASSERT(frame.isBaselineJS());
 
     bool frameOk = false;
     RootedScript script(cx, frame.baselineFrame()->script());
 
     if (script->hasScriptCounts()) {
@@ -617,30 +617,34 @@ HandleException(ResumeFromException* rfe
 #ifdef CHECK_OSIPOINT_REGISTERS
     if (JitOptions.checkOsiPointRegisters)
         activation->setCheckRegs(false);
 #endif
 
     // The Debugger onExceptionUnwind hook (reachable via
     // HandleExceptionBaseline below) may cause on-stack recompilation of
     // baseline scripts, which may patch return addresses on the stack. Since
-    // JitFrameIterators cache the previous frame's return address when
+    // JSJitFrameIter cache the previous frame's return address when
     // iterating, we need a variant here that is automatically updated should
     // on-stack recompilation occur.
-    DebugModeOSRVolatileJitFrameIterator iter(cx);
-    while (!iter.isEntry()) {
+    DebugModeOSRVolatileJitFrameIter iter(cx);
+    while (!iter.isJSJit() || !iter.asJSJit().isEntry()) {
+        while (!iter.isJSJit())
+            ++iter;
+        const JSJitFrameIter& frame = iter.asJSJit();
+
         bool overrecursed = false;
-        if (iter.isIonJS()) {
+        if (frame.isIonJS()) {
             // Search each inlined frame for live iterator objects, and close
             // them.
-            InlineFrameIterator frames(cx, &iter);
+            InlineFrameIterator frames(cx, &frame);
 
             // Invalidation state will be the same for all inlined scripts in the frame.
             IonScript* ionScript = nullptr;
-            bool invalidated = iter.checkInvalidation(&ionScript);
+            bool invalidated = frame.checkInvalidation(&ionScript);
 
 #ifdef JS_TRACE_LOGGING
             if (logger && cx->compartment()->isDebuggee() && logger->enabled()) {
                 logger->disable(/* force = */ true,
                                 "Forcefully disabled tracelogger, due to "
                                 "throwing an exception with an active Debugger "
                                 "in IonMonkey.");
             }
@@ -667,64 +671,64 @@ HandleException(ResumeFromException* rfe
                 if (!frames.more()) {
                     TraceLogStopEvent(logger, TraceLogger_IonMonkey);
                     TraceLogStopEvent(logger, TraceLogger_Scripts);
                     break;
                 }
                 ++frames;
             }
 
-            activation->removeIonFrameRecovery(iter.jsFrame());
+            activation->removeIonFrameRecovery(frame.jsFrame());
             if (invalidated)
                 ionScript->decrementInvalidationCount(cx->runtime()->defaultFreeOp());
 
-        } else if (iter.isBaselineJS()) {
+        } else if (frame.isBaselineJS()) {
             // Set a flag on the frame to signal to DebugModeOSR that we're
             // handling an exception. Also ensure the frame has an override
             // pc. We clear the frame's override pc when we leave this block,
             // this is fine because we're either:
             //
             // (1) Going to enter a catch or finally block. We don't want to
             //     keep the old pc when we're executing JIT code.
             // (2) Going to pop the frame, either here or a forced return.
             //     In this case nothing will observe the frame's pc.
             // (3) Performing an exception bailout. In this case
             //     FinishBailoutToBaseline will set the pc to the resume pc
             //     and clear it before it returns to JIT code.
             jsbytecode* pc;
-            iter.baselineScriptAndPc(nullptr, &pc);
-            AutoBaselineHandlingException handlingException(iter.baselineFrame(), pc);
-
-            HandleExceptionBaseline(cx, iter, rfe, pc);
+            frame.baselineScriptAndPc(nullptr, &pc);
+            AutoBaselineHandlingException handlingException(frame.baselineFrame(), pc);
+
+            HandleExceptionBaseline(cx, frame, rfe, pc);
 
             // If we are propagating an exception through a frame with
             // on-stack recompile info, we should free the allocated
             // RecompileInfo struct before we leave this block, as we will not
             // be returning to the recompile handler.
-            AutoDeleteDebugModeOSRInfo deleteDebugModeOSRInfo(iter.baselineFrame());
+            AutoDeleteDebugModeOSRInfo deleteDebugModeOSRInfo(frame.baselineFrame());
 
             if (rfe->kind != ResumeFromException::RESUME_ENTRY_FRAME &&
                 rfe->kind != ResumeFromException::RESUME_FORCED_RETURN)
             {
                 return;
             }
 
             TraceLogStopEvent(logger, TraceLogger_Baseline);
             TraceLogStopEvent(logger, TraceLogger_Scripts);
 
             // Unwind profiler pseudo-stack
-            JSScript* script = iter.script();
+            JSScript* script = frame.script();
             probes::ExitScript(cx, script, script->functionNonDelazifying(),
                                /* popProfilerFrame = */ false);
 
             if (rfe->kind == ResumeFromException::RESUME_FORCED_RETURN)
                 return;
         }
 
-        JitFrameLayout* current = iter.isScripted() ? iter.jsFrame() : nullptr;
+        JitFrameLayout* current = frame.isScripted() ? frame.jsFrame() : nullptr;
 
         ++iter;
 
         if (current) {
             // Unwind the frame by updating exitFP. This is necessary so that
             // (1) debugger exception unwind and leave frame hooks don't see this
             // frame when they use ScriptFrameIter, and (2) ScriptFrameIter does
             // not crash when accessing an IonScript that's destroyed by the
@@ -733,17 +737,17 @@ HandleException(ResumeFromException* rfe
         }
 
         if (overrecursed) {
             // We hit an overrecursion error during bailout. Report it now.
             ReportOverRecursed(cx);
         }
     }
 
-    rfe->stackPointer = iter.fp();
+    rfe->stackPointer = iter.asJSJit().fp();
 }
 
 // Turns a JitFrameLayout into an ExitFrameLayout. Note that it has to be a
 // bare exit frame so it's ignored by TraceJitExitFrame.
 void
 EnsureBareExitFrame(JSContext* cx, JitFrameLayout* frame)
 {
     ExitFrameLayout* exitFrame = reinterpret_cast<ExitFrameLayout*>(frame);
@@ -751,17 +755,17 @@ EnsureBareExitFrame(JSContext* cx, JitFr
     if (cx->activation()->asJit()->exitFP() == (uint8_t*)frame) {
         // If we already called this function for the current frame, do
         // nothing.
         MOZ_ASSERT(exitFrame->isBareExit());
         return;
     }
 
 #ifdef DEBUG
-    JitFrameIterator iter(cx);
+    JSJitFrameIter iter(cx);
     while (!iter.isScripted())
         ++iter;
     MOZ_ASSERT(iter.current() == frame, "|frame| must be the top JS frame");
 
     MOZ_ASSERT(!!cx->activation()->asJit()->exitFP());
     MOZ_ASSERT((uint8_t*)exitFrame->footer() >= cx->activation()->asJit()->exitFP(),
                "Must have space for ExitFooterFrame before exitFP");
 #endif
@@ -798,28 +802,28 @@ JitFrameLayout::slotRef(SafepointSlotEnt
 {
     if (where.stack)
         return (uintptr_t*)((uint8_t*)this - where.slot);
     return (uintptr_t*)((uint8_t*)argv() + where.slot);
 }
 
 #ifdef JS_NUNBOX32
 static inline uintptr_t
-ReadAllocation(const JitFrameIterator& frame, const LAllocation* a)
+ReadAllocation(const JSJitFrameIter& frame, const LAllocation* a)
 {
     if (a->isGeneralReg()) {
         Register reg = a->toGeneralReg()->reg();
         return frame.machineState().read(reg);
     }
     return *frame.jsFrame()->slotRef(SafepointSlotEntry(a));
 }
 #endif
 
 static void
-TraceThisAndArguments(JSTracer* trc, const JitFrameIterator& frame)
+TraceThisAndArguments(JSTracer* trc, const JSJitFrameIter& frame)
 {
     // Trace |this| and any extra actual arguments for an Ion frame. Tracinging
     // of formal arguments is taken care of by the frame's safepoint/snapshot,
     // except when the script might have lazy arguments or rest, in which case
     // we trace them as well. We also have to trace formals if we have a
     // LazyLink frame.
 
     JitFrameLayout* layout = frame.isExitFrameLayout<LazyLinkExitFrameLayout>()
@@ -853,29 +857,29 @@ TraceThisAndArguments(JSTracer* trc, con
     // Always trace the new.target from the frame. It's not in the snapshots.
     // +1 to pass |this|
     if (CalleeTokenIsConstructing(layout->calleeToken()))
         TraceRoot(trc, &argv[1 + newTargetOffset], "ion-newTarget");
 }
 
 #ifdef JS_NUNBOX32
 static inline void
-WriteAllocation(const JitFrameIterator& frame, const LAllocation* a, uintptr_t value)
+WriteAllocation(const JSJitFrameIter& frame, const LAllocation* a, uintptr_t value)
 {
     if (a->isGeneralReg()) {
         Register reg = a->toGeneralReg()->reg();
         frame.machineState().write(reg, value);
     } else {
         *frame.jsFrame()->slotRef(SafepointSlotEntry(a)) = value;
     }
 }
 #endif
 
 static void
-TraceIonJSFrame(JSTracer* trc, const JitFrameIterator& frame)
+TraceIonJSFrame(JSTracer* trc, const JSJitFrameIter& frame)
 {
     JitFrameLayout* layout = (JitFrameLayout*)frame.fp();
 
     layout->replaceCalleeToken(TraceCalleeToken(trc, layout->calleeToken()));
 
     IonScript* ionScript = nullptr;
     if (frame.checkInvalidation(&ionScript)) {
         // This frame has been invalidated, meaning that its IonScript is no
@@ -931,17 +935,17 @@ TraceIonJSFrame(JSTracer* trc, const Jit
             rawPayload = *v.payloadUIntPtr();
             WriteAllocation(frame, &payload, rawPayload);
         }
     }
 #endif
 }
 
 static void
-TraceBailoutFrame(JSTracer* trc, const JitFrameIterator& frame)
+TraceBailoutFrame(JSTracer* trc, const JSJitFrameIter& frame)
 {
     JitFrameLayout* layout = (JitFrameLayout*)frame.fp();
 
     layout->replaceCalleeToken(TraceCalleeToken(trc, layout->calleeToken()));
 
     // We have to trace the list of actual arguments, as only formal arguments
     // are represented in the Snapshot.
     TraceThisAndArguments(trc, frame);
@@ -967,17 +971,17 @@ TraceBailoutFrame(JSTracer* trc, const J
         if (!snapIter.moreInstructions())
             break;
         snapIter.nextInstruction();
     }
 
 }
 
 void
-UpdateIonJSFrameForMinorGC(JSTracer* trc, const JitFrameIterator& frame)
+UpdateIonJSFrameForMinorGC(JSTracer* trc, const JSJitFrameIter& frame)
 {
     // Minor GCs may move slots/elements allocated in the nursery. Update
     // any slots/elements pointers stored in this frame.
 
     JitFrameLayout* layout = (JitFrameLayout*)frame.fp();
 
     IonScript* ionScript = nullptr;
     if (frame.checkInvalidation(&ionScript)) {
@@ -1012,32 +1016,32 @@ UpdateIonJSFrameForMinorGC(JSTracer* trc
 
     while (safepoint.getSlotsOrElementsSlot(&entry)) {
         HeapSlot** slots = reinterpret_cast<HeapSlot**>(layout->slotRef(entry));
         nursery.forwardBufferPointer(slots);
     }
 }
 
 static void
-TraceBaselineStubFrame(JSTracer* trc, const JitFrameIterator& frame)
+TraceBaselineStubFrame(JSTracer* trc, const JSJitFrameIter& frame)
 {
     // Trace the ICStub pointer stored in the stub frame. This is necessary
     // so that we don't destroy the stub code after unlinking the stub.
 
     MOZ_ASSERT(frame.type() == JitFrame_BaselineStub);
     JitStubFrameLayout* layout = (JitStubFrameLayout*)frame.fp();
 
     if (ICStub* stub = layout->maybeStubPtr()) {
         MOZ_ASSERT(stub->makesGCCalls());
         stub->trace(trc);
     }
 }
 
 static void
-TraceIonICCallFrame(JSTracer* trc, const JitFrameIterator& frame)
+TraceIonICCallFrame(JSTracer* trc, const JSJitFrameIter& frame)
 {
     MOZ_ASSERT(frame.type() == JitFrame_IonICCall);
     IonICCallFrameLayout* layout = (IonICCallFrameLayout*)frame.fp();
     TraceRoot(trc, layout->stubCode(), "ion-ic-call-code");
 }
 
 #ifdef JS_CODEGEN_MIPS32
 uint8_t*
@@ -1072,17 +1076,17 @@ TraceJitExitFrameCopiedArguments(JSTrace
 static void
 TraceJitExitFrameCopiedArguments(JSTracer* trc, const VMFunction* f, ExitFooterFrame* footer)
 {
     // This is NO-OP on other platforms.
 }
 #endif
 
 static void
-TraceJitExitFrame(JSTracer* trc, const JitFrameIterator& frame)
+TraceJitExitFrame(JSTracer* trc, const JSJitFrameIter& frame)
 {
     ExitFooterFrame* footer = frame.exitFrame()->footer();
 
     // Trace the code of the code handling the exit path.  This is needed because
     // invalidated script are no longer traced because data are erased by the
     // invalidation and relocation data are no longer reliable.  So the VM
     // wrapper or the invalidation code may be GC if no JitCode keep reference
     // on them.
@@ -1241,44 +1245,42 @@ TraceJitExitFrame(JSTracer* trc, const J
             break;
         }
     }
 
     TraceJitExitFrameCopiedArguments(trc, f, footer);
 }
 
 static void
-TraceRectifierFrame(JSTracer* trc, const JitFrameIterator& frame)
+TraceRectifierFrame(JSTracer* trc, const JSJitFrameIter& frame)
 {
     // Trace thisv.
     //
     // Baseline JIT code generated as part of the ICCall_Fallback stub may use
     // it if we're calling a constructor that returns a primitive value.
     RectifierFrameLayout* layout = (RectifierFrameLayout*)frame.fp();
     TraceRoot(trc, &layout->argv()[0], "ion-thisv");
 }
 
 static void
-TraceJitActivation(JSTracer* trc, const JitActivationIterator& activations)
+TraceJitActivation(JSTracer* trc, JitActivation* activation)
 {
-    JitActivation* activation = activations->asJit();
-
 #ifdef CHECK_OSIPOINT_REGISTERS
     if (JitOptions.checkOsiPointRegisters) {
         // GC can modify spilled registers, breaking our register checks.
         // To handle this, we disable these checks for the current VM call
         // when a GC happens.
         activation->setCheckRegs(false);
     }
 #endif
 
     activation->traceRematerializedFrames(trc);
     activation->traceIonRecovery(trc);
 
-    for (JitFrameIterator frames(activations); !frames.done(); ++frames) {
+    for (JSJitFrameIter frames(activation); !frames.done(); ++frames) {
         switch (frames.type()) {
           case JitFrame_Exit:
             TraceJitExitFrame(trc, frames);
             break;
           case JitFrame_BaselineJS:
             frames.baselineFrame()->trace(trc, frames);
             break;
           case JitFrame_IonJS:
@@ -1297,83 +1299,96 @@ TraceJitActivation(JSTracer* trc, const 
             TraceIonICCallFrame(trc, frames);
             break;
           default:
             MOZ_CRASH("unexpected frame type");
         }
     }
 }
 
+static void
+TraceWasmActivation(JSTracer* trc, WasmActivation* act)
+{
+    for (wasm::WasmFrameIter iter(act); !iter.done(); ++iter)
+        iter.instance()->trace(trc);
+}
+
 void
 TraceJitActivations(JSContext* cx, const CooperatingContext& target, JSTracer* trc)
 {
-    for (JitActivationIterator activations(cx, target); !activations.done(); ++activations)
-        TraceJitActivation(trc, activations);
+    for (ActivationIterator activations(cx, target); !activations.done(); ++activations) {
+        if (activations->isJit())
+            TraceJitActivation(trc, activations->asJit());
+        if (activations->isWasm())
+            TraceWasmActivation(trc, activations->asWasm());
+    }
 }
 
 void
 UpdateJitActivationsForMinorGC(JSRuntime* rt, JSTracer* trc)
 {
     MOZ_ASSERT(JS::CurrentThreadIsHeapMinorCollecting());
     JSContext* cx = TlsContext.get();
     for (const CooperatingContext& target : rt->cooperatingContexts()) {
         for (JitActivationIterator activations(cx, target); !activations.done(); ++activations) {
-            for (JitFrameIterator frames(activations); !frames.done(); ++frames) {
-                if (frames.type() == JitFrame_IonJS)
-                    UpdateIonJSFrameForMinorGC(trc, frames);
+            for (OnlyJSJitFrameIter iter(activations); !iter.done(); ++iter) {
+                if (iter.frame().type() == JitFrame_IonJS)
+                    UpdateIonJSFrameForMinorGC(trc, iter.frame());
             }
         }
     }
 }
 
 void
 GetPcScript(JSContext* cx, JSScript** scriptRes, jsbytecode** pcRes)
 {
     JitSpew(JitSpew_IonSnapshots, "Recover PC & Script from the last frame.");
 
     // Recover the return address so that we can look it up in the
     // PcScriptCache, as script/pc computation is expensive.
-    JitActivationIterator iter(cx);
-    JitFrameIterator it(iter);
+    JitActivationIterator actIter(cx);
+    OnlyJSJitFrameIter it(actIter);
     uint8_t* retAddr;
-    if (it.isExitFrame()) {
+    if (it.frame().isExitFrame()) {
         ++it;
 
         // Skip rectifier frames.
-        if (it.isRectifier()) {
+        if (it.frame().isRectifier()) {
             ++it;
-            MOZ_ASSERT(it.isBaselineStub() || it.isBaselineJS() || it.isIonJS());
+            MOZ_ASSERT(it.frame().isBaselineStub() ||
+                       it.frame().isBaselineJS() ||
+                       it.frame().isIonJS());
         }
 
         // Skip Baseline/Ion stub and IC call frames.
-        if (it.isBaselineStub()) {
+        if (it.frame().isBaselineStub()) {
             ++it;
-            MOZ_ASSERT(it.isBaselineJS());
-        } else if (it.isIonICCall()) {
+            MOZ_ASSERT(it.frame().isBaselineJS());
+        } else if (it.frame().isIonICCall()) {
             ++it;
-            MOZ_ASSERT(it.isIonJS());
+            MOZ_ASSERT(it.frame().isIonJS());
         }
 
-        MOZ_ASSERT(it.isBaselineJS() || it.isIonJS());
+        MOZ_ASSERT(it.frame().isBaselineJS() || it.frame().isIonJS());
 
         // Don't use the return address if the BaselineFrame has an override pc.
         // The override pc is cheap to get, so we won't benefit from the cache,
         // and the override pc could change without the return address changing.
         // Moreover, sometimes when an override pc is present during exception
         // handling, the return address is set to nullptr as a sanity check,
         // since we do not return to the frame that threw the exception.
-        if (!it.isBaselineJS() || !it.baselineFrame()->hasOverridePc()) {
-            retAddr = it.returnAddressToFp();
+        if (!it.frame().isBaselineJS() || !it.frame().baselineFrame()->hasOverridePc()) {
+            retAddr = it.frame().returnAddressToFp();
             MOZ_ASSERT(retAddr);
         } else {
             retAddr = nullptr;
         }
     } else {
-        MOZ_ASSERT(it.isBailoutJS());
-        retAddr = it.returnAddress();
+        MOZ_ASSERT(it.frame().isBailoutJS());
+        retAddr = it.frame().returnAddress();
     }
 
     uint32_t hash;
     if (retAddr) {
         hash = PcScriptCache::Hash(retAddr);
 
         // Lazily initialize the cache. The allocation may safely fail and will not GC.
         if (MOZ_UNLIKELY(cx->ionPcScriptCache == nullptr)) {
@@ -1383,23 +1398,23 @@ GetPcScript(JSContext* cx, JSScript** sc
         }
 
         if (cx->ionPcScriptCache && cx->ionPcScriptCache->get(cx->runtime(), hash, retAddr, scriptRes, pcRes))
             return;
     }
 
     // Lookup failed: undertake expensive process to recover the innermost inlined frame.
     jsbytecode* pc = nullptr;
-    if (it.isIonJS() || it.isBailoutJS()) {
-        InlineFrameIterator ifi(cx, &it);
+    if (it.frame().isIonJS() || it.frame().isBailoutJS()) {
+        InlineFrameIterator ifi(cx, &it.frame());
         *scriptRes = ifi.script();
         pc = ifi.pc();
     } else {
-        MOZ_ASSERT(it.isBaselineJS());
-        it.baselineScriptAndPc(scriptRes, &pc);
+        MOZ_ASSERT(it.frame().isBaselineJS());
+        it.frame().baselineScriptAndPc(scriptRes, &pc);
     }
 
     if (pcRes)
         *pcRes = pc;
 
     // Add entry to cache.
     if (retAddr && cx->ionPcScriptCache)
         cx->ionPcScriptCache->add(hash, retAddr, pc, *scriptRes);
@@ -1491,17 +1506,17 @@ void
 RInstructionResults::trace(JSTracer* trc)
 {
     // Note: The vector necessary exists, otherwise this object would not have
     // been stored on the activation from where the trace function is called.
     TraceRange(trc, results_->length(), results_->begin(), "ion-recover-results");
 }
 
 
-SnapshotIterator::SnapshotIterator(const JitFrameIterator& iter, const MachineState* machineState)
+SnapshotIterator::SnapshotIterator(const JSJitFrameIter& iter, const MachineState* machineState)
   : snapshot_(iter.ionScript()->snapshots(),
               iter.snapshotOffset(),
               iter.ionScript()->snapshotsRVATableSize(),
               iter.ionScript()->snapshotsListSize()),
     recover_(snapshot_,
              iter.ionScript()->recovers(),
              iter.ionScript()->recoversSize()),
     fp_(iter.jsFrame()),
@@ -1908,17 +1923,17 @@ SnapshotIterator::initInstructionResults
         // cause a GC, we can ensure that the results are properly traced by the
         // activation.
         RInstructionResults tmp(fallback.frame->jsFrame());
         if (!fallback.activation->registerIonFrameRecovery(mozilla::Move(tmp)))
             return false;
 
         results = fallback.activation->maybeIonFrameRecovery(fp);
 
-        // Start a new snapshot at the beginning of the JitFrameIterator.  This
+        // Start a new snapshot at the beginning of the JSJitFrameIter.  This
         // SnapshotIterator is used for evaluating the content of all recover
         // instructions.  The result is then saved on the JitActivation.
         MachineState machine = fallback.frame->machineState();
         SnapshotIterator s(*fallback.frame, &machine);
         if (!s.computeInstructionResults(cx, results)) {
 
             // If the evaluation failed because of OOMs, then we discard the
             // current set of result that we collected so far.
@@ -2023,17 +2038,17 @@ SnapshotIterator::maybeReadAllocByIndex(
     }
 
     while (moreAllocations())
         skip();
 
     return s;
 }
 
-InlineFrameIterator::InlineFrameIterator(JSContext* cx, const JitFrameIterator* iter)
+InlineFrameIterator::InlineFrameIterator(JSContext* cx, const JSJitFrameIter* iter)
   : calleeTemplate_(cx),
     calleeRVA_(),
     script_(cx)
 {
     resetOn(iter);
 }
 
 InlineFrameIterator::InlineFrameIterator(JSContext* cx, const InlineFrameIterator* iter)
@@ -2051,17 +2066,17 @@ InlineFrameIterator::InlineFrameIterator
         // findNextFrame will iterate to the next frame and init. everything.
         // Therefore to settle on the same frame, we report one frame less readed.
         framesRead_ = iter->framesRead_ - 1;
         findNextFrame();
     }
 }
 
 void
-InlineFrameIterator::resetOn(const JitFrameIterator* iter)
+InlineFrameIterator::resetOn(const JSJitFrameIter* iter)
 {
     frame_ = iter;
     framesRead_ = 0;
     frameCount_ = UINT32_MAX;
 
     if (iter) {
         machine_ = iter->machineState();
         start_ = SnapshotIterator(*iter, &machine_);
@@ -2654,17 +2669,18 @@ JitProfilingFrameIterator::moveToNextFra
 
         returnAddressToFp_ = callFrame->returnAddress();
         fp_ = GetPreviousRawFrame<uint8_t*>(callFrame);
         type_ = JitFrame_IonJS;
         return;
     }
 
     if (prevType == JitFrame_Entry) {
-        // No previous frame, set to null to indicate that JitFrameIterator is done()
+        // No previous frame, set to null to indicate that OnlyJSJitFrameIter is
+        // done().
         returnAddressToFp_ = nullptr;
         fp_ = nullptr;
         type_ = JitFrame_Entry;
         return;
     }
 
     MOZ_CRASH("Bad frame type.");
 }
@@ -2689,21 +2705,22 @@ InvalidationBailoutStack::checkInvariant
     MOZ_ASSERT(rawBase <= osiPoint && osiPoint <= rawLimit);
 #endif