Bug 1315105 - Part 2: Implement <link rel=prerender> behind a pref, r=smaug
☠☠ backed out by be8630945ce9 ☠ ☠
authorMichael Layzell <michael@thelayzells.com>
Mon, 19 Dec 2016 15:05:31 +0800
changeset 326371 059753ec9117db25295484ebdf3b0b863df655f1
parent 326370 ab6c012704b9552a14d44d5ab1ec95e81ba3f7d2
child 326372 cb1e599697296f0ba393fb38e5c351e0a7e1e570
push id84943
push usercbook@mozilla.com
push dateMon, 19 Dec 2016 09:22:42 +0000
treeherdermozilla-inbound@5414d6a71785 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1315105
milestone53.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
Bug 1315105 - Part 2: Implement <link rel=prerender> behind a pref, r=smaug MozReview-Commit-ID: ARET98o1FTU
browser/base/content/tab-content.js
browser/base/content/tabbrowser.xml
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
docshell/base/nsIDocShell.idl
docshell/shistory/nsSHistory.cpp
dom/base/GroupedSHistory.cpp
dom/base/Link.cpp
dom/base/Link.h
dom/base/nsContentSink.cpp
dom/base/nsDocument.cpp
dom/base/nsIDocument.h
dom/base/nsStyleLinkElement.cpp
dom/base/nsStyleLinkElement.h
dom/html/HTMLLinkElement.cpp
dom/xul/test/file_bug1069772.xul
dom/xul/test/file_bug1271240.xul
embedding/browser/nsIWebBrowserChrome3.idl
toolkit/content/widgets/browser.xml
xpfe/appshell/nsContentTreeOwner.cpp
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -602,16 +602,107 @@ addEventListener("unload", () => {
 }, false);
 
 addMessageListener("Browser:AppTab", function(message) {
   if (docShell) {
     docShell.isAppTab = message.data.isAppTab;
   }
 });
 
+let PrerenderContentHandler = {
+  init() {
+    this._pending = [];
+    this._idMonotonic = 0;
+    this._initialized = true;
+    addMessageListener("Prerender:Canceled", this);
+    addMessageListener("Prerender:Swapped", this);
+  },
+
+  get initialized() {
+    return !!this._initialized;
+  },
+
+  receiveMessage(aMessage) {
+    switch (aMessage.name) {
+      case "Prerender:Canceled": {
+        for (let i = 0; i < this._pending.length; ++i) {
+          if (this._pending[i].id === aMessage.data.id) {
+            if (this._pending[i].failure) {
+              this._pending[i].failure.run();
+            }
+            // Remove the item from the array
+            this._pending.splice(i, 1);
+            break;
+          }
+        }
+        break;
+      }
+      case "Prerender:Swapped": {
+        for (let i = 0; i < this._pending.length; ++i) {
+          if (this._pending[i].id === aMessage.data.id) {
+            if (this._pending[i].success) {
+              this._pending[i].success.run();
+            }
+            // Remove the item from the array
+            this._pending.splice(i, 1);
+            break;
+          }
+        }
+        break;
+      }
+    }
+  },
+
+  startPrerenderingDocument(aHref, aReferrer) {
+    // XXX: Make this constant a pref
+    if (this._pending.length >= 2) {
+      return;
+    }
+
+    let id = ++this._idMonotonic;
+    sendAsyncMessage("Prerender:Request", {
+      href: aHref.spec,
+      referrer: aReferrer ? aReferrer.spec : null,
+      id: id,
+    });
+
+    this._pending.push({
+      href: aHref,
+      referrer: aReferrer,
+      id: id,
+      success: null,
+      failure: null,
+    });
+  },
+
+  shouldSwitchToPrerenderedDocument(aHref, aReferrer, aSuccess, aFailure) {
+    // Check if we think there is a prerendering document pending for the given
+    // href and referrer. If we think there is one, we will send a message to
+    // the parent process asking it to do a swap, and hook up the success and
+    // failure listeners.
+    for (let i = 0; i < this._pending.length; ++i) {
+      let p = this._pending[i];
+      if (p.href.equals(aHref) && p.referrer.equals(aReferrer)) {
+        p.success = aSuccess;
+        p.failure = aFailure;
+        sendAsyncMessage("Prerender:Swap", {id: p.id});
+        return true;
+      }
+    }
+
+    return false;
+  }
+};
+
+if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
+  // We only want to initialize the PrerenderContentHandler in the content
+  // process. Outside of the content process, this should be unused.
+  PrerenderContentHandler.init();
+}
+
 var WebBrowserChrome = {
   onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
     return BrowserUtils.onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab);
   },
 
   // Check whether this URI should load in the current process
   shouldLoadURI: function(aDocShell, aURI, aReferrer) {
     if (!E10SUtils.shouldLoadURI(aDocShell, aURI, aReferrer)) {
@@ -625,16 +716,30 @@ var WebBrowserChrome = {
   shouldLoadURIInThisProcess: function(aURI) {
     return E10SUtils.shouldLoadURIInThisProcess(aURI);
   },
 
   // Try to reload the currently active or currently loading page in a new process.
   reloadInFreshProcess: function(aDocShell, aURI, aReferrer) {
     E10SUtils.redirectLoad(aDocShell, aURI, aReferrer, true);
     return true;
+  },
+
+  startPrerenderingDocument: function(aHref, aReferrer) {
+    if (PrerenderContentHandler.initialized) {
+      PrerenderContentHandler.startPrerenderingDocument(aHref, aReferrer);
+    }
+  },
+
+  shouldSwitchToPrerenderedDocument: function(aHref, aReferrer, aSuccess, aFailure) {
+    if (PrerenderContentHandler.initialized) {
+      return PrerenderContentHandler.shouldSwitchToPrerenderedDocument(
+        aHref, aReferrer, aSuccess, aFailure);
+    }
+    return false;
   }
 };
 
 if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
   let tabchild = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                          .getInterface(Ci.nsITabChild);
   tabchild.webBrowserChrome = WebBrowserChrome;
 }
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1495,16 +1495,17 @@
 
       <method name="loadOneTab">
         <parameter name="aURI"/>
         <parameter name="aReferrerURI"/>
         <parameter name="aCharset"/>
         <parameter name="aPostData"/>
         <parameter name="aLoadInBackground"/>
         <parameter name="aAllowThirdPartyFixup"/>
+        <parameter name="aIsPrerendered"/>
         <body>
           <![CDATA[
             var aReferrerPolicy;
             var aFromExternal;
             var aRelatedToCurrent;
             var aAllowMixedContent;
             var aSkipAnimation;
             var aForceNotRemote;
@@ -1530,16 +1531,17 @@
               aSkipAnimation        = params.skipAnimation;
               aForceNotRemote       = params.forceNotRemote;
               aPreferredRemoteType  = params.preferredRemoteType;
               aNoReferrer           = params.noReferrer;
               aUserContextId        = params.userContextId;
               aRelatedBrowser       = params.relatedBrowser;
               aOriginPrincipal      = params.originPrincipal;
               aOpener               = params.opener;
+              aIsPrerendered        = params.isPrerendered;
             }
 
             var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
                          Services.prefs.getBoolPref("browser.tabs.loadInBackground");
             var owner = bgLoad ? null : this.selectedTab;
             var tab = this.addTab(aURI, {
                                   referrerURI: aReferrerURI,
                                   referrerPolicy: aReferrerPolicy,
@@ -1552,17 +1554,18 @@
                                   skipAnimation: aSkipAnimation,
                                   allowMixedContent: aAllowMixedContent,
                                   forceNotRemote: aForceNotRemote,
                                   preferredRemoteType: aPreferredRemoteType,
                                   noReferrer: aNoReferrer,
                                   userContextId: aUserContextId,
                                   originPrincipal: aOriginPrincipal,
                                   relatedBrowser: aRelatedBrowser,
-                                  opener: aOpener });
+                                  opener: aOpener,
+                                  isPrerendered: aIsPrerendered });
             if (!bgLoad)
               this.selectedTab = tab;
 
             return tab;
          ]]>
         </body>
       </method>
 
