Bug 1330332 - Add support for handling global history changes with PendingGlobalHistoryEntry for prerendering, r=smaug
authorMichael Layzell <michael@thelayzells.com>
Wed, 11 Jan 2017 11:16:14 -0500
changeset 377510 d00ea9cc0fc5590bd111a624ab75ea767645dcd2
parent 377509 dc6a325145178d50841fc11a2ac86cd4d87ed8bc
child 377511 031e8ac3457d8beb672b253291a801797016f6b0
push id1419
push userjlund@mozilla.com
push dateMon, 10 Apr 2017 20:44:07 +0000
treeherdermozilla-release@5e6801b73ef6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1330332
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 1330332 - Add support for handling global history changes with PendingGlobalHistoryEntry for prerendering, r=smaug MozReview-Commit-ID: 2j9JwRSGtTC
docshell/base/PendingGlobalHistoryEntry.cpp
docshell/base/PendingGlobalHistoryEntry.h
docshell/base/moz.build
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
new file mode 100644
--- /dev/null
+++ b/docshell/base/PendingGlobalHistoryEntry.cpp
@@ -0,0 +1,75 @@
+#include "mozilla/dom/PendingGlobalHistoryEntry.h"
+
+namespace mozilla {
+
+namespace dom {
+
+void
+PendingGlobalHistoryEntry::VisitURI(nsIURI* aURI,
+                             nsIURI* aLastVisitedURI,
+                             nsIURI* aReferrerURI,
+                             uint32_t aFlags)
+{
+  URIVisit visit;
+  visit.mURI = aURI;
+  visit.mLastVisitedURI = aLastVisitedURI;
+  visit.mReferrerURI = aReferrerURI;
+  visit.mFlags = aFlags;
+  mVisits.AppendElement(Move(visit));
+}
+
+void
+PendingGlobalHistoryEntry::SetURITitle(nsIURI* aURI,
+                                const nsAString& aTitle)
+{
+  URITitle title;
+  title.mURI = aURI;
+  title.mTitle.Assign(aTitle);
+  mTitles.AppendElement(title);
+}
+
+nsresult
+PendingGlobalHistoryEntry::ApplyChanges(IHistory* aHistory)
+{
+  nsresult rv;
+  for (const URIVisit& visit : mVisits) {
+    rv = aHistory->VisitURI(visit.mURI, visit.mLastVisitedURI, visit.mFlags);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  mVisits.Clear();
+
+  for (const URITitle& title : mTitles) {
+    aHistory->SetURITitle(title.mURI, title.mTitle);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  mTitles.Clear();
+
+  return NS_OK;
+}
+
+nsresult
+PendingGlobalHistoryEntry::ApplyChanges(nsIGlobalHistory2* aHistory)
+{
+  nsresult rv;
+  for (const URIVisit& visit : mVisits) {
+    bool redirect = (visit.mFlags & IHistory::REDIRECT_TEMPORARY) ||
+      (visit.mFlags & IHistory::REDIRECT_PERMANENT);
+    bool toplevel = (visit.mFlags & IHistory::TOP_LEVEL);
+
+    rv = aHistory->AddURI(visit.mURI, redirect, toplevel, visit.mReferrerURI);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  mVisits.Clear();
+
+  for (const URITitle& title : mTitles) {
+    rv = aHistory->SetPageTitle(title.mURI, title.mTitle);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  mTitles.Clear();
+
+  return NS_OK;
+}
+
+} // namespace dom
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/docshell/base/PendingGlobalHistoryEntry.h
@@ -0,0 +1,65 @@
+/* -*- 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 mozilla_dom_PendingGlobalHistoryEntry_h_
+#define mozilla_dom_PendingGlobalHistoryEntry_h_
+
+#include "mozilla/IHistory.h"
+#include "nsIGlobalHistory2.h"
+#include "nsIURI.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+namespace dom {
+
+// This class acts as a wrapper around a IHistory, in that it can be used to
+// record a series of operations on the IHistory, and then play them back at a
+// later time. This is used in Prerendering in order to record the Global
+// History changes being made by the prerendering docshell and then play them
+// back when the docshell is finished rendering.
+//
+// This class also handles applying the changes to nsIGlobalHistory2.
+class PendingGlobalHistoryEntry
+{
+public:
+  void VisitURI(nsIURI* aURI,
+                nsIURI* aLastVisitedURI,
+                nsIURI* aReferrerURI,
+                uint32_t aFlags);
+
+  void SetURITitle(nsIURI* aURI,
+                   const nsAString& aTitle);
+
+  nsresult ApplyChanges(IHistory* aHistory);
+
+  nsresult ApplyChanges(nsIGlobalHistory2* aHistory);
+
+private:
+  struct URIVisit
+  {
+    nsCOMPtr<nsIURI> mURI;
+    nsCOMPtr<nsIURI> mLastVisitedURI;
+    nsCOMPtr<nsIURI> mReferrerURI;
+    uint32_t mFlags = 0;
+  };
+  struct URITitle
+  {
+    nsCOMPtr<nsIURI> mURI;
+    nsString mTitle;
+  };
+  nsTArray<URIVisit> mVisits;
+  nsTArray<URITitle> mTitles;
+};
+
+} // namespace dom
+
+} // namespace mozilla
+
+#endif // mozilla_dom_PendingGlobalHistoryEntry_h_
+
--- a/docshell/base/moz.build
+++ b/docshell/base/moz.build
@@ -51,30 +51,35 @@ EXPORTS += [
     'SerializedLoadContext.h',
 ]
 
 EXPORTS.mozilla += [
     'IHistory.h',
     'LoadContext.h',
 ]
 
+EXPORTS.mozilla.dom += [
+    'PendingGlobalHistoryEntry.h',
+]
+
 UNIFIED_SOURCES += [
     'LoadContext.cpp',
     'nsAboutRedirector.cpp',
     'nsContextMenuInfo.cpp',
     'nsDefaultURIFixup.cpp',
     'nsDocShell.cpp',
     'nsDocShellEditorData.cpp',
     'nsDocShellEnumerator.cpp',
     'nsDocShellLoadInfo.cpp',
     'nsDocShellTransferableHooks.cpp',
     'nsDocShellTreeOwner.cpp',
     'nsDownloadHistory.cpp',
     'nsDSURIContentListener.cpp',
     'nsWebNavigationInfo.cpp',
+    'PendingGlobalHistoryEntry.cpp',
     'SerializedLoadContext.cpp',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/docshell/shistory',
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -11,16 +11,17 @@
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/Casting.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/ChromeUtils.h"
 #include "mozilla/dom/Element.h"
+#include "mozilla/dom/PendingGlobalHistoryEntry.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/ProfileTimelineMarkerBinding.h"
 #include "mozilla/dom/ScreenOrientation.h"
 #include "mozilla/dom/ToJSValue.h"
 #include "mozilla/dom/PermissionMessageUtils.h"
 #include "mozilla/dom/workers/ServiceWorkerManager.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/LoadInfo.h"
@@ -6166,17 +6167,29 @@ nsDocShell::SetIsActive(bool aIsActive)
   if (mItemType == nsIDocShellTreeItem::typeChrome) {
     return NS_ERROR_INVALID_ARG;
   }
 
   // Keep track ourselves.
   mIsActive = aIsActive;
 
   // Clear prerender flag if necessary.
-  mIsPrerendered &= !aIsActive;
+  if (mIsPrerendered && aIsActive) {
+    MOZ_ASSERT(mPrerenderGlobalHistory.get());
+    mIsPrerendered = false;
+    nsCOMPtr<IHistory> history = services::GetHistoryService();
+    nsresult rv = NS_OK;
+    if (history) {
+      rv = mPrerenderGlobalHistory->ApplyChanges(history);
+    } else if (mGlobalHistory) {
+      rv = mPrerenderGlobalHistory->ApplyChanges(mGlobalHistory);
+    }
+    mPrerenderGlobalHistory = nullptr;
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
 
   // Tell the PresShell about it.
   nsCOMPtr<nsIPresShell> pshell = GetPresShell();
   if (pshell) {
     pshell->SetIsActive(aIsActive);
   }
 
   // Tell the window about it
@@ -6247,16 +6260,17 @@ nsDocShell::GetIsActive(bool* aIsActive)
 
 NS_IMETHODIMP
 nsDocShell::SetIsPrerendered()
 {
   MOZ_ASSERT(!mIsPrerendered,
              "SetIsPrerendered() called on already prerendered docshell");
   SetIsActive(false);
   mIsPrerendered = true;
+  mPrerenderGlobalHistory = mozilla::MakeUnique<PendingGlobalHistoryEntry>();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::GetIsPrerendered(bool* aIsPrerendered)
 {
   *aIsPrerendered = mIsPrerendered;
   return NS_OK;
@@ -6497,24 +6511,18 @@ nsDocShell::SetTitle(const char16_t* aTi
   if (!parent) {
     nsCOMPtr<nsIBaseWindow> treeOwnerAsWin(do_QueryInterface(mTreeOwner));
     if (treeOwnerAsWin) {
       treeOwnerAsWin->SetTitle(aTitle);
     }
   }
 
   AssertOriginAttributesMatchPrivateBrowsing();
-  if (mCurrentURI && mLoadType != LOAD_ERROR_PAGE && mUseGlobalHistory &&
-      !UsePrivateBrowsing()) {
-    nsCOMPtr<IHistory> history = services::GetHistoryService();
-    if (history) {
-      history->SetURITitle(mCurrentURI, mTitle);
-    } else if (mGlobalHistory) {
-      mGlobalHistory->SetPageTitle(mCurrentURI, nsString(mTitle));
-    }
+  if (mCurrentURI && mLoadType != LOAD_ERROR_PAGE) {
+    UpdateGlobalHistoryTitle(mCurrentURI);
   }
 
   // Update SessionHistory with the document's title.
   if (mOSHE && mLoadType != LOAD_BYPASS_HISTORY &&
       mLoadType != LOAD_ERROR_PAGE) {
     mOSHE->SetTitle(mTitle);
   }
 
@@ -10395,24 +10403,17 @@ nsDocShell::InternalLoad(nsIURI* aURI,
         nsCOMPtr<nsISHEntry> shEntry;
         mSessionHistory->GetEntryAtIndex(index, false, 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.
        */
-      if (mUseGlobalHistory && !UsePrivateBrowsing()) {
-        nsCOMPtr<IHistory> history = services::GetHistoryService();
-        if (history) {
-          history->SetURITitle(aURI, mTitle);
-        } else if (mGlobalHistory) {
-          mGlobalHistory->SetPageTitle(aURI, mTitle);
-        }
-      }
+      UpdateGlobalHistoryTitle(aURI);
 
       SetDocCurrentStateObj(mOSHE);
 
       // Inform the favicon service that the favicon for oldURI also
       // applies to aURI.
       CopyFavicon(currentURI, aURI, doc->NodePrincipal(), UsePrivateBrowsing());
 
       RefPtr<nsGlobalWindow> scriptGlobal = mScriptGlobal;
@@ -12088,24 +12089,17 @@ nsDocShell::AddState(JS::Handle<JS::Valu
     if (mLoadType != LOAD_ERROR_PAGE) {
       FireDummyOnLocationChange();
     }
 
     AddURIVisit(newURI, oldURI, oldURI, 0);
 
     // AddURIVisit doesn't set the title for the new URI in global history,
     // so do that here.
-    if (mUseGlobalHistory && !UsePrivateBrowsing()) {
-      nsCOMPtr<IHistory> history = services::GetHistoryService();
-      if (history) {
-        history->SetURITitle(newURI, mTitle);
-      } else if (mGlobalHistory) {
-        mGlobalHistory->SetPageTitle(newURI, mTitle);
-      }
-    }
+    UpdateGlobalHistoryTitle(newURI);
 
     // Inform the favicon service that our old favicon applies to this new
     // URI.
     CopyFavicon(oldURI, newURI, document->NodePrincipal(), UsePrivateBrowsing());
   } else {
     FireDummyOnLocationChange();
   }
   document->SetStateObject(scContainer);
@@ -13047,43 +13041,54 @@ nsDocShell::AddURIVisit(nsIURI* aURI,
   // Only content-type docshells save URI visits.  Also don't do
   // anything here if we're not supposed to use global history.
   if (mItemType != typeContent || !mUseGlobalHistory || UsePrivateBrowsing()) {
     return;
   }
 
   nsCOMPtr<IHistory> history = services::GetHistoryService();
 
-  if (history) {
+  if (mPrerenderGlobalHistory || history) {
     uint32_t visitURIFlags = 0;
 
     if (!IsFrame()) {
       visitURIFlags |= IHistory::TOP_LEVEL;
     }
 
     if (aChannelRedirectFlags & nsIChannelEventSink::REDIRECT_TEMPORARY) {
       visitURIFlags |= IHistory::REDIRECT_TEMPORARY;
     } else if (aChannelRedirectFlags & nsIChannelEventSink::REDIRECT_PERMANENT) {
       visitURIFlags |= IHistory::REDIRECT_PERMANENT;
+    } else {
+      MOZ_ASSERT(!aChannelRedirectFlags,
+                 "One of REDIRECT_TEMPORARY or REDIRECT_PERMANENT must be set "
+                 "if any flags in aChannelRedirectFlags is set.");
     }
 
     if (aResponseStatus >= 300 && aResponseStatus < 400) {
       visitURIFlags |= IHistory::REDIRECT_SOURCE;
     }
     // Errors 400-501 and 505 are considered unrecoverable, in the sense a
     // simple retry attempt by the user is unlikely to solve them.
     // 408 is special cased, since may actually indicate a temporary
     // connection problem.
     else if (aResponseStatus != 408 &&
              ((aResponseStatus >= 400 && aResponseStatus <= 501) ||
               aResponseStatus == 505)) {
       visitURIFlags |= IHistory::UNRECOVERABLE_ERROR;
     }
 
-    (void)history->VisitURI(aURI, aPreviousURI, visitURIFlags);
+    if (mPrerenderGlobalHistory) {
+      mPrerenderGlobalHistory->VisitURI(aURI,
+                                        aPreviousURI,
+                                        aReferrerURI,
+                                        visitURIFlags);
+    } else {
+      (void)history->VisitURI(aURI, aPreviousURI, visitURIFlags);
+    }
   } else if (mGlobalHistory) {
     // Falls back to sync global history interface.
     (void)mGlobalHistory->AddURI(aURI,
                                  !!aChannelRedirectFlags,
                                  !IsFrame(),
                                  aReferrerURI);
   }
 }
@@ -14460,16 +14465,31 @@ nsDocShell::HasUnloadedParent()
     if (inUnload) {
       return true;
     }
     parent = parent->GetParentDocshell();
   }
   return false;
 }
 
+void
+nsDocShell::UpdateGlobalHistoryTitle(nsIURI* aURI)
+{
+  if (mUseGlobalHistory && !UsePrivateBrowsing()) {
+    nsCOMPtr<IHistory> history = services::GetHistoryService();
+    if (mPrerenderGlobalHistory) {
+      mPrerenderGlobalHistory->SetURITitle(aURI, mTitle);
+    } else if (history) {
+      history->SetURITitle(aURI, mTitle);
+    } else if (mGlobalHistory) {
+      mGlobalHistory->SetPageTitle(aURI, nsString(mTitle));
+    }
+  }
+}
+
 bool
 nsDocShell::IsInvisible()
 {
   return mInvisible;
 }
 
 void
 nsDocShell::SetInvisible(bool aInvisible)
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -59,16 +59,17 @@
 #include "prtime.h"
 #include "nsRect.h"
 #include "Units.h"
 #include "nsIDeprecationWarner.h"
 
 namespace mozilla {
 namespace dom {
 class EventTarget;
+class PendingGlobalHistoryEntry;
 typedef uint32_t ScreenOrientationInternal;
 } // namespace dom
 } // namespace mozilla
 
 class nsDocShell;
 class nsDOMNavigationTiming;
 class nsGlobalWindow;
 class nsIController;
@@ -802,16 +803,18 @@ protected:
         return EmptyCString();
     }
   }
 
   uint32_t GetInheritedFrameType();
 
   bool HasUnloadedParent();
 
+  void UpdateGlobalHistoryTitle(nsIURI* aURI);
+
   // Dimensions of the docshell
   nsIntRect mBounds;
   nsString mName;
   nsString mTitle;
   nsString mCustomUserAgent;
 
   /**
    * Content-Type Hint of the most-recently initiated load. Used for
@@ -1038,16 +1041,18 @@ private:
   nsCOMPtr<nsIPrincipal> mParentCharsetPrincipal;
   nsTObserverArray<nsWeakPtr> mPrivacyObservers;
   nsTObserverArray<nsWeakPtr> mReflowObservers;
   nsTObserverArray<nsWeakPtr> mScrollObservers;
   nsCString mOriginalUriString;
   nsWeakPtr mOpener;
   mozilla::OriginAttributes mOriginAttributes;
 
+  mozilla::UniquePtr<mozilla::dom::PendingGlobalHistoryEntry> mPrerenderGlobalHistory;
+
   // A depth count of how many times NotifyRunToCompletionStart
   // has been called without a matching NotifyRunToCompletionStop.
   uint32_t mJSRunToCompletionDepth;
 
   // Whether or not touch events are overridden. Possible values are defined
   // as constants in the nsIDocShell.idl file.
   uint32_t mTouchEventsOverride;