Merge inbound to mozilla-central. a=merge
authorMargareta Eliza Balazs <ebalazs@mozilla.com>
Wed, 05 Sep 2018 12:45:27 +0300
changeset 490501 019d95c8106db947529150d3d8ba02871ce04f9c
parent 490463 46e6b719f5bf12b6328d439d34a9fc43d83d1329 (current diff)
parent 490500 d1ded0e748012af9df9d358620dcb6c5c23f99d0 (diff)
child 490502 26990836dc5cc3cd1b8027392b79210e71094dc3
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
docshell/shistory/nsISHTransaction.idl
docshell/shistory/nsSHTransaction.cpp
docshell/shistory/nsSHTransaction.h
--- a/browser/base/content/test/general/browser_e10s_switchbrowser.js
+++ b/browser/base/content/test/general/browser_e10s_switchbrowser.js
@@ -14,17 +14,17 @@ function get_remote_history(browser) {
     let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
     let sessionHistory = webNav.sessionHistory;
     let result = {
       index: sessionHistory.index,
       entries: [],
     };
 
     for (let i = 0; i < sessionHistory.count; i++) {
-      let entry = sessionHistory.legacySHistory.getEntryAtIndex(i, false);
+      let entry = sessionHistory.legacySHistory.getEntryAtIndex(i);
       result.entries.push({
         uri: entry.URI.spec,
         title: entry.title,
       });
     }
 
     sendAsyncMessage("Test:History", result);
   }
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -996,16 +996,18 @@ BrowserGlue.prototype = {
     os.removeObserver(this, "shield-init-complete");
   },
 
   // runs on startup, before the first command line handler is invoked
   // (i.e. before the first window is opened)
   _beforeUIStartup: function BG__beforeUIStartup() {
     SessionStartup.init();
 
+    PdfJs.earlyInit();
+
     // check if we're in safe mode
     if (Services.appinfo.inSafeMode) {
       Services.ww.openWindow(null, "chrome://browser/content/safeMode.xul",
                              "_blank", "chrome,centerscreen,modal,resizable=no", null);
     }
 
     // apply distribution customizations
     this._distributionCustomizer.applyCustomizations();
--- a/browser/components/sessionstore/test/browser_687710_2.js
+++ b/browser/components/sessionstore/test/browser_687710_2.js
@@ -25,18 +25,18 @@ var state = {entries: [
   },
 ]};
 
 add_task(async function test() {
   let tab = BrowserTestUtils.addTab(gBrowser, "about:blank");
   await promiseTabState(tab, state);
   await ContentTask.spawn(tab.linkedBrowser, null, function() {
     function compareEntries(i, j, history) {
-      let e1 = history.getEntryAtIndex(i, false);
-      let e2 = history.getEntryAtIndex(j, false);
+      let e1 = history.getEntryAtIndex(i);
+      let e2 = history.getEntryAtIndex(j);
 
       ok(e1.sharesDocumentWith(e2),
          `${i} should share doc with ${j}`);
       is(e1.childCount, e2.childCount,
          `Child count mismatch (${i}, ${j})`);
 
       for (let c = 0; c < e1.childCount; c++) {
         let c1 = e1.GetChildAt(c);
--- a/browser/components/sessionstore/test/browser_705597.js
+++ b/browser/components/sessionstore/test/browser_705597.js
@@ -19,17 +19,17 @@ function test() {
   });
 
   let tab = BrowserTestUtils.addTab(gBrowser, "about:blank");
 
   let browser = tab.linkedBrowser;
 
   promiseTabState(tab, tabState).then(() => {
     let sessionHistory = browser.sessionHistory;
-    let entry = sessionHistory.legacySHistory.getEntryAtIndex(0, false);
+    let entry = sessionHistory.legacySHistory.getEntryAtIndex(0);
 
     whenChildCount(entry, 1, function() {
       whenChildCount(entry, 2, function() {
         promiseBrowserLoaded(browser).then(() => {
           return TabStateFlusher.flush(browser);
         }).then(() => {
           let {entries} = JSON.parse(ss.getTabState(tab));
           is(entries.length, 1, "tab has one history entry");
--- a/browser/components/sessionstore/test/browser_707862.js
+++ b/browser/components/sessionstore/test/browser_707862.js
@@ -19,23 +19,23 @@ function test() {
   });
 
   let tab = BrowserTestUtils.addTab(gBrowser, "about:blank");
 
   let browser = tab.linkedBrowser;
 
   promiseTabState(tab, tabState).then(() => {
     let sessionHistory = browser.sessionHistory;
-    let entry = sessionHistory.legacySHistory.getEntryAtIndex(0, false);
+    let entry = sessionHistory.legacySHistory.getEntryAtIndex(0);
 
     whenChildCount(entry, 1, function() {
       whenChildCount(entry, 2, function() {
         promiseBrowserLoaded(browser).then(() => {
           let newSessionHistory = browser.sessionHistory;
-          let newEntry = newSessionHistory.legacySHistory.getEntryAtIndex(0, false);
+          let newEntry = newSessionHistory.legacySHistory.getEntryAtIndex(0);
 
           whenChildCount(newEntry, 0, function() {
             // Make sure that we reset the state.
             let blankState = { windows: [{ tabs: [{ entries: [{ url: "about:blank",
                                                                 triggeringPrincipal_base64 }] }]}]};
             waitForBrowserState(blankState, finish);
           });
         });
--- a/browser/components/sessionstore/test/browser_docshell_uuid_consistency.js
+++ b/browser/components/sessionstore/test/browser_docshell_uuid_consistency.js
@@ -2,27 +2,27 @@
 add_task(async function duplicateTab() {
   const TEST_URL = "data:text/html,foo";
   let tab = BrowserTestUtils.addTab(gBrowser, TEST_URL);
   await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
 
   await ContentTask.spawn(tab.linkedBrowser, null, function() {
     let docshell = content.window.docShell
                                  .QueryInterface(Ci.nsIWebNavigation);
-    let shEntry = docshell.sessionHistory.legacySHistory.getEntryAtIndex(0, false);
+    let shEntry = docshell.sessionHistory.legacySHistory.getEntryAtIndex(0);
     is(shEntry.docshellID.toString(), docshell.historyID.toString());
   });
 
   let tab2 = gBrowser.duplicateTab(tab);
   await BrowserTestUtils.browserLoaded(tab2.linkedBrowser);
 
   await ContentTask.spawn(tab2.linkedBrowser, null, function() {
     let docshell = content.window.docShell
                                  .QueryInterface(Ci.nsIWebNavigation);
-    let shEntry = docshell.sessionHistory.legacySHistory.getEntryAtIndex(0, false);
+    let shEntry = docshell.sessionHistory.legacySHistory.getEntryAtIndex(0);
     is(shEntry.docshellID.toString(), docshell.historyID.toString());
   });
 
   BrowserTestUtils.removeTab(tab);
   BrowserTestUtils.removeTab(tab2);
 });
 
 // Second test - open a tab and navigate across processes, which triggers sessionrestore to persist history.
@@ -31,17 +31,17 @@ add_task(async function contentToChromeN
   let tab = BrowserTestUtils.addTab(gBrowser, TEST_URL);
   await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
 
   await ContentTask.spawn(tab.linkedBrowser, null, function() {
     let docshell = content.window.docShell
                                  .QueryInterface(Ci.nsIWebNavigation);
     let sh = docshell.sessionHistory;
     is(sh.count, 1);
-    is(sh.legacySHistory.getEntryAtIndex(0, false).docshellID.toString(), docshell.historyID.toString());
+    is(sh.legacySHistory.getEntryAtIndex(0).docshellID.toString(), docshell.historyID.toString());
   });
 
   // Force the browser to navigate to the chrome process.
   await ContentTask.spawn(tab.linkedBrowser, null, function() {
     const CHROME_URL = "about:config";
     let webnav = content.window.getInterface(Ci.nsIWebNavigation);
     webnav.loadURI(CHROME_URL, Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null);
   });
@@ -49,13 +49,13 @@ add_task(async function contentToChromeN
 
   // Check to be sure that we're in the chrome process.
   let docShell = tab.linkedBrowser.frameLoader.docShell;
 
   // 'cause we're in the chrome process, we can just directly poke at the shistory.
   let sh = docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory;
 
   is(sh.count, 2);
-  is(sh.legacySHistory.getEntryAtIndex(0, false).docshellID.toString(), docShell.historyID.toString());
-  is(sh.legacySHistory.getEntryAtIndex(1, false).docshellID.toString(), docShell.historyID.toString());
+  is(sh.legacySHistory.getEntryAtIndex(0).docshellID.toString(), docShell.historyID.toString());
+  is(sh.legacySHistory.getEntryAtIndex(1).docshellID.toString(), docShell.historyID.toString());
 
   BrowserTestUtils.removeTab(tab);
 });
--- a/browser/components/sessionstore/test/browser_history_persist.js
+++ b/browser/components/sessionstore/test/browser_history_persist.js
@@ -25,27 +25,27 @@ add_task(async function check_history_no
   browser = tab.linkedBrowser;
   await promiseTabState(tab, state);
 
   await ContentTask.spawn(browser, null, function() {
     let sessionHistory = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                                  .getInterface(Ci.nsISHistory);
 
     is(sessionHistory.count, 1, "Should be a single history entry");
-    is(sessionHistory.getEntryAtIndex(0, false).URI.spec, "about:blank", "Should be the right URL");
+    is(sessionHistory.getEntryAtIndex(0).URI.spec, "about:blank", "Should be the right URL");
   });
 
   // Load a new URL into the tab, it should replace the about:blank history entry
   browser.loadURI("about:robots");
   await promiseBrowserLoaded(browser);
   await ContentTask.spawn(browser, null, function() {
     let sessionHistory = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                                  .getInterface(Ci.nsISHistory);
     is(sessionHistory.count, 1, "Should be a single history entry");
-    is(sessionHistory.getEntryAtIndex(0, false).URI.spec, "about:robots", "Should be the right URL");
+    is(sessionHistory.getEntryAtIndex(0).URI.spec, "about:robots", "Should be the right URL");
   });
 
   // Cleanup.
   BrowserTestUtils.removeTab(tab);
 });
 
 /**
  * Check that entries default to being persisted when the attribute doesn't
@@ -68,25 +68,25 @@ add_task(async function check_history_de
   tab = BrowserTestUtils.addTab(gBrowser, "about:blank");
   browser = tab.linkedBrowser;
   await promiseTabState(tab, state);
   await ContentTask.spawn(browser, null, function() {
     let sessionHistory = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                                  .getInterface(Ci.nsISHistory);
 
     is(sessionHistory.count, 1, "Should be a single history entry");
-    is(sessionHistory.getEntryAtIndex(0, false).URI.spec, "about:blank", "Should be the right URL");
+    is(sessionHistory.getEntryAtIndex(0).URI.spec, "about:blank", "Should be the right URL");
   });
 
   // Load a new URL into the tab, it should replace the about:blank history entry
   browser.loadURI("about:robots");
   await promiseBrowserLoaded(browser);
   await ContentTask.spawn(browser, null, function() {
     let sessionHistory = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                                  .getInterface(Ci.nsISHistory);
     is(sessionHistory.count, 2, "Should be two history entries");
-    is(sessionHistory.getEntryAtIndex(0, false).URI.spec, "about:blank", "Should be the right URL");
-    is(sessionHistory.getEntryAtIndex(1, false).URI.spec, "about:robots", "Should be the right URL");
+    is(sessionHistory.getEntryAtIndex(0).URI.spec, "about:blank", "Should be the right URL");
+    is(sessionHistory.getEntryAtIndex(1).URI.spec, "about:robots", "Should be the right URL");
   });
 
   // Cleanup.
   BrowserTestUtils.removeTab(tab);
 });
--- a/browser/extensions/pdfjs/content/PdfJs.jsm
+++ b/browser/extensions/pdfjs/content/PdfJs.jsm
@@ -102,16 +102,20 @@ var PdfJs = {
                       "in the parent process.");
     }
     PdfjsChromeUtils.init();
     this.initPrefs();
 
     Services.ppmm.sharedData.set("pdfjs.enabled", this.checkEnabled());
   },
 
+  earlyInit() {
+    Services.ppmm.sharedData.set("pdfjs.enabled", this.checkEnabled());
+  },
+
   initPrefs: function initPrefs() {
     if (this._initialized) {
       return;
     }
     this._initialized = true;
 
     if (!getBoolPref(PREF_DISABLED, true)) {
       this._migrate();
--- a/build/build-clang/build-clang.py
+++ b/build/build-clang/build-clang.py
@@ -212,16 +212,18 @@ def build_one_stage(cc, cxx, asm, ld, ar
             "-DCMAKE_BUILD_TYPE=%s" % build_type,
             "-DCMAKE_INSTALL_PREFIX=%s" % inst_dir,
             "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64",
             "-DLLVM_ENABLE_ASSERTIONS=%s" % ("ON" if assertions else "OFF"),
             "-DPYTHON_EXECUTABLE=%s" % slashify_path(python_path),
             "-DLLVM_TOOL_LIBCXX_BUILD=%s" % ("ON" if build_libcxx else "OFF"),
             "-DLIBCXX_LIBCPPABI_VERSION=\"\"",
         ]
+        if is_linux():
+            cmake_args += ["-DLLVM_BINUTILS_INCDIR=%s/include" % gcc_dir]
         if is_windows():
             cmake_args.insert(-1, "-DLLVM_EXPORT_SYMBOLS_FOR_PLUGINS=ON")
             cmake_args.insert(-1, "-DLLVM_USE_CRT_RELEASE=MT")
         if ranlib is not None:
             cmake_args += ["-DCMAKE_RANLIB=%s" % slashify_path(ranlib)]
         if libtool is not None:
             cmake_args += ["-DCMAKE_LIBTOOL=%s" % slashify_path(libtool)]
         if osx_cross_compile:
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -317,18 +317,18 @@ nsDocShell::nsDocShell()
   , mChromeEventHandler(nullptr)
   , mDefaultScrollbarPref(Scrollbar_Auto, Scrollbar_Auto)
   , mCharsetReloadState(eCharsetReloadInit)
   , mOrientationLock(hal::eScreenOrientation_None)
   , mParentCharsetSource(0)
   , mMarginWidth(-1)
   , mMarginHeight(-1)
   , mItemType(typeContent)
-  , mPreviousTransIndex(-1)
-  , mLoadedTransIndex(-1)
+  , mPreviousEntryIndex(-1)
+  , mLoadedEntryIndex(-1)
   , mChildOffset(0)
   , mSandboxFlags(0)
   , mBusyFlags(BUSY_FLAGS_NONE)
   , mAppType(nsIDocShell::APP_TYPE_UNKNOWN)
   , mLoadType(0)
   , mDefaultLoadFlags(nsIRequest::LOAD_NORMAL)
   , mReferrerPolicy(0)
   , mFailedLoadType(0)
@@ -2281,75 +2281,75 @@ nsDocShell::SetUseErrorPages(bool aUseEr
   if (mObserveErrorPages) {
     mObserveErrorPages = false;
   }
   mUseErrorPages = aUseErrorPages;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsDocShell::GetPreviousTransIndex(int32_t* aPreviousTransIndex)
-{
-  *aPreviousTransIndex = mPreviousTransIndex;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsDocShell::GetLoadedTransIndex(int32_t* aLoadedTransIndex)
-{
-  *aLoadedTransIndex = mLoadedTransIndex;
+nsDocShell::GetPreviousEntryIndex(int32_t* aPreviousEntryIndex)
+{
+  *aPreviousEntryIndex = mPreviousEntryIndex;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetLoadedEntryIndex(int32_t* aLoadedEntryIndex)
+{
+  *aLoadedEntryIndex = mLoadedEntryIndex;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::HistoryPurged(int32_t aNumEntries)
 {
   // These indices are used for fastback cache eviction, to determine
   // which session history entries are candidates for content viewer
   // eviction.  We need to adjust by the number of entries that we
   // just purged from history, so that we look at the right session history
   // entries during eviction.
-  mPreviousTransIndex = std::max(-1, mPreviousTransIndex - aNumEntries);
-  mLoadedTransIndex = std::max(0, mLoadedTransIndex - aNumEntries);
+  mPreviousEntryIndex = std::max(-1, mPreviousEntryIndex - aNumEntries);
+  mLoadedEntryIndex = std::max(0, mLoadedEntryIndex - aNumEntries);
 
   nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList);
   while (iter.HasMore()) {
     nsCOMPtr<nsIDocShell> shell = do_QueryObject(iter.GetNext());
     if (shell) {
       shell->HistoryPurged(aNumEntries);
     }
   }
 
   return NS_OK;
 }
 
 nsresult
-nsDocShell::HistoryTransactionRemoved(int32_t aIndex)
+nsDocShell::HistoryEntryRemoved(int32_t aIndex)
 {
   // These indices are used for fastback cache eviction, to determine
   // which session history entries are candidates for content viewer
   // eviction.  We need to adjust by the number of entries that we
   // just purged from history, so that we look at the right session history
   // entries during eviction.
-  if (aIndex == mPreviousTransIndex) {
-    mPreviousTransIndex = -1;
-  } else if (aIndex < mPreviousTransIndex) {
-    --mPreviousTransIndex;
-  }
-  if (mLoadedTransIndex == aIndex) {
-    mLoadedTransIndex = 0;
-  } else if (aIndex < mLoadedTransIndex) {
-    --mLoadedTransIndex;
+  if (aIndex == mPreviousEntryIndex) {
+    mPreviousEntryIndex = -1;
+  } else if (aIndex < mPreviousEntryIndex) {
+    --mPreviousEntryIndex;
+  }
+  if (mLoadedEntryIndex == aIndex) {
+    mLoadedEntryIndex = 0;
+  } else if (aIndex < mLoadedEntryIndex) {
+    --mLoadedEntryIndex;
   }
 
   nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList);
   while (iter.HasMore()) {
     nsCOMPtr<nsIDocShell> shell = do_QueryObject(iter.GetNext());
     if (shell) {
-      static_cast<nsDocShell*>(shell.get())->HistoryTransactionRemoved(aIndex);
+      static_cast<nsDocShell*>(shell.get())->HistoryEntryRemoved(aIndex);
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::SetRecordProfileTimelineMarkers(bool aValue)
@@ -3819,17 +3819,17 @@ nsDocShell::AddChildSHEntryInternal(nsIS
      */
     nsCOMPtr<nsISHEntry> currentHE;
     int32_t index = mSessionHistory->Index();
     if (index < 0) {
       return NS_ERROR_FAILURE;
     }
 
     rv = mSessionHistory->LegacySHistory()->GetEntryAtIndex(
-      index, false, getter_AddRefs(currentHE));
+      index, getter_AddRefs(currentHE));
     NS_ENSURE_TRUE(currentHE, NS_ERROR_FAILURE);
 
     nsCOMPtr<nsISHEntry> currentEntry(do_QueryInterface(currentHE));
     if (currentEntry) {
       uint32_t cloneID = 0;
       nsCOMPtr<nsISHEntry> nextEntry;
       aCloneRef->GetID(&cloneID);
       rv = nsSHistory::CloneAndReplace(currentEntry, this, cloneID,
@@ -3861,31 +3861,31 @@ nsDocShell::AddChildSHEntryToParent(nsIS
    * mOSHE. This mOSHE will be used as the identification
    * for this subframe in the  CloneAndReplace function.
    */
 
   // In this case, we will end up calling AddEntry, which increases the
   // current index by 1
   RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
   if (rootSH) {
-    mPreviousTransIndex = rootSH->Index();
+    mPreviousEntryIndex = rootSH->Index();
   }
 
   nsresult rv;
   nsCOMPtr<nsIDocShell> parent = do_QueryInterface(GetAsSupports(mParent), &rv);
   if (parent) {
     rv = parent->AddChildSHEntry(mOSHE, aNewEntry, aChildOffset, mLoadType,
                                  aCloneChildren);
   }
 
   if (rootSH) {
-    mLoadedTransIndex = rootSH->Index();
+    mLoadedEntryIndex = rootSH->Index();
 #ifdef DEBUG_PAGE_CACHE
-    printf("Previous index: %d, Loaded index: %d\n\n", mPreviousTransIndex,
-           mLoadedTransIndex);
+    printf("Previous index: %d, Loaded index: %d\n\n", mPreviousEntryIndex,
+           mLoadedEntryIndex);
 #endif
   }
 
   return rv;
 }
 
 NS_IMETHODIMP
 nsDocShell::SetUseGlobalHistory(bool aUseGlobalHistory)
@@ -8115,22 +8115,22 @@ nsDocShell::RestoreFromHistory()
 
   // Set mFiredUnloadEvent = false so that the unload handler for the
   // *new* document will fire.
   mFiredUnloadEvent = false;
 
   mURIResultedInDocument = true;
   RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
   if (rootSH) {
-    mPreviousTransIndex = rootSH->Index();
+    mPreviousEntryIndex = rootSH->Index();
     rootSH->LegacySHistory()->UpdateIndex();
-    mLoadedTransIndex = rootSH->Index();
+    mLoadedEntryIndex = rootSH->Index();
 #ifdef DEBUG_PAGE_CACHE
-    printf("Previous index: %d, Loaded index: %d\n\n", mPreviousTransIndex,
-           mLoadedTransIndex);
+    printf("Previous index: %d, Loaded index: %d\n\n", mPreviousEntryIndex,
+           mLoadedEntryIndex);
 #endif
   }
 
   // Rather than call Embed(), we will retrieve the viewer from the session
   // history entry and swap it in.
   // XXX can we refactor this so that we can just call Embed()?
   PersistLayoutHistoryState();
   nsresult rv;
@@ -8647,17 +8647,17 @@ nsDocShell::CreateContentViewer(const ns
     // EndPageLoad. See bug 302115.
     if (mSessionHistory && !mLSHE) {
       int32_t idx;
       mSessionHistory->LegacySHistory()->GetRequestedIndex(&idx);
       if (idx == -1) {
         idx = mSessionHistory->Index();
       }
       mSessionHistory->LegacySHistory()->
-        GetEntryAtIndex(idx, false, getter_AddRefs(mLSHE));
+        GetEntryAtIndex(idx, getter_AddRefs(mLSHE));
     }
 
     mLoadType = LOAD_ERROR_PAGE;
   }
 
   bool onLocationChangeNeeded = OnLoadingSite(aOpenedChannel, false);
 
   // let's try resetting the load group if we need to...
@@ -10071,17 +10071,17 @@ nsDocShell::InternalLoad(nsIURI* aURI,
       SetHistoryEntry(&mLSHE, oldLSHE);
       /* Set the title for the SH entry for this target url. so that
        * SH menus in go/back/forward buttons won't be empty for this.
        */
       if (mSessionHistory) {
         int32_t index = mSessionHistory->Index();
         nsCOMPtr<nsISHEntry> shEntry;
         mSessionHistory->LegacySHistory()->GetEntryAtIndex(
-          index, false, getter_AddRefs(shEntry));
+          index, getter_AddRefs(shEntry));
         NS_ENSURE_TRUE(shEntry, NS_ERROR_FAILURE);
         shEntry->SetTitle(mTitle);
       }
 
       /* Set the title for the Global History entry for this anchor url.
        */
       UpdateGlobalHistoryTitle(aURI);
 
@@ -11568,17 +11568,17 @@ nsDocShell::OnNewURI(nsIURI* aURI, nsICh
     // points to the same SHEntry as our mLSHE.
     int32_t index = 0;
     mSessionHistory->LegacySHistory()->GetRequestedIndex(&index);
     if (index == -1) {
       index = mSessionHistory->Index();
     }
     nsCOMPtr<nsISHEntry> currentSH;
     mSessionHistory->LegacySHistory()->GetEntryAtIndex(
-      index, false, getter_AddRefs(currentSH));
+      index, getter_AddRefs(currentSH));
     if (currentSH != mLSHE) {
       mSessionHistory->LegacySHistory()->ReplaceEntry(index, mLSHE);
     }
   }
 
   // If this is a POST request, we do not want to include this in global
   // history.
   if (updateGHistory && aAddToGlobalHistory && !ChannelIsPost(aChannel)) {
@@ -11602,22 +11602,22 @@ nsDocShell::OnNewURI(nsIURI* aURI, nsICh
   }
 
   // If this was a history load or a refresh, or it was a history load but
   // later changed to LOAD_NORMAL_REPLACE due to redirection, update the index
   // in session history.
   if (rootSH &&
        ((mLoadType & (LOAD_CMD_HISTORY | LOAD_CMD_RELOAD)) ||
          mLoadType == LOAD_NORMAL_REPLACE)) {
-    mPreviousTransIndex = rootSH->Index();
+    mPreviousEntryIndex = rootSH->Index();
     rootSH->LegacySHistory()->UpdateIndex();
-    mLoadedTransIndex = rootSH->Index();
+    mLoadedEntryIndex = rootSH->Index();
 #ifdef DEBUG_PAGE_CACHE
     printf("Previous index: %d, Loaded index: %d\n\n",
-           mPreviousTransIndex, mLoadedTransIndex);
+           mPreviousEntryIndex, mLoadedEntryIndex);
 #endif
   }
 
   // aCloneSHChildren exactly means "we are not loading a new document".
   uint32_t locationFlags =
     aCloneSHChildren ? uint32_t(LOCATION_CHANGE_SAME_DOCUMENT) : 0;
 
   bool onLocationChangeNeeded = SetCurrentURI(aURI, aChannel,
@@ -12249,24 +12249,24 @@ nsDocShell::AddToSessionHistory(nsIURI* 
       } else {
         // If we're trying to replace an inexistant shistory entry, append.
         addToSHistory = true;
       }
     }
 
     if (addToSHistory) {
       // Add to session history
-      mPreviousTransIndex = mSessionHistory->Index();
+      mPreviousEntryIndex = mSessionHistory->Index();
 
       bool shouldPersist = ShouldAddToSessionHistory(aURI, aChannel);
       rv = mSessionHistory->LegacySHistory()->AddEntry(entry, shouldPersist);
-      mLoadedTransIndex = mSessionHistory->Index();
+      mLoadedEntryIndex = mSessionHistory->Index();
 #ifdef DEBUG_PAGE_CACHE
       printf("Previous index: %d, Loaded index: %d\n\n",
-             mPreviousTransIndex, mLoadedTransIndex);
+             mPreviousEntryIndex, mLoadedEntryIndex);
 #endif
     }
   } else {
     // This is a subframe.
     if (!mOSHE || !LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY)) {
       rv = AddChildSHEntryToParent(entry, mChildOffset, aCloneChildren);
     }
   }
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -249,17 +249,17 @@ public:
   // We need dummy OnLocationChange in some cases to update the UI without
   // updating security info.
   void FireDummyOnLocationChange()
   {
     FireOnLocationChange(this, nullptr, mCurrentURI,
                          LOCATION_CHANGE_SAME_DOCUMENT);
   }
 
-  nsresult HistoryTransactionRemoved(int32_t aIndex);
+  nsresult HistoryEntryRemoved(int32_t aIndex);
 
   // Notify Scroll observers when an async panning/zooming transform
   // has started being applied
   MOZ_CAN_RUN_SCRIPT_BOUNDARY
   void NotifyAsyncPanZoomStarted();
 
   // Notify Scroll observers when an async panning/zooming transform
   // is no longer applied
@@ -1026,21 +1026,21 @@ private: // data members
   int32_t mParentCharsetSource;
   int32_t mMarginWidth;
   int32_t mMarginHeight;
 
   // This can either be a content docshell or a chrome docshell. After
   // Create() is called, the type is not expected to change.
   int32_t mItemType;
 
-  // Index into the SHTransaction list, indicating the previous and current
-  // transaction at the time that this DocShell begins to load. Consequently
+  // Index into the nsISHEntry array, indicating the previous and current
+  // entry at the time that this DocShell begins to load. Consequently
   // root docshell's indices can differ from child docshells'.
-  int32_t mPreviousTransIndex;
-  int32_t mLoadedTransIndex;
+  int32_t mPreviousEntryIndex;
+  int32_t mLoadedEntryIndex;
 
   // Offset in the parent's child list.
   // -1 if the docshell is added dynamically to the parent shell.
   int32_t mChildOffset;
 
   uint32_t mSandboxFlags;
   uint32_t mBusyFlags;
   uint32_t mAppType;
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -534,22 +534,22 @@ interface nsIDocShell : nsIDocShellTreeI
 
   /**
    * The channel that failed to load and resulted in an error page.
    * May be null. Relevant only to error pages.
    */
   readonly attribute nsIChannel failedChannel;
 
   /**
-   * Keeps track of the previous SHTransaction index and the current
-   * SHTransaction index at the time that the doc shell begins to load.
+   * Keeps track of the previous nsISHEntry index and the current
+   * nsISHEntry index at the time that the doc shell begins to load.
    * Used for ContentViewer eviction.
    */
-  readonly attribute long previousTransIndex;
-  readonly attribute long loadedTransIndex;
+  readonly attribute long previousEntryIndex;
+  readonly attribute long loadedEntryIndex;
 
   /**
    * Notification that entries have been removed from the beginning of a
    * nsSHistory which has this as its rootDocShell.
    *
    * @param numEntries - The number of entries removed
    */
   void historyPurged(in long numEntries);
--- a/docshell/shistory/moz.build
+++ b/docshell/shistory/moz.build
@@ -4,17 +4,16 @@
 # 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/.
 
 XPIDL_SOURCES += [
     'nsIBFCacheEntry.idl',
     'nsISHEntry.idl',
     'nsISHistory.idl',
     'nsISHistoryListener.idl',
-    'nsISHTransaction.idl',
 ]
 
 XPIDL_MODULE = 'shistory'
 
 EXPORTS += [
     'nsSHEntryShared.h',
 ]
 
@@ -23,17 +22,16 @@ EXPORTS.mozilla.dom += [
     'ParentSHistory.h',
 ]
 
 UNIFIED_SOURCES += [
     'ChildSHistory.cpp',
     'nsSHEntry.cpp',
     'nsSHEntryShared.cpp',
     'nsSHistory.cpp',
-    'nsSHTransaction.cpp',
     'ParentSHistory.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/docshell/base',
     '/dom/base',
 ]
 
--- a/docshell/shistory/nsISHEntry.idl
+++ b/docshell/shistory/nsISHEntry.idl
@@ -26,17 +26,17 @@ interface nsISHistory;
 #include "nsRect.h"
 class nsDocShellEditorData;
 class nsSHEntryShared;
 %}
 [ref] native nsIntRect(nsIntRect);
 [ptr] native nsDocShellEditorDataPtr(nsDocShellEditorData);
 [ptr] native nsSHEntryShared(nsSHEntryShared);
 
-[scriptable, uuid(0dad26b8-a259-42c7-93f1-2fa7fc076e45)]
+[builtinclass, scriptable, uuid(0dad26b8-a259-42c7-93f1-2fa7fc076e45)]
 interface nsISHEntry : nsISupports
 {
     /**
      * A readonly property that returns the URI
      * of the current entry. The object returned is
      * of type nsIURI
      */
     readonly attribute nsIURI URI;
@@ -395,16 +395,23 @@ interface nsISHEntry : nsISupports
     nsISHEntry GetChildAt(in long aIndex);
 
     /**
      * Replaces a child which is for the same docshell as aNewChild
      * with aNewChild.
      * @throw if nothing was replaced.
      */
     void ReplaceChild(in nsISHEntry aNewChild);
+
+    /**
+     * When an entry is serving is within nsISHistory's array of entries, this
+     * property specifies if it should persist. If not it will be replaced by
+     * new additions to the list.
+     */
+    [infallible] attribute boolean persist;
 };
 
 %{ C++
 // {BFD1A791-AD9F-11d3-BDC7-0050040A9B44}
 #define NS_SHENTRY_CID \
 {0xbfd1a791, 0xad9f, 0x11d3, {0xbd, 0xc7, 0x0, 0x50, 0x4, 0xa, 0x9b, 0x44}}
 
 #define NS_SHENTRY_CONTRACTID \
deleted file mode 100644
--- a/docshell/shistory/nsISHTransaction.idl
+++ /dev/null
@@ -1,24 +0,0 @@
-/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "nsISupports.idl"
-
-interface nsISHEntry;
-
-[scriptable, uuid(2EDF705F-D252-4971-9F09-71DD0F760DC6)]
-interface nsISHTransaction : nsISupports
-{
-  /**
-   * The nsISHEntry for the current transaction.
-   */
-  attribute nsISHEntry sHEntry;
-
-  /**
-   * Specifies if this transaction should persist. If not it will be replaced
-   * by new additions to the list.
-   */
-  attribute boolean persist;
-};
-
--- a/docshell/shistory/nsISHistory.idl
+++ b/docshell/shistory/nsISHistory.idl
@@ -4,17 +4,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 interface nsIBFCacheEntry;
 interface nsIDocShell;
 interface nsISHEntry;
 interface nsISHistoryListener;
-interface nsISHTransaction;
 interface nsIURI;
 
 %{C++
 #include "nsTArrayForwardDeclare.h"
 %}
 
 [ref] native nsDocshellIDArray(nsTArray<nsID>);
 
@@ -44,20 +43,19 @@ interface nsISHistory: nsISupports
   /**
    * A readonly property of the interface that returns
    * the number of toplevel documents currently available
    * in session history.
    */
   readonly attribute long count;
 
   /**
-   * A readonly property of the interface that returns
-   * the index of the current document in session history.
+   * The index of the current document in session history.
    */
-  readonly attribute long index;
+  attribute long index;
 
   /**
    * A readonly property of the interface that returns
    * the index of the last document that started to load and
    * didn't finished yet. When document finishes the loading
    * value -1 is returned.
    */
   readonly attribute long requestedIndex;
@@ -65,31 +63,23 @@ interface nsISHistory: nsISupports
   /**
    * A read/write property of the interface, used to Get/Set
    * the maximum number of toplevel documents, session history
    * can hold for each instance.
    */
   attribute long maxLength;
 
   /**
-   * Called to obtain handle to the history entry at a
-   * given index.
+   * Get the history entry at a given index. Returns non-null on success.
    *
    * @param index             The index value whose entry is requested.
    *                          The oldest entry is located at index == 0.
-   * @param modifyIndex       A boolean flag that indicates if the current
-   *                          index of session history should be modified
-   *                          to the parameter index.
-   *
-   * @return                  <code>NS_OK</code> history entry for
-   *                          the index is obtained successfully.
-   *                          <code>NS_ERROR_FAILURE</code> Error in obtaining
-   *                          history entry for the given index.
+   * @return                  The found entry; never null.
    */
-  nsISHEntry getEntryAtIndex(in long aIndex, in boolean aModifyIndex);
+  nsISHEntry getEntryAtIndex(in long aIndex);
 
   /**
    * Called to purge older documents from history.
    * Documents can be removed from session history for various
    * reasons. For example to  control memory usage of the browser, to
    * prevent users from loading documents from history, to erase evidence of
    * prior page loads etc...
    *
@@ -154,21 +144,16 @@ interface nsISHistory: nsISupports
    * @param aPersist          If true this specifies that the entry should
    *                          persist in the list. If false, this means that
    *                          when new entries are added this element will not
    *                          appear in the session history list.
    */
   void addEntry(in nsISHEntry aEntry, in boolean aPersist);
 
   /**
-   * Get the transaction at a particular index.
-   */
-  nsISHTransaction GetTransactionAtIndex(in int32_t aIndex);
-
-  /**
    * Sets the toplevel docshell object to which this SHistory object belongs to.
    */
   void setRootDocShell(in nsIDocShell rootDocShell);
 
   /**
    * Update the index maintained by sessionHistory
    */
   void updateIndex();
--- a/docshell/shistory/nsSHEntry.cpp
+++ b/docshell/shistory/nsSHEntry.cpp
@@ -33,16 +33,17 @@ nsSHEntry::nsSHEntry()
   , mScrollPositionX(0)
   , mScrollPositionY(0)
   , mParent(nullptr)
   , mLoadReplace(false)
   , mURIWasModified(false)
   , mIsSrcdocEntry(false)
   , mScrollRestorationIsManual(false)
   , mLoadedInThisProcess(false)
+  , mPersist(true)
 {
 }
 
 nsSHEntry::nsSHEntry(const nsSHEntry& aOther)
   : mShared(aOther.mShared)
   , mURI(aOther.mURI)
   , mOriginalURI(aOther.mOriginalURI)
   , mResultPrincipalURI(aOther.mResultPrincipalURI)
@@ -996,8 +997,25 @@ nsSHEntry::SetSHistory(nsISHistory* aSHi
 
 NS_IMETHODIMP
 nsSHEntry::SetAsHistoryLoad()
 {
   // Set the LoadType by default to loadHistory during creation
   mLoadType = LOAD_HISTORY;
   return NS_OK;
 }
+
+NS_IMETHODIMP
+nsSHEntry::GetPersist(bool* aPersist)
+{
+  NS_ENSURE_ARG_POINTER(aPersist);
+
+  *aPersist = mPersist;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetPersist(bool aPersist)
+{
+  mPersist = aPersist;
+  return NS_OK;
+}
+
--- a/docshell/shistory/nsSHEntry.h
+++ b/docshell/shistory/nsSHEntry.h
@@ -56,11 +56,12 @@ private:
   nsCOMPtr<nsIStructuredCloneContainer> mStateData;
   nsString mSrcdocData;
   nsCOMPtr<nsIURI> mBaseURI;
   bool mLoadReplace;
   bool mURIWasModified;
   bool mIsSrcdocEntry;
   bool mScrollRestorationIsManual;
   bool mLoadedInThisProcess;
+  bool mPersist;
 };
 
 #endif /* nsSHEntry_h */
deleted file mode 100644
--- a/docshell/shistory/nsSHTransaction.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
-/* -*- 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 "nsSHTransaction.h"
-#include "nsISHEntry.h"
-
-nsSHTransaction::nsSHTransaction()
-  : mPersist(true)
-{
-}
-
-nsSHTransaction::~nsSHTransaction()
-{
-}
-
-NS_IMPL_ADDREF(nsSHTransaction)
-NS_IMPL_RELEASE(nsSHTransaction)
-
-NS_INTERFACE_MAP_BEGIN(nsSHTransaction)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISHTransaction)
-  NS_INTERFACE_MAP_ENTRY(nsISHTransaction)
-NS_INTERFACE_MAP_END
-
-NS_IMETHODIMP
-nsSHTransaction::GetSHEntry(nsISHEntry** aResult)
-{
-  NS_ENSURE_ARG_POINTER(aResult);
-  *aResult = mSHEntry;
-  NS_IF_ADDREF(*aResult);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsSHTransaction::SetSHEntry(nsISHEntry* aSHEntry)
-{
-  mSHEntry = aSHEntry;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsSHTransaction::SetPersist(bool aPersist)
-{
-  mPersist = aPersist;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsSHTransaction::GetPersist(bool* aPersist)
-{
-  NS_ENSURE_ARG_POINTER(aPersist);
-
-  *aPersist = mPersist;
-  return NS_OK;
-}
deleted file mode 100644
--- a/docshell/shistory/nsSHTransaction.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef nsSHTransaction_h
-#define nsSHTransaction_h
-
-#include "nsCOMPtr.h"
-#include "nsISHTransaction.h"
-
-class nsISHEntry;
-
-class nsSHTransaction : public nsISHTransaction
-{
-public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSISHTRANSACTION
-
-  nsSHTransaction();
-
-protected:
-  virtual ~nsSHTransaction();
-
-protected:
-  nsCOMPtr<nsISHEntry> mSHEntry;
-  bool mPersist;
-};
-
-#endif /* nsSHTransaction_h */
--- a/docshell/shistory/nsSHistory.cpp
+++ b/docshell/shistory/nsSHistory.cpp
@@ -14,17 +14,16 @@
 #include "nsIContentViewer.h"
 #include "nsIDocShell.h"
 #include "nsDocShellLoadInfo.h"
 #include "nsIDocShellTreeItem.h"
 #include "nsILayoutHistoryState.h"
 #include "nsIObserverService.h"
 #include "nsISHEntry.h"
 #include "nsISHistoryListener.h"
-#include "nsSHTransaction.h"
 #include "nsIURI.h"
 #include "nsNetUtil.h"
 #include "nsTArray.h"
 #include "prsystem.h"
 
 #include "mozilla/Attributes.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/MathAlgorithms.h"
@@ -180,42 +179,34 @@ nsSHistoryObserver::Observe(nsISupports*
   }
 
   return NS_OK;
 }
 
 namespace {
 
 already_AddRefed<nsIContentViewer>
-GetContentViewerForTransaction(nsISHTransaction* aTrans)
+GetContentViewerForEntry(nsISHEntry* aEntry)
 {
-  nsCOMPtr<nsISHEntry> entry;
-  aTrans->GetSHEntry(getter_AddRefs(entry));
-  if (!entry) {
-    return nullptr;
-  }
-
   nsCOMPtr<nsISHEntry> ownerEntry;
   nsCOMPtr<nsIContentViewer> viewer;
-  entry->GetAnyContentViewer(getter_AddRefs(ownerEntry),
-                             getter_AddRefs(viewer));
+  aEntry->GetAnyContentViewer(getter_AddRefs(ownerEntry),
+                              getter_AddRefs(viewer));
   return viewer.forget();
 }
 
 } // namespace
 
 void
-nsSHistory::EvictContentViewerForTransaction(nsISHTransaction* aTrans)
+nsSHistory::EvictContentViewerForEntry(nsISHEntry* aEntry)
 {
-  nsCOMPtr<nsISHEntry> entry;
-  aTrans->GetSHEntry(getter_AddRefs(entry));
   nsCOMPtr<nsIContentViewer> viewer;
   nsCOMPtr<nsISHEntry> ownerEntry;
-  entry->GetAnyContentViewer(getter_AddRefs(ownerEntry),
-                             getter_AddRefs(viewer));
+  aEntry->GetAnyContentViewer(getter_AddRefs(ownerEntry),
+                              getter_AddRefs(viewer));
   if (viewer) {
     NS_ASSERTION(ownerEntry, "Content viewer exists but its SHEntry is null");
 
     LOG_SHENTRY_SPEC(("Evicting content viewer 0x%p for "
                       "owning SHEntry 0x%p at %s.",
                       viewer.get(), ownerEntry.get(), _spec),
                       ownerEntry);
 
@@ -223,19 +214,19 @@ nsSHistory::EvictContentViewerForTransac
     // document teardown is able to correctly persist the state.
     ownerEntry->SetContentViewer(nullptr);
     ownerEntry->SyncPresentationState();
     viewer->Destroy();
   }
 
   // When dropping bfcache, we have to remove associated dynamic entries as well.
   int32_t index = -1;
-  GetIndexOfEntry(entry, &index);
+  GetIndexOfEntry(aEntry, &index);
   if (index != -1) {
-    RemoveDynEntries(index, entry);
+    RemoveDynEntries(index, aEntry);
   }
 }
 
 nsSHistory::nsSHistory()
   : mIndex(-1)
   , mRequestedIndex(-1)
   , mRootDocShell(nullptr)
 {
@@ -611,58 +602,48 @@ nsSHistory::AddEntry(nsISHEntry* aSHEntr
   aSHEntry->GetSHistory(getter_AddRefs(shistoryOfEntry));
   if (shistoryOfEntry && shistoryOfEntry != this) {
     NS_WARNING("The entry has been associated to another nsISHistory instance. "
                "Try nsISHEntry.clone() and nsISHEntry.abandonBFCacheEntry() "
                "first if you're copying an entry from another nsISHistory.");
     return NS_ERROR_FAILURE;
   }
 
+  nsCOMPtr<nsISHEntry> currentTxn;
+  if (mIndex >= 0) {
+    nsresult rv = GetEntryAtIndex(mIndex, getter_AddRefs(currentTxn));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
   aSHEntry->SetSHistory(this);
 
   // If we have a root docshell, update the docshell id of the root shentry to
   // match the id of that docshell
   if (mRootDocShell) {
     nsID docshellID = mRootDocShell->HistoryID();
     aSHEntry->SetDocshellID(&docshellID);
   }
 
-  nsCOMPtr<nsISHTransaction> currentTxn;
-  GetTransactionAtIndex(mIndex, getter_AddRefs(currentTxn));
-
-  bool currentPersist = true;
-  if (currentTxn) {
-    currentTxn->GetPersist(&currentPersist);
-  }
-
-  int32_t currentIndex = mIndex;
-
-  if (!currentPersist) {
-    NOTIFY_LISTENERS(OnHistoryReplaceEntry, (currentIndex));
-    NS_ENSURE_SUCCESS(currentTxn->SetSHEntry(aSHEntry), NS_ERROR_FAILURE);
-    currentTxn->SetPersist(aPersist);
+  if (currentTxn && !currentTxn->GetPersist()) {
+    NOTIFY_LISTENERS(OnHistoryReplaceEntry, (mIndex));
+    aSHEntry->SetPersist(aPersist);
+    mEntries[mIndex] = aSHEntry;
     return NS_OK;
   }
 
   nsCOMPtr<nsIURI> uri;
   aSHEntry->GetURI(getter_AddRefs(uri));
-  NOTIFY_LISTENERS(OnHistoryNewEntry, (uri, currentIndex));
-
-  // Note that a listener may have changed mIndex. So use mIndex instead of
-  // currentIndex.
+  NOTIFY_LISTENERS(OnHistoryNewEntry, (uri, mIndex));
 
-  nsCOMPtr<nsISHTransaction> txn = new nsSHTransaction();
-  txn->SetPersist(aPersist);
-  txn->SetSHEntry(aSHEntry);
-
-  // Remove all transactions after the current one, add the new one, and set
-  // the new one as the current one.
+  // Remove all entries after the current one, add the new one, and set the new
+  // one as the current one.
   MOZ_ASSERT(mIndex >= -1);
-  mTransactions.TruncateLength(mIndex + 1);
-  mTransactions.AppendElement(txn);
+  aSHEntry->SetPersist(aPersist);
+  mEntries.TruncateLength(mIndex + 1);
+  mEntries.AppendElement(aSHEntry);
   mIndex++;
 
   NOTIFY_LISTENERS(OnLengthChanged, (Length()));
   NOTIFY_LISTENERS(OnIndexChanged, (mIndex));
 
   // Purge History list if it is too long
   if (gHistoryMaxSize >= 0 && Length() > gHistoryMaxSize) {
     PurgeHistory(Length() - gHistoryMaxSize);
@@ -675,124 +656,99 @@ nsSHistory::AddEntry(nsISHEntry* aSHEntr
 NS_IMETHODIMP
 nsSHistory::GetCount(int32_t* aResult)
 {
   NS_ENSURE_ARG_POINTER(aResult);
   *aResult = Length();
   return NS_OK;
 }
 
-/* Get index of the history list */
 NS_IMETHODIMP
 nsSHistory::GetIndex(int32_t* aResult)
 {
   MOZ_ASSERT(aResult, "null out param?");
   *aResult = mIndex;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsSHistory::SetIndex(int32_t aIndex)
+{
+  if (aIndex < 0 || aIndex >= Length()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  mIndex = aIndex;
+  NOTIFY_LISTENERS(OnIndexChanged, (mIndex))
+
+  return NS_OK;
+}
+
 /* Get the requestedIndex */
 NS_IMETHODIMP
 nsSHistory::GetRequestedIndex(int32_t* aResult)
 {
   MOZ_ASSERT(aResult, "null out param?");
   *aResult = mRequestedIndex;
   return NS_OK;
 }
 
-/* Get the entry at a given index */
 NS_IMETHODIMP
-nsSHistory::GetEntryAtIndex(int32_t aIndex, bool aModifyIndex,
-                            nsISHEntry** aResult)
-{
-  nsresult rv;
-  nsCOMPtr<nsISHTransaction> txn;
-
-  /* GetTransactionAtIndex ensures aResult is valid and validates aIndex */
-  rv = GetTransactionAtIndex(aIndex, getter_AddRefs(txn));
-  if (NS_SUCCEEDED(rv) && txn) {
-    // Get the Entry from the transaction
-    rv = txn->GetSHEntry(aResult);
-    if (NS_SUCCEEDED(rv) && (*aResult)) {
-      // Set mIndex to the requested index, if asked to do so..
-      if (aModifyIndex) {
-        mIndex = aIndex;
-        NOTIFY_LISTENERS(OnIndexChanged, (mIndex))
-      }
-    }
-  }
-  return rv;
-}
-
-/* Get the transaction at a given index */
-NS_IMETHODIMP
-nsSHistory::GetTransactionAtIndex(int32_t aIndex, nsISHTransaction** aResult)
+nsSHistory::GetEntryAtIndex(int32_t aIndex, nsISHEntry** aResult)
 {
   NS_ENSURE_ARG_POINTER(aResult);
 
   if (aIndex < 0 || aIndex >= Length()) {
     return NS_ERROR_FAILURE;
   }
 
-  *aResult = mTransactions[aIndex];
+  *aResult = mEntries[aIndex];
   NS_ADDREF(*aResult);
   return NS_OK;
 }
 
 /* Get the index of a given entry */
 NS_IMETHODIMP
 nsSHistory::GetIndexOfEntry(nsISHEntry* aSHEntry, int32_t* aResult)
 {
   NS_ENSURE_ARG(aSHEntry);
   NS_ENSURE_ARG_POINTER(aResult);
   *aResult = -1;
 
   for (int32_t i = 0; i < Length(); i++) {
-    nsCOMPtr<nsISHEntry> entry;
-    nsresult rv = mTransactions[i]->GetSHEntry(getter_AddRefs(entry));
-    if (NS_FAILED(rv) || !entry) {
-      return NS_ERROR_FAILURE;
-    }
-
-    if (aSHEntry == entry) {
+    if (aSHEntry == mEntries[i]) {
       *aResult = i;
       return NS_OK;
     }
   }
 
   return NS_ERROR_FAILURE;
 }
 
 #ifdef DEBUG
 nsresult
 nsSHistory::PrintHistory()
 {
   for (int32_t i = 0; i < Length(); i++) {
-    nsCOMPtr<nsISHTransaction> txn = mTransactions[i];
-    nsCOMPtr<nsISHEntry> entry;
-    nsresult rv = txn->GetSHEntry(getter_AddRefs(entry));
-    if (NS_FAILED(rv) && !entry) {
-      return NS_ERROR_FAILURE;
-    }
-
+    nsCOMPtr<nsISHEntry> entry = mEntries[i];
     nsCOMPtr<nsILayoutHistoryState> layoutHistoryState;
     nsCOMPtr<nsIURI> uri;
     nsString title;
 
     entry->GetLayoutHistoryState(getter_AddRefs(layoutHistoryState));
     entry->GetURI(getter_AddRefs(uri));
     entry->GetTitle(getter_Copies(title));
 
 #if 0
     nsAutoCString url;
     if (uri) {
       uri->GetSpec(url);
     }
 
-    printf("**** SH Transaction #%d, Entry = %x\n", i, entry.get());
+    printf("**** SH Entry #%d: %x\n", i, entry.get());
     printf("\t\t URL = %s\n", url.get());
 
     printf("\t\t Title = %s\n", NS_LossyConvertUTF16toASCII(title).get());
     printf("\t\t layout History Data = %x\n", layoutHistoryState.get());
 #endif
   }
 
   return NS_OK;
@@ -845,17 +801,17 @@ nsSHistory::PurgeHistory(int32_t aNumEnt
                               (aNumEntries, &purgeHistory));
 
   if (!purgeHistory) {
     // Listener asked us not to purge
     return NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA;
   }
 
   // Remove the first `aNumEntries` entries.
-  mTransactions.RemoveElementsAt(0, aNumEntries);
+  mEntries.RemoveElementsAt(0, aNumEntries);
 
   // Adjust the indices, but don't let them go below -1.
   mIndex -= aNumEntries;
   mIndex = std::max(mIndex, -1);
   mRequestedIndex -= aNumEntries;
   mRequestedIndex = std::max(mRequestedIndex, -1);
 
   NOTIFY_LISTENERS(OnLengthChanged, (Length()));
@@ -897,40 +853,38 @@ nsSHistory::RemoveSHistoryListener(nsISH
 
 /* Replace an entry in the History list at a particular index.
  * Do not update index or count.
  */
 NS_IMETHODIMP
 nsSHistory::ReplaceEntry(int32_t aIndex, nsISHEntry* aReplaceEntry)
 {
   NS_ENSURE_ARG(aReplaceEntry);
-  nsresult rv;
-  nsCOMPtr<nsISHTransaction> currentTxn;
 
-  rv = GetTransactionAtIndex(aIndex, getter_AddRefs(currentTxn));
+  if (aIndex < 0 || aIndex >= Length()) {
+    return NS_ERROR_FAILURE;
+  }
 
-  if (currentTxn) {
-    nsCOMPtr<nsISHistory> shistoryOfEntry;
-    aReplaceEntry->GetSHistory(getter_AddRefs(shistoryOfEntry));
-    if (shistoryOfEntry && shistoryOfEntry != this) {
-      NS_WARNING("The entry has been associated to another nsISHistory instance. "
-                 "Try nsISHEntry.clone() and nsISHEntry.abandonBFCacheEntry() "
-                 "first if you're copying an entry from another nsISHistory.");
-      return NS_ERROR_FAILURE;
-    }
+  nsCOMPtr<nsISHistory> shistoryOfEntry;
+  aReplaceEntry->GetSHistory(getter_AddRefs(shistoryOfEntry));
+  if (shistoryOfEntry && shistoryOfEntry != this) {
+    NS_WARNING("The entry has been associated to another nsISHistory instance. "
+               "Try nsISHEntry.clone() and nsISHEntry.abandonBFCacheEntry() "
+               "first if you're copying an entry from another nsISHistory.");
+    return NS_ERROR_FAILURE;
+  }
 
-    aReplaceEntry->SetSHistory(this);
+  aReplaceEntry->SetSHistory(this);
 
-    NOTIFY_LISTENERS(OnHistoryReplaceEntry, (aIndex));
+  NOTIFY_LISTENERS(OnHistoryReplaceEntry, (aIndex));
 
-    // Set the replacement entry in the transaction
-    rv = currentTxn->SetSHEntry(aReplaceEntry);
-    rv = currentTxn->SetPersist(true);
-  }
-  return rv;
+  aReplaceEntry->SetPersist(true);
+  mEntries[aIndex] = aReplaceEntry;
+
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHistory::NotifyOnHistoryReload(nsIURI* aReloadURI, uint32_t aReloadFlags,
                                   bool* aCanReload)
 {
   NOTIFY_LISTENERS_CANCELABLE(OnHistoryReload, *aCanReload,
                               (aReloadURI, aReloadFlags, aCanReload));
@@ -948,17 +902,17 @@ nsSHistory::EvictOutOfRangeContentViewer
 }
 
 NS_IMETHODIMP
 nsSHistory::EvictAllContentViewers()
 {
   // XXXbz we don't actually do a good job of evicting things as we should, so
   // we might have viewers quite far from mIndex.  So just evict everything.
   for (int32_t i = 0; i < Length(); i++) {
-    EvictContentViewerForTransaction(mTransactions[i]);
+    EvictContentViewerForEntry(mEntries[i]);
   }
 
   return NS_OK;
 }
 
 nsresult
 nsSHistory::Reload(uint32_t aReloadFlags)
 {
@@ -1016,21 +970,21 @@ nsSHistory::EvictOutOfRangeWindowContent
   // XXX rename method to EvictContentViewersExceptAroundIndex, or something.
 
   // We need to release all content viewers that are no longer in the range
   //
   //  aIndex - VIEWER_WINDOW to aIndex + VIEWER_WINDOW
   //
   // to ensure that this SHistory object isn't responsible for more than
   // VIEWER_WINDOW content viewers.  But our job is complicated by the
-  // fact that two transactions which are related by either hash navigations or
+  // fact that two entries which are related by either hash navigations or
   // history.pushState will have the same content viewer.
   //
   // To illustrate the issue, suppose VIEWER_WINDOW = 3 and we have four
-  // linked transactions in our history.  Suppose we then add a new content
+  // linked entries in our history.  Suppose we then add a new content
   // viewer and call into this function.  So the history looks like:
   //
   //   A A A A B
   //     +     *
   //
   // where the letters are content viewers and + and * denote the beginning and
   // end of the range aIndex +/- VIEWER_WINDOW.
   //
@@ -1061,97 +1015,93 @@ nsSHistory::EvictOutOfRangeWindowContent
        "Length()=%d. Safe range [%d, %d]",
        aIndex, Length(), startSafeIndex, endSafeIndex));
 
   // The content viewers in range aIndex -/+ VIEWER_WINDOW will not be
   // evicted.  Collect a set of them so we don't accidentally evict one of them
   // if it appears outside this range.
   nsCOMArray<nsIContentViewer> safeViewers;
   for (int32_t i = startSafeIndex; i <= endSafeIndex; i++) {
-    nsCOMPtr<nsIContentViewer> viewer =
-      GetContentViewerForTransaction(mTransactions[i]);
+    nsCOMPtr<nsIContentViewer> viewer = GetContentViewerForEntry(mEntries[i]);
     safeViewers.AppendObject(viewer);
   }
 
   // Walk the SHistory list and evict any content viewers that aren't safe.
   // (It's important that the condition checks Length(), rather than a cached
   // copy of Length(), because the length might change between iterations.)
   for (int32_t i = 0; i < Length(); i++) {
-    nsCOMPtr<nsISHTransaction> trans = mTransactions[i];
-    nsCOMPtr<nsIContentViewer> viewer = GetContentViewerForTransaction(trans);
+    nsCOMPtr<nsISHEntry> entry = mEntries[i];
+    nsCOMPtr<nsIContentViewer> viewer = GetContentViewerForEntry(entry);
     if (safeViewers.IndexOf(viewer) == -1) {
-      EvictContentViewerForTransaction(trans);
+      EvictContentViewerForEntry(entry);
     }
   }
 }
 
 namespace {
 
-class TransactionAndDistance
+class EntryAndDistance
 {
 public:
-  TransactionAndDistance(nsSHistory* aSHistory, nsISHTransaction* aTrans, uint32_t aDist)
+  EntryAndDistance(nsSHistory* aSHistory, nsISHEntry* aEntry, uint32_t aDist)
     : mSHistory(aSHistory)
-    , mTransaction(aTrans)
+    , mEntry(aEntry)
     , mLastTouched(0)
     , mDistance(aDist)
   {
-    mViewer = GetContentViewerForTransaction(aTrans);
-    NS_ASSERTION(mViewer, "Transaction should have a content viewer");
+    mViewer = GetContentViewerForEntry(aEntry);
+    NS_ASSERTION(mViewer, "Entry should have a content viewer");
 
-    nsCOMPtr<nsISHEntry> shentry;
-    mTransaction->GetSHEntry(getter_AddRefs(shentry));
-
-    shentry->GetLastTouched(&mLastTouched);
+    mEntry->GetLastTouched(&mLastTouched);
   }
 
-  bool operator<(const TransactionAndDistance& aOther) const
+  bool operator<(const EntryAndDistance& aOther) const
   {
     // Compare distances first, and fall back to last-accessed times.
     if (aOther.mDistance != this->mDistance) {
       return this->mDistance < aOther.mDistance;
     }
 
     return this->mLastTouched < aOther.mLastTouched;
   }
 
-  bool operator==(const TransactionAndDistance& aOther) const
+  bool operator==(const EntryAndDistance& aOther) const
   {
     // This is a little silly; we need == so the default comaprator can be
     // instantiated, but this function is never actually called when we sort
-    // the list of TransactionAndDistance objects.
+    // the list of EntryAndDistance objects.
     return aOther.mDistance == this->mDistance &&
            aOther.mLastTouched == this->mLastTouched;
   }
 
   RefPtr<nsSHistory> mSHistory;
-  nsCOMPtr<nsISHTransaction> mTransaction;
+  nsCOMPtr<nsISHEntry> mEntry;
   nsCOMPtr<nsIContentViewer> mViewer;
   uint32_t mLastTouched;
   int32_t mDistance;
 };
 
 } // namespace
 
 // static
 void
 nsSHistory::GloballyEvictContentViewers()
 {
-  // First, collect from each SHistory object the transactions which have a
-  // cached content viewer.  Associate with each transaction its distance from
-  // its SHistory's current index.
+  // First, collect from each SHistory object the entries which have a cached
+  // content viewer. Associate with each entry its distance from its SHistory's
+  // current index.
 
-  nsTArray<TransactionAndDistance> transactions;
+  nsTArray<EntryAndDistance> entries;
 
   for (auto shist : gSHistoryList) {
 
-    // Maintain a list of the transactions which have viewers and belong to
+    // Maintain a list of the entries which have viewers and belong to
     // this particular shist object.  We'll add this list to the global list,
-    // |transactions|, eventually.
-    nsTArray<TransactionAndDistance> shTransactions;
+    // |entries|, eventually.
+    nsTArray<EntryAndDistance> shEntries;
 
     // Content viewers are likely to exist only within shist->mIndex -/+
     // VIEWER_WINDOW, so only search within that range.
     //
     // A content viewer might exist outside that range due to either:
     //
     //   * history.pushState or hash navigations, in which case a copy of the
     //     content viewer should exist within the range, or
@@ -1159,131 +1109,128 @@ nsSHistory::GloballyEvictContentViewers(
     //   * bugs which cause us not to call nsSHistory::EvictContentViewers()
     //     often enough.  Once we do call EvictContentViewers() for the
     //     SHistory object in question, we'll do a full search of its history
     //     and evict the out-of-range content viewers, so we don't bother here.
     //
     int32_t startIndex, endIndex;
     shist->WindowIndices(shist->mIndex, &startIndex, &endIndex);
     for (int32_t i = startIndex; i <= endIndex; i++) {
-      nsCOMPtr<nsISHTransaction> trans = shist->mTransactions[i];
+      nsCOMPtr<nsISHEntry> entry = shist->mEntries[i];
       nsCOMPtr<nsIContentViewer> contentViewer =
-        GetContentViewerForTransaction(trans);
+        GetContentViewerForEntry(entry);
 
       if (contentViewer) {
         // Because one content viewer might belong to multiple SHEntries, we
-        // have to search through shTransactions to see if we already know
+        // have to search through shEntries to see if we already know
         // about this content viewer.  If we find the viewer, update its
         // distance from the SHistory's index and continue.
         bool found = false;
-        for (uint32_t j = 0; j < shTransactions.Length(); j++) {
-          TransactionAndDistance& container = shTransactions[j];
+        for (uint32_t j = 0; j < shEntries.Length(); j++) {
+          EntryAndDistance& container = shEntries[j];
           if (container.mViewer == contentViewer) {
             container.mDistance = std::min(container.mDistance,
                                            DeprecatedAbs(i - shist->mIndex));
             found = true;
             break;
           }
         }
 
-        // If we didn't find a TransactionAndDistance for this content viewer,
-        // make a new one.
+        // If we didn't find a EntryAndDistance for this content viewer, make a
+        // new one.
         if (!found) {
-          TransactionAndDistance container(shist, trans,
-                                           DeprecatedAbs(i - shist->mIndex));
-          shTransactions.AppendElement(container);
+          EntryAndDistance container(shist, entry,
+                                     DeprecatedAbs(i - shist->mIndex));
+          shEntries.AppendElement(container);
         }
       }
     }
 
-    // We've found all the transactions belonging to shist which have viewers.
-    // Add those transactions to our global list and move on.
-    transactions.AppendElements(shTransactions);
+    // We've found all the entries belonging to shist which have viewers.
+    // Add those entries to our global list and move on.
+    entries.AppendElements(shEntries);
   }
 
   // We now have collected all cached content viewers.  First check that we
   // have enough that we actually need to evict some.
-  if ((int32_t)transactions.Length() <= sHistoryMaxTotalViewers) {
+  if ((int32_t)entries.Length() <= sHistoryMaxTotalViewers) {
     return;
   }
 
-  // If we need to evict, sort our list of transactions and evict the largest
+  // If we need to evict, sort our list of entries and evict the largest
   // ones.  (We could of course get better algorithmic complexity here by using
   // a heap or something more clever.  But sHistoryMaxTotalViewers isn't large,
   // so let's not worry about it.)
-  transactions.Sort();
+  entries.Sort();
 
-  for (int32_t i = transactions.Length() - 1; i >= sHistoryMaxTotalViewers;
+  for (int32_t i = entries.Length() - 1; i >= sHistoryMaxTotalViewers;
        --i) {
-    (transactions[i].mSHistory)->
-      EvictContentViewerForTransaction(transactions[i].mTransaction);
+    (entries[i].mSHistory)->EvictContentViewerForEntry(entries[i].mEntry);
   }
 }
 
 nsresult
-nsSHistory::FindTransactionForBFCache(nsIBFCacheEntry* aEntry,
-                                      nsISHTransaction** aResult,
-                                      int32_t* aResultIndex)
+nsSHistory::FindEntryForBFCache(nsIBFCacheEntry* aBFEntry,
+                                nsISHEntry** aResult,
+                                int32_t* aResultIndex)
 {
   *aResult = nullptr;
   *aResultIndex = -1;
 
   int32_t startIndex, endIndex;
   WindowIndices(mIndex, &startIndex, &endIndex);
 
   for (int32_t i = startIndex; i <= endIndex; ++i) {
-    nsCOMPtr<nsISHTransaction> trans = mTransactions[i];
-    nsCOMPtr<nsISHEntry> entry;
-    trans->GetSHEntry(getter_AddRefs(entry));
+    nsCOMPtr<nsISHEntry> shEntry = mEntries[i];
 
-    // Does entry have the same BFCacheEntry as the argument to this method?
-    if (entry->HasBFCacheEntry(aEntry)) {
-      trans.forget(aResult);
+    // Does shEntry have the same BFCacheEntry as the argument to this method?
+    if (shEntry->HasBFCacheEntry(aBFEntry)) {
+      shEntry.forget(aResult);
       *aResultIndex = i;
       return NS_OK;
     }
   }
   return NS_ERROR_FAILURE;
 }
 
 nsresult
-nsSHistory::EvictExpiredContentViewerForEntry(nsIBFCacheEntry* aEntry)
+nsSHistory::EvictExpiredContentViewerForEntry(nsIBFCacheEntry* aBFEntry)
 {
   int32_t index;
-  nsCOMPtr<nsISHTransaction> trans;
-  FindTransactionForBFCache(aEntry, getter_AddRefs(trans), &index);
+  nsCOMPtr<nsISHEntry> shEntry;
+  FindEntryForBFCache(aBFEntry, getter_AddRefs(shEntry), &index);
 
   if (index == mIndex) {
     NS_WARNING("How did the current SHEntry expire?");
     return NS_OK;
   }
 
-  if (trans) {
-    EvictContentViewerForTransaction(trans);
+  if (shEntry) {
+    EvictContentViewerForEntry(shEntry);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsSHistory::AddToExpirationTracker(nsIBFCacheEntry* aEntry)
+nsSHistory::AddToExpirationTracker(nsIBFCacheEntry* aBFEntry)
 {
-  RefPtr<nsSHEntryShared> entry = static_cast<nsSHEntryShared*>(aEntry);
+  RefPtr<nsSHEntryShared> entry = static_cast<nsSHEntryShared*>(aBFEntry);
   if (!mHistoryTracker || !entry) {
     return NS_ERROR_FAILURE;
   }
 
   mHistoryTracker->AddObject(entry);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsSHistory::RemoveFromExpirationTracker(nsIBFCacheEntry* aEntry)
+nsSHistory::RemoveFromExpirationTracker(nsIBFCacheEntry* aBFEntry)
 {
-  RefPtr<nsSHEntryShared> entry = static_cast<nsSHEntryShared*>(aEntry);
+  RefPtr<nsSHEntryShared> entry = static_cast<nsSHEntryShared*>(aBFEntry);
   MOZ_ASSERT(mHistoryTracker && !mHistoryTracker->IsEmpty());
   if (!mHistoryTracker || !entry) {
     return NS_ERROR_FAILURE;
   }
 
   mHistoryTracker->RemoveObject(entry);
   return NS_OK;
 }
@@ -1352,17 +1299,17 @@ RemoveFromSessionHistoryEntry(nsISHEntry
   return didRemove;
 }
 
 bool
 RemoveChildEntries(nsISHistory* aHistory, int32_t aIndex,
                    nsTArray<nsID>& aEntryIDs)
 {
   nsCOMPtr<nsISHEntry> root;
-  aHistory->GetEntryAtIndex(aIndex, false, getter_AddRefs(root));
+  aHistory->GetEntryAtIndex(aIndex, getter_AddRefs(root));
   return root ? RemoveFromSessionHistoryEntry(root, aEntryIDs) : false;
 }
 
 bool
 IsSameTree(nsISHEntry* aEntry1, nsISHEntry* aEntry2)
 {
   if (!aEntry1 && !aEntry2) {
     return true;
@@ -1401,35 +1348,35 @@ nsSHistory::RemoveDuplicate(int32_t aInd
   NS_ASSERTION(aIndex != 0 || aKeepNext,
                "If we're removing index 0 we must be keeping the next");
   NS_ASSERTION(aIndex != mIndex, "Shouldn't remove mIndex!");
 
   int32_t compareIndex = aKeepNext ? aIndex + 1 : aIndex - 1;
 
   nsresult rv;
   nsCOMPtr<nsISHEntry> root1, root2;
-  rv = GetEntryAtIndex(aIndex, false, getter_AddRefs(root1));
+  rv = GetEntryAtIndex(aIndex, getter_AddRefs(root1));
   NS_ENSURE_SUCCESS(rv, false);
-  rv = GetEntryAtIndex(compareIndex, false, getter_AddRefs(root2));
+  rv = GetEntryAtIndex(compareIndex, getter_AddRefs(root2));
   NS_ENSURE_SUCCESS(rv, false);
 
   if (IsSameTree(root1, root2)) {
-    mTransactions.RemoveElementAt(aIndex);
+    mEntries.RemoveElementAt(aIndex);
 
     if (mRootDocShell) {
-      static_cast<nsDocShell*>(mRootDocShell)->HistoryTransactionRemoved(aIndex);
+      static_cast<nsDocShell*>(mRootDocShell)->HistoryEntryRemoved(aIndex);
     }
 
-    // Adjust our indices to reflect the removed transaction
+    // Adjust our indices to reflect the removed entry.
     if (mIndex > aIndex) {
       mIndex = mIndex - 1;
       NOTIFY_LISTENERS(OnIndexChanged, (mIndex));
     }
 
-    // NB: If the transaction we are removing is the transaction currently
+    // NB: If the entry we are removing is the entry currently
     // being navigated to (mRequestedIndex) then we adjust the index
     // only if we're not keeping the next entry (because if we are keeping
     // the next entry (because the current is a duplicate of the next), then
     // that entry slides into the spot that we're currently pointing to.
     // We don't do this adjustment for mIndex because mIndex cannot equal
     // aIndex.
 
     // NB: We don't need to guard on mRequestedIndex being nonzero here,
@@ -1470,38 +1417,36 @@ nsSHistory::RemoveEntries(nsTArray<nsID>
 }
 
 void
 nsSHistory::RemoveDynEntries(int32_t aIndex, nsISHEntry* aEntry)
 {
   // Remove dynamic entries which are at the index and belongs to the container.
   nsCOMPtr<nsISHEntry> entry(aEntry);
   if (!entry) {
-    GetEntryAtIndex(aIndex, false, getter_AddRefs(entry));
+    GetEntryAtIndex(aIndex, getter_AddRefs(entry));
   }
 
   if (entry) {
     AutoTArray<nsID, 16> toBeRemovedEntries;
     GetDynamicChildren(entry, toBeRemovedEntries, true);
     if (toBeRemovedEntries.Length()) {
       RemoveEntries(toBeRemovedEntries, aIndex);
     }
   }
 }
 
 void
-nsSHistory::RemoveDynEntriesForBFCacheEntry(nsIBFCacheEntry* aEntry)
+nsSHistory::RemoveDynEntriesForBFCacheEntry(nsIBFCacheEntry* aBFEntry)
 {
   int32_t index;
-  nsCOMPtr<nsISHTransaction> trans;
-  FindTransactionForBFCache(aEntry, getter_AddRefs(trans), &index);
-  if (trans) {
-    nsCOMPtr<nsISHEntry> entry;
-    trans->GetSHEntry(getter_AddRefs(entry));
-    RemoveDynEntries(index, entry);
+  nsCOMPtr<nsISHEntry> shEntry;
+  FindEntryForBFCache(aBFEntry, getter_AddRefs(shEntry), &index);
+  if (shEntry) {
+    RemoveDynEntries(index, shEntry);
   }
 }
 
 NS_IMETHODIMP
 nsSHistory::UpdateIndex()
 {
   // Update the actual index with the right value.
   if (mIndex != mRequestedIndex && mRequestedIndex != -1) {
@@ -1515,17 +1460,17 @@ nsSHistory::UpdateIndex()
 
 nsresult
 nsSHistory::GetCurrentURI(nsIURI** aResultURI)
 {
   NS_ENSURE_ARG_POINTER(aResultURI);
   nsresult rv;
 
   nsCOMPtr<nsISHEntry> currentEntry;
-  rv = GetEntryAtIndex(mIndex, false, getter_AddRefs(currentEntry));
+  rv = GetEntryAtIndex(mIndex, getter_AddRefs(currentEntry));
   if (NS_FAILED(rv) && !currentEntry) {
     return rv;
   }
   rv = currentEntry->GetURI(aResultURI);
   return rv;
 }
 
 nsresult
@@ -1562,18 +1507,18 @@ nsSHistory::LoadEntry(int32_t aIndex, lo
     // The index is out of range
     return NS_ERROR_FAILURE;
   }
 
   // This is a normal local history navigation.
   // Keep note of requested history index in mRequestedIndex.
   mRequestedIndex = aIndex;
 
-  GetEntryAtIndex(mIndex, false, getter_AddRefs(prevEntry));
-  GetEntryAtIndex(mRequestedIndex, false, getter_AddRefs(nextEntry));
+  GetEntryAtIndex(mIndex, getter_AddRefs(prevEntry));
+  GetEntryAtIndex(mRequestedIndex, getter_AddRefs(nextEntry));
   if (!nextEntry || !prevEntry) {
     mRequestedIndex = -1;
     return NS_ERROR_FAILURE;
   }
 
   // Remember that this entry is getting loaded at this point in the sequence
 
   nextEntry->SetLastTouched(++gTouchCounter);
--- a/docshell/shistory/nsSHistory.h
+++ b/docshell/shistory/nsSHistory.h
@@ -17,17 +17,16 @@
 
 #include "mozilla/LinkedList.h"
 #include "mozilla/UniquePtr.h"
 
 class nsIDocShell;
 class nsDocShell;
 class nsSHistoryObserver;
 class nsISHEntry;
-class nsISHTransaction;
 
 class nsSHistory final : public mozilla::LinkedListElement<nsSHistory>,
                          public nsISHistory,
                          public nsSupportsWeakReference
 {
 public:
 
   // The timer based history tracker is used to evict bfcache on expiration.
@@ -139,53 +138,53 @@ private:
                         long aLoadType);
 
   nsresult LoadEntry(int32_t aIndex, long aLoadType, uint32_t aHistCmd);
 
 #ifdef DEBUG
   nsresult PrintHistory();
 #endif
 
-  // Find the transaction for a given bfcache entry. It only looks up between
+  // Find the history entry for a given bfcache entry. It only looks up between
   // the range where alive viewers may exist (i.e nsISHistory::VIEWER_WINDOW).
-  nsresult FindTransactionForBFCache(nsIBFCacheEntry* aEntry,
-                                     nsISHTransaction** aResult,
-                                     int32_t* aResultIndex);
+  nsresult FindEntryForBFCache(nsIBFCacheEntry* aBFEntry,
+                               nsISHEntry** aResult,
+                               int32_t* aResultIndex);
 
   // Evict content viewers in this window which don't lie in the "safe" range
   // around aIndex.
   void EvictOutOfRangeWindowContentViewers(int32_t aIndex);
-  void EvictContentViewerForTransaction(nsISHTransaction* aTrans);
+  void EvictContentViewerForEntry(nsISHEntry* aEntry);
   static void GloballyEvictContentViewers();
   static void GloballyEvictAllContentViewers();
 
   // Calculates a max number of total
   // content viewers to cache, based on amount of total memory
   static uint32_t CalcMaxTotalViewers();
 
   nsresult LoadNextPossibleEntry(int32_t aNewIndex, long aLoadType,
                                  uint32_t aHistCmd);
 
-  // aIndex is the index of the transaction which may be removed.
+  // aIndex is the index of the entry which may be removed.
   // If aKeepNext is true, aIndex is compared to aIndex + 1,
   // otherwise comparison is done to aIndex - 1.
   bool RemoveDuplicate(int32_t aIndex, bool aKeepNext);
 
   // Track all bfcache entries and evict on expiration.
   mozilla::UniquePtr<HistoryTracker> mHistoryTracker;
 
-  nsTArray<nsCOMPtr<nsISHTransaction>> mTransactions;
+  nsTArray<nsCOMPtr<nsISHEntry>> mEntries; // entries are never null
   int32_t mIndex;           // -1 means "no index"
   int32_t mRequestedIndex;  // -1 means "no requested index"
 
   void WindowIndices(int32_t aIndex, int32_t* aOutStartIndex,
                      int32_t* aOutEndIndex);
 
-  // Length of mTransactions.
-  int32_t Length() { return int32_t(mTransactions.Length()); }
+  // Length of mEntries.
+  int32_t Length() { return int32_t(mEntries.Length()); }
 
   // Session History listeners
   nsAutoTObserverArray<nsWeakPtr, 2> mListeners;
 
   // Weak reference. Do not refcount this.
   nsIDocShell* mRootDocShell;
 
   // Max viewers allowed total, across all SHistory objects
--- a/docshell/test/browser/browser_bug655273.js
+++ b/docshell/test/browser/browser_bug655273.js
@@ -18,13 +18,13 @@ add_task(async function test() {
         let oldTitle = cw.document.title;
         ok(oldTitle, 'Content window should initially have a title.');
         cw.history.pushState('', '', 'new_page');
 
         let shistory = cw.docShell
                          .QueryInterface(Ci.nsIWebNavigation)
                          .sessionHistory;
 
-        is(shistory.legacySHistory.getEntryAtIndex(shistory.index, false).title,
+        is(shistory.legacySHistory.getEntryAtIndex(shistory.index).title,
            oldTitle, 'SHEntry title after pushstate.');
       });
     });
 });
--- a/docshell/test/chrome/bug396519_window.xul
+++ b/docshell/test/chrome/bug396519_window.xul
@@ -54,31 +54,31 @@
       // runs, we should should be in a testable state
       setTimeout(doTest, 0);
     }
 
     function doTest() {
       var history = gBrowser.webNavigation.sessionHistory;
       if (history.count == gExpected.length) {
         for (var i=0; i<history.count; i++) {
-          var shEntry = history.legacySHistory.getEntryAtIndex(i,false).
+          var shEntry = history.legacySHistory.getEntryAtIndex(i).
                           QueryInterface(Ci.nsISHEntry);
           is(!!shEntry.contentViewer, gExpected[i], "content viewer "+i+", test "+gTestCount);
         }
 
         // Make sure none of the SHEntries share bfcache entries with one
         // another.
         for (var i = 0; i < history.count; i++) {
           for (var j = 0; j < history.count; j++) {
             if (j == i)
               continue;
 
-            let shentry1 = history.legacySHistory.getEntryAtIndex(i, false)
+            let shentry1 = history.legacySHistory.getEntryAtIndex(i)
                                   .QueryInterface(Ci.nsISHEntry);
-            let shentry2 = history.legacySHistory.getEntryAtIndex(j, false)
+            let shentry2 = history.legacySHistory.getEntryAtIndex(j)
                                   .QueryInterface(Ci.nsISHEntry);
             ok(!shentry1.sharesDocumentWith(shentry2),
                'Test ' + gTestCount + ': shentry[' + i + "] shouldn't " +
                "share document with shentry[" + j + ']');
           }
         }
       }
       else {
--- a/docshell/test/navigation/file_bug1326251.html
+++ b/docshell/test/navigation/file_bug1326251.html
@@ -44,18 +44,18 @@
         window.location = 'goback.html';
       },
       async function() {
         let windowWrap = SpecialPowers.wrap(window);
         let docShell = windowWrap.docShell;
         let shistory = docShell.QueryInterface(SpecialPowers.Ci.nsIWebNavigation)
                                .sessionHistory;
         // Now staticFrame has frame0 -> frame1 -> frame2.
-        opener.is(docShell.previousTransIndex, 3, 'docShell.previousTransIndex');
-        opener.is(docShell.loadedTransIndex, 2, 'docShell.loadedTransIndex');
+        opener.is(docShell.previousEntryIndex, 3, 'docShell.previousEntryIndex');
+        opener.is(docShell.loadedEntryIndex, 2, 'docShell.loadedEntryIndex');
         opener.is(shistory.index, 2, 'shistory.index');
         opener.is(history.length, 4, 'history.length');
         opener.is(document.getElementById('staticFrame').contentWindow.location.href, BASE_URL + 'frame2.html', 'staticFrame location');
         opener.ok(!document.getElementById('dynamicFrame'), 'dynamicFrame should not exist');
 
         // Test 4: Load a nested frame in the static frame, navigate the inner
         // static frame, add a inner dynamic frame and navigate the dynamic
         // frame. Then navigate the outer static frame and go back. The inner
@@ -104,18 +104,18 @@
       },
       async function() {
         let windowWrap = SpecialPowers.wrap(window);
         let docShell = windowWrap.docShell;
         let shistory = docShell.QueryInterface(SpecialPowers.Ci.nsIWebNavigation)
                                .sessionHistory;
         // staticFrame:       frame0 -> frame1 -> frame2 -> iframe_static
         // innerStaticFrame:                                frame0        -> frame1
-        opener.is(docShell.previousTransIndex, 5, 'docShell.previousTransIndex');
-        opener.is(docShell.loadedTransIndex, 4, 'docShell.loadedTransIndex');
+        opener.is(docShell.previousEntryIndex, 5, 'docShell.previousEntryIndex');
+        opener.is(docShell.loadedEntryIndex, 4, 'docShell.loadedEntryIndex');
         opener.is(shistory.index, 4, 'shistory.index');
         opener.is(history.length, 6, 'history.length');
         let staticFrame = document.getElementById('staticFrame');
         let innerStaticFrame = staticFrame.contentDocument.getElementById('staticFrame');
         opener.is(innerStaticFrame.contentDocument.location.href, BASE_URL + 'frame1.html', 'innerStaticFrame location');
         opener.ok(!staticFrame.contentDocument.getElementById('dynamicFrame'), 'innerDynamicFrame should not exist');
 
         // Test 6: Insert and navigate inner dynamic frame and then reload outer
--- a/docshell/test/navigation/file_bug534178.html
+++ b/docshell/test/navigation/file_bug534178.html
@@ -7,17 +7,17 @@
         var isOK = false;
         try {
           isOK = history.previous != location;
         } catch(ex) {
           // history.previous should throw if this is the first page in shistory.
           isOK = true;
         }
         document.body.textContent = isOK ? "PASSED" : "FAILED";
-        opener.ok(isOK, "Duplicate session history transactions should have been removed!");
+        opener.ok(isOK, "Duplicate session history entries should have been removed!");
         opener.nextTest();
         window.close();
       }
       function ifrload() {
         setTimeout(testDone, 0);
       }
       function test() {
         var ifr = document.getElementsByTagName("iframe")[0];
--- a/docshell/test/navigation/test_bug1375833.html
+++ b/docshell/test/navigation/test_bug1375833.html
@@ -44,17 +44,17 @@ https://bugzilla.mozilla.org/show_bug.cg
       ok(e.data.endsWith("file_bug1375833-frame2.html"), "check location");
       is(shistory.count, 4, "check history length");
       is(shistory.index, 3, "check history index");
 
       let newFrameDocShellId = String(getFrameDocShell().historyID);
       ok(newFrameDocShellId, "sanity check for docshell ID");
       is(newFrameDocShellId, frameDocShellId, "check docshell ID remains after reload");
 
-      let entry = shistory.legacySHistory.getEntryAtIndex(shistory.index, false);
+      let entry = shistory.legacySHistory.getEntryAtIndex(shistory.index);
       let frameEntry = entry.GetChildAt(0);
       is(String(frameEntry.docshellID), frameDocShellId, "check newly added shentry uses the same docshell ID");
 
       webNav.goBack();
       break;
     case 2:
       ok(e.data.endsWith("file_bug1375833-frame1.html"), "check location");
       is(shistory.count, 4, "check history length");
--- a/docshell/test/navigation/test_sessionhistory.html
+++ b/docshell/test/navigation/test_sessionhistory.html
@@ -21,17 +21,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 
 var testFiles =
   [ "file_bug462076_1.html",         // Dynamic frames before onload
     "file_bug462076_2.html",         // Dynamic frames when handling onload
     "file_bug462076_3.html",         // Dynamic frames after onload
     "file_bug508537_1.html",         // Dynamic frames and forward-back
     "file_document_write_1.html",    // Session history + document.write
     //"file_static_and_dynamic_1.html",// Static and dynamic frames and forward-back
-    "file_bug534178.html",           // Session history transaction clean-up.
+    "file_bug534178.html",           // Session history entry clean-up.
     "file_fragment_handling_during_load.html",
     "file_nested_frames.html",
     "file_shiftReload_and_pushState.html",
     "file_scrollRestoration.html",
     "file_bug1300461.html",
     "file_bug1326251.html",
     "file_bug1379762-1.html",
     "file_bug1379762-2.html",
--- a/docshell/test/test_bug509055.html
+++ b/docshell/test/test_bug509055.html
@@ -71,17 +71,17 @@ function* runTest() {
   yield undefined;
 
   var sh = SpecialPowers.wrap(popup)
                         .docShell
                         .QueryInterface(SpecialPowers.Ci.nsIWebNavigation)
                         .sessionHistory;
 
   // Get the title of the inner popup's current SHEntry
-  var sheTitle = sh.legacySHistory.getEntryAtIndex(sh.index, false).title;
+  var sheTitle = sh.legacySHistory.getEntryAtIndex(sh.index).title;
   is(sheTitle, "Changed", "SHEntry's title should change when we change.");
 
   popup.close();
 
   SimpleTest.executeSoon(SimpleTest.finish);
 }
 
 window.addEventListener('load', function() {
--- a/docshell/test/test_bug590573.html
+++ b/docshell/test/test_bug590573.html
@@ -104,17 +104,17 @@ function dumpSHistory(theWindow)
     return;
   }
 
   dump(" count: " + sh.count + "\n");
   dump(" index: " + sh.index + "\n");
   dump(" requestedIndex: " + sh.legacySHistory.requestedIndex + "\n");
 
   for (let i = 0; i < sh.count; i++) {
-    let shentry = sh.legacySHistory.getEntryAtIndex(i, false);
+    let shentry = sh.legacySHistory.getEntryAtIndex(i);
     dump(" " + i + ": " + shentry.URI.spec + '\n');
     for (let j = 0; j < shentry.childCount; j++) {
       let child = shentry.GetChildAt(j);
       dump("   child " + j + ": " + child.URI.spec + '\n');
     }
   }
 
   return sh;
--- a/dom/animation/Animation.h
+++ b/dom/animation/Animation.h
@@ -56,16 +56,17 @@ class Animation
 protected:
   virtual ~Animation() {}
 
 public:
   explicit Animation(nsIGlobalObject* aGlobal)
     : DOMEventTargetHelper(aGlobal)
     , mPlaybackRate(1.0)
     , mAnimationIndex(sNextAnimationIndex++)
+    , mCachedChildIndex(-1)
     , mPendingState(PendingState::NotPending)
     , mFinishedAtLastComposeStyle(false)
     , mIsRelevant(false)
     , mFinishedIsResolved(false)
     , mSyncWithGeometricAnimations(false)
   {
   }
 
@@ -400,16 +401,18 @@ public:
    * where the Animation may have been cancelled.
    *
    * We need to do this synchronously because after a CSS animation/transition
    * is canceled, it will be released by its owning element and may not still
    * exist when we would normally go to queue events on the next tick.
    */
   virtual void MaybeQueueCancelEvent(const StickyTimeDuration& aActiveTime) {};
 
+  int32_t& CachedChildIndexRef() { return mCachedChildIndex; }
+
 protected:
   void SilentlySetCurrentTime(const TimeDuration& aNewCurrentTime);
   void CancelNoUpdate();
   void PlayNoUpdate(ErrorResult& aRv, LimitBehavior aLimitBehavior);
   void ResumeAt(const TimeDuration& aReadyTime);
   void PauseAt(const TimeDuration& aReadyTime);
   void FinishPendingAt(const TimeDuration& aReadyTime)
   {
@@ -574,16 +577,20 @@ protected:
   // This is kNoIndex while the animation is in the idle state and is updated
   // each time the animation transitions out of the idle state.
   //
   // Note that subclasses such as CSSTransition and CSSAnimation may repurpose
   // this member to implement their own brand of sorting. As a result, it is
   // possible for two different objects to have the same index.
   uint64_t mAnimationIndex;
 
+  // While ordering Animation objects for event dispatch, the index of the
+  // target node in its parent may be cached in mCachedChildIndex.
+  int32_t mCachedChildIndex;
+
   // Indicates if the animation is in the pending state (and what state it is
   // waiting to enter when it finished pending). We use this rather than
   // checking if this animation is tracked by a PendingAnimationTracker because
   // the animation will continue to be pending even after it has been removed
   // from the PendingAnimationTracker while it is waiting for the next tick
   // (see TriggerOnNextTick for details).
   enum class PendingState : uint8_t
   {
--- a/dom/animation/AnimationEventDispatcher.h
+++ b/dom/animation/AnimationEventDispatcher.h
@@ -242,16 +242,20 @@ private:
   // and composite order.
   // https://drafts.csswg.org/web-animations/#update-animations-and-send-events
   void SortEvents()
   {
     if (mIsSorted) {
       return;
     }
 
+    for (auto& pending : mPendingEvents) {
+      pending.mAnimation->CachedChildIndexRef() = -1;
+    }
+
     // FIXME: Replace with mPendingEvents.StableSort when bug 1147091 is
     // fixed.
     std::stable_sort(mPendingEvents.begin(), mPendingEvents.end(),
                      AnimationEventInfoLessThan());
     mIsSorted = true;
   }
   void ScheduleDispatch();
 
--- a/dom/base/nsCCUncollectableMarker.cpp
+++ b/dom/base/nsCCUncollectableMarker.cpp
@@ -254,18 +254,17 @@ MarkDocShell(nsIDocShellTreeItem* aNode,
   MarkContentViewer(cview, aCleanupJS);
 
   nsCOMPtr<nsIWebNavigation> webNav = do_QueryInterface(shell);
   RefPtr<ChildSHistory> history = webNav->GetSessionHistory();
   if (history) {
     int32_t historyCount = history->Count();
     for (int32_t i = 0; i < historyCount; ++i) {
       nsCOMPtr<nsISHEntry> shEntry;
-      history->LegacySHistory()->GetEntryAtIndex(
-        i, false, getter_AddRefs(shEntry));
+      history->LegacySHistory()->GetEntryAtIndex(i, getter_AddRefs(shEntry));
 
       MarkSHEntry(shEntry, aCleanupJS);
     }
   }
 
   int32_t i, childCount;
   aNode->GetChildCount(&childCount);
   for (i = 0; i < childCount; ++i) {
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -2649,19 +2649,22 @@ nsContentUtils::GetCommonFlattenedTreeAn
 {
   return GetCommonAncestorInternal(aElement1, aElement2, [](Element* aElement) {
     return aElement->GetFlattenedTreeParentElementForStyle();
   });
 }
 
 /* static */
 bool
-nsContentUtils::PositionIsBefore(nsINode* aNode1, nsINode* aNode2)
-{
-  return (aNode2->CompareDocumentPosition(*aNode1) &
+nsContentUtils::PositionIsBefore(nsINode* aNode1, nsINode* aNode2,
+                                 int32_t* aNode1Index,
+                                 int32_t* aNode2Index)
+{
+  // Note, CompareDocumentPosition takes the latter params in different order.
+  return (aNode2->CompareDocumentPosition(*aNode1, aNode2Index, aNode1Index) &
     (Node_Binding::DOCUMENT_POSITION_PRECEDING |
      Node_Binding::DOCUMENT_POSITION_DISCONNECTED)) ==
     Node_Binding::DOCUMENT_POSITION_PRECEDING;
 }
 
 /* static */
 int32_t
 nsContentUtils::ComparePoints(nsINode* aParent1, int32_t aOffset1,
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -429,18 +429,24 @@ public:
    * style system, if any, for two given content nodes.
    */
   static Element* GetCommonFlattenedTreeAncestorForStyle(
       Element* aElement1, Element* aElement2);
 
   /**
    * Returns true if aNode1 is before aNode2 in the same connected
    * tree.
+   * aNode1Index and aNode2Index are in/out arguments. If non-null, and value is
+   * not -1, that value is used instead of calling slow ComputeIndexOf on the
+   * parent node. If value is -1, the value will be set to the return value of
+   * ComputeIndexOf.
    */
-  static bool PositionIsBefore(nsINode* aNode1, nsINode* aNode2);
+  static bool PositionIsBefore(nsINode* aNode1, nsINode* aNode2,
+                               int32_t* aNode1Index = nullptr,
+                               int32_t* aNode2Index = nullptr);
 
   /**
    *  Utility routine to compare two "points", where a point is a
    *  node/offset pair
    *  Returns -1 if point1 < point2, 1, if point1 > point2,
    *  0 if error or if point1 == point2.
    *  NOTE! If the two nodes aren't in the same connected subtree,
    *  the result is 1, and the optional aDisconnected parameter
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -747,17 +747,19 @@ nsINode::LookupPrefix(const nsAString& a
       }
     }
   }
 
   SetDOMStringToNull(aPrefix);
 }
 
 uint16_t
-nsINode::CompareDocumentPosition(nsINode& aOtherNode) const
+nsINode::CompareDocumentPosition(nsINode& aOtherNode,
+                                 int32_t* aThisIndex,
+                                 int32_t* aOtherIndex) const
 {
   if (this == &aOtherNode) {
     return 0;
   }
   if (GetPreviousSibling() == &aOtherNode) {
     MOZ_ASSERT(GetParentNode() == aOtherNode.GetParentNode());
     return Node_Binding::DOCUMENT_POSITION_PRECEDING;
   }
@@ -847,19 +849,48 @@ nsINode::CompareDocumentPosition(nsINode
   uint32_t len;
   for (len = std::min(pos1, pos2); len > 0; --len) {
     const nsINode* child1 = parents1.ElementAt(--pos1);
     const nsINode* child2 = parents2.ElementAt(--pos2);
     if (child1 != child2) {
       // child1 or child2 can be an attribute here. This will work fine since
       // ComputeIndexOf will return -1 for the attribute making the
       // attribute be considered before any child.
-      return parent->ComputeIndexOf(child1) < parent->ComputeIndexOf(child2) ?
+      int32_t child1Index;
+      bool cachedChild1Index = false;
+      if (&aOtherNode == child1 && aOtherIndex) {
+        cachedChild1Index = true;
+        child1Index = *aOtherIndex != -1 ?
+          *aOtherIndex : parent->ComputeIndexOf(child1);
+      } else {
+        child1Index = parent->ComputeIndexOf(child1);
+      }
+
+      int32_t child2Index;
+      bool cachedChild2Index = false;
+      if (this == child2 && aThisIndex) {
+        cachedChild2Index = true;
+        child2Index = *aThisIndex != -1 ?
+          *aThisIndex : parent->ComputeIndexOf(child2);
+      } else {
+        child2Index = parent->ComputeIndexOf(child2);
+      }
+
+      uint16_t retVal = child1Index < child2Index ?
         Node_Binding::DOCUMENT_POSITION_PRECEDING :
         Node_Binding::DOCUMENT_POSITION_FOLLOWING;
+
+      if (cachedChild1Index) {
+        *aOtherIndex = child1Index;
+      }
+      if (cachedChild2Index) {
+        *aThisIndex = child2Index;
+      }
+
+      return retVal;
     }
     parent = child1;
   }
 
   // We hit the end of one of the parent chains without finding a difference
   // between the chains. That must mean that one node is an ancestor of the
   // other. The one with the shortest chain must be the ancestor.
   return pos1 < pos2 ?
@@ -1455,22 +1486,78 @@ nsINode::GetPreviousSibling() const
 {
   // Do not expose circular linked list
   if (mPreviousOrLastSibling && !mPreviousOrLastSibling->mNextSibling) {
     return nullptr;
   }
   return mPreviousOrLastSibling;
 }
 
+// CACHE_POINTER_SHIFT indicates how many steps to downshift the |this| pointer.
+// It should be small enough to not cause collisions between adjecent objects,
+// and large enough to make sure that all indexes are used.
+#define CACHE_POINTER_SHIFT 6
+#define CACHE_NUM_SLOTS 128
+#define CACHE_CHILD_LIMIT 10
+
+#define CACHE_GET_INDEX(_parent) \
+  ((NS_PTR_TO_INT32(_parent) >> CACHE_POINTER_SHIFT) & \
+   (CACHE_NUM_SLOTS - 1))
+
+struct IndexCacheSlot
+{
+  const nsINode* mParent;
+  const nsINode* mChild;
+  int32_t mChildIndex;
+};
+
+static IndexCacheSlot sIndexCache[CACHE_NUM_SLOTS];
+
+static inline void
+AddChildAndIndexToCache(const nsINode* aParent, const nsINode* aChild,
+                        int32_t aChildIndex)
+{
+  uint32_t index = CACHE_GET_INDEX(aParent);
+  sIndexCache[index].mParent = aParent;
+  sIndexCache[index].mChild = aChild;
+  sIndexCache[index].mChildIndex = aChildIndex;
+}
+
+static inline void
+GetChildAndIndexFromCache(const nsINode* aParent,
+                          const nsINode** aChild,
+                          int32_t* aChildIndex)
+{
+  uint32_t index = CACHE_GET_INDEX(aParent);
+  if (sIndexCache[index].mParent == aParent) {
+    *aChild = sIndexCache[index].mChild;
+    *aChildIndex = sIndexCache[index].mChildIndex;
+  } else {
+    *aChild = nullptr;
+    *aChildIndex = -1;
+  }
+}
+
+static inline void
+RemoveFromCache(const nsINode* aParent)
+{
+  uint32_t index = CACHE_GET_INDEX(aParent);
+  if (sIndexCache[index].mParent == aParent) {
+    sIndexCache[index] = { nullptr, nullptr, -1 };
+  }
+}
+
 void
 nsINode::AppendChildToChildList(nsIContent* aKid)
 {
   MOZ_ASSERT(aKid);
   MOZ_ASSERT(!aKid->mNextSibling);
 
+  RemoveFromCache(this);
+
   if (mFirstChild) {
     nsIContent* lastChild = GetLastChild();
     lastChild->mNextSibling = aKid;
     aKid->mPreviousOrLastSibling = lastChild;
   } else {
     mFirstChild = aKid;
   }
 
@@ -1480,16 +1567,18 @@ nsINode::AppendChildToChildList(nsIConte
 }
 
 void
 nsINode::InsertChildToChildList(nsIContent* aKid, nsIContent* aNextSibling)
 {
   MOZ_ASSERT(aKid);
   MOZ_ASSERT(aNextSibling);
 
+  RemoveFromCache(this);
+
   nsIContent* previousSibling = aNextSibling->mPreviousOrLastSibling;
   aNextSibling->mPreviousOrLastSibling = aKid;
   aKid->mPreviousOrLastSibling = previousSibling;
   aKid->mNextSibling = aNextSibling;
 
   if (aNextSibling == mFirstChild) {
     MOZ_ASSERT(!previousSibling->mNextSibling);
     mFirstChild = aKid;
@@ -1501,16 +1590,18 @@ nsINode::InsertChildToChildList(nsIConte
 }
 
 void
 nsINode::DisconnectChild(nsIContent* aKid)
 {
   MOZ_ASSERT(aKid);
   MOZ_ASSERT(GetChildCount() > 0);
 
+  RemoveFromCache(this);
+
   nsIContent* previousSibling = aKid->GetPreviousSibling();
   nsCOMPtr<nsIContent> ref = aKid;
 
   if (aKid->mNextSibling) {
     aKid->mNextSibling->mPreviousOrLastSibling = aKid->mPreviousOrLastSibling;
   } else {
     // aKid is the last child in the list
     mFirstChild->mPreviousOrLastSibling = aKid->mPreviousOrLastSibling;
@@ -1552,21 +1643,58 @@ nsINode::ComputeIndexOf(const nsINode* a
   if (aChild->GetParentNode() != this) {
     return -1;
   }
 
   if (aChild == GetLastChild()) {
     return GetChildCount() - 1;
   }
 
+  if (mChildCount >= CACHE_CHILD_LIMIT) {
+    const nsINode* child;
+    int32_t childIndex;
+    GetChildAndIndexFromCache(this, &child, &childIndex);
+    if (child) {
+      if (child == aChild) {
+        return childIndex;
+      }
+
+      int32_t nextIndex = childIndex;
+      int32_t prevIndex = childIndex;
+      nsINode* prev = child->GetPreviousSibling();
+      nsINode* next = child->GetNextSibling();
+      do {
+        if (next) {
+          ++nextIndex;
+          if (next == aChild) {
+            AddChildAndIndexToCache(this, aChild, nextIndex);
+            return nextIndex;
+          }
+          next = next->GetNextSibling();
+        }
+        if (prev) {
+          --prevIndex;
+          if (prev == aChild) {
+            AddChildAndIndexToCache(this, aChild, prevIndex);
+            return prevIndex;
+          }
+          prev = prev->GetPreviousSibling();
+        }
+      } while (prev || next);
+    }
+  }
+
   int32_t index = 0;
   nsINode* current = mFirstChild;
   while (current) {
     MOZ_ASSERT(current->GetParentNode() == this);
     if (current == aChild) {
+      if (mChildCount >= CACHE_CHILD_LIMIT) {
+        AddChildAndIndexToCache(this, current, index);
+      }
       return index;
     }
     current = current->GetNextSibling();
     ++index;
   }
 
   return -1;
 }
--- a/dom/base/nsINode.h
+++ b/dom/base/nsINode.h
@@ -1764,17 +1764,21 @@ public:
   // from content privileged script for compatibility.
   void GetBaseURIFromJS(nsAString& aBaseURI,
                         CallerType aCallerType,
                         ErrorResult& aRv) const;
   bool HasChildNodes() const
   {
     return HasChildren();
   }
-  uint16_t CompareDocumentPosition(nsINode& aOther) const;
+
+  // See nsContentUtils::PositionIsBefore for aThisIndex and aOtherIndex usage.
+  uint16_t CompareDocumentPosition(nsINode& aOther,
+                                   int32_t* aThisIndex = nullptr,
+                                   int32_t* aOtherIndex = nullptr) const;
   void GetNodeValue(nsAString& aNodeValue)
   {
     GetNodeValueInternal(aNodeValue);
   }
   void SetNodeValue(const nsAString& aNodeValue,
                     mozilla::ErrorResult& aError)
   {
     SetNodeValueInternal(aNodeValue, aError);
--- a/dom/html/test/browser_bug649778.js
+++ b/dom/html/test/browser_bug649778.js
@@ -38,17 +38,17 @@ function checkCache(url, inMemory, shoul
                        Ci.nsICacheStorage.OPEN_READONLY,
                        new CheckCacheListener(inMemory, shouldExist));
 }
 function getPopupURL() {
   var sh = popup.docShell
                 .QueryInterface(Ci.nsIWebNavigation)
                 .sessionHistory;
 
-  return sh.legacySHistory.getEntryAtIndex(sh.index, false).URI.spec;
+  return sh.legacySHistory.getEntryAtIndex(sh.index).URI.spec;
 }
 
 var wyciwygURL;
 function testContinue() {
   wyciwygURL = getPopupURL();
   is(wyciwygURL.substring(0, 10), "wyciwyg://", "Unexpected URL.");
   popup.close()
 
--- a/dom/indexedDB/ActorsChild.cpp
+++ b/dom/indexedDB/ActorsChild.cpp
@@ -3477,26 +3477,18 @@ PreprocessHelper::ProcessCurrentStream()
         ContinueWithStatus(rv);
       }
       return;
     }
   }
 
   MOZ_ASSERT(mCurrentBytecodeFileDesc);
 
-  JS::BuildIdCharVector buildId;
-  bool ok = GetBuildId(&buildId);
-  if (NS_WARN_IF(!ok)) {
-    ContinueWithStatus(NS_ERROR_FAILURE);
-    return;
-  }
-
   RefPtr<JS::WasmModule> module =
     JS::DeserializeWasmModule(mCurrentBytecodeFileDesc,
-                              std::move(buildId),
                               nullptr,
                               0);
   if (NS_WARN_IF(!module)) {
     ContinueWithStatus(NS_ERROR_FAILURE);
     return;
   }
 
   mModuleSet.AppendElement(module);
--- a/dom/tests/mochitest/whatwg/test_bug500328.html
+++ b/dom/tests/mochitest/whatwg/test_bug500328.html
@@ -170,17 +170,17 @@ function getSHistory(theWindow)
 }
 
 function getSHTitle(sh, offset)
 {
   if (!offset)
     offset = 0;
 
   // False instructs the SHistory not to modify its current index.
-  return sh.legacySHistory.getEntryAtIndex(sh.index + offset, false).title;
+  return sh.legacySHistory.getEntryAtIndex(sh.index + offset).title;
 }
 
 // Tests that win's location ends with str
 function locationEndsWith(win, str) {
   var exp = new RegExp(str + "$");
   ok(win.location.toString().match(exp),
      "Wrong window location.  Expected it to end with " +
      str + ", but actuall was " + win.location);
--- a/js/src/builtin/SelfHostingDefines.h
+++ b/js/src/builtin/SelfHostingDefines.h
@@ -134,9 +134,19 @@
 #define STRING_GENERICS_TRIM_LEFT             22
 #define STRING_GENERICS_TRIM_RIGHT            23
 #define STRING_GENERICS_METHODS_LIMIT         24
 
 #define INTL_INTERNALS_OBJECT_SLOT 0
 
 #define NOT_OBJECT_KIND_DESCRIPTOR 0
 
+#define TYPEDARRAY_KIND_INT8            0
+#define TYPEDARRAY_KIND_UINT8           1
+#define TYPEDARRAY_KIND_INT16           2
+#define TYPEDARRAY_KIND_UINT16          3
+#define TYPEDARRAY_KIND_INT32           4
+#define TYPEDARRAY_KIND_UINT32          5
+#define TYPEDARRAY_KIND_FLOAT32         6
+#define TYPEDARRAY_KIND_FLOAT64         7
+#define TYPEDARRAY_KIND_UINT8CLAMPED    8
+
 #endif
--- a/js/src/builtin/String.cpp
+++ b/js/src/builtin/String.cpp
@@ -3746,21 +3746,23 @@ static const bool js_isUriUnescaped[] = 
 /* 10 */ true, true, true, true, true, true, true, true, true, true,
 /* 11 */ true, true, true, true, true, true, true, true, true, true,
 /* 12 */ true, true, true, ____, ____, ____, true, ____
 };
 
 #undef ____
 
 static inline bool
-TransferBufferToString(StringBuffer& sb, MutableHandleValue rval)
+TransferBufferToString(StringBuffer& sb, JSString* str, MutableHandleValue rval)
 {
-    JSString* str = sb.finishString();
-    if (!str)
-        return false;
+    if (!sb.empty()) {
+        str = sb.finishString();
+        if (!str)
+            return false;
+    }
     rval.setString(str);
     return true;
 }
 
 /*
  * ECMA 3, 15.1.3 URI Handling Function Properties
  *
  * The following are implementations of the algorithms
@@ -3771,34 +3773,49 @@ enum EncodeResult { Encode_Failure, Enco
 
 // Bug 1403318: GCC sometimes inlines this Encode function rather than the
 // caller Encode function. Annotate both functions with MOZ_NEVER_INLINE resp.
 // MOZ_ALWAYS_INLINE to ensure we get the desired inlining behavior.
 template <typename CharT>
 static MOZ_NEVER_INLINE EncodeResult
 Encode(StringBuffer& sb, const CharT* chars, size_t length, const bool* unescapedSet)
 {
-    Latin1Char hexBuf[4];
+    Latin1Char hexBuf[3];
     hexBuf[0] = '%';
-    hexBuf[3] = 0;
 
     auto appendEncoded = [&sb, &hexBuf](Latin1Char c) {
         static const char HexDigits[] = "0123456789ABCDEF"; /* NB: uppercase */
 
         hexBuf[1] = HexDigits[c >> 4];
         hexBuf[2] = HexDigits[c & 0xf];
         return sb.append(hexBuf, 3);
     };
 
+    auto appendRange = [&sb, chars, length](size_t start, size_t end) {
+        MOZ_ASSERT(start <= end);
+
+        if (start < end) {
+            if (start == 0) {
+                if (!sb.reserve(length))
+                    return false;
+            }
+            return sb.append(chars + start, chars + end);
+        }
+        return true;
+    };
+
+    size_t startAppend = 0;
     for (size_t k = 0; k < length; k++) {
         CharT c = chars[k];
         if (c < 128 && (js_isUriUnescaped[c] || (unescapedSet && unescapedSet[c]))) {
-            if (!sb.append(Latin1Char(c)))
+            continue;
+        } else {
+            if (!appendRange(startAppend, k))
                 return Encode_Failure;
-        } else {
+
             if (mozilla::IsSame<CharT, Latin1Char>::value) {
                 if (c < 0x80) {
                     if (!appendEncoded(c))
                         return Encode_Failure;
                 } else {
                     if (!appendEncoded(0xC0 | (c >> 6)) || !appendEncoded(0x80 | (c & 0x3F)))
                         return Encode_Failure;
                 }
@@ -3823,34 +3840,39 @@ Encode(StringBuffer& sb, const CharT* ch
 
                 uint8_t utf8buf[4];
                 size_t L = OneUcs4ToUtf8Char(utf8buf, v);
                 for (size_t j = 0; j < L; j++) {
                     if (!appendEncoded(utf8buf[j]))
                         return Encode_Failure;
                 }
             }
+
+            startAppend = k + 1;
         }
     }
 
+    if (startAppend > 0) {
+        if (!appendRange(startAppend, length))
+            return Encode_Failure;
+    }
+
     return Encode_Success;
 }
 
 static MOZ_ALWAYS_INLINE bool
 Encode(JSContext* cx, HandleLinearString str, const bool* unescapedSet, MutableHandleValue rval)
 {
     size_t length = str->length();
     if (length == 0) {
         rval.setString(cx->runtime()->emptyString);
         return true;
     }
 
     StringBuffer sb(cx);
-    if (!sb.reserve(length))
-        return false;
 
     EncodeResult res;
     if (str->hasLatin1Chars()) {
         AutoCheckCannotGC nogc;
         res = Encode(sb, str->latin1Chars(nogc), str->length(), unescapedSet);
     } else {
         AutoCheckCannotGC nogc;
         res = Encode(sb, str->twoByteChars(nogc), str->length(), unescapedSet);
@@ -3860,46 +3882,55 @@ Encode(JSContext* cx, HandleLinearString
         return false;
 
     if (res == Encode_BadUri) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_URI);
         return false;
     }
 
     MOZ_ASSERT(res == Encode_Success);
-    return TransferBufferToString(sb, rval);
+    return TransferBufferToString(sb, str, rval);
 }
 
 enum DecodeResult { Decode_Failure, Decode_BadUri, Decode_Success };
 
 template <typename CharT>
 static DecodeResult
 Decode(StringBuffer& sb, const CharT* chars, size_t length, const bool* reservedSet)
 {
+    auto appendRange = [&sb, chars](size_t start, size_t end) {
+        MOZ_ASSERT(start <= end);
+
+        if (start < end)
+            return sb.append(chars + start, chars + end);
+        return true;
+    };
+
+    size_t startAppend = 0;
     for (size_t k = 0; k < length; k++) {
         CharT c = chars[k];
         if (c == '%') {
             size_t start = k;
             if ((k + 2) >= length)
                 return Decode_BadUri;
 
             if (!JS7_ISHEX(chars[k+1]) || !JS7_ISHEX(chars[k+2]))
                 return Decode_BadUri;
 
             uint32_t B = JS7_UNHEX(chars[k+1]) * 16 + JS7_UNHEX(chars[k+2]);
             k += 2;
             if (B < 128) {
-                c = CharT(B);
-                if (reservedSet && reservedSet[c]) {
-                    if (!sb.append(chars + start, k - start + 1))
-                        return Decode_Failure;
-                } else {
-                    if (!sb.append(c))
-                        return Decode_Failure;
-                }
+                Latin1Char ch = Latin1Char(B);
+                if (reservedSet && reservedSet[ch])
+                    continue;
+
+                if (!appendRange(startAppend, start))
+                    return Decode_Failure;
+                if (!sb.append(ch))
+                    return Decode_Failure;
             } else {
                 int n = 1;
                 while (B & (0x80 >> n))
                     n++;
 
                 if (n == 1 || n > 4)
                     return Decode_BadUri;
 
@@ -3919,37 +3950,44 @@ Decode(StringBuffer& sb, const CharT* ch
                     B = JS7_UNHEX(chars[k+1]) * 16 + JS7_UNHEX(chars[k+2]);
                     if ((B & 0xC0) != 0x80)
                         return Decode_BadUri;
 
                     k += 2;
                     octets[j] = char(B);
                 }
 
+                if (!appendRange(startAppend, start))
+                    return Decode_Failure;
+
                 uint32_t v = JS::Utf8ToOneUcs4Char(octets, n);
                 MOZ_ASSERT(v >= 128);
                 if (v >= unicode::NonBMPMin) {
                     if (v > unicode::NonBMPMax)
                         return Decode_BadUri;
 
                     if (!sb.append(unicode::LeadSurrogate(v)))
                         return Decode_Failure;
                     if (!sb.append(unicode::TrailSurrogate(v)))
                         return Decode_Failure;
                 } else {
                     if (!sb.append(char16_t(v)))
                         return Decode_Failure;
                 }
             }
-        } else {
-            if (!sb.append(c))
-                return Decode_Failure;
+
+            startAppend = k + 1;
         }
     }
 
+    if (startAppend > 0) {
+        if (!appendRange(startAppend, length))
+            return Decode_Failure;
+    }
+
     return Decode_Success;
 }
 
 static bool
 Decode(JSContext* cx, HandleLinearString str, const bool* reservedSet, MutableHandleValue rval)
 {
     size_t length = str->length();
     if (length == 0) {
@@ -3972,17 +4010,17 @@ Decode(JSContext* cx, HandleLinearString
         return false;
 
     if (res == Decode_BadUri) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_URI);
         return false;
     }
 
     MOZ_ASSERT(res == Decode_Success);
-    return TransferBufferToString(sb, rval);
+    return TransferBufferToString(sb, str, rval);
 }
 
 static bool
 str_decodeURI(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedLinearString str(cx, ArgToLinearString(cx, args, 0));
     if (!str)
@@ -4019,28 +4057,31 @@ str_encodeURI_Component(JSContext* cx, u
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedLinearString str(cx, ArgToLinearString(cx, args, 0));
     if (!str)
         return false;
 
     return Encode(cx, str, nullptr, args.rval());
 }
 
-bool
-js::EncodeURI(JSContext* cx, StringBuffer& sb, const char* chars, size_t length)
+JSString*
+js::EncodeURI(JSContext* cx, const char* chars, size_t length)
 {
+    StringBuffer sb(cx);
     EncodeResult result = Encode(sb, reinterpret_cast<const Latin1Char*>(chars), length,
                                  js_isUriReservedPlusPound);
     if (result == EncodeResult::Encode_Failure)
-        return false;
+        return nullptr;
     if (result == EncodeResult::Encode_BadUri) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_URI);
-        return false;
+        return nullptr;
     }
-    return true;
+    if (sb.empty())
+        return NewStringCopyN<CanGC>(cx, chars, length);
+    return sb.finishString();
 }
 
 static bool
 FlatStringMatchHelper(JSContext* cx, HandleString str, HandleString pattern, bool* isFlat, int32_t* match)
 {
     RootedLinearString linearPattern(cx, pattern->ensureLinear(cx));
     if (!linearPattern)
         return false;
--- a/js/src/builtin/TypedArray.js
+++ b/js/src/builtin/TypedArray.js
@@ -1186,32 +1186,50 @@ function TypedArraySort(comparefn) {
         len = callFunction(CallTypedArrayMethodIfWrapped, obj, "TypedArrayLengthMethod");
     }
 
     // Arrays with less than two elements remain unchanged when sorted.
     if (len <= 1)
         return obj;
 
     if (comparefn === undefined) {
-        if (IsUint8TypedArray(obj)) {
+        var kind = GetTypedArrayKind(obj);
+        switch (kind) {
+          case TYPEDARRAY_KIND_UINT8:
+          case TYPEDARRAY_KIND_UINT8CLAMPED:
             return CountingSort(obj, len, false /* signed */, TypedArrayCompareInt);
-        } else if (IsInt8TypedArray(obj)) {
+          case TYPEDARRAY_KIND_INT8:
             return CountingSort(obj, len, true /* signed */, TypedArrayCompareInt);
-        } else if (IsUint16TypedArray(obj)) {
-            return RadixSort(obj, len, buffer, 2 /* nbytes */, false /* signed */, false /* floating */, TypedArrayCompareInt);
-        } else if (IsInt16TypedArray(obj)) {
-            return RadixSort(obj, len, buffer, 2 /* nbytes */, true /* signed */, false /* floating */, TypedArrayCompareInt);
-        } else if (IsUint32TypedArray(obj)) {
-            return RadixSort(obj, len, buffer, 4 /* nbytes */, false /* signed */, false /* floating */, TypedArrayCompareInt);
-        } else if (IsInt32TypedArray(obj)) {
-            return RadixSort(obj, len, buffer, 4 /* nbytes */, true /* signed */, false /* floating */, TypedArrayCompareInt);
-        } else if (IsFloat32TypedArray(obj)) {
-            return RadixSort(obj, len, buffer, 4 /* nbytes */, true /* signed */, true /* floating */, TypedArrayCompare);
+          case TYPEDARRAY_KIND_UINT16:
+            return RadixSort(obj, len, buffer,
+                             2 /* nbytes */, false /* signed */, false /* floating */,
+                             TypedArrayCompareInt);
+          case TYPEDARRAY_KIND_INT16:
+            return RadixSort(obj, len, buffer,
+                             2 /* nbytes */, true /* signed */, false /* floating */,
+                             TypedArrayCompareInt);
+          case TYPEDARRAY_KIND_UINT32:
+            return RadixSort(obj, len, buffer,
+                             4 /* nbytes */, false /* signed */, false /* floating */,
+                             TypedArrayCompareInt);
+          case TYPEDARRAY_KIND_INT32:
+            return RadixSort(obj, len, buffer,
+                             4 /* nbytes */, true /* signed */, false /* floating */,
+                             TypedArrayCompareInt);
+          case TYPEDARRAY_KIND_FLOAT32:
+            return RadixSort(obj, len, buffer,
+                             4 /* nbytes */, true /* signed */, true /* floating */,
+                             TypedArrayCompare);
+          case TYPEDARRAY_KIND_FLOAT64:
+          default:
+            // Include |default| to ensure Ion marks this call as the
+            // last instruction in the if-statement.
+            assert(kind === TYPEDARRAY_KIND_FLOAT64, "unexpected typed array kind");
+            return QuickSort(obj, len, TypedArrayCompare);
         }
-        return QuickSort(obj, len, TypedArrayCompare);
     }
 
     // To satisfy step 2 from TypedArray SortCompare described in 22.2.3.26
     // the user supplied comparefn is wrapped.
     var wrappedCompareFn = function(x, y) {
         // Step a.
         var v = +comparefn(x, y);
 
--- a/js/src/jsapi-tests/testXDR.cpp
+++ b/js/src/jsapi-tests/testXDR.cpp
@@ -19,17 +19,17 @@ GetBuildId(JS::BuildIdCharVector* buildI
 {
     const char buildid[] = "testXDR";
     return buildId->append(buildid, sizeof(buildid));
 }
 
 static JSScript*
 FreezeThaw(JSContext* cx, JS::HandleScript script)
 {
-    JS::SetBuildIdOp(cx, GetBuildId);
+    JS::SetProcessBuildIdOp(::GetBuildId);
 
     // freeze
     JS::TranscodeBuffer buffer;
     JS::TranscodeResult rs = JS::EncodeScript(cx, buffer, script);
     if (rs != JS::TranscodeResult_Ok)
         return nullptr;
 
     // thaw
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -7621,19 +7621,19 @@ JS::FinishIncrementalEncoding(JSContext*
     if (!script)
         return false;
     if (!script->scriptSource()->xdrFinalizeEncoder(buffer))
         return false;
     return true;
 }
 
 JS_PUBLIC_API(void)
-JS::SetBuildIdOp(JSContext* cx, JS::BuildIdOp buildIdOp)
-{
-    cx->runtime()->buildIdOp = buildIdOp;
+JS::SetProcessBuildIdOp(JS::BuildIdOp buildIdOp)
+{
+    GetBuildId = buildIdOp;
 }
 
 JS_PUBLIC_API(void)
 JS::SetAsmJSCacheOps(JSContext* cx, const JS::AsmJSCacheOps* ops)
 {
     cx->runtime()->asmJSCacheOps = *ops;
 }
 
@@ -7649,20 +7649,19 @@ JS::IsWasmModuleObject(HandleObject obj)
 JS_PUBLIC_API(RefPtr<JS::WasmModule>)
 JS::GetWasmModule(HandleObject obj)
 {
     MOZ_ASSERT(JS::IsWasmModuleObject(obj));
     return &CheckedUnwrap(obj)->as<WasmModuleObject>().module();
 }
 
 JS_PUBLIC_API(RefPtr<JS::WasmModule>)
-JS::DeserializeWasmModule(PRFileDesc* bytecode, JS::BuildIdCharVector&& buildId,
-                          UniqueChars filename, unsigned line)
-{
-    return wasm::DeserializeModule(bytecode, std::move(buildId), std::move(filename), line);
+JS::DeserializeWasmModule(PRFileDesc* bytecode, UniqueChars filename, unsigned line)
+{
+    return wasm::DeserializeModule(bytecode, std::move(filename), line);
 }
 
 JS_PUBLIC_API(void)
 JS::SetProcessLargeAllocationFailureCallback(JS::LargeAllocationFailureCallback lafc)
 {
     MOZ_ASSERT(!OnLargeAllocationFailure);
     OnLargeAllocationFailure = lafc;
 }
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -4649,71 +4649,59 @@ SetAsmJSCacheOps(JSContext* cx, const As
 
 /**
  * Return the buildId (represented as a sequence of characters) associated with
  * the currently-executing build. If the JS engine is embedded such that a
  * single cache entry can be observed by different compiled versions of the JS
  * engine, it is critical that the buildId shall change for each new build of
  * the JS engine.
  */
+
 typedef js::Vector<char, 0, js::SystemAllocPolicy> BuildIdCharVector;
 
 typedef bool
 (* BuildIdOp)(BuildIdCharVector* buildId);
 
 extern JS_PUBLIC_API(void)
-SetBuildIdOp(JSContext* cx, BuildIdOp buildIdOp);
+SetProcessBuildIdOp(BuildIdOp buildIdOp);
 
 /**
  * The WasmModule interface allows the embedding to hold a reference to the
  * underying C++ implementation of a JS WebAssembly.Module object for purposes
  * of efficient postMessage() and (de)serialization from a random thread.
  *
- * For postMessage() sharing:
- *
- * - GetWasmModule() is called when making a structured clone of payload
+ * In particular, this allows postMessage() of a WebAssembly.Module:
+ * GetWasmModule() is called when making a structured clone of a payload
  * containing a WebAssembly.Module object. The structured clone buffer holds a
  * refcount of the JS::WasmModule until createObject() is called in the target
  * agent's JSContext. The new WebAssembly.Module object continues to hold the
  * JS::WasmModule and thus the final reference of a JS::WasmModule may be
  * dropped from any thread and so the virtual destructor (and all internal
  * methods of the C++ module) must be thread-safe.
  */
 
-class WasmModuleListener
-{
-  protected:
-    virtual ~WasmModuleListener() {}
-
-  public:
-    // These method signatures are chosen to exactly match nsISupports so that a
-    // plain nsISupports-implementing class can trivially implement this
-    // interface too. We can't simply #include "nsISupports.h" so we use MFBT
-    // equivalents for all the platform-dependent types.
-    virtual MozExternalRefCountType MOZ_XPCOM_ABI AddRef() = 0;
-    virtual MozExternalRefCountType MOZ_XPCOM_ABI Release() = 0;
-
-    virtual void onCompilationComplete() = 0;
-};
-
 struct WasmModule : js::AtomicRefCounted<WasmModule>
 {
     virtual ~WasmModule() {}
     virtual JSObject* createObject(JSContext* cx) = 0;
 };
 
 extern JS_PUBLIC_API(bool)
 IsWasmModuleObject(HandleObject obj);
 
 extern JS_PUBLIC_API(RefPtr<WasmModule>)
 GetWasmModule(HandleObject obj);
 
+/**
+ * This function will be removed when bug 1487479 expunges the last remaining
+ * bits of wasm IDB support.
+ */
+
 extern JS_PUBLIC_API(RefPtr<WasmModule>)
-DeserializeWasmModule(PRFileDesc* bytecode, BuildIdCharVector&& buildId,
-                      JS::UniqueChars filename, unsigned line);
+DeserializeWasmModule(PRFileDesc* bytecode, JS::UniqueChars filename, unsigned line);
 
 /**
  * Convenience class for imitating a JS level for-of loop. Typical usage:
  *
  *     ForOfIterator it(cx);
  *     if (!it.init(iterable))
  *       return false;
  *     RootedValue val(cx);
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -3629,17 +3629,16 @@ WorkerMain(WorkerInput* input)
         js_delete(sc);
         js_delete(input);
     });
 
     sc->isWorker = true;
     JS_SetContextPrivate(cx, sc);
     JS_SetGrayGCRootsTracer(cx, TraceGrayRoots, nullptr);
     SetWorkerContextOptions(cx);
-    JS::SetBuildIdOp(cx, ShellBuildId);
 
     JS_SetFutexCanWait(cx);
     JS::SetWarningReporter(cx, WarningReporter);
     js::SetPreserveWrapperCallback(cx, DummyPreserveWrapperCallback);
     JS_InitDestroyPrincipalsCallback(cx, ShellPrincipals::destroy);
     JS_SetDestroyCompartmentCallback(cx, DestroyShellCompartmentPrivate);
 
     js::UseInternalJobQueues(cx);
@@ -9879,16 +9878,18 @@ main(int argc, char** argv, char** envp)
     }
 
     if (op.getBoolOption("suppress-minidump"))
         js::NoteIntentionalCrash();
 
     if (!InitSharedObjectMailbox())
         return 1;
 
+    JS::SetProcessBuildIdOp(ShellBuildId);
+
     // The fake CPU count must be set before initializing the Runtime,
     // which spins up the thread pool.
     int32_t cpuCount = op.getIntOption("cpu-count"); // What we're really setting
     if (cpuCount < 0)
         cpuCount = op.getIntOption("thread-count");  // Legacy name
     if (cpuCount >= 0)
         SetFakeCPUCount(cpuCount);
 
@@ -9919,17 +9920,16 @@ main(int argc, char** argv, char** envp)
         JS_SetGCParametersBasedOnAvailableMemory(cx, availMem);
 
     JS_SetTrustedPrincipals(cx, &ShellPrincipals::fullyTrusted);
     JS_SetSecurityCallbacks(cx, &ShellPrincipals::securityCallbacks);
     JS_InitDestroyPrincipalsCallback(cx, ShellPrincipals::destroy);
     JS_SetDestroyCompartmentCallback(cx, DestroyShellCompartmentPrivate);
 
     JS_AddInterruptCallback(cx, ShellInterruptCallback);
-    JS::SetBuildIdOp(cx, ShellBuildId);
     JS::SetAsmJSCacheOps(cx, &asmJSCacheOps);
 
     bufferStreamState =
         js_new<ExclusiveWaitableData<BufferStreamState>>(mutexid::BufferStreamState);
     if (!bufferStreamState)
         return 1;
     auto shutdownBufferStreams = MakeScopeExit([] {
         ShutdownBufferStreams();
--- a/js/src/util/Text.h
+++ b/js/src/util/Text.h
@@ -214,14 +214,14 @@ inline bool
 FileEscapedString(FILE* fp, const char* chars, size_t length, uint32_t quote)
 {
     Fprinter out(fp);
     bool res = EscapedStringPrinter(out, chars, length, quote);
     out.finish();
     return res;
 }
 
-bool
-EncodeURI(JSContext* cx, StringBuffer& sb, const char* chars, size_t length);
+JSString*
+EncodeURI(JSContext* cx, const char* chars, size_t length);
 
 } // namespace js
 
 #endif // util_Text_h
--- a/js/src/vm/JSContext.h
+++ b/js/src/vm/JSContext.h
@@ -221,17 +221,16 @@ struct JSContext : public JS::RootingCon
     JSAtomState& names() { return *runtime_->commonNames; }
     js::StaticStrings& staticStrings() { return *runtime_->staticStrings; }
     js::SharedImmutableStringsCache& sharedImmutableStrings() {
         return runtime_->sharedImmutableStrings();
     }
     bool permanentAtomsPopulated() { return runtime_->permanentAtomsPopulated(); }
     const js::FrozenAtomSet& permanentAtoms() { return *runtime_->permanentAtoms(); }
     js::WellKnownSymbols& wellKnownSymbols() { return *runtime_->wellKnownSymbols; }
-    JS::BuildIdOp buildIdOp() { return runtime_->buildIdOp; }
     const JS::AsmJSCacheOps& asmJSCacheOps() { return runtime_->asmJSCacheOps; }
     js::PropertyName* emptyString() { return runtime_->emptyString; }
     js::FreeOp* defaultFreeOp() { return runtime_->defaultFreeOp(); }
     void* stackLimitAddress(JS::StackKind kind) { return &nativeStackLimit[kind]; }
     void* stackLimitAddressForJitCode(JS::StackKind kind);
     uintptr_t stackLimit(JS::StackKind kind) { return nativeStackLimit[kind]; }
     uintptr_t stackLimitForJitCode(JS::StackKind kind);
     size_t gcSystemPageSize() { return js::gc::SystemPageSize(); }
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -61,16 +61,17 @@ using mozilla::NegativeInfinity;
 using mozilla::PodZero;
 using mozilla::PositiveInfinity;
 using JS::AutoStableStringChars;
 using JS::DoubleNaNValue;
 
 /* static */ MOZ_THREAD_LOCAL(JSContext*) js::TlsContext;
 /* static */ Atomic<size_t> JSRuntime::liveRuntimesCount;
 Atomic<JS::LargeAllocationFailureCallback> js::OnLargeAllocationFailure;
+Atomic<JS::BuildIdOp> js::GetBuildId;
 
 namespace js {
     bool gCanUseExtraThreads = true;
 } // namespace js
 
 void
 js::DisableExtraThreads()
 {
@@ -114,17 +115,16 @@ JSRuntime::JSRuntime(JSRuntime* parentRu
     realmNameCallback(nullptr),
     externalStringSizeofCallback(nullptr),
     securityCallbacks(&NullSecurityCallbacks),
     DOMcallbacks(nullptr),
     destroyPrincipals(nullptr),
     readPrincipals(nullptr),
     warningReporter(nullptr),
     geckoProfiler_(thisFromCtor()),
-    buildIdOp(nullptr),
     trustedPrincipals_(nullptr),
     wrapObjectCallbacks(&DefaultWrapObjectCallbacks),
     preserveWrapperCallback(nullptr),
     scriptEnvironmentPreparer(nullptr),
     ctypesActivityCallback(nullptr),
     windowProxyClass_(nullptr),
     scriptDataLock(mutexid::RuntimeScriptData),
 #ifdef DEBUG
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -395,18 +395,16 @@ struct JSRuntime : public js::MallocProv
                                                  mozilla::LinkedList<JS::PersistentRooted<void*>>>> heapRoots;
 
     void tracePersistentRoots(JSTracer* trc);
     void finishPersistentRoots();
 
     void finishRoots();
 
   public:
-    js::UnprotectedData<JS::BuildIdOp> buildIdOp;
-
     /* AsmJSCache callbacks are runtime-wide. */
     js::UnprotectedData<JS::AsmJSCacheOps> asmJSCacheOps;
 
   private:
     js::UnprotectedData<const JSPrincipals*> trustedPrincipals_;
   public:
     void setTrustedPrincipals(const JSPrincipals* p) { trustedPrincipals_ = p; }
     const JSPrincipals* trustedPrincipals() const { return trustedPrincipals_; }
@@ -1188,11 +1186,14 @@ SetValueRangeToNull(Value* vec, size_t l
 }
 
 extern const JSSecurityCallbacks NullSecurityCallbacks;
 
 // This callback is set by JS::SetProcessLargeAllocationFailureCallback
 // and may be null. See comment in jsapi.h.
 extern mozilla::Atomic<JS::LargeAllocationFailureCallback> OnLargeAllocationFailure;
 
+// This callback is set by JS::SetBuildIdOp and may be null. See comment in jsapi.h.
+extern mozilla::Atomic<JS::BuildIdOp> GetBuildId;
+
 } /* namespace js */
 
 #endif /* vm_Runtime_h */
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -1014,74 +1014,49 @@ intrinsic_SharedArrayBuffersMemorySame(J
     }
 
     args.rval().setBoolean(lhs->as<SharedArrayBufferObject>().rawBufferObject() ==
                            rhs->as<SharedArrayBufferObject>().rawBufferObject());
     return true;
 }
 
 static bool
-intrinsic_IsSpecificTypedArray(JSContext* cx, unsigned argc, Value* vp, Scalar::Type type)
+intrinsic_GetTypedArrayKind(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 1);
     MOZ_ASSERT(args[0].isObject());
 
+    static_assert(TYPEDARRAY_KIND_INT8 == Scalar::Type::Int8,
+                  "TYPEDARRAY_KIND_INT8 doesn't match the scalar type");
+    static_assert(TYPEDARRAY_KIND_UINT8 == Scalar::Type::Uint8,
+                  "TYPEDARRAY_KIND_UINT8 doesn't match the scalar type");
+    static_assert(TYPEDARRAY_KIND_INT16 == Scalar::Type::Int16,
+                  "TYPEDARRAY_KIND_INT16 doesn't match the scalar type");
+    static_assert(TYPEDARRAY_KIND_UINT16 == Scalar::Type::Uint16,
+                  "TYPEDARRAY_KIND_UINT16 doesn't match the scalar type");
+    static_assert(TYPEDARRAY_KIND_INT32 == Scalar::Type::Int32,
+                  "TYPEDARRAY_KIND_INT32 doesn't match the scalar type");
+    static_assert(TYPEDARRAY_KIND_UINT32 == Scalar::Type::Uint32,
+                  "TYPEDARRAY_KIND_UINT32 doesn't match the scalar type");
+    static_assert(TYPEDARRAY_KIND_FLOAT32 == Scalar::Type::Float32,
+                  "TYPEDARRAY_KIND_FLOAT32 doesn't match the scalar type");
+    static_assert(TYPEDARRAY_KIND_FLOAT64 == Scalar::Type::Float64,
+                  "TYPEDARRAY_KIND_FLOAT64 doesn't match the scalar type");
+    static_assert(TYPEDARRAY_KIND_UINT8CLAMPED == Scalar::Type::Uint8Clamped,
+                  "TYPEDARRAY_KIND_UINT8CLAMPED doesn't match the scalar type");
+
     JSObject* obj = &args[0].toObject();
-
-    bool isArray = JS_GetArrayBufferViewType(obj) == type;
-
-    args.rval().setBoolean(isArray);
+    Scalar::Type type = JS_GetArrayBufferViewType(obj);
+
+    args.rval().setInt32(static_cast<int32_t>(type));
     return true;
 }
 
 static bool
-intrinsic_IsUint8TypedArray(JSContext* cx, unsigned argc, Value* vp)
-{
-    return intrinsic_IsSpecificTypedArray(cx, argc, vp, Scalar::Uint8) ||
-           intrinsic_IsSpecificTypedArray(cx, argc, vp, Scalar::Uint8Clamped);
-}
-
-static bool
-intrinsic_IsInt8TypedArray(JSContext* cx, unsigned argc, Value* vp)
-{
-    return intrinsic_IsSpecificTypedArray(cx, argc, vp, Scalar::Int8);
-}
-
-static bool
-intrinsic_IsUint16TypedArray(JSContext* cx, unsigned argc, Value* vp)
-{
-    return intrinsic_IsSpecificTypedArray(cx, argc, vp, Scalar::Uint16);
-}
-
-static bool
-intrinsic_IsInt16TypedArray(JSContext* cx, unsigned argc, Value* vp)
-{
-    return intrinsic_IsSpecificTypedArray(cx, argc, vp, Scalar::Int16);
-}
-
-static bool
-intrinsic_IsUint32TypedArray(JSContext* cx, unsigned argc, Value* vp)
-{
-    return intrinsic_IsSpecificTypedArray(cx, argc, vp, Scalar::Uint32);
-}
-
-static bool
-intrinsic_IsInt32TypedArray(JSContext* cx, unsigned argc, Value* vp)
-{
-    return intrinsic_IsSpecificTypedArray(cx, argc, vp, Scalar::Int32);
-}
-
-static bool
-intrinsic_IsFloat32TypedArray(JSContext* cx, unsigned argc, Value* vp)
-{
-    return intrinsic_IsSpecificTypedArray(cx, argc, vp, Scalar::Float32);
-}
-
-static bool
 intrinsic_IsPossiblyWrappedTypedArray(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 1);
 
     bool isTypedArray = false;
     if (args[0].isObject()) {
         JSObject* obj = CheckedUnwrap(&args[0].toObject());
@@ -2540,23 +2515,17 @@ static const JSFunctionSpec intrinsic_fu
           intrinsic_ArrayBufferByteLength<SharedArrayBufferObject>,     1,0),
     JS_FN("PossiblyWrappedSharedArrayBufferByteLength",
           intrinsic_PossiblyWrappedArrayBufferByteLength<SharedArrayBufferObject>, 1,0),
     JS_FN("SharedArrayBufferCopyData",
           intrinsic_ArrayBufferCopyData<SharedArrayBufferObject>,       6,0),
     JS_FN("SharedArrayBuffersMemorySame",
           intrinsic_SharedArrayBuffersMemorySame,                       2,0),
 
-    JS_FN("IsUint8TypedArray",        intrinsic_IsUint8TypedArray,      1,0),
-    JS_FN("IsInt8TypedArray",         intrinsic_IsInt8TypedArray,       1,0),
-    JS_FN("IsUint16TypedArray",       intrinsic_IsUint16TypedArray,     1,0),
-    JS_FN("IsInt16TypedArray",        intrinsic_IsInt16TypedArray,      1,0),
-    JS_FN("IsUint32TypedArray",       intrinsic_IsUint32TypedArray,     1,0),
-    JS_FN("IsInt32TypedArray",        intrinsic_IsInt32TypedArray,      1,0),
-    JS_FN("IsFloat32TypedArray",      intrinsic_IsFloat32TypedArray,    1,0),
+    JS_FN("GetTypedArrayKind", intrinsic_GetTypedArrayKind,             1,0),
     JS_INLINABLE_FN("IsTypedArray",
                     intrinsic_IsInstanceOfBuiltin<TypedArrayObject>,    1,0,
                     IntrinsicIsTypedArray),
     JS_INLINABLE_FN("IsPossiblyWrappedTypedArray",intrinsic_IsPossiblyWrappedTypedArray,1,0,
                     IntrinsicIsPossiblyWrappedTypedArray),
 
     JS_FN("TypedArrayBuffer",        intrinsic_TypedArrayBuffer,        1,0),
     JS_FN("TypedArrayByteOffset",    intrinsic_TypedArrayByteOffset,    1,0),
--- a/js/src/vm/Xdr.cpp
+++ b/js/src/vm/Xdr.cpp
@@ -81,18 +81,18 @@ XDRState<mode>::codeChars(char16_t* char
     return Ok();
 }
 
 template<XDRMode mode>
 static XDRResult
 VersionCheck(XDRState<mode>* xdr)
 {
     JS::BuildIdCharVector buildId;
-    MOZ_ASSERT(xdr->cx()->buildIdOp());
-    if (!xdr->cx()->buildIdOp()(&buildId)) {
+    MOZ_ASSERT(GetBuildId);
+    if (!GetBuildId(&buildId)) {
         ReportOutOfMemory(xdr->cx());
         return xdr->fail(JS::TranscodeResult_Throw);
     }
     MOZ_ASSERT(!buildId.empty());
 
     uint32_t buildIdLength;
     if (mode == XDR_ENCODE)
         buildIdLength = buildId.length();
--- a/js/src/wasm/AsmJS.cpp
+++ b/js/src/wasm/AsmJS.cpp
@@ -2074,17 +2074,17 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED Modu
 
     bool startFunctionBodies() {
         if (!arrayViews_.empty())
             env_.memoryUsage = MemoryUsage::Unshared;
         else
             env_.memoryUsage = MemoryUsage::None;
         return true;
     }
-    SharedModule finish() {
+    SharedModule finish(UniqueLinkData* linkData) {
         MOZ_ASSERT(env_.funcTypes.empty());
         if (!env_.funcTypes.resize(funcImportMap_.count() + funcDefs_.length()))
             return nullptr;
         for (FuncImportMap::Range r = funcImportMap_.all(); !r.empty(); r.popFront()) {
             uint32_t funcIndex = r.front().value();
             MOZ_ASSERT(!env_.funcTypes[funcIndex]);
             env_.funcTypes[funcIndex] = &env_.types[r.front().key().sigIndex()].funcType();
         }
@@ -2117,18 +2117,18 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED Modu
         ScriptedCaller scriptedCaller;
         if (parser_.ss->filename()) {
             scriptedCaller.line = 0;  // unused
             scriptedCaller.filename = DuplicateString(parser_.ss->filename());
             if (!scriptedCaller.filename)
                 return nullptr;
         }
 
-        MutableCompileArgs args = cx_->new_<CompileArgs>();
-        if (!args || !args->initFromContext(cx_, std::move(scriptedCaller)))
+        MutableCompileArgs args = cx_->new_<CompileArgs>(cx_, std::move(scriptedCaller));
+        if (!args)
             return nullptr;
 
         uint32_t codeSectionSize = 0;
         for (const Func& func : funcDefs_)
             codeSectionSize += func.bytes().length();
 
         env_.codeSection.emplace();
         env_.codeSection->start = 0;
@@ -2150,17 +2150,17 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED Modu
                                    std::move(func.callSiteLineNums()))) {
                 return nullptr;
             }
         }
 
         if (!mg.finishFuncDefs())
             return nullptr;
 
-        return mg.finishModule(*bytes);
+        return mg.finishModule(*bytes, linkData);
     }
 };
 
 /*****************************************************************************/
 // Numeric literal utilities
 
 static bool
 IsNumericNonFloatLiteral(ParseNode* pn)
@@ -5680,17 +5680,18 @@ CheckModuleEnd(ModuleValidator &m)
     if (tk != TokenKind::Eof && tk != TokenKind::RightCurly)
         return m.failCurrentOffset("top-level export (return) must be the last statement");
 
     m.parser().tokenStream.anyCharsAccess().ungetToken();
     return true;
 }
 
 static SharedModule
-CheckModule(JSContext* cx, AsmJSParser& parser, ParseNode* stmtList, unsigned* time)
+CheckModule(JSContext* cx, AsmJSParser& parser, ParseNode* stmtList, UniqueLinkData* linkData,
+            unsigned* time)
 {
     int64_t before = PRMJ_Now();
 
     ParseNode* moduleFunctionNode = parser.pc->functionBox()->functionNode;
     MOZ_ASSERT(moduleFunctionNode);
 
     ModuleValidator m(cx, parser, moduleFunctionNode);
     if (!m.init())
@@ -5721,17 +5722,17 @@ CheckModule(JSContext* cx, AsmJSParser& 
         return nullptr;
 
     if (!CheckModuleReturn(m))
         return nullptr;
 
     if (!CheckModuleEnd(m))
         return nullptr;
 
-    SharedModule module = m.finish();
+    SharedModule module = m.finish(linkData);
     if (!module)
         return nullptr;
 
     *time = (PRMJ_Now() - before) / PRMJ_USEC_PER_MSEC;
     return module;
 }
 
 /*****************************************************************************/
@@ -6321,16 +6322,82 @@ AsmJSMetadata::sizeOfExcludingThis(Mallo
            SizeOfVectorExcludingThis(asmJSFuncNames, mallocSizeOf) +
            globalArgumentName.sizeOfExcludingThis(mallocSizeOf) +
            importArgumentName.sizeOfExcludingThis(mallocSizeOf) +
            bufferArgumentName.sizeOfExcludingThis(mallocSizeOf);
 }
 
 namespace {
 
+// Assumptions captures ambient state that must be the same when compiling and
+// deserializing a module for the compiled code to be valid. If it's not, then
+// the module must be recompiled from scratch.
+
+struct Assumptions
+{
+    uint32_t              cpuId;
+    JS::BuildIdCharVector buildId;
+
+    Assumptions();
+    bool init();
+
+    bool operator==(const Assumptions& rhs) const;
+    bool operator!=(const Assumptions& rhs) const { return !(*this == rhs); }
+
+    size_t serializedSize() const;
+    uint8_t* serialize(uint8_t* cursor) const;
+    const uint8_t* deserialize(const uint8_t* cursor, size_t remain);
+};
+
+Assumptions::Assumptions()
+  : cpuId(ObservedCPUFeatures()),
+    buildId()
+{}
+
+bool
+Assumptions::init()
+{
+    return GetBuildId && GetBuildId(&buildId);
+}
+
+bool
+Assumptions::operator==(const Assumptions& rhs) const
+{
+    return cpuId == rhs.cpuId &&
+           buildId.length() == rhs.buildId.length() &&
+           ArrayEqual(buildId.begin(), rhs.buildId.begin(), buildId.length());
+}
+
+size_t
+Assumptions::serializedSize() const
+{
+    return sizeof(uint32_t) +
+           SerializedPodVectorSize(buildId);
+}
+
+uint8_t*
+Assumptions::serialize(uint8_t* cursor) const
+{
+    // The format of serialized Assumptions must never change in a way that
+    // would cause old cache files written with by an old build-id to match the
+    // assumptions of a different build-id.
+
+    cursor = WriteScalar<uint32_t>(cursor, cpuId);
+    cursor = SerializePodVector(cursor, buildId);
+    return cursor;
+}
+
+const uint8_t*
+Assumptions::deserialize(const uint8_t* cursor, size_t remain)
+{
+    (cursor = ReadScalarChecked<uint32_t>(cursor, &remain, &cpuId)) &&
+    (cursor = DeserializePodVectorChecked(cursor, &remain, &buildId));
+    return cursor;
+}
+
 class ModuleChars
 {
   protected:
     uint32_t isFunCtor_;
     Vector<CacheableChars, 0, SystemAllocPolicy> funCtorArgs_;
 
   public:
     static uint32_t beginOffset(AsmJSParser& parser) {
@@ -6512,29 +6579,34 @@ struct ScopedCacheEntryOpenedForRead
         if (memory)
             cx->asmJSCacheOps().closeEntryForRead(serializedSize, memory, handle);
     }
 };
 
 } // unnamed namespace
 
 static JS::AsmJSCacheResult
-StoreAsmJSModuleInCache(AsmJSParser& parser, Module& module, JSContext* cx)
+StoreAsmJSModuleInCache(AsmJSParser& parser, Module& module, const LinkData& linkData, JSContext* cx)
 {
     ModuleCharsForStore moduleChars;
     if (!moduleChars.init(parser))
         return JS::AsmJSCache_InternalError;
 
     MOZ_RELEASE_ASSERT(module.bytecode().length() == 0);
 
-    size_t compiledSize = module.compiledSerializedSize();
-    MOZ_RELEASE_ASSERT(compiledSize <= UINT32_MAX);
-
-    size_t serializedSize = sizeof(uint32_t) +
-                            compiledSize +
+    size_t moduleSize = module.serializedSize(linkData);
+    MOZ_RELEASE_ASSERT(moduleSize <= UINT32_MAX);
+
+    Assumptions assumptions;
+    if (!assumptions.init())
+        return JS::AsmJSCache_InternalError;
+
+    size_t serializedSize = assumptions.serializedSize() +
+                            sizeof(uint32_t) +
+                            moduleSize +
                             moduleChars.serializedSize();
 
     JS::OpenAsmJSCacheEntryForWriteOp open = cx->asmJSCacheOps().openEntryForWrite;
     if (!open)
         return JS::AsmJSCache_Disabled_Internal;
 
     const char16_t* begin = parser.tokenStream.codeUnitPtrAt(ModuleChars::beginOffset(parser));
     const char16_t* end = parser.tokenStream.codeUnitPtrAt(ModuleChars::endOffset(parser));
@@ -6542,24 +6614,22 @@ StoreAsmJSModuleInCache(AsmJSParser& par
     ScopedCacheEntryOpenedForWrite entry(cx, serializedSize);
     JS::AsmJSCacheResult openResult =
         open(cx->global(), begin, end, serializedSize, &entry.memory, &entry.handle);
     if (openResult != JS::AsmJSCache_Success)
         return openResult;
 
     uint8_t* cursor = entry.memory;
 
-    // Everything serialized before the Module must not change incompatibly
-    // between any two builds (regardless of platform, architecture, ...).
-    // (The Module::assumptionsMatch() guard everything in the Module and
-    // afterwards.)
-    cursor = WriteScalar<uint32_t>(cursor, compiledSize);
-
-    module.compiledSerialize(cursor, compiledSize);
-    cursor += compiledSize;
+    cursor = assumptions.serialize(cursor);
+
+    cursor = WriteScalar<uint32_t>(cursor, moduleSize);
+
+    module.serialize(linkData, cursor, moduleSize);
+    cursor += moduleSize;
 
     cursor = moduleChars.serialize(cursor);
 
     MOZ_RELEASE_ASSERT(cursor == entry.memory + serializedSize);
 
     return JS::AsmJSCache_Success;
 }
 
@@ -6577,42 +6647,42 @@ LookupAsmJSModuleInCache(JSContext* cx, 
 
     const char16_t* begin = parser.tokenStream.codeUnitPtrAt(ModuleChars::beginOffset(parser));
     const char16_t* limit = parser.tokenStream.rawLimit();
 
     ScopedCacheEntryOpenedForRead entry(cx);
     if (!open(cx->global(), begin, limit, &entry.serializedSize, &entry.memory, &entry.handle))
         return true;
 
-    size_t remain = entry.serializedSize;
     const uint8_t* cursor = entry.memory;
 
-    uint32_t compiledSize;
-    cursor = ReadScalarChecked<uint32_t>(cursor, &remain, &compiledSize);
+    Assumptions deserializedAssumptions;
+    cursor = deserializedAssumptions.deserialize(cursor, entry.serializedSize);
     if (!cursor)
         return true;
 
-    Assumptions assumptions;
-    if (!assumptions.initBuildIdFromContext(cx))
-        return false;
-
-    if (!Module::assumptionsMatch(assumptions, cursor, remain))
+    Assumptions currentAssumptions;
+    if (!currentAssumptions.init() || currentAssumptions != deserializedAssumptions)
+        return true;
+
+    uint32_t moduleSize;
+    cursor = ReadScalar<uint32_t>(cursor, &moduleSize);
+    if (!cursor)
         return true;
 
     MutableAsmJSMetadata asmJSMetadata = cx->new_<AsmJSMetadata>();
     if (!asmJSMetadata)
         return false;
 
-    *module = Module::deserialize(/* bytecodeBegin = */ nullptr, /* bytecodeSize = */ 0,
-                                  cursor, compiledSize, asmJSMetadata.get());
+    *module = Module::deserialize(cursor, moduleSize, asmJSMetadata.get());
     if (!*module) {
         ReportOutOfMemory(cx);
         return false;
     }
-    cursor += compiledSize;
+    cursor += moduleSize;
 
     // Due to the hash comparison made by openEntryForRead, this should succeed
     // with high probability.
     ModuleCharsForLookup moduleChars;
     cursor = moduleChars.deserialize(cursor);
     if (!moduleChars.match(parser))
         return true;
 
@@ -6770,26 +6840,27 @@ js::CompileAsmJS(JSContext* cx, AsmJSPar
     if (!LookupAsmJSModuleInCache(cx, parser, &loadedFromCache, &module, &message))
         return false;
 
     // If not present in the cache, parse, validate and generate code in a
     // single linear pass over the chars of the asm.js module.
     if (!loadedFromCache) {
         // "Checking" parses, validates and compiles, producing a fully compiled
         // WasmModuleObject as result.
+        UniqueLinkData linkData;
         unsigned time;
-        module = CheckModule(cx, parser, stmtList, &time);
+        module = CheckModule(cx, parser, stmtList, &linkData, &time);
         if (!module)
             return NoExceptionPending(cx);
 
         // Try to store the AsmJSModule in the embedding's cache. The
         // AsmJSModule must be stored before static linking since static linking
         // specializes the AsmJSModule to the current process's address space
         // and therefore must be executed after a cache hit.
-        JS::AsmJSCacheResult cacheResult = StoreAsmJSModuleInCache(parser, *module, cx);
+        JS::AsmJSCacheResult cacheResult = StoreAsmJSModuleInCache(parser, *module, *linkData, cx);
 
         // Build the string message to display in the developer console.
         message = BuildConsoleMessage(time, cacheResult);
         if (!message)
             return NoExceptionPending(cx);
     }
 
     // Hand over ownership to a GC object wrapper which can then be referenced
--- a/js/src/wasm/WasmCode.cpp
+++ b/js/src/wasm/WasmCode.cpp
@@ -35,16 +35,83 @@
 
 using namespace js;
 using namespace js::jit;
 using namespace js::wasm;
 using mozilla::BinarySearch;
 using mozilla::MakeEnumeratedRange;
 using mozilla::PodAssign;
 
+size_t
+LinkData::SymbolicLinkArray::serializedSize() const
+{
+    size_t size = 0;
+    for (const Uint32Vector& offsets : *this)
+        size += SerializedPodVectorSize(offsets);
+    return size;
+}
+
+uint8_t*
+LinkData::SymbolicLinkArray::serialize(uint8_t* cursor) const
+{
+    for (const Uint32Vector& offsets : *this)
+        cursor = SerializePodVector(cursor, offsets);
+    return cursor;
+}
+
+const uint8_t*
+LinkData::SymbolicLinkArray::deserialize(const uint8_t* cursor)
+{
+    for (Uint32Vector& offsets : *this) {
+        cursor = DeserializePodVector(cursor, &offsets);
+        if (!cursor)
+            return nullptr;
+    }
+    return cursor;
+}
+
+size_t
+LinkData::SymbolicLinkArray::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
+{
+    size_t size = 0;
+    for (const Uint32Vector& offsets : *this)
+        size += offsets.sizeOfExcludingThis(mallocSizeOf);
+    return size;
+}
+
+size_t
+LinkData::serializedSize() const
+{
+    return sizeof(pod()) +
+           SerializedPodVectorSize(internalLinks) +
+           symbolicLinks.serializedSize();
+}
+
+uint8_t*
+LinkData::serialize(uint8_t* cursor) const
+{
+    MOZ_ASSERT(tier == Tier::Serialized);
+
+    cursor = WriteBytes(cursor, &pod(), sizeof(pod()));
+    cursor = SerializePodVector(cursor, internalLinks);
+    cursor = symbolicLinks.serialize(cursor);
+    return cursor;
+}
+
+const uint8_t*
+LinkData::deserialize(const uint8_t* cursor)
+{
+    MOZ_ASSERT(tier == Tier::Serialized);
+
+    (cursor = ReadBytes(cursor, &pod(), sizeof(pod()))) &&
+    (cursor = DeserializePodVector(cursor, &internalLinks)) &&
+    (cursor = symbolicLinks.deserialize(cursor));
+    return cursor;
+}
+
 CodeSegment::~CodeSegment()
 {
     if (unregisterOnDestroy_)
         UnregisterCodeSegment(this);
 }
 
 static uint32_t
 RoundupCodeLength(uint32_t codeLength)
@@ -129,19 +196,19 @@ FreeCode::operator()(uint8_t* bytes)
 
 #ifdef MOZ_VTUNE
     vtune::UnmarkBytes(bytes, codeLength);
 #endif
     DeallocateExecutableMemory(bytes, codeLength);
 }
 
 static bool
-StaticallyLink(const ModuleSegment& ms, const LinkDataTier& linkData)
+StaticallyLink(const ModuleSegment& ms, const LinkData& linkData)
 {
-    for (LinkDataTier::InternalLink link : linkData.internalLinks) {
+    for (LinkData::InternalLink link : linkData.internalLinks) {
         CodeLabel label;
         label.patchAt()->bind(link.patchAtOffset);
         label.target()->bind(link.targetOffset);
 #ifdef JS_CODELABEL_LINKMODE
         label.setLinkMode(static_cast<CodeLabel::LinkMode>(link.mode));
 #endif
         Assembler::Bind(ms.base(), label);
     }
@@ -162,19 +229,19 @@ StaticallyLink(const ModuleSegment& ms, 
                                                PatchedImmPtr((void*)-1));
         }
     }
 
     return true;
 }
 
 static void
-StaticallyUnlink(uint8_t* base, const LinkDataTier& linkData)
+StaticallyUnlink(uint8_t* base, const LinkData& linkData)
 {
-    for (LinkDataTier::InternalLink link : linkData.internalLinks) {
+    for (LinkData::InternalLink link : linkData.internalLinks) {
         CodeLabel label;
         label.patchAt()->bind(link.patchAtOffset);
         label.target()->bind(-size_t(base)); // to reset immediate to null
 #ifdef JS_CODELABEL_LINKMODE
         label.setLinkMode(static_cast<CodeLabel::LinkMode>(link.mode));
 #endif
         Assembler::Bind(base, label);
     }
@@ -270,56 +337,56 @@ SendCodeRangesToProfiler(const ModuleSeg
         vtune::MarkWasm(vtune::GenerateUniqueMethodID(), name.begin(), (void*)start, size);
 #endif
     }
 }
 
 ModuleSegment::ModuleSegment(Tier tier,
                              UniqueCodeBytes codeBytes,
                              uint32_t codeLength,
-                             const LinkDataTier& linkData)
+                             const LinkData& linkData)
   : CodeSegment(std::move(codeBytes), codeLength, CodeSegment::Kind::Module),
     tier_(tier),
     trapCode_(base() + linkData.trapOffset)
 {
 }
 
 /* static */ UniqueModuleSegment
-ModuleSegment::create(Tier tier, MacroAssembler& masm, const LinkDataTier& linkData)
+ModuleSegment::create(Tier tier, MacroAssembler& masm, const LinkData& linkData)
 {
     uint32_t codeLength = masm.bytesNeeded();
 
     UniqueCodeBytes codeBytes = AllocateCodeBytes(codeLength);
     if (!codeBytes)
         return nullptr;
 
     // We'll flush the icache after static linking, in initialize().
     masm.executableCopy(codeBytes.get(), /* flushICache = */ false);
 
     return js::MakeUnique<ModuleSegment>(tier, std::move(codeBytes), codeLength, linkData);
 }
 
 /* static */ UniqueModuleSegment
-ModuleSegment::create(Tier tier, const Bytes& unlinkedBytes, const LinkDataTier& linkData)
+ModuleSegment::create(Tier tier, const Bytes& unlinkedBytes, const LinkData& linkData)
 {
     uint32_t codeLength = unlinkedBytes.length();
 
     UniqueCodeBytes codeBytes = AllocateCodeBytes(codeLength);
     if (!codeBytes)
         return nullptr;
 
     memcpy(codeBytes.get(), unlinkedBytes.begin(), codeLength);
 
     return js::MakeUnique<ModuleSegment>(tier, std::move(codeBytes), codeLength, linkData);
 }
 
 bool
 ModuleSegment::initialize(const CodeTier& codeTier,
                           const ShareableBytes& bytecode,
-                          const LinkDataTier& linkData,
+                          const LinkData& linkData,
                           const Metadata& metadata,
                           const MetadataTier& metadataTier)
 {
     if (!StaticallyLink(*this, linkData))
         return false;
 
     ExecutableAllocator::cacheFlush(base(), RoundupCodeLength(length()));
 
@@ -342,29 +409,29 @@ ModuleSegment::serializedSize() const
 void
 ModuleSegment::addSizeOfMisc(mozilla::MallocSizeOf mallocSizeOf, size_t* code, size_t* data) const
 {
     CodeSegment::addSizeOfMisc(mallocSizeOf, code);
     *data += mallocSizeOf(this);
 }
 
 uint8_t*
-ModuleSegment::serialize(uint8_t* cursor, const LinkDataTier& linkData) const
+ModuleSegment::serialize(uint8_t* cursor, const LinkData& linkData) const
 {
     MOZ_ASSERT(tier() == Tier::Serialized);
 
     cursor = WriteScalar<uint32_t>(cursor, length());
     uint8_t* serializedBase = cursor;
     cursor = WriteBytes(cursor, base(), length());
     StaticallyUnlink(serializedBase, linkData);
     return cursor;
 }
 
 /* static */ const uint8_t*
-ModuleSegment::deserialize(const uint8_t* cursor, const LinkDataTier& linkData,
+ModuleSegment::deserialize(const uint8_t* cursor, const LinkData& linkData,
                            UniqueModuleSegment* segment)
 {
     uint32_t length;
     cursor = ReadScalar<uint32_t>(cursor, &length);
     if (!cursor)
         return nullptr;
 
     UniqueCodeBytes bytes = AllocateCodeBytes(length);
@@ -976,25 +1043,25 @@ Metadata::getFuncName(NameContext ctx, c
 size_t
 CodeTier::serializedSize() const
 {
     return segment_->serializedSize() +
            metadata_->serializedSize();
 }
 
 uint8_t*
-CodeTier::serialize(uint8_t* cursor, const LinkDataTier& linkData) const
+CodeTier::serialize(uint8_t* cursor, const LinkData& linkData) const
 {
     cursor = metadata_->serialize(cursor);
     cursor = segment_->serialize(cursor, linkData);
     return cursor;
 }
 
 /* static */ const uint8_t*
-CodeTier::deserialize(const uint8_t* cursor, const LinkDataTier& linkData,
+CodeTier::deserialize(const uint8_t* cursor, const LinkData& linkData,
                       UniqueCodeTier* codeTier)
 {
     auto metadata = js::MakeUnique<MetadataTier>(Tier::Serialized);
     if (!metadata)
         return nullptr;
     cursor = metadata->deserialize(cursor);
     if (!cursor)
         return nullptr;
@@ -1079,30 +1146,30 @@ JumpTables::init(CompileMode mode, const
 Code::Code(UniqueCodeTier tier1, const Metadata& metadata, JumpTables&& maybeJumpTables)
   : tier1_(std::move(tier1)),
     metadata_(&metadata),
     profilingLabels_(mutexid::WasmCodeProfilingLabels, CacheableCharsVector()),
     jumpTables_(std::move(maybeJumpTables))
 {}
 
 bool
-Code::initialize(const ShareableBytes& bytecode, const LinkDataTier& linkData)
+Code::initialize(const ShareableBytes& bytecode, const LinkData& linkData)
 {
     MOZ_ASSERT(!initialized());
 
     if (!tier1_->initialize(*this, bytecode, linkData, *metadata_))
         return false;
 
     MOZ_ASSERT(initialized());
     return true;
 }
 
 bool
 Code::setTier2(UniqueCodeTier tier2, const ShareableBytes& bytecode,
-               const LinkDataTier& linkData) const
+               const LinkData& linkData) const
 {
     MOZ_RELEASE_ASSERT(!hasTier2());
     MOZ_RELEASE_ASSERT(tier2->tier() == Tier::Ion && tier1_->tier() == Tier::Baseline);
 
     if (!tier2->initialize(*this, bytecode, linkData, *metadata_))
         return false;
 
     tier2_ = std::move(tier2);
@@ -1356,17 +1423,17 @@ Code::addSizeOfMiscIfNotSeen(MallocSizeO
 
     for (auto t : tiers())
         codeTier(t).addSizeOfMisc(mallocSizeOf, code, data);
 }
 
 bool
 CodeTier::initialize(const Code& code,
                      const ShareableBytes& bytecode,
-                     const LinkDataTier& linkData,
+                     const LinkData& linkData,
                      const Metadata& metadata)
 {
     MOZ_ASSERT(!initialized());
     code_ = &code;
 
     MOZ_ASSERT(lazyStubs_.lock()->empty());
 
     // See comments in CodeSegment::initialize() for why this must be last.
@@ -1385,39 +1452,39 @@ Code::serializedSize() const
 }
 
 uint8_t*
 Code::serialize(uint8_t* cursor, const LinkData& linkData) const
 {
     MOZ_RELEASE_ASSERT(!metadata().debugEnabled);
 
     cursor = metadata().serialize(cursor);
-    cursor = codeTier(Tier::Serialized).serialize(cursor, linkData.tier(Tier::Serialized));
+    cursor = codeTier(Tier::Serialized).serialize(cursor, linkData);
     return cursor;
 }
 
 /* static */ const uint8_t*
 Code::deserialize(const uint8_t* cursor,
                   const ShareableBytes& bytecode,
                   const LinkData& linkData,
                   Metadata& metadata,
                   SharedCode* out)
 {
     cursor = metadata.deserialize(cursor);
     if (!cursor)
         return nullptr;
 
     UniqueCodeTier codeTier;
-    cursor = CodeTier::deserialize(cursor, linkData.tier(Tier::Serialized), &codeTier);
+    cursor = CodeTier::deserialize(cursor, linkData, &codeTier);
     if (!cursor)
         return nullptr;
 
     JumpTables jumpTables;
     if (!jumpTables.init(CompileMode::Once, codeTier->segment(), codeTier->metadata().codeRanges))
         return nullptr;
 
     MutableCode code = js_new<Code>(std::move(codeTier), metadata, std::move(jumpTables));
-    if (!code || !code->initialize(bytecode, linkData.tier(Tier::Serialized)))
+    if (!code || !code->initialize(bytecode, linkData))
         return nullptr;
 
     *out = code;
     return cursor;
 }
--- a/js/src/wasm/WasmCode.h
+++ b/js/src/wasm/WasmCode.h
@@ -22,24 +22,65 @@
 #include "js/HashTable.h"
 #include "threading/ExclusiveData.h"
 #include "vm/MutexIDs.h"
 #include "wasm/WasmTypes.h"
 
 namespace js {
 
 struct AsmJSMetadata;
-class WasmInstanceObject;
 
 namespace wasm {
 
-struct LinkDataTier;
 struct MetadataTier;
 struct Metadata;
-class LinkData;
+
+// LinkData contains all the metadata necessary to patch all the locations
+// that depend on the absolute address of a ModuleSegment. This happens in a
+// "linking" step after compilation and after the module's code is serialized.
+// The LinkData is serialized along with the Module but does not (normally, see
+// Module::debugLinkData_ comment) persist after (de)serialization, which
+// distinguishes it from Metadata, which is stored in the Code object.
+
+struct LinkDataCacheablePod
+{
+    uint32_t trapOffset = 0;
+
+    LinkDataCacheablePod() = default;
+};
+
+struct LinkData : LinkDataCacheablePod
+{
+    const Tier tier;
+
+    explicit LinkData(Tier tier) : tier(tier) {}
+
+    LinkDataCacheablePod& pod() { return *this; }
+    const LinkDataCacheablePod& pod() const { return *this; }
+
+    struct InternalLink {
+        uint32_t patchAtOffset;
+        uint32_t targetOffset;
+#ifdef JS_CODELABEL_LINKMODE
+        uint32_t mode;
+#endif
+    };
+    typedef Vector<InternalLink, 0, SystemAllocPolicy> InternalLinkVector;
+
+    struct SymbolicLinkArray : EnumeratedArray<SymbolicAddress, SymbolicAddress::Limit, Uint32Vector> {
+        WASM_DECLARE_SERIALIZABLE(SymbolicLinkArray)
+    };
+
+    InternalLinkVector  internalLinks;
+    SymbolicLinkArray   symbolicLinks;
+
+    WASM_DECLARE_SERIALIZABLE(LinkData)
+};
+
+typedef UniquePtr<LinkData> UniqueLinkData;
 
 // ShareableBytes is a reference-counted Vector of bytes.
 
 struct ShareableBytes : ShareableBase<ShareableBytes>
 {
     // Vector is 'final', so instead make Vector a member and add boilerplate.
     Bytes bytes;
     ShareableBytes() = default;
@@ -140,42 +181,42 @@ class ModuleSegment : public CodeSegment
 {
     const Tier      tier_;
     uint8_t* const  trapCode_;
 
   public:
     ModuleSegment(Tier tier,
                   UniqueCodeBytes codeBytes,
                   uint32_t codeLength,
-                  const LinkDataTier& linkData);
+                  const LinkData& linkData);
 
     static UniqueModuleSegment create(Tier tier,
                                       jit::MacroAssembler& masm,
-                                      const LinkDataTier& linkData);
+                                      const LinkData& linkData);
     static UniqueModuleSegment create(Tier tier,
                                       const Bytes& unlinkedBytes,
-                                      const LinkDataTier& linkData);
+                                      const LinkData& linkData);
 
     bool initialize(const CodeTier& codeTier,
                     const ShareableBytes& bytecode,
-                    const LinkDataTier& linkData,
+                    const LinkData& linkData,
                     const Metadata& metadata,
                     const MetadataTier& metadataTier);
 
     Tier tier() const { return tier_; }
 
     // Pointers to stubs to which PC is redirected from the signal-handler.
 
     uint8_t* trapCode() const { return trapCode_; }
 
     // Structured clone support:
 
     size_t serializedSize() const;
-    uint8_t* serialize(uint8_t* cursor, const LinkDataTier& linkData) const;
-    static const uint8_t* deserialize(const uint8_t* cursor, const LinkDataTier& linkData,
+    uint8_t* serialize(uint8_t* cursor, const LinkData& linkData) const;
+    static const uint8_t* deserialize(const uint8_t* cursor, const LinkData& linkData,
                                       UniqueModuleSegment* segment);
 
     const CodeRange* lookupRange(const void* pc) const;
 
     void addSizeOfMisc(mozilla::MallocSizeOf mallocSizeOf, size_t* code, size_t* data) const;
 };
 
 // A FuncExport represents a single function definition inside a wasm Module
@@ -614,30 +655,30 @@ class CodeTier
         segment_(std::move(segment)),
         lazyStubs_(mutexForTier(segment_->tier()))
     {}
 
     bool initialized() const { return !!code_ && segment_->initialized(); }
 
     bool initialize(const Code& code,
                     const ShareableBytes& bytecode,
-                    const LinkDataTier& linkData,
+                    const LinkData& linkData,
                     const Metadata& metadata);
 
     Tier tier() const { return segment_->tier(); }
     const ExclusiveData<LazyStubTier>& lazyStubs() const { return lazyStubs_; }
     const MetadataTier& metadata() const { return *metadata_.get(); }
     const ModuleSegment& segment() const { return *segment_.get(); }
     const Code& code() const { MOZ_ASSERT(initialized()); return *code_; }
 
     const CodeRange* lookupRange(const void* pc) const;
 
     size_t serializedSize() const;
-    uint8_t* serialize(uint8_t* cursor, const LinkDataTier& linkData) const;
-    static const uint8_t* deserialize(const uint8_t* cursor, const LinkDataTier& linkData,
+    uint8_t* serialize(uint8_t* cursor, const LinkData& linkData) const;
+    static const uint8_t* deserialize(const uint8_t* cursor, const LinkData& linkData,
                                       UniqueCodeTier* codeTier);
     void addSizeOfMisc(MallocSizeOf mallocSizeOf, size_t* code, size_t* data) const;
 };
 
 // Jump tables to take tiering into account, when calling either from wasm to
 // wasm (through rabaldr) or from jit to wasm (jit entry).
 
 class JumpTables
@@ -704,27 +745,27 @@ class Code : public ShareableBase<Code>
     SharedMetadata                      metadata_;
     ExclusiveData<CacheableCharsVector> profilingLabels_;
     JumpTables                          jumpTables_;
 
   public:
     Code(UniqueCodeTier tier1, const Metadata& metadata, JumpTables&& maybeJumpTables);
     bool initialized() const { return tier1_->initialized(); }
 
-    bool initialize(const ShareableBytes& bytecode, const LinkDataTier& linkData);
+    bool initialize(const ShareableBytes& bytecode, const LinkData& linkData);
 
     void setTieringEntry(size_t i, void* target) const { jumpTables_.setTieringEntry(i, target); }
     void** tieringJumpTable() const { return jumpTables_.tiering(); }
 
     void setJitEntry(size_t i, void* target) const { jumpTables_.setJitEntry(i, target); }
     void** getAddressOfJitEntry(size_t i) const { return jumpTables_.getAddressOfJitEntry(i); }
     uint32_t getFuncIndex(JSFunction* fun) const;
 
     bool setTier2(UniqueCodeTier tier2, const ShareableBytes& bytecode,
-                  const LinkDataTier& linkData) const;
+                  const LinkData& linkData) const;
     void commitTier2() const;
 
     bool hasTier2() const { return hasTier2_; }
     Tiers tiers() const;
     bool hasTier(Tier t) const;
 
     Tier stableTier() const;    // This is stable during a run
     Tier bestTier() const;      // This may transition from Baseline -> Ion at any time
--- a/js/src/wasm/WasmCompile.cpp
+++ b/js/src/wasm/WasmCompile.cpp
@@ -29,68 +29,56 @@
 #include "wasm/WasmOpIter.h"
 #include "wasm/WasmSignalHandlers.h"
 #include "wasm/WasmValidate.h"
 
 using namespace js;
 using namespace js::jit;
 using namespace js::wasm;
 
-template <class DecoderT>
-static bool
-DecodeFunctionBody(DecoderT& d, ModuleGenerator& mg, uint32_t funcIndex)
+uint32_t
+wasm::ObservedCPUFeatures()
 {
-    uint32_t bodySize;
-    if (!d.readVarU32(&bodySize))
-        return d.fail("expected number of function body bytes");
+    enum Arch {
+        X86 = 0x1,
+        X64 = 0x2,
+        ARM = 0x3,
+        MIPS = 0x4,
+        MIPS64 = 0x5,
+        ARM64 = 0x6,
+        ARCH_BITS = 3
+    };
 
-    if (bodySize > MaxFunctionBytes)
-        return d.fail("function body too big");
-
-    const size_t offsetInModule = d.currentOffset();
-
-    // Skip over the function body; it will be validated by the compilation thread.
-    const uint8_t* bodyBegin;
-    if (!d.readBytes(bodySize, &bodyBegin))
-        return d.fail("function body length too big");
-
-    return mg.compileFuncDef(funcIndex, offsetInModule, bodyBegin, bodyBegin + bodySize);
+#if defined(JS_CODEGEN_X86)
+    MOZ_ASSERT(uint32_t(jit::CPUInfo::GetSSEVersion()) <= (UINT32_MAX >> ARCH_BITS));
+    return X86 | (uint32_t(jit::CPUInfo::GetSSEVersion()) << ARCH_BITS);
+#elif defined(JS_CODEGEN_X64)
+    MOZ_ASSERT(uint32_t(jit::CPUInfo::GetSSEVersion()) <= (UINT32_MAX >> ARCH_BITS));
+    return X64 | (uint32_t(jit::CPUInfo::GetSSEVersion()) << ARCH_BITS);
+#elif defined(JS_CODEGEN_ARM)
+    MOZ_ASSERT(jit::GetARMFlags() <= (UINT32_MAX >> ARCH_BITS));
+    return ARM | (jit::GetARMFlags() << ARCH_BITS);
+#elif defined(JS_CODEGEN_ARM64)
+    MOZ_ASSERT(jit::GetARM64Flags() <= (UINT32_MAX >> ARCH_BITS));
+    return ARM64 | (jit::GetARM64Flags() << ARCH_BITS);
+#elif defined(JS_CODEGEN_MIPS32)
+    MOZ_ASSERT(jit::GetMIPSFlags() <= (UINT32_MAX >> ARCH_BITS));
+    return MIPS | (jit::GetMIPSFlags() << ARCH_BITS);
+#elif defined(JS_CODEGEN_MIPS64)
+    MOZ_ASSERT(jit::GetMIPSFlags() <= (UINT32_MAX >> ARCH_BITS));
+    return MIPS64 | (jit::GetMIPSFlags() << ARCH_BITS);
+#elif defined(JS_CODEGEN_NONE)
+    return 0;
+#else
+# error "unknown architecture"
+#endif
 }
 
-template <class DecoderT>
-static bool
-DecodeCodeSection(const ModuleEnvironment& env, DecoderT& d, ModuleGenerator& mg)
-{
-    if (!env.codeSection) {
-        if (env.numFuncDefs() != 0)
-            return d.fail("expected code section");
-
-        return mg.finishFuncDefs();
-    }
-
-    uint32_t numFuncDefs;
-    if (!d.readVarU32(&numFuncDefs))
-        return d.fail("expected function body count");
-
-    if (numFuncDefs != env.numFuncDefs())
-        return d.fail("function body count does not match function signature count");
-
-    for (uint32_t funcDefIndex = 0; funcDefIndex < numFuncDefs; funcDefIndex++) {
-        if (!DecodeFunctionBody(d, mg, env.numFuncImports() + funcDefIndex))
-            return false;
-    }
-
-    if (!d.finishSection(*env.codeSection, "code"))
-        return false;
-
-    return mg.finishFuncDefs();
-}
-
-bool
-CompileArgs::initFromContext(JSContext* cx, ScriptedCaller&& scriptedCaller)
+CompileArgs::CompileArgs(JSContext* cx, ScriptedCaller&& scriptedCaller)
+  : scriptedCaller(std::move(scriptedCaller))
 {
 #ifdef ENABLE_WASM_GC
     bool gcEnabled = cx->options().wasmGc();
 #else
     bool gcEnabled = false;
 #endif
 
     baselineEnabled = cx->options().wasmBaseline() || gcEnabled;
@@ -99,19 +87,16 @@ CompileArgs::initFromContext(JSContext* 
     gcTypesEnabled = gcEnabled ? HasGcTypes::True : HasGcTypes::False;
     testTiering = (cx->options().testWasmAwaitTier2() || JitOptions.wasmDelayTier2) && !gcEnabled;
 
     // Debug information such as source view or debug traps will require
     // additional memory and permanently stay in baseline code, so we try to
     // only enable it when a developer actually cares: when the debugger tab
     // is open.
     debugEnabled = cx->realm()->debuggerObservesAsmJS();
-
-    this->scriptedCaller = std::move(scriptedCaller);
-    return assumptions.initBuildIdFromContext(cx);
 }
 
 // Classify the current system as one of a set of recognizable classes.  This
 // really needs to get our tier-1 systems right.
 //
 // TODO: We don't yet have a good measure of how fast a system is.  We
 // distinguish between mobile and desktop because these are very different kinds
 // of systems, but we could further distinguish between low / medium / high end
@@ -414,16 +399,66 @@ InitialCompileFlags(const CompileArgs& a
     } else {
         *mode = CompileMode::Once;
         *tier = debugEnabled || !ionEnabled ? Tier::Baseline : Tier::Ion;
     }
 
     *debug = debugEnabled ? DebugEnabled::True : DebugEnabled::False;
 }
 
+template <class DecoderT>
+static bool
+DecodeFunctionBody(DecoderT& d, ModuleGenerator& mg, uint32_t funcIndex)
+{
+    uint32_t bodySize;
+    if (!d.readVarU32(&bodySize))
+        return d.fail("expected number of function body bytes");
+
+    if (bodySize > MaxFunctionBytes)
+        return d.fail("function body too big");
+
+    const size_t offsetInModule = d.currentOffset();
+
+    // Skip over the function body; it will be validated by the compilation thread.
+    const uint8_t* bodyBegin;
+    if (!d.readBytes(bodySize, &bodyBegin))
+        return d.fail("function body length too big");
+
+    return mg.compileFuncDef(funcIndex, offsetInModule, bodyBegin, bodyBegin + bodySize);
+}
+
+template <class DecoderT>
+static bool
+DecodeCodeSection(const ModuleEnvironment& env, DecoderT& d, ModuleGenerator& mg)
+{
+    if (!env.codeSection) {
+        if (env.numFuncDefs() != 0)
+            return d.fail("expected code section");
+
+        return mg.finishFuncDefs();
+    }
+
+    uint32_t numFuncDefs;
+    if (!d.readVarU32(&numFuncDefs))
+        return d.fail("expected function body count");
+
+    if (numFuncDefs != env.numFuncDefs())
+        return d.fail("function body count does not match function signature count");
+
+    for (uint32_t funcDefIndex = 0; funcDefIndex < numFuncDefs; funcDefIndex++) {
+        if (!DecodeFunctionBody(d, mg, env.numFuncImports() + funcDefIndex))
+            return false;
+    }
+
+    if (!d.finishSection(*env.codeSection, "code"))
+        return false;
+
+    return mg.finishFuncDefs();
+}
+
 SharedModule
 wasm::CompileBuffer(const CompileArgs& args, const ShareableBytes& bytecode, UniqueChars* error,
                     UniqueCharsVector* warnings)
 {
     MOZ_RELEASE_ASSERT(wasm::HaveSignalHandlers());
 
     Decoder d(bytecode.bytes, 0, error, warnings);
 
--- a/js/src/wasm/WasmCompile.h
+++ b/js/src/wasm/WasmCompile.h
@@ -19,56 +19,58 @@
 #ifndef wasm_compile_h
 #define wasm_compile_h
 
 #include "wasm/WasmModule.h"
 
 namespace js {
 namespace wasm {
 
+// Return a uint32_t which captures the observed properties of the CPU that
+// affect compilation. If code compiled now is to be serialized and executed
+// later, the ObservedCPUFeatures() must be ensured to be the same.
+
+uint32_t
+ObservedCPUFeatures();
+
 // Describes the JS scripted caller of a request to compile a wasm module.
 
 struct ScriptedCaller
 {
     UniqueChars filename;
     bool filenameIsURL;
     unsigned line;
 
     ScriptedCaller() : filenameIsURL(false), line(0) {}
 };
 
 // Describes all the parameters that control wasm compilation.
 
 struct CompileArgs : ShareableBase<CompileArgs>
 {
-    Assumptions assumptions;
     ScriptedCaller scriptedCaller;
     UniqueChars sourceMapURL;
     bool baselineEnabled;
     bool debugEnabled;
     bool ionEnabled;
     bool sharedMemoryEnabled;
     HasGcTypes gcTypesEnabled;
     bool testTiering;
 
-    CompileArgs(Assumptions&& assumptions, ScriptedCaller&& scriptedCaller)
-      : assumptions(std::move(assumptions)),
-        scriptedCaller(std::move(scriptedCaller)),
+    explicit CompileArgs(ScriptedCaller&& scriptedCaller)
+      : scriptedCaller(std::move(scriptedCaller)),
         baselineEnabled(false),
         debugEnabled(false),
         ionEnabled(false),
         sharedMemoryEnabled(false),
         gcTypesEnabled(HasGcTypes::False),
         testTiering(false)
     {}
 
-    // If CompileArgs is constructed without arguments, initFromContext() must
-    // be called to complete initialization.
-    CompileArgs() = default;
-    bool initFromContext(JSContext* cx, ScriptedCaller&& scriptedCaller);
+    CompileArgs(JSContext* cx, ScriptedCaller&& scriptedCaller);
 };
 
 typedef RefPtr<CompileArgs> MutableCompileArgs;
 typedef RefPtr<const CompileArgs> SharedCompileArgs;
 
 // Return the estimated compiled (machine) code size for the given bytecode size
 // compiled at the given tier.
 
--- a/js/src/wasm/WasmDebug.cpp
+++ b/js/src/wasm/WasmDebug.cpp
@@ -450,24 +450,26 @@ DebugState::debugDisplayURL(JSContext* c
     // - URI encoded filename from metadata (if can be encoded), plus ":";
     // - 64-bit hash of the module bytes (as hex dump).
 
     js::StringBuffer result(cx);
     if (!result.append("wasm:"))
         return nullptr;
 
     if (const char* filename = metadata().filename.get()) {
-        js::StringBuffer filenamePrefix(cx);
         // EncodeURI returns false due to invalid chars or OOM -- fail only
         // during OOM.
-        if (!EncodeURI(cx, filenamePrefix, filename, strlen(filename))) {
-            if (!cx->isExceptionPending())
+        JSString* filenamePrefix = EncodeURI(cx, filename, strlen(filename));
+        if (!filenamePrefix) {
+            if (cx->isThrowingOutOfMemory())
                 return nullptr;
+
+            MOZ_ASSERT(!cx->isThrowingOverRecursed());
             cx->clearPendingException(); // ignore invalid URI
-        } else if (!result.append(filenamePrefix.finishString())) {
+        } else if (!result.append(filenamePrefix)) {
             return nullptr;
         }
     }
 
     if (metadata().debugEnabled) {
         if (!result.append(":"))
             return nullptr;
 
--- a/js/src/wasm/WasmGenerator.cpp
+++ b/js/src/wasm/WasmGenerator.cpp
@@ -66,17 +66,17 @@ static const unsigned COMPILATION_LIFO_D
 static const uint32_t BAD_CODE_RANGE = UINT32_MAX;
 
 ModuleGenerator::ModuleGenerator(const CompileArgs& args, ModuleEnvironment* env,
                                  const Atomic<bool>* cancelled, UniqueChars* error)
   : compileArgs_(&args),
     error_(error),
     cancelled_(cancelled),
     env_(env),
-    linkDataTier_(nullptr),
+    linkData_(nullptr),
     metadataTier_(nullptr),
     taskState_(mutexid::WasmCompileTaskState),
     lifo_(GENERATOR_LIFO_DEFAULT_CHUNK_SIZE),
     masmAlloc_(&lifo_),
     masm_(masmAlloc_),
     debugTrapCodeOffset_(),
     lastPatchedCallSite_(0),
     startOfUnpatchedCallsites_(0),
@@ -178,27 +178,24 @@ ModuleGenerator::init(Metadata* maybeAsm
     }
 
     if (compileArgs_->sourceMapURL) {
         metadata_->sourceMapURL = DuplicateString(compileArgs_->sourceMapURL.get());
         if (!metadata_->sourceMapURL)
             return false;
     }
 
-    linkDataTier_ = js::MakeUnique<LinkDataTier>(tier());
-    if (!linkDataTier_)
+    linkData_ = js::MakeUnique<LinkData>(tier());
+    if (!linkData_)
         return false;
 
     metadataTier_ = js::MakeUnique<MetadataTier>(tier());
     if (!metadataTier_)
         return false;
 
-    if (!assumptions_.clone(compileArgs_->assumptions))
-        return false;
-
     // The funcToCodeRange_ maps function indices to code-range indices and all
     // elements will be initialized by the time module generation is finished.
 
     if (!funcToCodeRange_.appendN(BAD_CODE_RANGE, env_->funcTypes.length()))
         return false;
 
     // Pre-reserve space for large Vectors to avoid the significant cost of the
     // final reallocs. In particular, the MacroAssembler can be enormous, so be
@@ -509,18 +506,18 @@ ModuleGenerator::noteCodeRange(uint32_t 
       case CodeRange::ImportInterpExit:
         metadataTier_->funcImports[codeRange.funcIndex()].initInterpExitOffset(codeRange.begin());
         break;
       case CodeRange::DebugTrap:
         MOZ_ASSERT(!debugTrapCodeOffset_);
         debugTrapCodeOffset_ = codeRange.begin();
         break;
       case CodeRange::TrapExit:
-        MOZ_ASSERT(!linkDataTier_->trapOffset);
-        linkDataTier_->trapOffset = codeRange.begin();
+        MOZ_ASSERT(!linkData_->trapOffset);
+        linkData_->trapOffset = codeRange.begin();
         break;
       case CodeRange::Throw:
         // Jumped to by other stubs, so nothing to do.
         break;
       case CodeRange::FarJumpIsland:
       case CodeRange::BuiltinThunk:
         MOZ_CRASH("Unexpected CodeRange kind");
     }
@@ -581,28 +578,28 @@ ModuleGenerator::linkCompiledCode(const 
     }
 
     auto callFarJumpOp = [=](uint32_t, CallFarJump* cfj) { cfj->offsetBy(offsetInModule); };
     if (!AppendForEach(&callFarJumps_, code.callFarJumps, callFarJumpOp))
         return false;
 
     for (const SymbolicAccess& access : code.symbolicAccesses) {
         uint32_t patchAt = offsetInModule + access.patchAt.offset();
-        if (!linkDataTier_->symbolicLinks[access.target].append(patchAt))
+        if (!linkData_->symbolicLinks[access.target].append(patchAt))
             return false;
     }
 
     for (const CodeLabel& codeLabel : code.codeLabels) {
-        LinkDataTier::InternalLink link;
+        LinkData::InternalLink link;
         link.patchAtOffset = offsetInModule + codeLabel.patchAt().offset();
         link.targetOffset = offsetInModule + codeLabel.target().offset();
 #ifdef JS_CODELABEL_LINKMODE
         link.mode = codeLabel.linkMode();
 #endif
-        if (!linkDataTier_->internalLinks.append(link))
+        if (!linkData_->internalLinks.append(link))
             return false;
     }
 
     return true;
 }
 
 static bool
 ExecuteCompileTask(CompileTask* task, UniqueChars* error)
@@ -932,74 +929,82 @@ ModuleGenerator::finish(const ShareableB
     // All functions and stubs have been compiled, finish linking and metadata.
 
     if (!finishCode())
         return nullptr;
 
     if (!finishMetadata(bytecode))
         return nullptr;
 
-    return ModuleSegment::create(tier(), masm_, *linkDataTier_);
+    return ModuleSegment::create(tier(), masm_, *linkData_);
 }
 
 SharedModule
-ModuleGenerator::finishModule(const ShareableBytes& bytecode)
+ModuleGenerator::finishModule(const ShareableBytes& bytecode, UniqueLinkData* linkData)
 {
     MOZ_ASSERT(mode() == CompileMode::Once || mode() == CompileMode::Tier1);
 
     UniqueModuleSegment moduleSegment = finish(bytecode);
     if (!moduleSegment)
         return nullptr;
 
     JumpTables jumpTables;
     if (!jumpTables.init(mode(), *moduleSegment, metadataTier_->codeRanges))
         return nullptr;
 
-    UniqueConstBytes maybeDebuggingBytes;
-    if (env_->debugEnabled()) {
-        MOZ_ASSERT(mode() == CompileMode::Once);
-        Bytes bytes;
-        if (!bytes.resize(masm_.bytesNeeded()))
-            return nullptr;
-        masm_.executableCopy(bytes.begin(), /* flushICache = */ false);
-        maybeDebuggingBytes = js::MakeUnique<Bytes>(std::move(bytes));
-        if (!maybeDebuggingBytes)
-            return nullptr;
-    }
-
     auto codeTier = js::MakeUnique<CodeTier>(std::move(metadataTier_), std::move(moduleSegment));
     if (!codeTier)
         return nullptr;
 
     MutableCode code = js_new<Code>(std::move(codeTier), *metadata_, std::move(jumpTables));
-    if (!code || !code->initialize(bytecode, *linkDataTier_))
+    if (!code || !code->initialize(bytecode, *linkData_))
         return nullptr;
 
     StructTypeVector structTypes;
     for (TypeDef& td : env_->types) {
         if (td.isStructType() && !structTypes.append(std::move(td.structType())))
             return nullptr;
     }
 
-    SharedModule module(js_new<Module>(std::move(assumptions_),
-                                       *code,
-                                       std::move(maybeDebuggingBytes),
-                                       LinkData(std::move(linkDataTier_)),
+    UniqueBytes debugUnlinkedCode;
+    UniqueLinkData debugLinkData;
+    if (env_->debugEnabled()) {
+        MOZ_ASSERT(mode() == CompileMode::Once);
+        MOZ_ASSERT(tier() == Tier::Debug);
+
+        debugUnlinkedCode = js::MakeUnique<Bytes>();
+        if (!debugUnlinkedCode || !debugUnlinkedCode->resize(masm_.bytesNeeded()))
+            return nullptr;
+
+        masm_.executableCopy(debugUnlinkedCode->begin(), /* flushICache = */ false);
+
+        debugLinkData = std::move(linkData_);
+    }
+
+    SharedModule module(js_new<Module>(*code,
                                        std::move(env_->imports),
                                        std::move(env_->exports),
                                        std::move(env_->dataSegments),
                                        std::move(env_->elemSegments),
                                        std::move(structTypes),
-                                       bytecode));
+                                       bytecode,
+                                       std::move(debugUnlinkedCode),
+                                       std::move(debugLinkData)));
     if (!module)
         return nullptr;
 
     if (mode() == CompileMode::Tier1)
         module->startTier2(*compileArgs_);
 
+    if (linkData) {
+        MOZ_ASSERT(isAsmJS());
+        MOZ_ASSERT(!env_->debugEnabled());
+        *linkData = std::move(linkData_);
+    }
+
     return module;
 }
 
 bool
 ModuleGenerator::finishTier2(Module& module)
 {
     MOZ_ASSERT(mode() == CompileMode::Tier2);
     MOZ_ASSERT(tier() == Tier::Ion);
@@ -1017,17 +1022,17 @@ ModuleGenerator::finishTier2(Module& mod
         return false;
 
     if (MOZ_UNLIKELY(JitOptions.wasmDelayTier2)) {
         // Introduce an artificial delay when testing wasmDelayTier2, since we
         // want to exercise both tier1 and tier2 code in this case.
         std::this_thread::sleep_for(std::chrono::milliseconds(500));
     }
 
-    return module.finishTier2(std::move(linkDataTier_), std::move(tier2), env_);
+    return module.finishTier2(*linkData_, std::move(tier2), std::move(*env_));
 }
 
 size_t
 CompiledCode::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
 {
     size_t trapSitesSize = 0;
     for (const TrapSiteVector& vec : trapSites)
         trapSitesSize += vec.sizeOfExcludingThis(mallocSizeOf);
--- a/js/src/wasm/WasmGenerator.h
+++ b/js/src/wasm/WasmGenerator.h
@@ -149,18 +149,17 @@ class MOZ_STACK_CLASS ModuleGenerator
 
     // Constant parameters
     SharedCompileArgs const         compileArgs_;
     UniqueChars* const              error_;
     const Atomic<bool>* const       cancelled_;
     ModuleEnvironment* const        env_;
 
     // Data that is moved into the result of finish()
-    Assumptions                     assumptions_;
-    UniqueLinkDataTier              linkDataTier_;
+    UniqueLinkData                  linkData_;
     UniqueMetadataTier              metadataTier_;
     MutableMetadata                 metadata_;
 
     // Data scoped to the ModuleGenerator's lifetime
     ExclusiveCompileTaskState       taskState_;
     LifoAlloc                       lifo_;
     jit::JitContext                 jcx_;
     jit::TempAllocator              masmAlloc_;
@@ -220,16 +219,16 @@ class MOZ_STACK_CLASS ModuleGenerator
     // or finishTier2().
 
     MOZ_MUST_USE bool finishFuncDefs();
 
     // If env->mode is Once or Tier1, finishModule() must be called to generate
     // a new Module. Otherwise, if env->mode is Tier2, finishTier2() must be
     // called to augment the given Module with tier 2 code.
 
-    SharedModule finishModule(const ShareableBytes& bytecode);
+    SharedModule finishModule(const ShareableBytes& bytecode, UniqueLinkData* linkData = nullptr);
     MOZ_MUST_USE bool finishTier2(Module& module);
 };
 
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_generator_h
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -358,18 +358,18 @@ wasm::Eval(JSContext* cx, Handle<TypedAr
         ReportOutOfMemory(cx);
         return false;
     }
 
     ScriptedCaller scriptedCaller;
     if (!DescribeScriptedCaller(cx, &scriptedCaller, "wasm_eval"))
         return false;
 
-    MutableCompileArgs compileArgs = cx->new_<CompileArgs>();
-    if (!compileArgs || !compileArgs->initFromContext(cx, std::move(scriptedCaller)))
+    MutableCompileArgs compileArgs = cx->new_<CompileArgs>(cx, std::move(scriptedCaller));
+    if (!compileArgs)
         return false;
 
     UniqueChars error;
     UniqueCharsVector warnings;
     SharedModule module = CompileBuffer(*compileArgs, *bytecode, &error, &warnings);
     if (!module) {
         if (error) {
             JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_COMPILE_ERROR,
@@ -889,24 +889,17 @@ GetBufferSource(JSContext* cx, JSObject*
 
 static MutableCompileArgs
 InitCompileArgs(JSContext* cx, const char* introducer)
 {
     ScriptedCaller scriptedCaller;
     if (!DescribeScriptedCaller(cx, &scriptedCaller, introducer))
         return nullptr;
 
-    MutableCompileArgs compileArgs = cx->new_<CompileArgs>();
-    if (!compileArgs)
-        return nullptr;
-
-    if (!compileArgs->initFromContext(cx, std::move(scriptedCaller)))
-        return nullptr;
-
-    return compileArgs;
+    return cx->new_<CompileArgs>(cx, std::move(scriptedCaller));
 }
 
 static bool
 ReportCompileWarnings(JSContext* cx, const UniqueCharsVector& warnings)
 {
     // Avoid spamming the console.
     size_t numWarnings = Min<size_t>(warnings.length(), 3);
 
--- a/js/src/wasm/WasmModule.cpp
+++ b/js/src/wasm/WasmModule.cpp
@@ -32,149 +32,16 @@
 #include "vm/ArrayBufferObject-inl.h"
 #include "vm/Debugger-inl.h"
 #include "vm/JSAtom-inl.h"
 
 using namespace js;
 using namespace js::jit;
 using namespace js::wasm;
 
-size_t
-LinkDataTier::SymbolicLinkArray::serializedSize() const
-{
-    size_t size = 0;
-    for (const Uint32Vector& offsets : *this)
-        size += SerializedPodVectorSize(offsets);
-    return size;
-}
-
-uint8_t*
-LinkDataTier::SymbolicLinkArray::serialize(uint8_t* cursor) const
-{
-    for (const Uint32Vector& offsets : *this)
-        cursor = SerializePodVector(cursor, offsets);
-    return cursor;
-}
-
-const uint8_t*
-LinkDataTier::SymbolicLinkArray::deserialize(const uint8_t* cursor)
-{
-    for (Uint32Vector& offsets : *this) {
-        cursor = DeserializePodVector(cursor, &offsets);
-        if (!cursor)
-            return nullptr;
-    }
-    return cursor;
-}
-
-size_t
-LinkDataTier::SymbolicLinkArray::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
-{
-    size_t size = 0;
-    for (const Uint32Vector& offsets : *this)
-        size += offsets.sizeOfExcludingThis(mallocSizeOf);
-    return size;
-}
-
-size_t
-LinkDataTier::serializedSize() const
-{
-    return sizeof(pod()) +
-           SerializedPodVectorSize(internalLinks) +
-           symbolicLinks.serializedSize();
-}
-
-uint8_t*
-LinkDataTier::serialize(uint8_t* cursor) const
-{
-    MOZ_ASSERT(tier == Tier::Serialized);
-
-    cursor = WriteBytes(cursor, &pod(), sizeof(pod()));
-    cursor = SerializePodVector(cursor, internalLinks);
-    cursor = symbolicLinks.serialize(cursor);
-    return cursor;
-}
-
-const uint8_t*
-LinkDataTier::deserialize(const uint8_t* cursor)
-{
-    (cursor = ReadBytes(cursor, &pod(), sizeof(pod()))) &&
-    (cursor = DeserializePodVector(cursor, &internalLinks)) &&
-    (cursor = symbolicLinks.deserialize(cursor));
-    return cursor;
-}
-
-size_t
-LinkDataTier::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
-{
-    return internalLinks.sizeOfExcludingThis(mallocSizeOf) +
-           symbolicLinks.sizeOfExcludingThis(mallocSizeOf);
-}
-
-void
-LinkData::setTier2(UniqueLinkDataTier tier) const
-{
-    MOZ_RELEASE_ASSERT(tier->tier == Tier::Ion && tier1_->tier == Tier::Baseline);
-    MOZ_RELEASE_ASSERT(!tier2_.get());
-    tier2_ = std::move(tier);
-}
-
-const LinkDataTier&
-LinkData::tier(Tier tier) const
-{
-    switch (tier) {
-      case Tier::Baseline:
-        if (tier1_->tier == Tier::Baseline)
-            return *tier1_;
-        MOZ_CRASH("No linkData at this tier");
-      case Tier::Ion:
-        if (tier1_->tier == Tier::Ion)
-            return *tier1_;
-        if (tier2_)
-            return *tier2_;
-        MOZ_CRASH("No linkData at this tier");
-      default:
-        MOZ_CRASH();
-    }
-}
-
-size_t
-LinkData::serializedSize() const
-{
-    return tier(Tier::Serialized).serializedSize();
-}
-
-uint8_t*
-LinkData::serialize(uint8_t* cursor) const
-{
-    cursor = tier(Tier::Serialized).serialize(cursor);
-    return cursor;
-}
-
-const uint8_t*
-LinkData::deserialize(const uint8_t* cursor)
-{
-    MOZ_ASSERT(!tier1_);
-    tier1_ = js::MakeUnique<LinkDataTier>(Tier::Serialized);
-    if (!tier1_)
-        return nullptr;
-    cursor = tier1_->deserialize(cursor);
-    return cursor;
-}
-
-size_t
-LinkData::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
-{
-    size_t sum = 0;
-    sum += tier1_->sizeOfExcludingThis(mallocSizeOf);
-    if (tier2_)
-        sum += tier2_->sizeOfExcludingThis(mallocSizeOf);
-    return sum;
-}
-
 class Module::Tier2GeneratorTaskImpl : public Tier2GeneratorTask
 {
     SharedModule            module_;
     SharedCompileArgs       compileArgs_;
     Atomic<bool>            cancelled_;
 
   public:
     Tier2GeneratorTaskImpl(Module& module, const CompileArgs& compileArgs)
@@ -208,28 +75,27 @@ Module::startTier2(const CompileArgs& ar
     // This flag will be cleared asynchronously by ~Tier2GeneratorTaskImpl()
     // on success or failure.
     testingTier2Active_ = true;
 
     StartOffThreadWasmTier2Generator(std::move(task));
 }
 
 bool
-Module::finishTier2(UniqueLinkDataTier linkData2, UniqueCodeTier tier2Arg, ModuleEnvironment* env2)
+Module::finishTier2(const LinkData& linkData, UniqueCodeTier tier2Arg, ModuleEnvironment&& env2)
 {
     MOZ_ASSERT(code().bestTier() == Tier::Baseline && tier2Arg->tier() == Tier::Ion);
 
     // Install the data in the data structures. They will not be visible
     // until commitTier2().
 
-    if (!code().setTier2(std::move(tier2Arg), *bytecode_, *linkData2))
+    if (!code().setTier2(std::move(tier2Arg), *bytecode_, linkData))
         return false;
-    linkData().setTier2(std::move(linkData2));
     for (uint32_t i = 0; i < elemSegments_.length(); i++)
-        elemSegments_[i].setTier2(std::move(env2->elemSegments[i].elemCodeRangeIndices(Tier::Ion)));
+        elemSegments_[i].setTier2(std::move(env2.elemSegments[i].elemCodeRangeIndices(Tier::Ion)));
 
     // Before we can make tier-2 live, we need to compile tier2 versions of any
     // extant tier1 lazy stubs (otherwise, tiering would break the assumption
     // that any extant exported wasm function has had a lazy entry stub already
     // compiled for it).
     {
         // We need to prevent new tier1 stubs generation until we've committed
         // the newer tier2 stubs, otherwise we might not generate one tier2
@@ -242,17 +108,17 @@ Module::finishTier2(UniqueLinkDataTier l
 
         MOZ_ASSERT(stubs2->empty());
 
         Uint32Vector funcExportIndices;
         for (size_t i = 0; i < metadataTier1.funcExports.length(); i++) {
             const FuncExport& fe = metadataTier1.funcExports[i];
             if (fe.hasEagerStubs())
                 continue;
-            MOZ_ASSERT(!env2->isAsmJS(), "only wasm functions are lazily exported");
+            MOZ_ASSERT(!env2.isAsmJS(), "only wasm functions are lazily exported");
             if (!stubs1->hasStub(fe.funcIndex()))
                 continue;
             if (!funcExportIndices.emplaceBack(i))
                 return false;
         }
 
         HasGcTypes gcTypesEnabled = code().metadata().temporaryHasGcTypes;
         const CodeTier& tier2 = code().codeTier(Tier::Ion);
@@ -289,101 +155,64 @@ Module::finishTier2(UniqueLinkDataTier l
 void
 Module::testingBlockOnTier2Complete() const
 {
     while (testingTier2Active_)
         std::this_thread::sleep_for(std::chrono::milliseconds(1));
 }
 
 /* virtual */ size_t
-Module::compiledSerializedSize() const
+Module::serializedSize(const LinkData& linkData) const
 {
-    MOZ_ASSERT(!testingTier2Active_);
-
-    // The compiled debug code must not be saved, set compiled size to 0,
-    // so Module::assumptionsMatch will return false during assumptions
-    // deserialization.
-    if (metadata().debugEnabled)
-        return 0;
-
-    if (!code_->hasTier(Tier::Serialized))
-        return 0;
-
-    return assumptions_.serializedSize() +
-           linkData_.serializedSize() +
+    return linkData.serializedSize() +
            SerializedVectorSize(imports_) +
            SerializedVectorSize(exports_) +
            SerializedPodVectorSize(dataSegments_) +
            SerializedVectorSize(elemSegments_) +
            SerializedVectorSize(structTypes_) +
            code_->serializedSize();
 }
 
 /* virtual */ void
-Module::compiledSerialize(uint8_t* compiledBegin, size_t compiledSize) const
+Module::serialize(const LinkData& linkData, uint8_t* begin, size_t size) const
 {
-    MOZ_ASSERT(!testingTier2Active_);
-
-    if (metadata().debugEnabled) {
-        MOZ_RELEASE_ASSERT(compiledSize == 0);
-        return;
-    }
+    MOZ_RELEASE_ASSERT(!testingTier2Active_);
+    MOZ_RELEASE_ASSERT(!metadata().debugEnabled);
+    MOZ_RELEASE_ASSERT(code_->hasTier(Tier::Serialized));
 
-    if (!code_->hasTier(Tier::Serialized)) {
-        MOZ_RELEASE_ASSERT(compiledSize == 0);
-        return;
-    }
-
-    uint8_t* cursor = compiledBegin;
-    cursor = assumptions_.serialize(cursor);
-    cursor = linkData_.serialize(cursor);
+    uint8_t* cursor = begin;
+    cursor = linkData.serialize(cursor);
     cursor = SerializeVector(cursor, imports_);
     cursor = SerializeVector(cursor, exports_);
     cursor = SerializePodVector(cursor, dataSegments_);
     cursor = SerializeVector(cursor, elemSegments_);
     cursor = SerializeVector(cursor, structTypes_);
-    cursor = code_->serialize(cursor, linkData_);
-    MOZ_RELEASE_ASSERT(cursor == compiledBegin + compiledSize);
-}
-
-/* static */ bool
-Module::assumptionsMatch(const Assumptions& current, const uint8_t* compiledBegin, size_t remain)
-{
-    Assumptions cached;
-    if (!cached.deserialize(compiledBegin, remain))
-        return false;
-
-    return current == cached;
+    cursor = code_->serialize(cursor, linkData);
+    MOZ_RELEASE_ASSERT(cursor == begin + size);
 }
 
 /* static */ SharedModule
-Module::deserialize(const uint8_t* bytecodeBegin, size_t bytecodeSize,
-                    const uint8_t* compiledBegin, size_t compiledSize,
-                    Metadata* maybeMetadata)
+Module::deserialize(const uint8_t* begin, size_t size, Metadata* maybeMetadata)
 {
-    MutableBytes bytecode = js_new<ShareableBytes>();
-    if (!bytecode || !bytecode->bytes.initLengthUninitialized(bytecodeSize))
-        return nullptr;
-
-    if (bytecodeSize)
-        memcpy(bytecode->bytes.begin(), bytecodeBegin, bytecodeSize);
-
-    Assumptions assumptions;
-    const uint8_t* cursor = assumptions.deserialize(compiledBegin, compiledSize);
-    if (!cursor)
-        return nullptr;
-
     MutableMetadata metadata(maybeMetadata);
     if (!metadata) {
         metadata = js_new<Metadata>();
         if (!metadata)
             return nullptr;
     }
 
-    LinkData linkData;
+    const uint8_t* cursor = begin;
+
+    // Temporary. (asm.js doesn't save bytecode)
+    MOZ_RELEASE_ASSERT(maybeMetadata->isAsmJS());
+    MutableBytes bytecode = js_new<ShareableBytes>();
+    if (!bytecode)
+        return nullptr;
+
+    LinkData linkData(Tier::Serialized);
     cursor = linkData.deserialize(cursor);
     if (!cursor)
         return nullptr;
 
     ImportVector imports;
     cursor = DeserializeVector(cursor, &imports);
     if (!cursor)
         return nullptr;
@@ -408,23 +237,20 @@ Module::deserialize(const uint8_t* bytec
     if (!cursor)
         return nullptr;
 
     SharedCode code;
     cursor = Code::deserialize(cursor, *bytecode, linkData, *metadata, &code);
     if (!cursor)
         return nullptr;
 
-    MOZ_RELEASE_ASSERT(cursor == compiledBegin + compiledSize);
+    MOZ_RELEASE_ASSERT(cursor == begin + size);
     MOZ_RELEASE_ASSERT(!!maybeMetadata == code->metadata().isAsmJS());
 
-    return js_new<Module>(std::move(assumptions),
-                          *code,
-                          nullptr,            // Serialized code is never debuggable
-                          std::move(linkData),
+    return js_new<Module>(*code,
                           std::move(imports),
                           std::move(exports),
                           std::move(dataSegments),
                           std::move(elemSegments),
                           std::move(structTypes),
                           *bytecode);
 }
 
@@ -462,39 +288,34 @@ MapFile(PRFileDesc* file, PRFileInfo* in
     // mapped, so unconditionally close the PRFileMap, regardless of whether
     // PR_MemMap succeeds.
     uint8_t* memory = (uint8_t*)PR_MemMap(map, 0, info->size);
     PR_CloseFileMap(map);
     return UniqueMapping(memory, MemUnmap(info->size));
 }
 
 SharedModule
-wasm::DeserializeModule(PRFileDesc* bytecodeFile, JS::BuildIdCharVector&& buildId,
-                        UniqueChars filename, unsigned line)
+wasm::DeserializeModule(PRFileDesc* bytecodeFile, UniqueChars filename, unsigned line)
 {
     PRFileInfo bytecodeInfo;
     UniqueMapping bytecodeMapping = MapFile(bytecodeFile, &bytecodeInfo);
     if (!bytecodeMapping)
         return nullptr;
 
-    // Since the compiled file's assumptions don't match, we must recompile from
-    // bytecode. The bytecode file format is simply that of a .wasm (see
-    // Module::bytecodeSerialize).
-
     MutableBytes bytecode = js_new<ShareableBytes>();
     if (!bytecode || !bytecode->bytes.initLengthUninitialized(bytecodeInfo.size))
         return nullptr;
 
     memcpy(bytecode->bytes.begin(), bytecodeMapping.get(), bytecodeInfo.size);
 
     ScriptedCaller scriptedCaller;
     scriptedCaller.filename = std::move(filename);
     scriptedCaller.line = line;
 
-    MutableCompileArgs args = js_new<CompileArgs>(Assumptions(std::move(buildId)), std::move(scriptedCaller));
+    MutableCompileArgs args = js_new<CompileArgs>(std::move(scriptedCaller));
     if (!args)
         return nullptr;
 
     // The true answer to whether shared memory is enabled is provided by
     // cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled()
     // where cx is the context that originated the call that caused this
     // deserialization attempt to happen.  We don't have that context here, so
     // we assume that shared memory is enabled; we will catch a wrong assumption
@@ -516,26 +337,24 @@ Module::addSizeOfMisc(MallocSizeOf mallo
                       Metadata::SeenSet* seenMetadata,
                       ShareableBytes::SeenSet* seenBytes,
                       Code::SeenSet* seenCode,
                       size_t* code,
                       size_t* data) const
 {
     code_->addSizeOfMiscIfNotSeen(mallocSizeOf, seenMetadata, seenCode, code, data);
     *data += mallocSizeOf(this) +
-             assumptions_.sizeOfExcludingThis(mallocSizeOf) +
-             linkData_.sizeOfExcludingThis(mallocSizeOf) +
              SizeOfVectorExcludingThis(imports_, mallocSizeOf) +
              SizeOfVectorExcludingThis(exports_, mallocSizeOf) +
              dataSegments_.sizeOfExcludingThis(mallocSizeOf) +
              SizeOfVectorExcludingThis(elemSegments_, mallocSizeOf) +
              SizeOfVectorExcludingThis(structTypes_, mallocSizeOf) +
              bytecode_->sizeOfIncludingThisIfNotSeen(mallocSizeOf, seenBytes);
-    if (unlinkedCodeForDebugging_)
-        *data += unlinkedCodeForDebugging_->sizeOfExcludingThis(mallocSizeOf);
+    if (debugUnlinkedCode_)
+        *data += debugUnlinkedCode_->sizeOfExcludingThis(mallocSizeOf);
 }
 
 
 // Extracting machine code as JS object. The result has the "code" property, as
 // a Uint8Array, and the "segments" property as array objects. The objects
 // contain offsets in the "code" array and basic information about a code
 // segment/function body.
 bool
@@ -1110,23 +929,26 @@ Module::instantiate(JSContext* cx,
     if (!tlsData) {
         ReportOutOfMemory(cx);
         return false;
     }
 
     SharedCode code(code_);
 
     if (metadata().debugEnabled) {
+        MOZ_ASSERT(debugUnlinkedCode_);
+        MOZ_ASSERT(debugLinkData_);
+
         // The first time through, use the pre-linked code in the module but
         // mark it as busy. Subsequently, instantiate the copy of the code
         // bytes that we keep around for debugging instead, because the debugger
         // may patch the pre-linked code at any time.
-        if (!codeIsBusy_.compareExchange(false, true)) {
+        if (!debugCodeClaimed_.compareExchange(false, true)) {
             Tier tier = Tier::Baseline;
-            auto segment = ModuleSegment::create(tier, *unlinkedCodeForDebugging_, linkData(tier));
+            auto segment = ModuleSegment::create(tier, *debugUnlinkedCode_, *debugLinkData_);
             if (!segment) {
                 ReportOutOfMemory(cx);
                 return false;
             }
 
             UniqueMetadataTier metadataTier = js::MakeUnique<MetadataTier>(tier);
             if (!metadataTier || !metadataTier->clone(metadata(tier)))
                 return false;
@@ -1135,17 +957,17 @@ Module::instantiate(JSContext* cx,
             if (!codeTier)
                 return false;
 
             JumpTables jumpTables;
             if (!jumpTables.init(CompileMode::Once, codeTier->segment(), metadata(tier).codeRanges))
                 return false;
 
             MutableCode debugCode = js_new<Code>(std::move(codeTier), metadata(), std::move(jumpTables));
-            if (!debugCode || !debugCode->initialize(*bytecode_, linkData(tier))) {
+            if (!debugCode || !debugCode->initialize(*bytecode_, *debugLinkData_)) {
                 ReportOutOfMemory(cx);
                 return false;
             }
 
             code = debugCode;
         }
     }
 
--- a/js/src/wasm/WasmModule.h
+++ b/js/src/wasm/WasmModule.h
@@ -28,163 +28,101 @@
 #include "wasm/WasmTable.h"
 #include "wasm/WasmValidate.h"
 
 namespace js {
 namespace wasm {
 
 struct CompileArgs;
 
-// LinkData contains all the metadata necessary to patch all the locations
-// that depend on the absolute address of a ModuleSegment.
-//
-// LinkData is built incrementally by ModuleGenerator and then stored immutably
-// in Module. LinkData is distinct from Metadata in that LinkData is owned and
-// destroyed by the Module since it is not needed after instantiation; Metadata
-// is needed at runtime.
-
-struct LinkDataTierCacheablePod
-{
-    uint32_t trapOffset = 0;
-
-    LinkDataTierCacheablePod() = default;
-};
-
-struct LinkDataTier : LinkDataTierCacheablePod
-{
-    const Tier tier;
-
-    explicit LinkDataTier(Tier tier) : tier(tier) {}
-
-    LinkDataTierCacheablePod& pod() { return *this; }
-    const LinkDataTierCacheablePod& pod() const { return *this; }
-
-    struct InternalLink {
-        uint32_t patchAtOffset;
-        uint32_t targetOffset;
-#ifdef JS_CODELABEL_LINKMODE
-        uint32_t mode;
-#endif
-    };
-    typedef Vector<InternalLink, 0, SystemAllocPolicy> InternalLinkVector;
-
-    struct SymbolicLinkArray : EnumeratedArray<SymbolicAddress, SymbolicAddress::Limit, Uint32Vector> {
-        WASM_DECLARE_SERIALIZABLE(SymbolicLinkArray)
-    };
-
-    InternalLinkVector  internalLinks;
-    SymbolicLinkArray   symbolicLinks;
-
-    WASM_DECLARE_SERIALIZABLE(LinkData)
-};
-
-typedef UniquePtr<LinkDataTier> UniqueLinkDataTier;
-
-class LinkData
-{
-    UniqueLinkDataTier         tier1_; // Always present
-    mutable UniqueLinkDataTier tier2_; // Access only if hasTier2() is true
-
-  public:
-    LinkData() {}
-    explicit LinkData(UniqueLinkDataTier tier) : tier1_(std::move(tier)) {}
-
-    void setTier2(UniqueLinkDataTier linkData) const;
-    const LinkDataTier& tier(Tier tier) const;
-
-    WASM_DECLARE_SERIALIZABLE(LinkData)
-};
-
 // Module represents a compiled wasm module and primarily provides two
 // operations: instantiation and serialization. A Module can be instantiated any
 // number of times to produce new Instance objects. A Module can be serialized
 // any number of times such that the serialized bytes can be deserialized later
 // to produce a new, equivalent Module.
 //
 // Fully linked-and-instantiated code (represented by Code and its owned
 // ModuleSegment) can be shared between instances, provided none of those
 // instances are being debugged. If patchable code is needed then each instance
 // must have its own Code. Module eagerly creates a new Code and gives it to the
 // first instance; it then instantiates new Code objects from a copy of the
 // unlinked code that it keeps around for that purpose.
 
 class Module : public JS::WasmModule
 {
-    const Assumptions       assumptions_;
     const SharedCode        code_;
-    const UniqueConstBytes  unlinkedCodeForDebugging_;
-    const LinkData          linkData_;
     const ImportVector      imports_;
     const ExportVector      exports_;
     const DataSegmentVector dataSegments_;
     const ElemSegmentVector elemSegments_;
     const StructTypeVector  structTypes_;
     const SharedBytes       bytecode_;
 
+    // These fields are only meaningful when code_->metadata().debugEnabled.
+    // `debugCodeClaimed_` is set to false initially and then to true when
+    // `code_` is already being used for an instance and can't be shared because
+    // it may be patched by the debugger. Subsequent instances must then create
+    // copies by linking the `debugUnlinkedCode_` using `debugLinkData_`.
+    // This could all be removed if debugging didn't need to perform
+    // per-instance code patching.
+
+    mutable Atomic<bool>    debugCodeClaimed_;
+    const UniqueConstBytes  debugUnlinkedCode_;
+    const UniqueLinkData    debugLinkData_;
+
     // This flag is only used for testing purposes and is racily updated as soon
     // as tier-2 compilation finishes (in success or failure).
 
     mutable Atomic<bool>    testingTier2Active_;
 
-    // `codeIsBusy_` is set to false initially and then to true when `code_` is
-    // already being used for an instance and can't be shared because it may be
-    // patched by the debugger. Subsequent instances must then create copies
-    // by linking the `unlinkedCodeForDebugging_`.
-
-    mutable Atomic<bool>    codeIsBusy_;
-
     bool instantiateFunctions(JSContext* cx, Handle<FunctionVector> funcImports) const;
     bool instantiateMemory(JSContext* cx, MutableHandleWasmMemoryObject memory) const;
     bool instantiateTable(JSContext* cx,
                           MutableHandleWasmTableObject table,
                           SharedTableVector* tables) const;
     bool instantiateGlobals(JSContext* cx, HandleValVector globalImportValues,
                             WasmGlobalObjectVector& globalObjs) const;
     bool initSegments(JSContext* cx,
                       HandleWasmInstanceObject instance,
                       Handle<FunctionVector> funcImports,
                       HandleWasmMemoryObject memory,
                       HandleValVector globalImportValues) const;
 
     class Tier2GeneratorTaskImpl;
 
   public:
-    Module(Assumptions&& assumptions,
-           const Code& code,
-           UniqueConstBytes unlinkedCodeForDebugging,
-           LinkData&& linkData,
+    Module(const Code& code,
            ImportVector&& imports,
            ExportVector&& exports,
            DataSegmentVector&& dataSegments,
            ElemSegmentVector&& elemSegments,
            StructTypeVector&& structTypes,
-           const ShareableBytes& bytecode)
-      : assumptions_(std::move(assumptions)),
-        code_(&code),
-        unlinkedCodeForDebugging_(std::move(unlinkedCodeForDebugging)),
-        linkData_(std::move(linkData)),
+           const ShareableBytes& bytecode,
+           UniqueConstBytes debugUnlinkedCode = nullptr,
+           UniqueLinkData debugLinkData = nullptr)
+      : code_(&code),
         imports_(std::move(imports)),
         exports_(std::move(exports)),
         dataSegments_(std::move(dataSegments)),
         elemSegments_(std::move(elemSegments)),
         structTypes_(std::move(structTypes)),
         bytecode_(&bytecode),
-        testingTier2Active_(false),
-        codeIsBusy_(false)
+        debugCodeClaimed_(false),
+        debugUnlinkedCode_(std::move(debugUnlinkedCode)),
+        debugLinkData_(std::move(debugLinkData)),
+        testingTier2Active_(false)
     {
-        MOZ_ASSERT_IF(metadata().debugEnabled, unlinkedCodeForDebugging_);
+        MOZ_ASSERT_IF(metadata().debugEnabled, debugUnlinkedCode_ && debugLinkData_);
     }
     ~Module() override { /* Note: can be called on any thread */ }
 
     const Code& code() const { return *code_; }
     const ModuleSegment& moduleSegment(Tier t) const { return code_->segment(t); }
     const Metadata& metadata() const { return code_->metadata(); }
     const MetadataTier& metadata(Tier t) const { return code_->metadata(t); }
-    const LinkData& linkData() const { return linkData_; }
-    const LinkDataTier& linkData(Tier t) const { return linkData_.tier(t); }
     const ImportVector& imports() const { return imports_; }
     const ExportVector& exports() const { return exports_; }
     const ShareableBytes& bytecode() const { return *bytecode_; }
     uint32_t codeLength(Tier t) const { return code_->segment(t).length(); }
 
     // Instantiate this module with the given imports:
 
     bool instantiate(JSContext* cx,
@@ -197,31 +135,27 @@ class Module : public JS::WasmModule
                      MutableHandleWasmInstanceObject instanceObj) const;
 
     // Tier-2 compilation may be initiated after the Module is constructed at
     // most once. When tier-2 compilation completes, ModuleGenerator calls
     // finishTier2() from a helper thread, passing tier-variant data which will
     // be installed and made visible.
 
     void startTier2(const CompileArgs& args);
-    bool finishTier2(UniqueLinkDataTier linkData2, UniqueCodeTier tier2, ModuleEnvironment* env2);
+    bool finishTier2(const LinkData& linkData, UniqueCodeTier tier2, ModuleEnvironment&& env2);
 
     void testingBlockOnTier2Complete() const;
     bool testingTier2Active() const { return testingTier2Active_; }
 
     // Currently dead, but will be ressurrected with shell tests (bug 1330661)
     // and HTTP cache integration.
 
-    size_t compiledSerializedSize() const;
-    void compiledSerialize(uint8_t* compiledBegin, size_t compiledSize) const;
-
-    static bool assumptionsMatch(const Assumptions& current, const uint8_t* compiledBegin,
-                                 size_t remain);
-    static RefPtr<Module> deserialize(const uint8_t* bytecodeBegin, size_t bytecodeSize,
-                                      const uint8_t* compiledBegin, size_t compiledSize,
+    size_t serializedSize(const LinkData& linkData) const;
+    void serialize(const LinkData& linkData, uint8_t* begin, size_t size) const;
+    static RefPtr<Module> deserialize(const uint8_t* begin, size_t size,
                                       Metadata* maybeMetadata = nullptr);
 
     // JS API and JS::WasmModule implementation:
 
     JSObject* createObject(JSContext* cx) override;
 
     // about:memory reporting:
 
@@ -236,15 +170,14 @@ class Module : public JS::WasmModule
     bool extractCode(JSContext* cx, Tier tier, MutableHandleValue vp) const;
 };
 
 typedef RefPtr<Module> SharedModule;
 
 // JS API implementations:
 
 SharedModule
-DeserializeModule(PRFileDesc* bytecode, JS::BuildIdCharVector&& buildId, UniqueChars filename,
-                  unsigned line);
+DeserializeModule(PRFileDesc* bytecode, UniqueChars filename, unsigned line);
 
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_module_h
--- a/js/src/wasm/WasmTypes.cpp
+++ b/js/src/wasm/WasmTypes.cpp
@@ -131,54 +131,16 @@ wasm::IsRoundingFunction(SymbolicAddress
       case SymbolicAddress::NearbyIntF:
         *mode = jit::RoundingMode::NearestTiesToEven;
         return true;
       default:
         return false;
     }
 }
 
-static uint32_t
-GetCPUID()
-{
-    enum Arch {
-        X86 = 0x1,
-        X64 = 0x2,
-        ARM = 0x3,
-        MIPS = 0x4,
-        MIPS64 = 0x5,
-        ARM64 = 0x6,
-        ARCH_BITS = 3
-    };
-
-#if defined(JS_CODEGEN_X86)
-    MOZ_ASSERT(uint32_t(jit::CPUInfo::GetSSEVersion()) <= (UINT32_MAX >> ARCH_BITS));
-    return X86 | (uint32_t(jit::CPUInfo::GetSSEVersion()) << ARCH_BITS);
-#elif defined(JS_CODEGEN_X64)
-    MOZ_ASSERT(uint32_t(jit::CPUInfo::GetSSEVersion()) <= (UINT32_MAX >> ARCH_BITS));
-    return X64 | (uint32_t(jit::CPUInfo::GetSSEVersion()) << ARCH_BITS);
-#elif defined(JS_CODEGEN_ARM)
-    MOZ_ASSERT(jit::GetARMFlags() <= (UINT32_MAX >> ARCH_BITS));
-    return ARM | (jit::GetARMFlags() << ARCH_BITS);
-#elif defined(JS_CODEGEN_ARM64)
-    MOZ_ASSERT(jit::GetARM64Flags() <= (UINT32_MAX >> ARCH_BITS));
-    return ARM64 | (jit::GetARM64Flags() << ARCH_BITS);
-#elif defined(JS_CODEGEN_MIPS32)
-    MOZ_ASSERT(jit::GetMIPSFlags() <= (UINT32_MAX >> ARCH_BITS));
-    return MIPS | (jit::GetMIPSFlags() << ARCH_BITS);
-#elif defined(JS_CODEGEN_MIPS64)
-    MOZ_ASSERT(jit::GetMIPSFlags() <= (UINT32_MAX >> ARCH_BITS));
-    return MIPS64 | (jit::GetMIPSFlags() << ARCH_BITS);
-#elif defined(JS_CODEGEN_NONE)
-    return 0;
-#else
-# error "unknown architecture"
-#endif
-}
-
 size_t
 FuncType::serializedSize() const
 {
     return sizeof(ret_) +
            SerializedPodVectorSize(args_);
 }
 
 uint8_t*
@@ -519,84 +481,16 @@ ElemSegment::deserialize(const uint8_t* 
 
 size_t
 ElemSegment::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
 {
     return elemFuncIndices.sizeOfExcludingThis(mallocSizeOf) +
            elemCodeRangeIndices(Tier::Serialized).sizeOfExcludingThis(mallocSizeOf);
 }
 
-Assumptions::Assumptions(JS::BuildIdCharVector&& buildId)
-  : cpuId(GetCPUID()),
-    buildId(std::move(buildId))
-{}
-
-Assumptions::Assumptions()
-  : cpuId(GetCPUID()),
-    buildId()
-{}
-
-bool
-Assumptions::initBuildIdFromContext(JSContext* cx)
-{
-    if (!cx->buildIdOp() || !cx->buildIdOp()(&buildId)) {
-        ReportOutOfMemory(cx);
-        return false;
-    }
-    return true;
-}
-
-bool
-Assumptions::clone(const Assumptions& other)
-{
-    cpuId = other.cpuId;
-    return buildId.appendAll(other.buildId);
-}
-
-bool
-Assumptions::operator==(const Assumptions& rhs) const
-{
-    return cpuId == rhs.cpuId &&
-           buildId.length() == rhs.buildId.length() &&
-           ArrayEqual(buildId.begin(), rhs.buildId.begin(), buildId.length());
-}
-
-size_t
-Assumptions::serializedSize() const
-{
-    return sizeof(uint32_t) +
-           SerializedPodVectorSize(buildId);
-}
-
-uint8_t*
-Assumptions::serialize(uint8_t* cursor) const
-{
-    // The format of serialized Assumptions must never change in a way that
-    // would cause old cache files written with by an old build-id to match the
-    // assumptions of a different build-id.
-
-    cursor = WriteScalar<uint32_t>(cursor, cpuId);
-    cursor = SerializePodVector(cursor, buildId);
-    return cursor;
-}
-
-const uint8_t*
-Assumptions::deserialize(const uint8_t* cursor, size_t remain)
-{
-    (cursor = ReadScalarChecked<uint32_t>(cursor, &remain, &cpuId)) &&
-    (cursor = DeserializePodVectorChecked(cursor, &remain, &buildId));
-    return cursor;
-}
-
-size_t
-Assumptions::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
-{
-    return buildId.sizeOfExcludingThis(mallocSizeOf);
-}
-
 //  Heap length on ARM should fit in an ARM immediate. We approximate the set
 //  of valid ARM immediates with the predicate:
 //    2^n for n in [16, 24)
 //  or
 //    2^24 * n for n >= 1.
 bool
 wasm::IsValidARMImmediate(uint32_t i)
 {
--- a/js/src/wasm/WasmTypes.h
+++ b/js/src/wasm/WasmTypes.h
@@ -1810,43 +1810,16 @@ enum class SymbolicAddress
     js_jit_gAtomic64Lock,
 #endif
     Limit
 };
 
 bool
 IsRoundingFunction(SymbolicAddress callee, jit::RoundingMode* mode);
 
-// Assumptions captures ambient state that must be the same when compiling and
-// deserializing a module for the compiled code to be valid. If it's not, then
-// the module must be recompiled from scratch.
-
-struct Assumptions
-{
-    uint32_t              cpuId;
-    JS::BuildIdCharVector buildId;
-
-    explicit Assumptions(JS::BuildIdCharVector&& buildId);
-
-    // If Assumptions is constructed without arguments, initBuildIdFromContext()
-    // must be called to complete initialization.
-    Assumptions();
-    bool initBuildIdFromContext(JSContext* cx);
-
-    bool clone(const Assumptions& other);
-
-    bool operator==(const Assumptions& rhs) const;
-    bool operator!=(const Assumptions& rhs) const { return !(*this == rhs); }
-
-    size_t serializedSize() const;
-    uint8_t* serialize(uint8_t* cursor) const;
-    const uint8_t* deserialize(const uint8_t* cursor, size_t remain);
-    size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
-};
-
 // Represents the resizable limits of memories and tables.
 
 struct Limits
 {
     uint32_t initial;
     Maybe<uint32_t> maximum;
 
     // `shared` is Shareable::False for tables but may be Shareable::True for
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -22,16 +22,17 @@
 #include "nsExceptionHandler.h"
 #include "nsIMemoryInfoDumper.h"
 #include "nsIMemoryReporter.h"
 #include "nsIObserverService.h"
 #include "nsIDebug2.h"
 #include "nsIDocShell.h"
 #include "nsIDocument.h"
 #include "nsIRunnable.h"
+#include "nsIPlatformInfo.h"
 #include "nsPIDOMWindow.h"
 #include "nsPrintfCString.h"
 #include "nsWindowSizes.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/Services.h"
 #include "mozilla/dom/ScriptLoader.h"
 #include "mozilla/dom/ScriptSettings.h"
@@ -1039,16 +1040,39 @@ OnLargeAllocationFailureCallback()
     RefPtr<LargeAllocationFailureRunnable> r = new LargeAllocationFailureRunnable;
     if (NS_WARN_IF(NS_FAILED(NS_DispatchToMainThread(r)))) {
         return;
     }
 
     r->BlockUntilDone();
 }
 
+bool
+mozilla::GetBuildId(JS::BuildIdCharVector* aBuildID)
+{
+    nsCOMPtr<nsIPlatformInfo> info = do_GetService("@mozilla.org/xre/app-info;1");
+    if (!info) {
+        return false;
+    }
+
+    nsCString buildID;
+    nsresult rv = info->GetPlatformBuildID(buildID);
+    NS_ENSURE_SUCCESS(rv, false);
+
+    if (!aBuildID->resize(buildID.Length())) {
+        return false;
+    }
+
+    for (size_t i = 0; i < buildID.Length(); i++) {
+        (*aBuildID)[i] = buildID[i];
+    }
+
+    return true;
+}
+
 size_t
 XPCJSRuntime::SizeOfIncludingThis(MallocSizeOf mallocSizeOf)
 {
     size_t n = 0;
     n += mallocSizeOf(this);
     n += mWrappedJSMap->SizeOfIncludingThis(mallocSizeOf);
     n += mIID2NativeInterfaceMap->SizeOfIncludingThis(mallocSizeOf);
     n += mClassInfo2NativeSetMap->ShallowSizeOfIncludingThis(mallocSizeOf);
@@ -2946,16 +2970,17 @@ XPCJSRuntime::Initialize(JSContext* cx)
     JS_SetWrapObjectCallbacks(cx, &WrapObjectCallbacks);
     js::SetPreserveWrapperCallback(cx, PreserveWrapper);
     JS_InitReadPrincipalsCallback(cx, nsJSPrincipals::ReadPrincipals);
     JS_SetAccumulateTelemetryCallback(cx, AccumulateTelemetryCallback);
     JS_SetSetUseCounterCallback(cx, SetUseCounterCallback);
     js::SetWindowProxyClass(cx, &OuterWindowProxyClass);
     js::SetXrayJitInfo(&gXrayJitInfo);
     JS::SetProcessLargeAllocationFailureCallback(OnLargeAllocationFailureCallback);
+    JS::SetProcessBuildIdOp(GetBuildId);
 
     // The JS engine needs to keep the source code around in order to implement
     // Function.prototype.toSource(). It'd be nice to not have to do this for
     // chrome code and simply stub out requests for source on it. Life is not so
     // easy, unfortunately. Nobody relies on chrome toSource() working in core
     // browser code, but chrome tests use it. The worst offenders are addons,
     // which like to monkeypatch chrome functions by calling toSource() on them
     // and using regular expressions to modify them. We avoid keeping most browser
--- a/js/xpconnect/src/xpcpublic.h
+++ b/js/xpconnect/src/xpcpublic.h
@@ -752,11 +752,18 @@ bool IsNotUAWidget(JSContext* cx, JSObje
 bool IsChromeOrXBLOrUAWidget(JSContext* cx, JSObject* /* unused */);
 
 /**
  * Same as IsChromeOrXBLOrUAWidget but can be used in worker threads as well.
  */
 bool ThreadSafeIsChromeOrXBLOrUAWidget(JSContext* cx, JSObject* obj);
 
 } // namespace dom
+
+/**
+ * Fill the given vector with the buildid.
+ */
+bool
+GetBuildId(JS::BuildIdCharVector* aBuildID);
+
 } // namespace mozilla
 
 #endif
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -2242,18 +2242,18 @@ nsDocumentViewer::Show(void)
       // SHistory and we need the SHistory to evict content viewers
       nsCOMPtr<nsIDocShellTreeItem> root;
       treeItem->GetSameTypeRootTreeItem(getter_AddRefs(root));
       nsCOMPtr<nsIWebNavigation> webNav = do_QueryInterface(root);
       RefPtr<ChildSHistory> history = webNav->GetSessionHistory();
       if (history) {
         int32_t prevIndex,loadedIndex;
         nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(treeItem);
-        docShell->GetPreviousTransIndex(&prevIndex);
-        docShell->GetLoadedTransIndex(&loadedIndex);
+        docShell->GetPreviousEntryIndex(&prevIndex);
+        docShell->GetLoadedEntryIndex(&loadedIndex);
 #ifdef DEBUG_PAGE_CACHE
         printf("About to evict content viewers: prev=%d, loaded=%d\n",
                prevIndex, loadedIndex);
 #endif
         history->LegacySHistory()->EvictOutOfRangeContentViewers(loadedIndex);
       }
     }
   }
--- a/layout/style/AnimationCommon.h
+++ b/layout/style/AnimationCommon.h
@@ -117,24 +117,27 @@ public:
     : mTarget(&aElement, aPseudoType)
   { }
 
   bool Equals(const OwningElementRef& aOther) const
   {
     return mTarget == aOther.mTarget;
   }
 
-  bool LessThan(const OwningElementRef& aOther) const
+  bool LessThan(int32_t& aChildIndex, const OwningElementRef& aOther,
+                int32_t& aOtherChildIndex) const
   {
     MOZ_ASSERT(mTarget.mElement && aOther.mTarget.mElement,
                "Elements to compare should not be null");
 
     if (mTarget.mElement != aOther.mTarget.mElement) {
       return nsContentUtils::PositionIsBefore(mTarget.mElement,
-                                              aOther.mTarget.mElement);
+                                              aOther.mTarget.mElement,
+                                              &aChildIndex,
+                                              &aOtherChildIndex);
     }
 
     return mTarget.mPseudoType == CSSPseudoElementType::NotPseudo ||
           (mTarget.mPseudoType == CSSPseudoElementType::before &&
            aOther.mTarget.mPseudoType == CSSPseudoElementType::after);
   }
 
   bool IsSet() const { return !!mTarget.mElement; }
--- a/layout/style/nsAnimationManager.cpp
+++ b/layout/style/nsAnimationManager.cpp
@@ -150,17 +150,20 @@ CSSAnimation::HasLowerCompositeOrderThan
 
   // 0. Object-equality case
   if (&aOther == this) {
     return false;
   }
 
   // 1. Sort by document order
   if (!mOwningElement.Equals(aOther.mOwningElement)) {
-    return mOwningElement.LessThan(aOther.mOwningElement);
+    return mOwningElement.LessThan(
+      const_cast<CSSAnimation*>(this)->CachedChildIndexRef(),
+      aOther.mOwningElement,
+      const_cast<CSSAnimation*>(&aOther)->CachedChildIndexRef());
   }
 
   // 2. (Same element and pseudo): Sort by position in animation-name
   return mAnimationIndex < aOther.mAnimationIndex;
 }
 
 void
 CSSAnimation::QueueEvents(const StickyTimeDuration& aActiveTime)
--- a/layout/style/nsTransitionManager.cpp
+++ b/layout/style/nsTransitionManager.cpp
@@ -357,17 +357,20 @@ CSSTransition::HasLowerCompositeOrderTha
 
   // 0. Object-equality case
   if (&aOther == this) {
     return false;
   }
 
   // 1. Sort by document order
   if (!mOwningElement.Equals(aOther.mOwningElement)) {
-    return mOwningElement.LessThan(aOther.mOwningElement);
+    return mOwningElement.LessThan(
+      const_cast<CSSTransition*>(this)->CachedChildIndexRef(),
+      aOther.mOwningElement,
+      const_cast<CSSTransition*>(&aOther)->CachedChildIndexRef());
   }
 
   // 2. (Same element and pseudo): Sort by transition generation
   if (mAnimationIndex != aOther.mAnimationIndex) {
     return mAnimationIndex < aOther.mAnimationIndex;
   }
 
   // 3. (Same transition generation): Sort by transition property
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -2265,17 +2265,17 @@ var BrowserApp = {
         "historyItems": listitems,
         "toIndex": toIndex
       };
     }
 
     let browser = this.selectedBrowser;
     let hist = browser.sessionHistory.legacySHistory;
     for (let i = toIndex; i >= fromIndex; i--) {
-      let entry = hist.getEntryAtIndex(i, false);
+      let entry = hist.getEntryAtIndex(i);
       let item = {
         title: entry.title || entry.URI.displaySpec,
         url: entry.URI.displaySpec,
         selected: (i == selIndex)
       };
       listitems.push(item);
     }
 
--- a/mobile/android/config/mozconfigs/android-aarch64/nightly
+++ b/mobile/android/config/mozconfigs/android-aarch64/nightly
@@ -1,13 +1,22 @@
 . "$topsrcdir/mobile/android/config/mozconfigs/common"
 
 # Android
 ac_add_options --with-android-min-sdk=21
 ac_add_options --target=aarch64-linux-android
 
 ac_add_options --with-branding=mobile/android/branding/nightly
 
+export AR="$topsrcdir/clang/bin/llvm-ar"
+export NM="$topsrcdir/clang/bin/llvm-nm"
+export RANLIB="$topsrcdir/clang/bin/llvm-ranlib"
+
+# Enable LTO if the NDK is available.
+if [ -z "$NO_NDK" ]; then
+  ac_add_options --enable-lto
+fi
+
 export MOZILLA_OFFICIAL=1
 export MOZ_TELEMETRY_REPORTING=1
 export MOZ_ANDROID_POCKET=1
 
 . "$topsrcdir/mobile/android/config/mozconfigs/common.override"
--- a/mobile/android/config/mozconfigs/android-api-16/nightly
+++ b/mobile/android/config/mozconfigs/android-api-16/nightly
@@ -11,9 +11,18 @@ ac_add_options --target=arm-linux-androi
 
 ac_add_options --with-branding=mobile/android/branding/nightly
 
 export MOZILLA_OFFICIAL=1
 export MOZ_TELEMETRY_REPORTING=1
 export MOZ_ANDROID_MMA=1
 export MOZ_ANDROID_POCKET=1
 
+export AR="$topsrcdir/clang/bin/llvm-ar"
+export NM="$topsrcdir/clang/bin/llvm-nm"
+export RANLIB="$topsrcdir/clang/bin/llvm-ranlib"
+
+# Enable LTO if the NDK is available.
+if [ -z "$NO_NDK" ]; then
+  ac_add_options --enable-lto
+fi
+
 . "$topsrcdir/mobile/android/config/mozconfigs/common.override"
--- a/mobile/android/config/mozconfigs/android-x86/nightly
+++ b/mobile/android/config/mozconfigs/android-x86/nightly
@@ -9,9 +9,18 @@ ac_add_options --target=i686-linux-andro
 ac_add_options --with-android-min-sdk=16
 
 ac_add_options --with-branding=mobile/android/branding/nightly
 
 export MOZILLA_OFFICIAL=1
 export MOZ_TELEMETRY_REPORTING=1
 export MOZ_ANDROID_POCKET=1
 
+export AR="$topsrcdir/clang/bin/llvm-ar"
+export NM="$topsrcdir/clang/bin/llvm-nm"
+export RANLIB="$topsrcdir/clang/bin/llvm-ranlib"
+
+# Enable LTO if the NDK is available.
+if [ -z "$NO_NDK" ]; then
+  ac_add_options --enable-lto
+fi
+
 . "$topsrcdir/mobile/android/config/mozconfigs/common.override"
--- a/netwerk/locales/en-US/necko.properties
+++ b/netwerk/locales/en-US/necko.properties
@@ -37,12 +37,17 @@ DirFileLabel=File:
 
 PhishingAuth=You are about to visit “%1$S”. This site may be attempting to trick you into thinking you are visiting a different site. Use extreme caution.
 PhishingAuthAccept=I understand and will be very careful
 SuperfluousAuth=You are about to log in to the site “%1$S” with the username “%2$S”, but the website does not require authentication. This may be an attempt to trick you.\n\nIs “%1$S” the site you want to visit?
 AutomaticAuth=You are about to log in to the site “%1$S” with the username “%2$S”.
 
 TrackerUriBlocked=The resource at “%1$S” was blocked because content blocking is enabled.
 UnsafeUriBlocked=The resource at “%1$S” was blocked by Safe Browsing.
+CookieBlockedByPermission=Request to access cookies or storage on “%1$S” was blocked because of custom cookie permission.
+CookieBlockedTracker=Request to access cookie or storage on “%1$S” was blocked because it came from a tracker and content blocking is enabled.
+CookieBlockedAll=Request to access cookie or storage on “%1$S” was blocked because we are blocking all storage access requests.
+CookieBlockedForeign=Request to access cookie or storage on “%1$S” was blocked because we are blocking all third-party storage access requests and content blocking is enabled.
+CookieBlockedSlowTrackingContent=The resource at “%1$S” was blocked because content blocking is enabled and the resource was classified as a slow tracking resource.
 
 # LOCALIZATION NOTE (nsICookieManagerAPIDeprecated): don't localize originAttributes.
 # %1$S is the deprecated API; %2$S is the interface suffix that the given deprecated API belongs to.
 nsICookieManagerAPIDeprecated=“%1$S” is changed. Update your code and pass the correct originAttributes. Read more on MDN: https://developer.mozilla.org/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsICookieManager%2$S
--- a/testing/raptor/mach_commands.py
+++ b/testing/raptor/mach_commands.py
@@ -14,16 +14,17 @@ import json
 import shutil
 import socket
 import subprocess
 
 import mozfile
 from mach.decorators import CommandProvider, Command
 from mozboot.util import get_state_dir
 from mozbuild.base import MozbuildObject, MachCommandBase
+from mozbuild.base import MachCommandConditions as conditions
 
 HERE = os.path.dirname(os.path.realpath(__file__))
 BENCHMARK_REPOSITORY = 'https://github.com/mozilla/perf-automation'
 BENCHMARK_REVISION = '4befd28725c687b91ce749420eab29352ecbcab4'
 
 
 class RaptorRunner(MozbuildObject):
     def run_test(self, raptor_args, app=None):
@@ -156,21 +157,22 @@ def create_parser():
 
 @CommandProvider
 class MachRaptor(MachCommandBase):
     @Command('raptor-test', category='testing',
              description='Run raptor performance tests.',
              parser=create_parser)
     def run_raptor_test(self, **kwargs):
 
-        from mozrunner.devices.android_device import verify_android_device
+        build_obj = MozbuildObject.from_environment(cwd=HERE)
 
-        build_obj = MozbuildObject.from_environment(cwd=HERE)
-        if not verify_android_device(build_obj, install=True, app=kwargs['binary']):
-            return 1
+        if conditions.is_android(build_obj) or kwargs['app'] == 'geckoview':
+            from mozrunner.devices.android_device import verify_android_device
+            if not verify_android_device(build_obj, install=True, app=kwargs['binary']):
+                return 1
 
         debug_command = '--debug-command'
         if debug_command in sys.argv:
             sys.argv.remove(debug_command)
 
         raptor = self._spawn(RaptorRunner)
 
         try:
--- a/toolkit/actors/PurgeSessionHistoryChild.jsm
+++ b/toolkit/actors/PurgeSessionHistoryChild.jsm
@@ -16,17 +16,17 @@ class PurgeSessionHistoryChild extends A
     let sessionHistory = this.docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory;
     if (!sessionHistory) {
       return;
     }
 
     // place the entry at current index at the end of the history list, so it won't get removed
     if (sessionHistory.index < sessionHistory.count - 1) {
       let legacy = sessionHistory.legacySHistory;
-      let indexEntry = legacy.getEntryAtIndex(sessionHistory.index, false);
+      let indexEntry = legacy.getEntryAtIndex(sessionHistory.index);
       indexEntry.QueryInterface(Ci.nsISHEntry);
       legacy.addEntry(indexEntry, true);
     }
 
     let purge = sessionHistory.count;
     if (this.content.location.href != "about:blank") {
       --purge; // Don't remove the page the user's staring at from shistory
     }
--- a/toolkit/components/antitracking/AntiTrackingCommon.cpp
+++ b/toolkit/components/antitracking/AntiTrackingCommon.cpp
@@ -12,21 +12,23 @@
 #include "mozilla/IntegerPrintfMacros.h"
 #include "mozilla/Logging.h"
 #include "mozilla/StaticPrefs.h"
 #include "mozIThirdPartyUtil.h"
 #include "nsContentUtils.h"
 #include "nsGlobalWindowInner.h"
 #include "nsICookiePermission.h"
 #include "nsICookieService.h"
+#include "nsIDocShell.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsIIOService.h"
 #include "nsIParentChannel.h"
 #include "nsIPermissionManager.h"
 #include "nsIPrincipal.h"
+#include "nsIScriptError.h"
 #include "nsIURI.h"
 #include "nsIURL.h"
 #include "nsIWebProgressListener.h"
 #include "nsNetUtil.h"
 #include "nsPIDOMWindow.h"
 #include "nsScriptSecurityManager.h"
 #include "prtime.h"
 
@@ -181,16 +183,81 @@ CheckContentBlockingAllowList(nsIHttpCha
     }
   }
 
   LOG(("Could not check the content blocking allow list because the top "
        "window wasn't accessible"));
   return false;
 }
 
+void
+ReportBlockingToConsole(nsPIDOMWindowOuter* aWindow, nsIHttpChannel* aChannel,
+                        uint32_t aRejectedReason)
+{
+  MOZ_ASSERT(aWindow && aChannel);
+  MOZ_ASSERT(aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION ||
+             aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER ||
+             aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL ||
+             aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN ||
+             aRejectedReason == nsIWebProgressListener::STATE_BLOCKED_SLOW_TRACKING_CONTENT);
+
+  if (!StaticPrefs::browser_contentblocking_enabled()) {
+    return;
+  }
+
+  nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
+  if (NS_WARN_IF(!docShell)) {
+    return;
+  }
+
+  nsCOMPtr<nsIDocument> doc = docShell->GetDocument();
+  if (NS_WARN_IF(!doc)) {
+    return;
+  }
+
+  const char* message = nullptr;
+  switch (aRejectedReason) {
+    case nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION:
+      message = "CookieBlockedByPermission";
+      break;
+
+    case nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER:
+      message = "CookieBlockedTracker";
+      break;
+
+    case nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL:
+      message = "CookieBlockedAll";
+      break;
+
+    case nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN:
+      message = "CookieBlockedForeign";
+      break;
+
+    case nsIWebProgressListener::STATE_BLOCKED_SLOW_TRACKING_CONTENT:
+      message = "CookieBlockedSlowTrackingContent";
+      break;
+
+    default:
+      return;
+  }
+
+  MOZ_ASSERT(message);
+
+  nsCOMPtr<nsIURI> uri;
+  aChannel->GetURI(getter_AddRefs(uri));
+  NS_ConvertUTF8toUTF16 spec(uri->GetSpecOrDefault());
+  const char16_t* params[] = { spec.get() };
+
+  nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+                                  NS_LITERAL_CSTRING("Content Blocking"),
+                                  doc,
+                                  nsContentUtils::eNECKO_PROPERTIES,
+                                  message,
+                                  params, ArrayLength(params));
+}
 } // anonymous
 
 /* static */ RefPtr<AntiTrackingCommon::StorageAccessGrantPromise>
 AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(const nsAString& aOrigin,
                                                          nsPIDOMWindowInner* aParentWindow)
 {
   MOZ_ASSERT(aParentWindow);
 
@@ -911,16 +978,18 @@ AntiTrackingCommon::NotifyRejection(nsIC
   NS_ENSURE_SUCCESS_VOID(rv);
 
   nsCOMPtr<nsPIDOMWindowOuter> pwin = nsPIDOMWindowOuter::From(win);
   if (!pwin) {
     return;
   }
 
   pwin->NotifyContentBlockingState(aRejectedReason, aChannel);
+
+  ReportBlockingToConsole(pwin, httpChannel, aRejectedReason);
 }
 
 /* static */ void
 AntiTrackingCommon::NotifyRejection(nsPIDOMWindowInner* aWindow,
                                     uint32_t aRejectedReason)
 {
   MOZ_ASSERT(aWindow);
   MOZ_ASSERT(aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION ||
@@ -941,12 +1010,16 @@ AntiTrackingCommon::NotifyRejection(nsPI
   }
 
   nsCOMPtr<nsPIDOMWindowOuter> pwin;
   auto* outer = nsGlobalWindowOuter::Cast(aWindow->GetOuterWindow());
   if (outer) {
     pwin = outer->GetTopOuter();
   }
 
-  if (pwin) {
-    pwin->NotifyContentBlockingState(aRejectedReason, httpChannel);
+  if (!pwin) {
+    return;
   }
+
+  pwin->NotifyContentBlockingState(aRejectedReason, httpChannel);
+
+  ReportBlockingToConsole(pwin, httpChannel, aRejectedReason);
 }
--- a/toolkit/components/extensions/docs/lifecycle.rst
+++ b/toolkit/components/extensions/docs/lifecycle.rst
@@ -27,24 +27,34 @@ To handle this, extensions can be notifi
 or updated.  Extension updates are a subtle case -- consider an API that
 makes some durable change based on the presence of a manifest property.
 If an extension uses the manifest key in one version and then is updated
 to a new version that no longer uses the manifest key,
 the ``onManifestEntry()`` method for the API is no longer called,
 but an API can examine the new manifest after an update to detect that
 the key has been removed.
 
+Handling lifecycle events
+-------------------------
+
 To be notified of update and uninstall events, an extension lists these
 events in the API manifest:
 
 .. code-block:: json
 
    "myapi": {
      "schema": "...",
      "url": "...",
      "events": ["update", "uninstall"]
    }
 
 If these properties are present, the ``onUpdate()`` and ``onUninstall()``
 methods will be called for the relevant ``ExtensionAPI`` instances when
 an extension that uses the API is updated or uninstalled.
 
+Note that these events can be triggered on extensions that are inactive.
+For that reason, these events can only be handled by extension APIs that
+are built into the browser.  Or, in other words, these events cannot be
+handled by APIs that are implemented in WebExtension experiments.  If the
+implementation of an API relies on these events for corectness, the API
+must be built into the browser and not delievered via an experiment.
+
 .. Should we even document onStartup()?  I think no...
--- a/toolkit/components/places/PlacesBackups.jsm
+++ b/toolkit/components/places/PlacesBackups.jsm
@@ -14,16 +14,27 @@ XPCOMUtils.defineLazyModuleGetters(this,
   BookmarkJSONUtils: "resource://gre/modules/BookmarkJSONUtils.jsm",
   OS: "resource://gre/modules/osfile.jsm",
 });
 
 XPCOMUtils.defineLazyGetter(this, "filenamesRegex",
   () => /^bookmarks-([0-9-]+)(?:_([0-9]+)){0,1}(?:_([a-z0-9=+-]{24})){0,1}\.(json(lz4)?)$/i
 );
 
+async function limitBackups(aMaxBackups, backupFiles) {
+  if (typeof aMaxBackups == "number" && aMaxBackups > -1 &&
+      backupFiles.length >= aMaxBackups) {
+    let numberOfBackupsToDelete = backupFiles.length - aMaxBackups;
+    while (numberOfBackupsToDelete--) {
+      let oldestBackup = backupFiles.pop();
+      await OS.File.remove(oldestBackup);
+    }
+  }
+}
+
 /**
  * Appends meta-data information to a given filename.
  */
 function appendMetaDataToFilename(aFilename, aMetaData) {
   let matches = aFilename.match(filenamesRegex);
   return "bookmarks-" + matches[1] +
                   "_" + aMetaData.count +
                   "_" + aMetaData.hash +
@@ -235,16 +246,23 @@ var PlacesBackups = {
     if (OS.Path.dirname(aFilePath) == backupFolderPath) {
       // We are creating a backup in the default backups folder,
       // so just update the internal cache.
       if (!this._backupFiles) {
         await this.getBackupFiles();
       }
       this._backupFiles.unshift(aFilePath);
     } else {
+      let aMaxBackup = Services.prefs.getIntPref("browser.bookmarks.max_backups");
+      if (aMaxBackup === 0) {
+        if (!this._backupFiles)
+          await this.getBackupFiles();
+        limitBackups(aMaxBackup, this._backupFiles);
+        return nodeCount;
+      }
       // If we are saving to a folder different than our backups folder, then
       // we also want to create a new compressed version in it.
       // This way we ensure the latest valid backup is the same saved by the
       // user.  See bug 424389.
       let mostRecentBackupFile = await this.getMostRecentBackup();
       if (!mostRecentBackupFile ||
           hash != getHashFromFilename(OS.Path.basename(mostRecentBackupFile))) {
         let name = this.getFilenameForDate(undefined, true);
@@ -264,19 +282,19 @@ var PlacesBackups = {
         } else {
           // There is no backup for today, add the new one.
           if (!this._backupFiles)
             await this.getBackupFiles();
           this._backupFiles.unshift(newFilePath);
         }
         let jsonString = await OS.File.read(aFilePath);
         await OS.File.writeAtomic(newFilePath, jsonString, { compression: "lz4" });
+        await limitBackups(aMaxBackup, this._backupFiles);
       }
     }
-
     return nodeCount;
   },
 
   /**
    * Creates a dated backup in <profile>/bookmarkbackups.
    * Stores the bookmarks using a lz4 compressed JSON file.
    *
    * @param [optional] int aMaxBackups
@@ -284,32 +302,22 @@ var PlacesBackups = {
    *                       all existing backups are removed and aForceBackup is
    *                       ignored, so a new one won't be created.
    * @param [optional] bool aForceBackup
    *                        Forces creating a backup even if one was already
    *                        created that day (overwrites).
    * @return {Promise}
    */
   create: function PB_create(aMaxBackups, aForceBackup) {
-    let limitBackups = async () => {
-      let backupFiles = await this.getBackupFiles();
-      if (typeof aMaxBackups == "number" && aMaxBackups > -1 &&
-          backupFiles.length >= aMaxBackups) {
-        let numberOfBackupsToDelete = backupFiles.length - aMaxBackups;
-        while (numberOfBackupsToDelete--) {
-          let oldestBackup = this._backupFiles.pop();
-          await OS.File.remove(oldestBackup);
-        }
-      }
-    };
-
     return (async () => {
       if (aMaxBackups === 0) {
         // Backups are disabled, delete any existing one and bail out.
-        await limitBackups(0);
+        if (!this._backupFiles)
+          await this.getBackupFiles();
+        await limitBackups(0, this._backupFiles);
         return;
       }
 
       // Ensure to initialize _backupFiles
       if (!this._backupFiles)
         await this.getBackupFiles();
       let newBackupFilename = this.getFilenameForDate(undefined, true);
       // If we already have a backup for today we should do nothing, unless we
@@ -361,17 +369,17 @@ var PlacesBackups = {
       }
 
       // Append metadata to the backup filename.
       let newBackupFileWithMetadata = OS.Path.join(backupFolder, newFilenameWithMetaData);
       await OS.File.move(newBackupFile, newBackupFileWithMetadata);
       this._backupFiles.unshift(newBackupFileWithMetadata);
 
       // Limit the number of backups.
-      await limitBackups(aMaxBackups);
+      await limitBackups(aMaxBackups, this._backupFiles);
     })();
   },
 
   /**
    * Gets the bookmark count for backup file.
    *
    * @param aFilePath
    *        File path The backup file.
--- a/toolkit/components/places/tests/unit/test_utils_backups_create.js
+++ b/toolkit/components/places/tests/unit/test_utils_backups_create.js
@@ -5,52 +5,47 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
  /**
   * Check for correct functionality of bookmarks backups
   */
 
 const NUMBER_OF_BACKUPS = 10;
 
-add_task(async function() {
+async function createBackups(nBackups, dateObj, bookmarksBackupDir) {
   // Generate random dates.
-  let dateObj = new Date();
   let dates = [];
-  while (dates.length < NUMBER_OF_BACKUPS) {
+  while (dates.length < nBackups) {
     // Use last year to ensure today's backup is the newest.
     let randomDate = new Date(dateObj.getFullYear() - 1,
                               Math.floor(12 * Math.random()),
                               Math.floor(28 * Math.random()));
     if (!dates.includes(randomDate.getTime()))
       dates.push(randomDate.getTime());
   }
   // Sort dates from oldest to newest.
   dates.sort();
 
-  // Get and cleanup the backups folder.
-  let backupFolderPath = await PlacesBackups.getBackupFolder();
-  let bookmarksBackupDir = new FileUtils.File(backupFolderPath);
-
   // Fake backups are created backwards to ensure we won't consider file
   // creation time.
   // Create fake backups for the newest dates.
   for (let i = dates.length - 1; i >= 0; i--) {
     let backupFilename = PlacesBackups.getFilenameForDate(new Date(dates[i]));
     let backupFile = bookmarksBackupDir.clone();
     backupFile.append(backupFilename);
     backupFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0666", 8));
     info("Creating fake backup " + backupFile.leafName);
     if (!backupFile.exists())
       do_throw("Unable to create fake backup " + backupFile.leafName);
   }
 
-  await PlacesBackups.create(NUMBER_OF_BACKUPS);
-  // Add today's backup.
-  dates.push(dateObj.getTime());
+  return dates;
+}
 
+async function checkBackups(dates, bookmarksBackupDir) {
   // Check backups.  We have 11 dates but we the max number is 10 so the
   // oldest backup should have been removed.
   for (let i = 0; i < dates.length; i++) {
     let backupFilename;
     let shouldExist;
     let backupFile;
     if (i > 0) {
       let files = bookmarksBackupDir.directoryEntries;
@@ -67,19 +62,69 @@ add_task(async function() {
       backupFilename = PlacesBackups.getFilenameForDate(new Date(dates[i]));
       backupFile = bookmarksBackupDir.clone();
       backupFile.append(backupFilename);
       shouldExist = false;
     }
     if (backupFile.exists() != shouldExist)
       do_throw("Backup should " + (shouldExist ? "" : "not") + " exist: " + backupFilename);
   }
+}
 
+async function cleanupFiles(bookmarksBackupDir) {
   // Cleanup backups folder.
   // XXX: Can't use bookmarksBackupDir.remove(true) because file lock happens
   // on WIN XP.
   let files = bookmarksBackupDir.directoryEntries;
   while (files.hasMoreElements()) {
     let entry = files.nextFile;
     entry.remove(false);
   }
+  // Clear cache to match the manual removing of files
+  delete PlacesBackups._backupFiles;
   Assert.ok(!bookmarksBackupDir.directoryEntries.hasMoreElements());
+}
+
+add_task(async function test_create_backups() {
+  let backupFolderPath = await PlacesBackups.getBackupFolder();
+  let bookmarksBackupDir = new FileUtils.File(backupFolderPath);
+
+  let dateObj = new Date();
+  let dates = await createBackups(NUMBER_OF_BACKUPS, dateObj, bookmarksBackupDir);
+  // Add today's backup.
+  await PlacesBackups.create(NUMBER_OF_BACKUPS);
+  dates.push(dateObj.getTime());
+  await checkBackups(dates, bookmarksBackupDir);
+  await cleanupFiles(bookmarksBackupDir);
 });
+
+add_task(async function test_saveBookmarks_with_no_backups() {
+  let backupFolderPath = await PlacesBackups.getBackupFolder();
+  let bookmarksBackupDir = new FileUtils.File(backupFolderPath);
+
+  Services.prefs.setIntPref("browser.bookmarks.max_backups", 0);
+
+  let filePath = do_get_tempdir().path + "/backup.json";
+  await PlacesBackups.saveBookmarksToJSONFile(filePath);
+  let files = bookmarksBackupDir.directoryEntries;
+  Assert.ok(!files.hasMoreElements(), "Should have no backup files.");
+  await OS.File.remove(filePath);
+  // We don't need to call cleanupFiles as we are not creating any
+  // backups but need to reset the cache.
+  delete PlacesBackups._backupFiles;
+});
+
+add_task(async function test_saveBookmarks_with_backups() {
+  let backupFolderPath = await PlacesBackups.getBackupFolder();
+  let bookmarksBackupDir = new FileUtils.File(backupFolderPath);
+
+  Services.prefs.setIntPref("browser.bookmarks.max_backups", NUMBER_OF_BACKUPS);
+
+  let filePath = do_get_tempdir().path + "/backup.json";
+  let dateObj = new Date();
+  let dates = await createBackups(NUMBER_OF_BACKUPS, dateObj, bookmarksBackupDir);
+
+  await PlacesBackups.saveBookmarksToJSONFile(filePath);
+  dates.push(dateObj.getTime());
+  await checkBackups(dates, bookmarksBackupDir);
+  await OS.File.remove(filePath);
+  await cleanupFiles(bookmarksBackupDir);
+});
--- a/toolkit/components/reader/ReaderMode.jsm
+++ b/toolkit/components/reader/ReaderMode.jsm
@@ -98,17 +98,17 @@ var ReaderMode = {
    * if not, append the about:reader page in the history instead.
    */
   enterReaderMode(docShell, win) {
     let url = win.document.location.href;
     let readerURL = "about:reader?url=" + encodeURIComponent(url);
     let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
     let sh = webNav.sessionHistory;
     if (webNav.canGoForward) {
-      let forwardEntry = sh.legacySHistory.getEntryAtIndex(sh.index + 1, false);
+      let forwardEntry = sh.legacySHistory.getEntryAtIndex(sh.index + 1);
       let forwardURL = forwardEntry.URI.spec;
       if (forwardURL && (forwardURL == readerURL || !readerURL)) {
         webNav.goForward();
         return;
       }
     }
 
     win.document.location = readerURL;
@@ -119,17 +119,17 @@ var ReaderMode = {
    * if not, append the original page in the history instead.
    */
   leaveReaderMode(docShell, win) {
     let url = win.document.location.href;
     let originalURL = this.getOriginalUrl(url);
     let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
     let sh = webNav.sessionHistory;
     if (webNav.canGoBack) {
-      let prevEntry = sh.legacySHistory.getEntryAtIndex(sh.index - 1, false);
+      let prevEntry = sh.legacySHistory.getEntryAtIndex(sh.index - 1);
       let prevURL = prevEntry.URI.spec;
       if (prevURL && (prevURL == originalURL || !originalURL)) {
         webNav.goBack();
         return;
       }
     }
 
     let referrerURI, principal;
--- a/toolkit/components/remotebrowserutils/tests/browser/browser_RemoteWebNavigation.js
+++ b/toolkit/components/remotebrowserutils/tests/browser/browser_RemoteWebNavigation.js
@@ -54,19 +54,19 @@ add_task(async function test_history() {
                                 SYSTEMPRINCIPAL);
   await waitForLoad(DUMMY2);
 
   await ContentTask.spawn(browser, [DUMMY1, DUMMY2], function([dummy1, dummy2]) {
     let history = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                           .getInterface(Ci.nsISHistory);
     is(history.count, 2, "Should be two history items");
     is(history.index, 1, "Should be at the right place in history");
-    let entry = history.getEntryAtIndex(0, false);
+    let entry = history.getEntryAtIndex(0);
     is(entry.URI.spec, dummy1, "Should have the right history entry");
-    entry = history.getEntryAtIndex(1, false);
+    entry = history.getEntryAtIndex(1);
     is(entry.URI.spec, dummy2, "Should have the right history entry");
   });
 
   let promise = waitForPageShow();
   browser.webNavigation.goBack();
   await promise;
   await checkHistoryIndex(browser, 0);
 
@@ -87,17 +87,17 @@ add_task(async function test_history() {
 add_task(async function test_flags() {
   function checkHistory(browser, { count, index }) {
     return ContentTask.spawn(browser, [ DUMMY2, count, index ],
       function([ dummy2, count, index ]) {
         let history = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                               .getInterface(Ci.nsISHistory);
         is(history.count, count, "Should be one history item");
         is(history.index, index, "Should be at the right place in history");
-        let entry = history.getEntryAtIndex(index, false);
+        let entry = history.getEntryAtIndex(index);
         is(entry.URI.spec, dummy2, "Should have the right history entry");
       });
   }
 
   gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
   let browser = gBrowser.selectedBrowser;
 
   browser.webNavigation.loadURI(DUMMY1,
--- a/toolkit/modules/E10SUtils.jsm
+++ b/toolkit/modules/E10SUtils.jsm
@@ -272,17 +272,17 @@ var E10SUtils = {
       return false;
     }
 
     // Allow history load if loaded in this process before.
     let webNav = aDocShell.QueryInterface(Ci.nsIWebNavigation);
     let sessionHistory = webNav.sessionHistory;
     let requestedIndex = sessionHistory.legacySHistory.requestedIndex;
     if (requestedIndex >= 0) {
-      if (sessionHistory.legacySHistory.getEntryAtIndex(requestedIndex, false).loadedInThisProcess) {
+      if (sessionHistory.legacySHistory.getEntryAtIndex(requestedIndex).loadedInThisProcess) {
         return true;
       }
 
       // If not originally loaded in this process allow it if the URI would
       // normally be allowed to load in this process by default.
       let remoteType = Services.appinfo.remoteType;
       return remoteType ==
         this.getRemoteTypeForURIObject(aURI, true, remoteType, webNav.currentURI);
--- a/toolkit/modules/sessionstore/SessionHistory.jsm
+++ b/toolkit/modules/sessionstore/SessionHistory.jsm
@@ -76,28 +76,25 @@ var SessionHistoryInternal = {
 
     let data = {entries: [], userContextId: loadContext.originAttributes.userContextId };
     // We want to keep track how many entries we *could* have collected and
     // how many we skipped, so we can sanitiy-check the current history index
     // and also determine whether we need to get any fallback data or not.
     let skippedCount = 0, entryCount = 0;
 
     if (history && history.count > 0) {
-      // Loop over the transactions so we can get the persist property for each
-      // one.
       let shistory = history.legacySHistory.QueryInterface(Ci.nsISHistory);
       let count = shistory.count;
       for ( ; entryCount < count; entryCount++) {
-        let txn = shistory.GetTransactionAtIndex(entryCount);
+        let shEntry = shistory.getEntryAtIndex(entryCount);
         if (entryCount <= aFromIdx) {
           skippedCount++;
           continue;
         }
-        let entry = this.serializeEntry(txn.sHEntry);
-        entry.persist = txn.persist;
+        let entry = this.serializeEntry(shEntry);
         data.entries.push(entry);
       }
 
       // Ensure the index isn't out of bounds if an exception was thrown above.
       data.index = Math.min(history.index + 1, entryCount);
     }
 
     // If either the session history isn't available yet or doesn't have any
@@ -247,16 +244,18 @@ var SessionHistoryInternal = {
         }
       }
 
       if (children.length) {
         entry.children = children;
       }
     }
 
+    entry.persist = shEntry.persist;
+
     return entry;
   },
 
   /**
    * Get an object that is a serializable representation of a PresState.
    *
    * @param layoutHistoryState
    *        nsILayoutHistoryState instance
@@ -310,17 +309,17 @@ var SessionHistoryInternal = {
         continue;
       let persist = "persist" in entry ? entry.persist : true;
       history.addEntry(this.deserializeEntry(entry, idMap, docIdentMap), persist);
     }
 
     // Select the right history entry.
     let index = tabData.index - 1;
     if (index < history.count && history.index != index) {
-      history.getEntryAtIndex(index, true);
+      history.index = index;
     }
     return history;
   },
 
   /**
    * Expands serialized history data into a session-history-entry instance.
    *
    * @param entry
--- a/uriloader/base/nsIWebProgressListener.idl
+++ b/uriloader/base/nsIWebProgressListener.idl
@@ -325,17 +325,17 @@ interface nsIWebProgressListener : nsISu
    *   Rejected because cookie policy blocks 3rd party cookies.
    *
    * STATE_BLOCKED_SLOW_TRACKING_CONTENT
    *   Rejected because of the FastBlock feature.
    */
   const unsigned long STATE_COOKIES_BLOCKED_BY_PERMISSION = 0x10000000;
   const unsigned long STATE_COOKIES_BLOCKED_TRACKER       = 0x20000000;
   const unsigned long STATE_COOKIES_BLOCKED_ALL           = 0x40000000;
-  const unsigned long STATE_COOKIES_BLOCKED_FOREIGN       = 0x80000000;
+  const unsigned long STATE_COOKIES_BLOCKED_FOREIGN       = 0x00000080;
   const unsigned long STATE_BLOCKED_SLOW_TRACKING_CONTENT = 0x00000040;
 
   /**
    * Notification indicating the state has changed for one of the requests
    * associated with aWebProgress.
    *
    * @param aWebProgress
    *        The nsIWebProgress instance that fired the notification
--- a/xpcom/base/CycleCollectedJSContext.cpp
+++ b/xpcom/base/CycleCollectedJSContext.cpp
@@ -33,17 +33,16 @@
 #include "nsCycleCollectionParticipant.h"
 #include "nsCycleCollector.h"
 #include "nsDOMJSUtils.h"
 #include "nsDOMMutationObserver.h"
 #include "nsJSUtils.h"
 #include "nsWrapperCache.h"
 #include "nsStringBuffer.h"
 
-#include "nsIPlatformInfo.h"
 #include "nsThread.h"
 #include "nsThreadUtils.h"
 #include "xpcpublic.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 namespace mozilla {
--- a/xpcom/base/CycleCollectedJSRuntime.cpp
+++ b/xpcom/base/CycleCollectedJSRuntime.cpp
@@ -86,17 +86,16 @@
 #include "nsStringBuffer.h"
 #include "GeckoProfiler.h"
 
 #ifdef MOZ_GECKO_PROFILER
 #include "ProfilerMarkerPayload.h"
 #endif
 
 #include "nsIException.h"
-#include "nsIPlatformInfo.h"
 #include "nsThread.h"
 #include "nsThreadUtils.h"
 #include "xpcpublic.h"
 
 #ifdef NIGHTLY_BUILD
 // For performance reasons, we make the JS Dev Error Interceptor a Nightly-only feature.
 #define MOZ_JS_DEV_ERROR_INTERCEPTOR = 1
 #endif // NIGHTLY_BUILD
@@ -481,39 +480,16 @@ NoteJSChildGrayWrapperShim(void* aData, 
 static const JSZoneParticipant sJSZoneCycleCollectorGlobal;
 
 static
 void JSObjectsTenuredCb(JSContext* aContext, void* aData)
 {
   static_cast<CycleCollectedJSRuntime*>(aData)->JSObjectsTenured();
 }
 
-bool
-mozilla::GetBuildId(JS::BuildIdCharVector* aBuildID)
-{
-  nsCOMPtr<nsIPlatformInfo> info = do_GetService("@mozilla.org/xre/app-info;1");
-  if (!info) {
-    return false;
-  }
-
-  nsCString buildID;
-  nsresult rv = info->GetPlatformBuildID(buildID);
-  NS_ENSURE_SUCCESS(rv, false);
-
-  if (!aBuildID->resize(buildID.Length())) {
-    return false;
-  }
-
-  for (size_t i = 0; i < buildID.Length(); i++) {
-    (*aBuildID)[i] = buildID[i];
-  }
-
-  return true;
-}
-
 static void
 MozCrashWarningReporter(JSContext*, JSErrorReport*)
 {
   MOZ_CRASH("Why is someone touching JSAPI without an AutoJSAPI?");
 }
 
 CycleCollectedJSRuntime::CycleCollectedJSRuntime(JSContext* aCx)
   : mGCThingCycleCollectorGlobal(sGCThingCycleCollectorGlobal)
@@ -550,17 +526,16 @@ CycleCollectedJSRuntime::CycleCollectedJ
     // relevant to the main-thread.
     mPrevGCNurseryCollectionCallback = JS::SetGCNurseryCollectionCallback(
       aCx, GCNurseryCollectionCallback);
   }
 
   JS_SetObjectsTenuredCallback(aCx, JSObjectsTenuredCb, this);
   JS::SetOutOfMemoryCallback(aCx, OutOfMemoryCallback, this);
   JS_SetExternalStringSizeofCallback(aCx, SizeofExternalStringCallback);
-  JS::SetBuildIdOp(aCx, GetBuildId);
   JS::SetWarningReporter(aCx, MozCrashWarningReporter);
 
   js::AutoEnterOOMUnsafeRegion::setAnnotateOOMAllocationSizeCallback(
     CrashReporter::AnnotateOOMAllocationSize);
 
   static js::DOMCallbacks DOMcallbacks = {
     InstanceClassHasProtoAtDepth
   };
--- a/xpcom/base/CycleCollectedJSRuntime.h
+++ b/xpcom/base/CycleCollectedJSRuntime.h
@@ -422,14 +422,11 @@ inline bool AddToCCKind(JS::TraceKind aK
 {
   return aKind == JS::TraceKind::Object ||
          aKind == JS::TraceKind::Script ||
          aKind == JS::TraceKind::LazyScript ||
          aKind == JS::TraceKind::Scope ||
          aKind == JS::TraceKind::RegExpShared;
 }
 
-bool
-GetBuildId(JS::BuildIdCharVector* aBuildID);
-
 } // namespace mozilla
 
 #endif // mozilla_CycleCollectedJSRuntime_h
--- a/xpcom/base/nsCycleCollector.cpp
+++ b/xpcom/base/nsCycleCollector.cpp
@@ -152,23 +152,25 @@
 
 #include "base/process_util.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/CycleCollectedJSContext.h"
 #include "mozilla/CycleCollectedJSRuntime.h"
 #include "mozilla/DebugOnly.h"
+#include "mozilla/HashFunctions.h"
 #include "mozilla/HashTable.h"
 #include "mozilla/HoldDropJSObjects.h"
 /* This must occur *after* base/process_util.h to avoid typedefs conflicts. */
 #include "mozilla/LinkedList.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Move.h"
 #include "mozilla/SegmentedVector.h"
+#include "mozilla/Variant.h"
 
 #include "nsCycleCollectionParticipant.h"
 #include "nsCycleCollectionNoteRootCallback.h"
 #include "nsDeque.h"
 #include "nsExceptionHandler.h"
 #include "nsCycleCollector.h"
 #include "nsThreadUtils.h"
 #include "nsXULAppAPI.h"
@@ -2087,16 +2089,53 @@ private:
   nsCycleCollectionParticipant* mJSParticipant;
   nsCycleCollectionParticipant* mJSZoneParticipant;
   nsCString mNextEdgeName;
   RefPtr<nsCycleCollectorLogger> mLogger;
   bool mMergeZones;
   nsAutoPtr<NodePool::Enumerator> mCurrNode;
   uint32_t mNoteChildCount;
 
+  class GraphCache
+  {
+  public:
+    // This either returns a pointer if present, or an index, if it isn't.
+    Variant<PtrInfo*, uint32_t> GetEntryOrIndex(void* aPtr)
+    {
+      uint32_t hash = mozilla::HashGeneric(aPtr);
+      uint32_t index = hash % kCacheSize;
+      PtrInfo* result = mCache[index];
+      if (result && result->mPointer == aPtr) {
+        return AsVariant(result);
+      }
+
+      return AsVariant(index);
+    }
+
+    void Add(uint32_t aIndex, PtrInfo* aPtrInfo)
+    {
+      mCache[aIndex] = aPtrInfo;
+    }
+
+    void Remove(void* aPtr)
+    {
+      uint32_t hash = mozilla::HashGeneric(aPtr);
+      uint32_t index = hash % kCacheSize;
+      PtrInfo* pinfo = mCache[index];
+      if (pinfo && pinfo->mPointer == aPtr) {
+        mCache[index] = nullptr;
+      }
+    }
+  private:
+    const static uint32_t kCacheSize = 491;
+    PtrInfo* mCache[kCacheSize] = {0};
+  };
+
+  GraphCache mGraphCache;
+
 public:
   CCGraphBuilder(CCGraph& aGraph,
                  CycleCollectorResults& aResults,
                  CycleCollectedJSRuntime* aCCRuntime,
                  nsCycleCollectorLogger* aLogger,
                  bool aMergeZones);
   virtual ~CCGraphBuilder();
 
@@ -2108,16 +2147,20 @@ public:
   bool AddPurpleRoot(void* aRoot, nsCycleCollectionParticipant* aParti);
 
   // This is called when all roots have been added to the graph, to prepare for BuildGraph().
   void DoneAddingRoots();
 
   // Do some work traversing nodes in the graph. Returns true if this graph building is finished.
   bool BuildGraph(SliceBudget& aBudget);
 
+  void RemoveCachedEntry(void* aPtr)
+  {
+    mGraphCache.Remove(aPtr);
+  }
 private:
   PtrInfo* AddNode(void* aPtr, nsCycleCollectionParticipant* aParticipant);
   PtrInfo* AddWeakMapNode(JS::GCCellPtr aThing);
   PtrInfo* AddWeakMapNode(JSObject* aObject);
 
   void SetFirstChild()
   {
     mCurrPi->SetFirstChild(mEdgeBuilder.Mark());
@@ -2205,16 +2248,20 @@ CCGraphBuilder::CCGraphBuilder(CCGraph& 
   , mNodeBuilder(aGraph.mNodes)
   , mEdgeBuilder(aGraph.mEdges)
   , mJSParticipant(nullptr)
   , mJSZoneParticipant(nullptr)
   , mLogger(aLogger)
   , mMergeZones(aMergeZones)
   , mNoteChildCount(0)
 {
+  // 4096 is an allocation bucket size.
+  static_assert(sizeof(CCGraphBuilder) <= 4096,
+                "Don't create too large CCGraphBuilder objects");
+
   if (aCCRuntime) {
     mJSParticipant = aCCRuntime->GCThingParticipant();
     mJSZoneParticipant = aCCRuntime->ZoneParticipant();
   }
 
   if (mLogger) {
     mFlags |= nsCycleCollectionTraversalCallback::WANT_DEBUG_INFO;
     if (mLogger->IsAllTraces()) {
@@ -2235,16 +2282,25 @@ CCGraphBuilder::~CCGraphBuilder()
 
 PtrInfo*
 CCGraphBuilder::AddNode(void* aPtr, nsCycleCollectionParticipant* aParticipant)
 {
   if (mGraph.mOutOfMemory) {
     return nullptr;
   }
 
+  Variant<PtrInfo*, uint32_t> cacheVariant = mGraphCache.GetEntryOrIndex(aPtr);
+  if (cacheVariant.is<PtrInfo*>()) {
+    MOZ_ASSERT(cacheVariant.as<PtrInfo*>()->mParticipant == aParticipant,
+               "nsCycleCollectionParticipant shouldn't change!");
+    return cacheVariant.as<PtrInfo*>();
+  }
+
+  MOZ_ASSERT(cacheVariant.is<uint32_t>());
+
   PtrInfo* result;
   auto p = mGraph.mPtrInfoMap.lookupForAdd(aPtr);
   if (!p) {
     // New entry
     result = mNodeBuilder.Add(aPtr, aParticipant);
     if (!result) {
       return nullptr;
     }
@@ -2258,16 +2314,18 @@ CCGraphBuilder::AddNode(void* aPtr, nsCy
     }
 
   } else {
     result = *p;
     MOZ_ASSERT(result->mParticipant == aParticipant,
                "nsCycleCollectionParticipant shouldn't change!");
   }
 
+  mGraphCache.Add(cacheVariant.as<uint32_t>(), result);
+
   return result;
 }
 
 bool
 CCGraphBuilder::AddPurpleRoot(void* aRoot, nsCycleCollectionParticipant* aParti)
 {
   ToParticipant(aRoot, &aParti);
 
@@ -4039,16 +4097,19 @@ nsCycleCollector::Shutdown(bool aDoColle
 void
 nsCycleCollector::RemoveObjectFromGraph(void* aObj)
 {
   if (IsIdle()) {
     return;
   }
 
   mGraph.RemoveObjectFromMap(aObj);
+  if (mBuilder) {
+    mBuilder->RemoveCachedEntry(aObj);
+  }
 }
 
 void
 nsCycleCollector::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                                       size_t* aObjectSize,
                                       size_t* aGraphSize,
                                       size_t* aPurpleBufferSize) const
 {