@@ -1930,28 +1933,32 @@
       </method>
 
       <method name="_createBrowser">
         <parameter name="aParams"/>
         <body>
           <![CDATA[
             // Supported parameters:
             // userContextId, remote, remoteType, isPreloadBrowser,
-            // uriIsAboutBlank, permanentKey
+            // uriIsAboutBlank, permanentKey, isPrerendered
 
             const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
             let b = document.createElementNS(NS_XUL, "browser");
             b.permanentKey = aParams.permanentKey || {};
             b.setAttribute("type", "content");
             b.setAttribute("message", "true");
             b.setAttribute("messagemanagergroup", "browsers");
             b.setAttribute("contextmenu", this.getAttribute("contentcontextmenu"));
             b.setAttribute("tooltip", this.getAttribute("contenttooltip"));
 
+            if (aParams.isPrerendered) {
+              b.setAttribute("prerendered", "true");
+            }
+
             if (aParams.userContextId) {
               b.setAttribute("usercontextid", aParams.userContextId);
             }
 
             // remote parameter used by some addons, use default in this case.
             if (aParams.remote && !aParams.remoteType) {
               aParams.remoteType = E10SUtils.DEFAULT_REMOTE_TYPE;
             }
@@ -2030,17 +2037,17 @@
         <parameter name="aTab"/>
         <parameter name="aURI"/>
         <parameter name="aParams"/>
         <body>
           <![CDATA[
             "use strict";
 
             // Supported parameters:
-            // forceNotRemote, preferredRemoteType, userContextId
+            // forceNotRemote, preferredRemoteType, userContextId, isPrerendered
 
             let uriIsAboutBlank = !aURI || aURI == "about:blank";
 
             let remoteType =
               aParams.forceNotRemote ? E10SUtils.NOT_REMOTE
               : E10SUtils.getRemoteTypeForURI(aURI, gMultiProcessBrowser,
                                               aParams.preferredRemoteType);
 
@@ -2063,17 +2070,18 @@
 
             if (!browser) {
               // No preloaded browser found, create one.
               browser = this._createBrowser({permanentKey: aTab.permanentKey,
                                              remoteType,
                                              uriIsAboutBlank: uriIsAboutBlank,
                                              userContextId: aParams.userContextId,
                                              relatedBrowser: aParams.relatedBrowser,
-                                             opener: aParams.opener});
+                                             opener: aParams.opener,
+                                             isPrerendered: aParams.isPrerendered});
             }
 
             let notificationbox = this.getNotificationBox(browser);
             let uniqueId = this._generateUniquePanelID();
             notificationbox.id = uniqueId;
             aTab.linkedPanel = uniqueId;
             aTab.linkedBrowser = browser;
             aTab.hasBrowser = true;
@@ -2123,16 +2131,17 @@
 
       <method name="addTab">
         <parameter name="aURI"/>
         <parameter name="aReferrerURI"/>
         <parameter name="aCharset"/>
         <parameter name="aPostData"/>
         <parameter name="aOwner"/>
         <parameter name="aAllowThirdPartyFixup"/>
+        <parameter name="aIsPrerendered"/>
         <body>
           <![CDATA[
             "use strict";
 
             const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
             var aReferrerPolicy;
             var aFromExternal;
             var aRelatedToCurrent;
@@ -2165,16 +2174,17 @@
               aPreferredRemoteType      = params.preferredRemoteType;
               aNoReferrer               = params.noReferrer;
               aUserContextId            = params.userContextId;
               aEventDetail              = params.eventDetail;
               aRelatedBrowser           = params.relatedBrowser;
               aOriginPrincipal          = params.originPrincipal;
               aDisallowInheritPrincipal = params.disallowInheritPrincipal;
               aOpener                   = params.opener;
+              aIsPrerendered            = params.isPrerendered;
             }
 
             // if we're adding tabs, we're past interrupt mode, ditch the owner
             if (this.mCurrentTab.owner)
               this.mCurrentTab.owner = null;
 
             var t = document.createElementNS(NS_XUL, "tab");
 
@@ -2182,16 +2192,20 @@
 
             if (!aURI || isBlankPageURL(aURI)) {
               t.setAttribute("label", this.mStringBundle.getString("tabs.emptyTabTitle"));
             } else if (aURI.toLowerCase().startsWith("javascript:")) {
               // This can go away when bug 672618 or bug 55696 are fixed.
               t.setAttribute("label", aURI);
             }
 
+            if (aIsPrerendered) {
+              t.setAttribute("hidden", "true");
+            }
+
             if (aUserContextId) {
               t.setAttribute("usercontextid", aUserContextId);
               ContextualIdentityService.setTabStyle(t);
             }
 
             t.setAttribute("onerror", "this.removeAttribute('image');");
             t.className = "tabbrowser-tab";
 
@@ -2234,16 +2248,17 @@
             // trigger SessionStore.jsm to run code that expects the existence
             // of tab.linkedBrowser.
             let browserParams = {
               forceNotRemote: aForceNotRemote,
               preferredRemoteType: aPreferredRemoteType,
               userContextId:  aUserContextId,
               relatedBrowser: aRelatedBrowser,
               opener: aOpener,
+              isPrerendered: aIsPrerendered,
             };
             let { usingPreloadedContent } = this._linkBrowserToTab(t, aURI, browserParams);
             let b = t.linkedBrowser;
 
             // Dispatch a new tab notification.  We do this once we're
             // entirely done, so that things are in a consistent state
             // even if the event listener opens or closes tabs.
             var detail = aEventDetail || {};
@@ -4785,16 +4800,84 @@
                 detail: data,
               });
 
               browser.dispatchEvent(event);
 
               break;
             }
 
+            case "Prerender:Request": {
+              let sendCancelPrerendering = () => {
+                browser.frameloader.messageManager.
+                  sendAsyncMessage("Prerender:Canceled", { id: data.id });
+              };
+
+              let tab = this.getTabForBrowser(browser);
+              if (!tab) {
+                // No tab?
+                sendCancelPrerendering();
+                break;
+              }
+
+              if (tab.hidden) {
+                // Skip prerender on hidden tab.
+                sendCancelPrerendering();
+                break;
+              }
+
+              if (browser.canGoForward) {
+                // Skip prerender on history navigation as we don't support it
+                // yet. Remove this check once bug 1323650 is implemented.
+                sendCancelPrerendering();
+                break;
+              }
+
+              if (!data.href) {
+                // If we don't have data.href, loadOneTab will load about:blank
+                // which is meaningless for prerendering.
+                sendCancelPrerendering();
+                break;
+              }
+
+              let groupedSHistory = browser.frameLoader.ensureGroupedSHistory();
+
+              let newTab = this.loadOneTab(data.href, {
+                referrerURI: (data.referrer ? makeURI(data.referrer) : null),
+                referrerPolicy: Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT,
+                postData: null,
+                allowThirdPartyFixup: true,
+                relatedToCurrent: true,
+                isPrerendered: true,
+              });
+              let partialSHistory = newTab.linkedBrowser.frameLoader.partialSessionHistory;
+              groupedSHistory.addPrerenderingPartialSHistory(partialSHistory, data.id);
+              break;
+            }
+
+            case "Prerender:Cancel": {
+              let groupedSHistory = browser.frameLoader.groupedSessionHistory;
+              if (groupedSHistory) {
+                groupedSHistory.cancelPrerendering(data.id);
+              }
+              break;
+            }
+
+            case "Prerender:Swap": {
+              let frameloader = browser.frameLoader;
+              let groupedSHistory = browser.frameLoader.groupedSessionHistory;
+              if (groupedSHistory) {
+                groupedSHistory.activatePrerendering(data.id).then(
+                  () => frameloader.messageManager.sendAsyncMessage("Prerender:Swapped", data),
+                  () => frameloader.messageManager.sendAsyncMessage("Prerender:Canceled", data),
+                );
+              }
+              break;
+            }
+
           }
           return undefined;
         ]]></body>
       </method>
 
       <method name="observe">
         <parameter name="aSubject"/>
         <parameter name="aTopic"/>
@@ -4917,16 +5000,21 @@
           messageManager.addMessageListener("RefreshBlocker:Blocked", this);
           messageManager.addMessageListener("Browser:WindowCreated", this);
 
           // To correctly handle keypresses for potential FindAsYouType, while
           // the tab's find bar is not yet initialized.
           this._findAsYouType = Services.prefs.getBoolPref("accessibility.typeaheadfind");
           Services.prefs.addObserver("accessibility.typeaheadfind", this, false);
           messageManager.addMessageListener("Findbar:Keypress", this);
+
+          // Add listeners for prerender messages
+          messageManager.addMessageListener("Prerender:Request", this);
+          messageManager.addMessageListener("Prerender:Cancel", this);
+          messageManager.addMessageListener("Prerender:Swap", this);
         ]]>
       </constructor>
 
       <method name="_generateUniquePanelID">
         <body><![CDATA[
           if (!this._uniquePanelIDCounter) {
             this._uniquePanelIDCounter = 0;
           }
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -1570,16 +1570,17 @@ nsDocShell::LoadURI(nsIURI* aURI,
                       postStream,
                       headersStream,
                       loadType,
                       nullptr, // No SHEntry
                       aFirstParty,
                       srcdoc,
                       sourceDocShell,
                       baseURI,
+                      false,
                       nullptr,  // No nsIDocShell
                       nullptr); // No nsIRequest
 }
 
 NS_IMETHODIMP
 nsDocShell::LoadStream(nsIInputStream* aStream, nsIURI* aURI,
                        const nsACString& aContentType,
                        const nsACString& aContentCharset,
@@ -5356,18 +5357,18 @@ nsDocShell::LoadErrorPage(nsIURI* aURI, 
   nsresult rv = NS_NewURI(getter_AddRefs(errorPageURI), errorPageUrl);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return InternalLoad(errorPageURI, nullptr, false, nullptr,
                       mozilla::net::RP_Default,
                       nsContentUtils::GetSystemPrincipal(), nullptr,
                       INTERNAL_LOAD_FLAGS_NONE, EmptyString(),
                       nullptr, NullString(), nullptr, nullptr, LOAD_ERROR_PAGE,
-                      nullptr, true, NullString(), this, nullptr, nullptr,
-                      nullptr);
+                      nullptr, true, NullString(), this, nullptr, false,
+                      nullptr, nullptr);
 }
 
 NS_IMETHODIMP
 nsDocShell::Reload(uint32_t aReloadFlags)
 {
   if (!IsNavigationAllowed()) {
     return NS_OK; // JS may not handle returning of an error code
   }
@@ -5449,16 +5450,17 @@ nsDocShell::Reload(uint32_t aReloadFlags
                       nullptr,         // No post data
                       nullptr,         // No headers data
                       loadType,        // Load type
                       nullptr,         // No SHEntry
                       true,
                       srcdoc,          // srcdoc argument for iframe
                       this,            // For reloads we are the source
                       baseURI,
+                      false,
                       nullptr,         // No nsIDocShell
                       nullptr);        // No nsIRequest
   }
 
   return rv;
 }
 
 NS_IMETHODIMP
@@ -7945,17 +7947,18 @@ nsDocShell::EnsureContentViewer()
   }
 
   return rv;
 }
 
 nsresult
 nsDocShell::CreateAboutBlankContentViewer(nsIPrincipal* aPrincipal,
                                           nsIURI* aBaseURI,
-                                          bool aTryToSaveOldPresentation)
+                                          bool aTryToSaveOldPresentation,
+                                          bool aCheckPermitUnload)
 {
   nsCOMPtr<nsIDocument> blankDoc;
   nsCOMPtr<nsIContentViewer> viewer;
   nsresult rv = NS_ERROR_FAILURE;
 
   /* mCreatingDocument should never be true at this point. However, it's
      a theoretical possibility. We want to know about it and make it stop,
      and this sounds like a job for an assertion. */
@@ -7978,41 +7981,42 @@ nsDocShell::CreateAboutBlankContentViewe
       mOriginAttributes));
   }
 
   // Make sure timing is created.  But first record whether we had it
   // already, so we don't clobber the timing for an in-progress load.
   bool hadTiming = mTiming;
   bool toBeReset = MaybeInitTiming();
   if (mContentViewer) {
-    // We've got a content viewer already. Make sure the user
-    // permits us to discard the current document and replace it
-    // with about:blank. And also ensure we fire the unload events
-    // in the current document.
-
-    // Unload gets fired first for
-    // document loaded from the session history.
-    mTiming->NotifyBeforeUnload();
-
-    bool okToUnload;
-    rv = mContentViewer->PermitUnload(&okToUnload);
-
-    if (NS_SUCCEEDED(rv) && !okToUnload) {
-      // The user chose not to unload the page, interrupt the load.
-      MaybeResetInitTiming(toBeReset);
-      return NS_ERROR_FAILURE;
+    if (aCheckPermitUnload) {
+      // We've got a content viewer already. Make sure the user
+      // permits us to discard the current document and replace it
+      // with about:blank. And also ensure we fire the unload events
+      // in the current document.
+
+      // Unload gets fired first for
+      // document loaded from the session history.
+      mTiming->NotifyBeforeUnload();
+
+      bool okToUnload;
+      rv = mContentViewer->PermitUnload(&okToUnload);
+
+      if (NS_SUCCEEDED(rv) && !okToUnload) {
+        // The user chose not to unload the page, interrupt the load.
+        MaybeResetInitTiming(toBeReset);
+        return NS_ERROR_FAILURE;
+      }
+      if (mTiming) {
+        mTiming->NotifyUnloadAccepted(mCurrentURI);
+      }
     }
 
     mSavingOldViewer = aTryToSaveOldPresentation &&
                        CanSavePresentation(LOAD_NORMAL, nullptr, nullptr);
 
-    if (mTiming) {
-      mTiming->NotifyUnloadAccepted(mCurrentURI);
-    }
-
     // Make sure to blow away our mLoadingURI just in case.  No loads
     // from inside this pagehide.
     mLoadingURI = nullptr;
 
     // Stop any in-progress loading, so that we don't accidentally trigger any
     // PageShow notifications from Embed() interrupting our loading below.
     Stop();
 
@@ -8086,16 +8090,22 @@ nsDocShell::CreateAboutBlankContentViewe
 }
 
 NS_IMETHODIMP
 nsDocShell::CreateAboutBlankContentViewer(nsIPrincipal* aPrincipal)
 {
   return CreateAboutBlankContentViewer(aPrincipal, nullptr);
 }
 
+NS_IMETHODIMP
+nsDocShell::ForceCreateAboutBlankContentViewer(nsIPrincipal* aPrincipal)
+{
+  return CreateAboutBlankContentViewer(aPrincipal, nullptr, true, false);
+}
+
 bool
 nsDocShell::CanSavePresentation(uint32_t aLoadType,
                                 nsIRequest* aNewRequest,
                                 nsIDocument* aNewDocument)
 {
   if (!mOSHE) {
     return false;  // no entry to save into
   }
@@ -9567,17 +9577,17 @@ public:
                     nsIURI* aOriginalURI, bool aLoadReplace,
                     nsIURI* aReferrer, uint32_t aReferrerPolicy,
                     nsIPrincipal* aTriggeringPrincipal,
                     nsIPrincipal* aPrincipalToInherit, uint32_t aFlags,
                     const char* aTypeHint, nsIInputStream* aPostData,
                     nsIInputStream* aHeadersData, uint32_t aLoadType,
                     nsISHEntry* aSHEntry, bool aFirstParty,
                     const nsAString& aSrcdoc, nsIDocShell* aSourceDocShell,
-                    nsIURI* aBaseURI)
+                    nsIURI* aBaseURI, bool aCheckForPrerender)
     : mSrcdoc(aSrcdoc)
     , mDocShell(aDocShell)
     , mURI(aURI)
     , mOriginalURI(aOriginalURI)
     , mLoadReplace(aLoadReplace)
     , mReferrer(aReferrer)
     , mReferrerPolicy(aReferrerPolicy)
     , mTriggeringPrincipal(aTriggeringPrincipal)
@@ -9585,16 +9595,17 @@ public:
     , mPostData(aPostData)
     , mHeadersData(aHeadersData)
     , mSHEntry(aSHEntry)
     , mFlags(aFlags)
     , mLoadType(aLoadType)
     , mFirstParty(aFirstParty)
     , mSourceDocShell(aSourceDocShell)
     , mBaseURI(aBaseURI)
+    , mCheckForPrerender(aCheckForPrerender)
   {
     // Make sure to keep null things null as needed
     if (aTypeHint) {
       mTypeHint = aTypeHint;
     }
   }
 
   NS_IMETHOD
@@ -9604,17 +9615,17 @@ public:
                                    mLoadReplace,
                                    mReferrer,
                                    mReferrerPolicy,
                                    mTriggeringPrincipal, mPrincipalToInherit,
                                    mFlags, EmptyString(), mTypeHint.get(),
                                    NullString(), mPostData, mHeadersData,
                                    mLoadType, mSHEntry, mFirstParty,
                                    mSrcdoc, mSourceDocShell, mBaseURI,
-                                   nullptr, nullptr);
+                                   mCheckForPrerender, nullptr, nullptr);
   }
 
 private:
   // Use IDL strings so .get() returns null by default
   nsXPIDLString mWindowTarget;
   nsXPIDLCString mTypeHint;
   nsString mSrcdoc;
 
@@ -9629,16 +9640,17 @@ private:
   nsCOMPtr<nsIInputStream> mPostData;
   nsCOMPtr<nsIInputStream> mHeadersData;
   nsCOMPtr<nsISHEntry> mSHEntry;
   uint32_t mFlags;
   uint32_t mLoadType;
   bool mFirstParty;
   nsCOMPtr<nsIDocShell> mSourceDocShell;
   nsCOMPtr<nsIURI> mBaseURI;
+  bool mCheckForPrerender;
 };
 
 /**
  * Returns true if we started an asynchronous load (i.e., from the network), but
  * the document we're loading there hasn't yet become this docshell's active
  * document.
  *
  * When JustStartedNetworkLoad is true, you should be careful about modifying
@@ -9701,16 +9713,17 @@ nsDocShell::InternalLoad(nsIURI* aURI,
                          nsIInputStream* aPostData,
                          nsIInputStream* aHeadersData,
                          uint32_t aLoadType,
                          nsISHEntry* aSHEntry,
                          bool aFirstParty,
                          const nsAString& aSrcdoc,
                          nsIDocShell* aSourceDocShell,
                          nsIURI* aBaseURI,
+                         bool aCheckForPrerender,
                          nsIDocShell** aDocShell,
                          nsIRequest** aRequest)
 {
   MOZ_ASSERT(aTriggeringPrincipal, "need a valid TriggeringPrincipal");
 
   nsresult rv = NS_OK;
   mOriginalUriString.Truncate();
 
@@ -10060,16 +10073,17 @@ nsDocShell::InternalLoad(nsIURI* aURI,
                                         aPostData,
                                         aHeadersData,
                                         aLoadType,
                                         aSHEntry,
                                         aFirstParty,
                                         aSrcdoc,
                                         aSourceDocShell,
                                         aBaseURI,
+                                        aCheckForPrerender,
                                         aDocShell,
                                         aRequest);
       if (rv == NS_ERROR_NO_CONTENT) {
         // XXXbz except we never reach this code!
         if (isNewWindow) {
           //
           // At this point, a new window has been created, but the
           // URI did not have any data associated with it...
@@ -10130,17 +10144,17 @@ nsDocShell::InternalLoad(nsIURI* aURI,
 
       // Do this asynchronously
       nsCOMPtr<nsIRunnable> ev =
         new InternalLoadEvent(this, aURI, aOriginalURI, aLoadReplace,
                               aReferrer, aReferrerPolicy,
                               aTriggeringPrincipal, principalToInherit,
                               aFlags, aTypeHint, aPostData, aHeadersData,
                               aLoadType, aSHEntry, aFirstParty, aSrcdoc,
-                              aSourceDocShell, aBaseURI);
+                              aSourceDocShell, aBaseURI, false);
       return NS_DispatchToCurrentThread(ev);
     }
 
     // Just ignore this load attempt
     return NS_OK;
   }
 
   // If a source docshell has been passed, check to see if we are sandboxed
@@ -10517,16 +10531,35 @@ nsDocShell::InternalLoad(nsIURI* aURI,
       return NS_OK;
     }
   }
 
   if (mTiming && timeBeforeUnload) {
     mTiming->NotifyUnloadAccepted(mCurrentURI);
   }
 
+  if (browserChrome3 && aCheckForPrerender) {
+    nsCOMPtr<nsIRunnable> ev =
+      new InternalLoadEvent(this, aURI, aOriginalURI, aLoadReplace,
+                            aReferrer, aReferrerPolicy,
+                            aTriggeringPrincipal, principalToInherit,
+                            aFlags, aTypeHint, aPostData, aHeadersData,
+                            aLoadType, aSHEntry, aFirstParty, aSrcdoc,
+                            aSourceDocShell, aBaseURI, false);
+    // We don't need any success handler since in that case
+    // OnPartialSessionHistoryDeactive would be called, and it would ensure
+    // docshell loads about:blank.
+    bool shouldSwitch = false;
+    rv = browserChrome3->ShouldSwitchToPrerenderedDocument(
+      aURI, mCurrentURI, nullptr, ev, &shouldSwitch);
+    if (NS_SUCCEEDED(rv) && shouldSwitch) {
+      return NS_OK;
+    }
+  }
+
   // Whenever a top-level browsing context is navigated, the user agent MUST
   // lock the orientation of the document to the document's default
   // orientation. We don't explicitly check for a top-level browsing context
   // here because orientation is only set on top-level browsing contexts.
   if (OrientationLock() != eScreenOrientation_None) {
 #ifdef DEBUG
     nsCOMPtr<nsIDocShellTreeItem> parent;
     GetSameTypeParent(getter_AddRefs(parent));
@@ -12520,16 +12553,17 @@ nsDocShell::LoadHistoryEntry(nsISHEntry*
                     postData,           // Post data stream
                     nullptr,            // No headers stream
                     aLoadType,          // Load type
                     aEntry,             // SHEntry
                     true,
                     srcdoc,
                     nullptr,            // Source docshell, see comment above
                     baseURI,
+                    false,
                     nullptr,            // No nsIDocShell
                     nullptr);           // No nsIRequest
   return rv;
 }
 
 NS_IMETHODIMP
 nsDocShell::GetShouldSaveLayoutState(bool* aShould)
 {
@@ -14025,16 +14059,17 @@ nsDocShell::OnLinkClickSync(nsIContent* 
                              aPostDataStream,           // Post data stream
                              aHeadersDataStream,        // Headers stream
                              LOAD_LINK,                 // Load type
                              nullptr,                   // No SHEntry
                              true,                      // first party site
                              NullString(),              // No srcdoc
                              this,                      // We are the source
                              nullptr,                   // baseURI not needed
+                             true,                      // Check for prerendered doc
                              aDocShell,                 // DocShell out-param
                              aRequest);                 // Request out-param
   if (NS_SUCCEEDED(rv)) {
     DispatchPings(this, aContent, aURI, referer, refererPolicy);
   }
   return rv;
 }
 
@@ -14682,16 +14717,24 @@ nsDocShell::GetTabChild()
 nsICommandManager*
 nsDocShell::GetCommandManager()
 {
   NS_ENSURE_SUCCESS(EnsureCommandHandler(), nullptr);
   return mCommandManager;
 }
 
 NS_IMETHODIMP
+nsDocShell::GetIsProcessLocked(bool* aIsLocked)
+{
+  MOZ_ASSERT(aIsLocked);
+  *aIsLocked = GetProcessLockReason() != PROCESS_LOCK_NONE;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDocShell::GetProcessLockReason(uint32_t* aReason)
 {
   MOZ_ASSERT(aReason);
 
   nsPIDOMWindowOuter* outer = GetWindow();
   MOZ_ASSERT(outer);
 
   // Check if we are a toplevel window
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -322,17 +322,18 @@ protected:
   virtual void DestroyChildren() override;
 
   // Content Viewer Management
   nsresult EnsureContentViewer();
   // aPrincipal can be passed in if the caller wants. If null is
   // passed in, the about:blank principal will end up being used.
   nsresult CreateAboutBlankContentViewer(nsIPrincipal* aPrincipal,
                                          nsIURI* aBaseURI,
-                                         bool aTryToSaveOldPresentation = true);
+                                         bool aTryToSaveOldPresentation = true,
+                                         bool aCheckPermitUnload = true);
   nsresult CreateContentViewer(const nsACString& aContentType,
                                nsIRequest* aRequest,
                                nsIStreamListener** aContentHandler);
   nsresult NewContentViewerObj(const nsACString& aContentType,
                                nsIRequest* aRequest, nsILoadGroup* aLoadGroup,
                                nsIStreamListener** aContentHandler,
                                nsIContentViewer** aViewer);
   nsresult SetupNewViewer(nsIContentViewer* aNewViewer);
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -182,20 +182,21 @@ interface nsIDocShell : nsIDocShellTreeI
                               in uint32_t aFlags,
                               in AString aWindowTarget,
                               in string aTypeHint,
                               in AString aFileName,
                               in nsIInputStream aPostDataStream,
                               in nsIInputStream aHeadersStream,
                               in unsigned long aLoadFlags,
                               in nsISHEntry aSHEntry,
-                              in boolean firstParty,
+                              in boolean aFirstParty,
                               in AString aSrcdoc,
                               in nsIDocShell aSourceDocShell,
                               in nsIURI aBaseURI,
+                              in boolean aCheckForPrerender,
                               out nsIDocShell aDocShell,
                               out nsIRequest aRequest);
 
   /**
    * Do either a history.pushState() or history.replaceState() operation,
    * depending on the value of aReplace.
    */
   [implicit_jscontext]
@@ -669,16 +670,23 @@ interface nsIDocShell : nsIDocShellTreeI
 
   /**
    * Create a new about:blank document and content viewer.
    * @param aPrincipal the principal to use for the new document.
    */
   void createAboutBlankContentViewer(in nsIPrincipal aPrincipal);
 
   /**
+   * Like createAboutBlankContentViewer, but don't check for permit unload.
+   * Only used by special session history operation.
+   * @param aPrincipal the principal to use for the new document.
+   */
+  [noscript] void forceCreateAboutBlankContentViewer(in nsIPrincipal aPrincipal);
+
+  /**
    * Upon getting, returns the canonical encoding label of the document
    * currently loaded into this docshell.
    *
    * Upon setting, sets forcedCharset for compatibility with legacy callers.
    */
   attribute ACString charset;
 
   /**
@@ -1102,17 +1110,22 @@ interface nsIDocShell : nsIDocShellTreeI
    * Don't override the platform/pref default behaviour for touch events.
    */
   const unsigned long TOUCHEVENTS_OVERRIDE_NONE = 2;
 
   /**
    * A DocShell is locked to the current process if it would be
    * content-observable for a process switch to occur before performing a
    * navigation load. It is important to ensure that a DocShell is not process
-   * locked before perfoming process changing loads.
+   * locked before performing process changing loads.
+   */
+  [infallible] readonly attribute boolean isProcessLocked;
+  /**
+   * Return PROCESS_LOCK_NONE if docShell is not locked to current process,
+   * otherwise return the reason why process is locked.
    */
   [infallible] readonly attribute unsigned long processLockReason;
   /**
    * The DocShell is not locked to the current process, and a navigation may
    * proceed in a new process.
    */
   const unsigned long PROCESS_LOCK_NONE = 0;
   /**
--- a/docshell/shistory/nsSHistory.cpp
+++ b/docshell/shistory/nsSHistory.cpp
@@ -505,17 +505,20 @@ nsSHistory::OnPartialSessionHistoryDeact
   // Ensure the deactive docshell loads about:blank.
   nsCOMPtr<nsIWebNavigation> webNav = do_QueryInterface(mRootDocShell);
   nsCOMPtr<nsIURI> currentURI;
   webNav->GetCurrentURI(getter_AddRefs(currentURI));
   if (NS_IsAboutBlank(currentURI)) {
     return NS_OK;
   }
 
-  if (NS_FAILED(mRootDocShell->CreateAboutBlankContentViewer(nullptr))) {
+  // At this point we've swapped out to an invisble tab, and can not prompt here.
+  // The check should have been done in nsDocShell::InternalLoad, so we'd
+  // just force docshell to load about:blank.
+  if (NS_FAILED(mRootDocShell->ForceCreateAboutBlankContentViewer(nullptr))) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
 /* Get index of the history list */
 NS_IMETHODIMP
--- a/dom/base/GroupedSHistory.cpp
+++ b/dom/base/GroupedSHistory.cpp
@@ -220,17 +220,26 @@ GroupedSHistory::PurgePartialHistories(u
 
   // Remove references.
   mPartialHistories.RemoveElementsAt(aLastPartialIndexToKeep + 1,
                                      lastIndex - aLastPartialIndexToKeep);
 }
 
 /* static */ bool
 GroupedSHistory::GroupedHistoryEnabled() {
-  return Preferences::GetBool("browser.groupedhistory.enabled", false);
+  static bool sGroupedSHistoryEnabled = false;
+  static bool sGroupedSHistoryPrefCached = false;
+  if (!sGroupedSHistoryPrefCached) {
+    sGroupedSHistoryPrefCached = true;
+    Preferences::AddBoolVarCache(&sGroupedSHistoryEnabled,
+                                 "browser.groupedhistory.enabled",
+                                 false);
+  }
+
+  return sGroupedSHistoryEnabled;
 }
 
 void
 GroupedSHistory::PurgePrerendering()
 {
   nsTArray<PrerenderingHistory> histories = Move(mPrerenderingHistories);
   // Remove the frameloaders which are owned by the prerendering history, and
   // remove them from mPrerenderingHistories.
--- a/dom/base/Link.cpp
+++ b/dom/base/Link.cpp
@@ -80,17 +80,17 @@ Link::CancelDNSPrefetch(nsWrapperCache::
     mElement->UnsetFlags(aRequestedFlag);
     // Possible that hostname could have changed since binding, but since this
     // covers common cases, most DNS prefetch requests will be canceled
     nsHTMLDNSPrefetch::CancelPrefetchLow(this, NS_ERROR_ABORT);
   }
 }
 
 void
-Link::TryDNSPrefetchPreconnectOrPrefetch()
+Link::TryDNSPrefetchPreconnectOrPrefetchOrPrerender()
 {
   MOZ_ASSERT(mElement->IsInComposedDoc());
   if (!ElementHasHref()) {
     return;
   }
 
   nsAutoString rel;
   if (!mElement->GetAttr(kNameSpaceID_None, nsGkAtoms::rel, rel)) {
@@ -123,16 +123,24 @@ Link::TryDNSPrefetchPreconnectOrPrefetch
     nsCOMPtr<nsIURI> uri(GetURI());
     if (uri && mElement->OwnerDoc()) {
       mElement->OwnerDoc()->MaybePreconnect(uri,
         mElement->AttrValueToCORSMode(mElement->GetParsedAttr(nsGkAtoms::crossorigin)));
       return;
     }
   }
 
+  if (linkTypes & nsStyleLinkElement::ePRERENDER) {
+    nsCOMPtr<nsIURI> uri(GetURI());
+    if (uri && mElement->OwnerDoc()) {
+      mElement->OwnerDoc()->PrerenderHref(uri);
+      return;
+    }
+  }
+
   if (linkTypes & nsStyleLinkElement::eDNS_PREFETCH) {
     if (nsHTMLDNSPrefetch::IsAllowed(mElement->OwnerDoc())) {
       nsHTMLDNSPrefetch::PrefetchLow(this);
     }
   }
 }
 
 void
--- a/dom/base/Link.h
+++ b/dom/base/Link.h
@@ -118,17 +118,17 @@ public:
   bool ElementHasHref() const;
 
   // This is called by HTMLAnchorElement.
   void TryDNSPrefetch();
   void CancelDNSPrefetch(nsWrapperCache::FlagsType aDeferredFlag,
                          nsWrapperCache::FlagsType aRequestedFlag);
 
   // This is called by HTMLLinkElement.
-  void TryDNSPrefetchPreconnectOrPrefetch();
+  void TryDNSPrefetchPreconnectOrPrefetchOrPrerender();
   void CancelPrefetch();
 
 protected:
   virtual ~Link();
 
   /**
    * Return true if the link has associated URI.
    */
--- a/dom/base/nsContentSink.cpp
+++ b/dom/base/nsContentSink.cpp
@@ -711,16 +711,24 @@ nsContentSink::ProcessLink(const nsSubst
   }
 
   bool hasPrefetch = linkTypes & nsStyleLinkElement::ePREFETCH;
   // prefetch href if relation is "next" or "prefetch"
   if (hasPrefetch || (linkTypes & nsStyleLinkElement::eNEXT)) {
     PrefetchHref(aHref, mDocument, hasPrefetch);
   }
 
+  if (linkTypes & nsStyleLinkElement::ePRERENDER) {
+    nsCOMPtr<nsIURI> href;
+    nsresult rv = NS_NewURI(getter_AddRefs(href), aHref);
+    if (NS_SUCCEEDED(rv)) {
+      mDocument->PrerenderHref(href);
+    }
+  }
+
   if (!aHref.IsEmpty() && (linkTypes & nsStyleLinkElement::eDNS_PREFETCH)) {
     PrefetchDNS(aHref);
   }
 
   if (!aHref.IsEmpty() && (linkTypes & nsStyleLinkElement::ePRECONNECT)) {
     Preconnect(aHref, aCrossOrigin);
   }
 
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -2913,16 +2913,83 @@ nsIDocument::NoteScriptTrackingStatus(co
 }
 
 bool
 nsIDocument::IsScriptTracking(const nsACString& aURL) const
 {
   return mTrackingScripts.Contains(aURL);
 }
 
+bool
+nsIDocument::PrerenderHref(nsIURI* aHref)
+{
+  MOZ_ASSERT(aHref);
+
+  static bool sPrerenderEnabled = false;
+  static bool sPrerenderPrefCached = false;
+  if (!sPrerenderPrefCached) {
+    sPrerenderPrefCached = true;
+    Preferences::AddBoolVarCache(&sPrerenderEnabled,
+                                 "dom.linkPrerender.enabled",
+                                 false);
+  }
+
+  // Check if prerender is enabled
+  if (!sPrerenderEnabled) {
+    return false;
+  }
+
+  nsCOMPtr<nsIURI> referrer = GetDocumentURI();
+  bool urisMatch = false;
+  aHref->EqualsExceptRef(referrer, &urisMatch);
+  if (urisMatch) {
+    // Prerender current document isn't quite meaningful, and we may not be able
+    // to load it out of process.
+    return false;
+  }
+
+  nsCOMPtr<nsIDocShell> docShell = GetDocShell();
+  nsCOMPtr<nsIWebNavigation> webNav = do_QueryInterface(docShell);
+  NS_ENSURE_TRUE(webNav, false);
+
+  bool canGoForward = false;
+  nsresult rv = webNav->GetCanGoForward(&canGoForward);
+  if (NS_FAILED(rv) || canGoForward) {
+    // Skip prerender on history navigation as we don't support it yet.
+    // Remove this check once bug 1323650 is implemented.
+    return false;
+  }
+
+  // Check if the document is in prerender state. We don't prerender in a
+  // prerendered document.
+  if (docShell->GetIsPrerendered()) {
+    return false;
+  }
+
+  // Adopting an out-of-process prerendered document is conceptually similar to
+  // switching dochshell's process, since it's the same browsing context from
+  // other browsing contexts' perspective. If we're locked in current process,
+  // we can not prerender out-of-process.
+  if (docShell->GetIsProcessLocked()) {
+    return false;
+  }
+
+  TabChild* tabChild = TabChild::GetFrom(docShell);
+  NS_ENSURE_TRUE(tabChild, false);
+
+  nsCOMPtr<nsIWebBrowserChrome3> wbc3;
+  tabChild->GetWebBrowserChrome(getter_AddRefs(wbc3));
+  NS_ENSURE_TRUE(wbc3, false);
+
+  rv = wbc3->StartPrerenderingDocument(aHref, referrer);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  return true;
+}
+
 NS_IMETHODIMP
 nsDocument::GetApplicationCache(nsIApplicationCache **aApplicationCache)
 {
   NS_IF_ADDREF(*aApplicationCache = mApplicationCache);
 
   return NS_OK;
 }
 
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2876,16 +2876,18 @@ public:
   EventTargetFor(mozilla::dom::TaskCategory aCategory) const override;
 
   // The URLs passed to these functions should match what
   // JS::DescribeScriptedCaller() returns, since these APIs are used to
   // determine whether some code is being called from a tracking script.
   void NoteScriptTrackingStatus(const nsACString& aURL, bool isTracking);
   bool IsScriptTracking(const nsACString& aURL) const;
 
+  bool PrerenderHref(nsIURI* aHref);
+
 protected:
   bool GetUseCounter(mozilla::UseCounter aUseCounter)
   {
     return mUseCounters[aUseCounter];
   }
 
   void SetChildDocumentUseCounter(mozilla::UseCounter aUseCounter)
   {
--- a/dom/base/nsStyleLinkElement.cpp
+++ b/dom/base/nsStyleLinkElement.cpp
@@ -157,17 +157,19 @@ static uint32_t ToLinkMask(const nsAStri
     return nsStyleLinkElement::eNEXT;
   else if (aLink.EqualsLiteral("alternate"))
     return nsStyleLinkElement::eALTERNATE;
   else if (aLink.EqualsLiteral("import") &&
            nsStyleLinkElement::IsImportEnabled())
     return nsStyleLinkElement::eHTMLIMPORT;
   else if (aLink.EqualsLiteral("preconnect"))
     return nsStyleLinkElement::ePRECONNECT;
-  else 
+  else if (aLink.EqualsLiteral("prerender"))
+    return nsStyleLinkElement::ePRERENDER;
+  else
     return 0;
 }
 
 uint32_t nsStyleLinkElement::ParseLinkTypes(const nsAString& aTypes, nsIPrincipal* aPrincipal)
 {
   uint32_t linkMask = 0;
   nsAString::const_iterator start, done;
   aTypes.BeginReading(start);
--- a/dom/base/nsStyleLinkElement.h
+++ b/dom/base/nsStyleLinkElement.h
@@ -58,17 +58,18 @@ public:
 
   enum RelValue {
     ePREFETCH =     0x00000001,
     eDNS_PREFETCH = 0x00000002,
     eSTYLESHEET =   0x00000004,
     eNEXT =         0x00000008,
     eALTERNATE =    0x00000010,
     eHTMLIMPORT =   0x00000020,
-    ePRECONNECT =   0x00000040
+    ePRECONNECT =   0x00000040,
+    ePRERENDER =    0x00000080
   };
 
   // The return value is a bitwise or of 0 or more RelValues.
   // aPrincipal is used to check if HTML imports is enabled for the
   // provided principal.
   static uint32_t ParseLinkTypes(const nsAString& aTypes,
                                  nsIPrincipal* aPrincipal);
 
--- a/dom/html/HTMLLinkElement.cpp
+++ b/dom/html/HTMLLinkElement.cpp
@@ -164,17 +164,17 @@ HTMLLinkElement::BindToTree(nsIDocument*
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Link must be inert in ShadowRoot.
   if (aDocument && !GetContainingShadow()) {
     aDocument->RegisterPendingLinkUpdate(this);
   }
 
   if (IsInComposedDoc()) {
-    TryDNSPrefetchPreconnectOrPrefetch();
+    TryDNSPrefetchPreconnectOrPrefetchOrPrerender();
   }
 
   void (HTMLLinkElement::*update)() = &HTMLLinkElement::UpdateStyleSheetInternal;
   nsContentUtils::AddScriptRunner(NewRunnableMethod(this, update));
 
   void (HTMLLinkElement::*updateImport)() = &HTMLLinkElement::UpdateImport;
   nsContentUtils::AddScriptRunner(NewRunnableMethod(this, updateImport));
 
@@ -384,17 +384,17 @@ HTMLLinkElement::AfterSetAttr(int32_t aN
       }
 
       if (aName == nsGkAtoms::href) {
         UpdateImport();
       }
 
       if ((aName == nsGkAtoms::rel || aName == nsGkAtoms::href) &&
           IsInComposedDoc()) {
-        TryDNSPrefetchPreconnectOrPrefetch();
+        TryDNSPrefetchPreconnectOrPrefetchOrPrerender();
       }
 
       UpdateStyleSheetInternal(nullptr, nullptr,
                                dropSheet ||
                                (aName == nsGkAtoms::title ||
                                 aName == nsGkAtoms::media ||
                                 aName == nsGkAtoms::type));
     }
--- a/dom/xul/test/file_bug1069772.xul
+++ b/dom/xul/test/file_bug1069772.xul
@@ -103,18 +103,18 @@ https://bugzilla.mozilla.org/show_bug.cg
       is(content.document.hidden, v != 'visible', 'check doc.hidden');
       is(iframe.contentDocument.visibilityState, v, 'check iframe doc.visibilityState');
       is(iframe.contentDocument.hidden, v != 'visible', 'check iframe doc.hidden');
     }).then(() => browser);
   }
 
   function makePrerenderedBrowserActive(browser) {
     let promise = waitForVisibilityChange(browser);
-    browser.setAttribute('prerendered', false);
-    browser.makePrerenderedBrowserActive();
+    browser.removeAttribute('prerendered');
+    browser.frameLoader.makePrerenderedLoaderActive();
     return promise.then(() => browser);
   }
 
   function hideBrowser(browser) {
     let promise = waitForVisibilityChange(browser);
     browser.docShellIsActive = false;
     return promise.then(() => browser);
   }
--- a/dom/xul/test/file_bug1271240.xul
+++ b/dom/xul/test/file_bug1271240.xul
@@ -66,17 +66,17 @@ https://bugzilla.mozilla.org/show_bug.cg
       browser.frameLoader.tabParent : browser.frameLoader.docShell;
     ok(docShellOrTabParent, 'docShellOrTabParent should not be null');
     is(docShellOrTabParent.isPrerendered, prerendered,
       'isPrerendered should be ' + prerendered);
     return browser;
   }
 
   function makePrerenderedBrowserActive(browser) {
-    browser.makePrerenderedBrowserActive();
+    browser.frameLoader.makePrerenderedLoaderActive();
     return browser;
   }
 
   ]]>
   </script>
   <!-- <browser type="content-primary" flex="1" id="content" />
   <browser type="content-primary" flex="1" id="content-remote" remote="true" /> -->
 </window>
--- a/embedding/browser/nsIWebBrowserChrome3.idl
+++ b/embedding/browser/nsIWebBrowserChrome3.idl
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsIWebBrowserChrome2.idl"
 #include "nsIURI.idl"
 #include "nsIDOMNode.idl"
 
 interface nsIDocShell;
 interface nsIInputStream;
+interface nsIRunnable;
 
 /**
  * nsIWebBrowserChrome3 is an extension to nsIWebBrowserChrome2.
  */
 [scriptable, uuid(542b6625-35a9-426a-8257-c12a345383b0)]
 interface nsIWebBrowserChrome3 : nsIWebBrowserChrome2
 {
   /**
@@ -55,9 +56,41 @@ interface nsIWebBrowserChrome3 : nsIWebB
    * available memory.
    *
    * @param aDocShell
    *        The docshell performing the load.
    */
   bool reloadInFreshProcess(in nsIDocShell aDocShell,
                             in nsIURI aURI,
                             in nsIURI aReferrer);
+
+  /**
+   * Tell the browser to start prerendering the given document. This prerendering
+   * _must_ be for the toplevel document.
+   *
+   * @param aHref The URI to begin prerendering
+   * @param aReferrer The URI of the document requesting the prerender.
+   */
+  void startPrerenderingDocument(in nsIURI aHref, in nsIURI aReferrer);
+
+  /**
+   * Check if there's a prerendered document which matches given URI /
+   * referrer, and try to switch to the prerendered document immediately if
+   * there is.
+   *
+   * @param aHref
+   *        The URI which is being loaded.
+   * @param aReferrer
+   *        The referrer for the current load.
+   * @param aSuccess
+   *        (Optional) a runnable which will be run if the swap is successful.
+   * @param aFailure
+   *        (Optional) a runnable which will be run if the swap is not
+   *        successful. Note it's not invoked if the function returns false.
+   *
+   * @return True if there is a matched prerendered document to swap with,
+   *         false otherwise.
+   */
+  bool shouldSwitchToPrerenderedDocument(in nsIURI aHref,
+                                         in nsIURI aReferrer,
+                                         in nsIRunnable aSuccess,
+                                         in nsIRunnable aFailure);
 };
--- a/toolkit/content/widgets/browser.xml
+++ b/toolkit/content/widgets/browser.xml
@@ -302,27 +302,16 @@
 
       <method name="preserveLayers">
         <parameter name="preserve"/>
         <body>
           // Only useful for remote browsers.
         </body>
       </method>
 
-      <method name="makePrerenderedBrowserActive">
-        <body>
-        <![CDATA[
-          let frameLoader = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
-          if (frameLoader) {
-            frameLoader.makePrerenderedLoaderActive();
-          }
-        ]]>
-        </body>
-      </method>
-
       <property name="imageDocument"
                 readonly="true">
         <getter>
           <![CDATA[
             var document = this.contentDocument;
             if (!document || !(document instanceof Components.interfaces.nsIImageDocument))
               return null;
 
--- a/xpfe/appshell/nsContentTreeOwner.cpp
+++ b/xpfe/appshell/nsContentTreeOwner.cpp
@@ -418,16 +418,34 @@ NS_IMETHODIMP nsContentTreeOwner::Reload
                                                        nsIURI* aReferrer,
                                                        bool* aRetVal)
 {
   NS_WARNING("Cannot reload in fresh process from a nsContentTreeOwner!");
   *aRetVal = false;
   return NS_OK;
 }
 
+NS_IMETHODIMP nsContentTreeOwner::StartPrerenderingDocument(nsIURI* aHref,
+                                                            nsIURI* aReferrer)
+{
+  NS_WARNING("Cannot prerender a document in the parent process");
+  return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsContentTreeOwner::ShouldSwitchToPrerenderedDocument(nsIURI* aHref,
+                                                                    nsIURI* aReferrer,
+                                                                    nsIRunnable* aSuccess,
+                                                                    nsIRunnable* aFailure,
+                                                                    bool* aRetval)
+{
+  NS_WARNING("Cannot switch to prerendered document in the parent process");
+  *aRetval = false;
+  return NS_OK;
+}
+
 //*****************************************************************************
 // nsContentTreeOwner::nsIWebBrowserChrome2
 //*****************************************************************************   
 
 NS_IMETHODIMP nsContentTreeOwner::SetStatusWithContext(uint32_t aStatusType,
                                                        const nsAString &aStatusText,
                                                        nsISupports *aStatusContext)
 {