Bug 1419536 P3 Handle creating and activating the ClientSource in nsDocShell and nsGlobalWindow. r=baku r=freesamael
authorBen Kelly <ben@wanderview.com>
Tue, 21 Nov 2017 15:13:05 -0500
changeset 431768 3e2118a29baf7cb034e4c6d59e57eb67c8f40c37
parent 431767 061c5b3ee4d4a7874f4496835cdf1b7c29875a67
child 431769 61d93d8a46679c63f56b0ad4719834e87a7a7f72
push idunknown
push userunknown
push dateunknown
reviewersbaku, freesamael
bugs1419536
milestone59.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 1419536 P3 Handle creating and activating the ClientSource in nsDocShell and nsGlobalWindow. r=baku r=freesamael
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
docshell/base/nsIDocShell.idl
dom/base/nsGlobalWindowInner.cpp
dom/base/nsGlobalWindowInner.h
dom/base/nsGlobalWindowOuter.cpp
dom/base/nsPIDOMWindow.h
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -8,16 +8,21 @@
 
 #include <algorithm>
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/Casting.h"
+#include "mozilla/dom/ClientChannelHelper.h"
+#include "mozilla/dom/ClientHandle.h"
+#include "mozilla/dom/ClientInfo.h"
+#include "mozilla/dom/ClientManager.h"
+#include "mozilla/dom/ClientSource.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/HTMLAnchorElement.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"
@@ -3385,16 +3390,90 @@ nsDocShell::GetParent(nsIDocShellTreeIte
 already_AddRefed<nsDocShell>
 nsDocShell::GetParentDocshell()
 {
   nsCOMPtr<nsIDocShell> docshell = do_QueryInterface(GetAsSupports(mParent));
   return docshell.forget().downcast<nsDocShell>();
 }
 
 void
+nsDocShell::MaybeCreateInitialClientSource(nsIPrincipal* aPrincipal)
+{
+  // If there is an existing document then there is no need to create
+  // a client for a future initial about:blank document.
+  if (mScriptGlobal && mScriptGlobal->GetExtantDoc()) {
+    MOZ_DIAGNOSTIC_ASSERT(
+      mScriptGlobal->GetCurrentInnerWindowInternal()->GetClientInfo().isSome());
+    MOZ_DIAGNOSTIC_ASSERT(!mInitialClientSource);
+    return;
+  }
+
+  // Don't recreate the initial client source.  We call this multiple times
+  // when DoChannelLoad() is called before CreateAboutBlankContentViewer.
+  if (mInitialClientSource) {
+    return;
+  }
+
+  // Don't pre-allocate the client when we are sandboxed.  The inherited
+  // principal does not take sandboxing into account.
+  // TODO: Refactor sandboxing principal code out so we can use it here.
+  if (!aPrincipal && (mSandboxFlags & SANDBOXED_ORIGIN)) {
+    return;
+  }
+
+  nsIPrincipal* principal = aPrincipal ? aPrincipal
+                                       : GetInheritedPrincipal(false);
+
+  // Sometimes there is no principal available when we are called from
+  // CreateAboutBlankContentViewer.  For example, sometimes the principal
+  // is only extracted from the load context after the document is created
+  // in nsDocument::ResetToURI().  Ideally we would do something similar
+  // here, but for now lets just avoid the issue by not preallocating the
+  // client.
+  if (!principal) {
+    return;
+  }
+
+  nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
+  if (!win) {
+    return;
+  }
+
+  mInitialClientSource =
+    ClientManager::CreateSource(ClientType::Window,
+                                win->EventTargetFor(TaskCategory::Other),
+                                principal);
+
+  // Mark the initial client as execution ready, but owned by the docshell.
+  // If the client is actually used this will cause ClientSource to force
+  // the creation of the initial about:blank by calling nsDocShell::GetDocument().
+  mInitialClientSource->DocShellExecutionReady(this);
+}
+
+Maybe<ClientInfo>
+nsDocShell::GetInitialClientInfo() const
+{
+  if (mInitialClientSource) {
+    Maybe<ClientInfo> result;
+    result.emplace(mInitialClientSource->Info());
+    return Move(result);
+  }
+
+  nsGlobalWindowInner* innerWindow =
+    mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr;
+  nsIDocument* doc = innerWindow ? innerWindow->GetExtantDoc() : nullptr;
+
+  if (!doc || !doc->IsInitialDocument()) {
+    return Maybe<ClientInfo>();
+  }
+
+  return innerWindow->GetClientInfo();
+}
+
+void
 nsDocShell::RecomputeCanExecuteScripts()
 {
   bool old = mCanExecuteScripts;
   RefPtr<nsDocShell> parent = GetParentDocshell();
 
   // If we have no tree owner, that means that we've been detached from the
   // docshell tree (this is distinct from having no parent dochshell, which
   // is the case for root docshells). It would be nice to simply disallow
@@ -5916,16 +5995,19 @@ nsDocShell::Destroy()
       const char* msg = mItemType == typeContent ?
         NS_WEBNAVIGATION_DESTROY : NS_CHROME_WEBNAVIGATION_DESTROY;
       serv->NotifyObservers(GetAsSupports(this), msg, nullptr);
     }
   }
 
   mIsBeingDestroyed = true;
 
+  // Brak the cycle with the initial client, if present.
+  mInitialClientSource.reset();
+
   // Make sure we don't record profile timeline markers anymore
   SetRecordProfileTimelineMarkers(false);
 
   // Remove our pref observers
   if (mObserveErrorPages) {
     mObserveErrorPages = false;
   }
 
@@ -7751,16 +7833,20 @@ nsDocShell::EndPageLoad(nsIWebProgress* 
         internalLoadGroup->OnEndPageLoad(aChannel);
       }
     }
   }
 
   // Timing is picked up by the window, we don't need it anymore
   mTiming = nullptr;
 
+  // Make sure to discard the initial client if we never created the initial
+  // about:blank document.
+  mInitialClientSource.reset();
+
   // clean up reload state for meta charset
   if (eCharsetReloadRequested == mCharsetReloadState) {
     mCharsetReloadState = eCharsetReloadStopOrigional;
   } else {
     mCharsetReloadState = eCharsetReloadInit;
   }
 
   // Save a pointer to the currently-loading history entry.
@@ -8247,16 +8333,19 @@ nsDocShell::CreateAboutBlankContentViewe
       if (aPrincipal) {
         principal = NullPrincipal::CreateWithInheritedAttributes(aPrincipal);
       } else {
         principal = NullPrincipal::CreateWithInheritedAttributes(this);
       }
     } else {
       principal = aPrincipal;
     }
+
+    MaybeCreateInitialClientSource(principal);
+
     // generate (about:blank) document to load
     blankDoc = nsContentDLF::CreateBlankDocument(mLoadGroup, principal, this);
     if (blankDoc) {
       // Hack: set the base URI manually, since this document never
       // got Reset() with a channel.
       blankDoc->SetBaseURI(aBaseURI);
 
       // Copy our sandbox flags to the document. These are immutable
@@ -11742,16 +11831,34 @@ nsDocShell::DoChannelLoad(nsIChannel* aC
 
   uint32_t openFlags = 0;
   if (mLoadType == LOAD_LINK) {
     openFlags |= nsIURILoader::IS_CONTENT_PREFERRED;
   }
   if (!mAllowContentRetargeting) {
     openFlags |= nsIURILoader::DONT_RETARGET;
   }
+
+  nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
+  NS_ENSURE_TRUE(win, NS_ERROR_FAILURE);
+
+  MaybeCreateInitialClientSource();
+
+  // Since we are loading a document we need to make sure the proper reserved
+  // and initial client data is stored on the nsILoadInfo.  The
+  // ClientChannelHelper does this and ensures that it is propagated properly
+  // on redirects.  We pass no reserved client here so that the helper will
+  // create the reserved ClientSource if necessary.
+  Maybe<ClientInfo> noReservedClient;
+  rv = AddClientChannelHelper(aChannel,
+                              Move(noReservedClient),
+                              GetInitialClientInfo(),
+                              win->EventTargetFor(TaskCategory::Other));
+  NS_ENSURE_SUCCESS(rv, rv);
+
   rv = aURILoader->OpenURI(aChannel, openFlags, this);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // We're about to load a new page and it may take time before necko
   // gives back any data, so main thread might have a chance to process a
   // collector slice
   nsJSContext::MaybeRunNextCollectorSlice(this, JS::gcreason::DOCSHELL);
 
@@ -15144,16 +15251,22 @@ nsDocShell::InFrameSwap()
     if (shell->mInFrameSwap) {
       return true;
     }
     shell = shell->GetParentDocshell();
   } while (shell);
   return false;
 }
 
+UniquePtr<ClientSource>
+nsDocShell::TakeInitialClientSource()
+{
+  return Move(mInitialClientSource);
+}
+
 NS_IMETHODIMP
 nsDocShell::IssueWarning(uint32_t aWarning, bool aAsError)
 {
   if (mContentViewer) {
     nsCOMPtr<nsIDocument> doc = mContentViewer->GetDocument();
     if (doc) {
       doc->WarnOnceAbout(nsIDocument::DeprecatedOperations(aWarning), aAsError);
     }
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -14,16 +14,17 @@
 #include "nsIBaseWindow.h"
 #include "nsINetworkInterceptController.h"
 #include "nsIScrollable.h"
 #include "nsITextScroll.h"
 #include "nsIContentViewerContainer.h"
 #include "nsIDOMStorageManager.h"
 #include "nsDocLoader.h"
 #include "mozilla/BasePrincipal.h"
+#include "mozilla/Maybe.h"
 #include "mozilla/Move.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/WeakPtr.h"
 #include "mozilla/TimeStamp.h"
 #include "GeckoProfiler.h"
 #include "mozilla/dom/ProfileTimelineMarkerBinding.h"
 #include "mozilla/LinkedList.h"
 #include "jsapi.h"
@@ -64,16 +65,18 @@
 #include "nsIDeprecationWarner.h"
 #include "nsILoadURIDelegate.h"
 
 namespace mozilla {
 class Encoding;
 class HTMLEditor;
 enum class TaskCategory;
 namespace dom {
+class ClientInfo;
+class ClientSource;
 class EventTarget;
 class PendingGlobalHistoryEntry;
 typedef uint32_t ScreenOrientationInternal;
 } // namespace dom
 } // namespace mozilla
 
 class nsDocShell;
 class nsDOMNavigationTiming;
@@ -818,16 +821,36 @@ protected:
 
   nsIChannel* GetCurrentDocChannel();
 
   bool ShouldBlockLoadingForBackButton();
 
   // Convenience method for getting our parent docshell. Can return null
   already_AddRefed<nsDocShell> GetParentDocshell();
 
+  // Possibly create a ClientSource object to represent an initial about:blank
+  // window that has not been allocated yet.  Normally we try not to create
+  // this about:blank window until something calls GetDocument().  We still need
+  // the ClientSource to exist for this conceptual window, though.
+  //
+  // The ClientSource is created with the given principal if specified.  If
+  // the principal is not provided we will attempt to inherit it when we
+  // are sure it will match what the real about:blank window principal
+  // would have been.  There are some corner cases where we cannot easily
+  // determine the correct principal and will not create the ClientSource.
+  // In these cases the initial about:blank will appear to not exist until
+  // its real document and window are created.
+  void MaybeCreateInitialClientSource(nsIPrincipal* aPrincipal = nullptr);
+
+  // Return the ClientInfo for the initial about:blank window, if it exists
+  // or we have speculatively created a ClientSource in
+  // MaybeCreateInitialClientSource().  This can return a ClientInfo object
+  // even if GetExtantDoc() returns nullptr.
+  mozilla::Maybe<mozilla::dom::ClientInfo> GetInitialClientInfo() const;
+
 protected:
   nsresult GetCurScrollPos(int32_t aScrollOrientation, int32_t* aCurPos);
   nsresult SetCurScrollPosEx(int32_t aCurHorizontalPos,
                              int32_t aCurVerticalPos);
 
   // Override the parent setter from nsDocLoader
   virtual nsresult SetDocLoaderParent(nsDocLoader* aLoader) override;
 
@@ -1145,16 +1168,18 @@ private:
   nsTObserverArray<nsWeakPtr> mReflowObservers;
   nsTObserverArray<nsWeakPtr> mScrollObservers;
   nsCString mOriginalUriString;
   nsWeakPtr mOpener;
   mozilla::OriginAttributes mOriginAttributes;
 
   mozilla::UniquePtr<mozilla::dom::PendingGlobalHistoryEntry> mPrerenderGlobalHistory;
 
+  mozilla::UniquePtr<mozilla::dom::ClientSource> mInitialClientSource;
+
   // 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;
 
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -7,34 +7,39 @@
 #include "domstubs.idl"
 #include "nsIDocShellTreeItem.idl"
 #include "nsIRequest.idl"
 
 %{ C++
 #include "js/TypeDecls.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/NotNull.h"
+#include "mozilla/UniquePtr.h"
 #include "nsCOMPtr.h"
 #include "nsIURI.h"
 class nsPresContext;
 class nsIPresShell;
 namespace mozilla {
 class Encoding;
 class HTMLEditor;
+namespace dom {
+class ClientSource;
+} // namespace dom
 }
 %}
 
 /**
  * The nsIDocShell interface.
  */
 
 [ptr] native nsPresContext(nsPresContext);
 [ptr] native nsIPresShell(nsIPresShell);
 [ref] native MaybeURI(mozilla::Maybe<nsCOMPtr<nsIURI>>);
 [ref] native Encoding(const mozilla::Encoding*);
+      native UniqueClientSource(mozilla::UniquePtr<mozilla::dom::ClientSource>);
 
 interface nsIURI;
 interface nsIChannel;
 interface nsIContentViewer;
 interface nsIDOMEventTarget;
 interface nsIDocShellLoadInfo;
 interface nsIEditor;
 interface nsIEditingSession;
@@ -1167,16 +1172,27 @@ interface nsIDocShell : nsIDocShellTreeI
    */
   attribute boolean useTrackingProtection;
 
  /**
   * Fire a dummy location change event asynchronously.
   */
   [noscript] void dispatchLocationChangeEvent();
 
+  /**
+   * Take ownership of the ClientSource representing an initial about:blank
+   * document that was never needed.  As an optimization we avoid creating
+   * this document if no code calls GetDocument(), but we still need a
+   * ClientSource object to represent the about:blank window.  This may return
+   * nullptr; for example if the docshell has created a real window and document
+   * already.
+   */
+  [noscript, nostdcall, notxpcom]
+  UniqueClientSource TakeInitialClientSource();
+
 %{C++
   /**
    * These methods call nsDocShell::GetHTMLEditorInternal() and
    * nsDocShell::SetHTMLEditorInternal() with static_cast.
    */
   mozilla::HTMLEditor* GetHTMLEditor();
   nsresult SetHTMLEditor(mozilla::HTMLEditor* aHTMLEditor);
 %}
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -1,14 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "mozilla/dom/ClientManager.h"
+#include "mozilla/dom/ClientSource.h"
+
 #define FORWARD_TO_OUTER(method, args, err_rval)                        \
   PR_BEGIN_MACRO                                                        \
   MOZ_RELEASE_ASSERT(IsInnerWindow());                                  \
   nsGlobalWindowOuter *outer = GetOuterWindowInternal();                \
   if (!HasActiveDocument()) {                                           \
     NS_WARNING(outer ?                                                  \
                "Inner window does not have active document." :          \
                "No outer window available!");                           \
@@ -1095,16 +1098,19 @@ nsGlobalWindowInner::FreeInnerObjects()
   DisableGamepadUpdates();
   mHasGamepad = false;
   mGamepads.Clear();
   DisableVRUpdates();
   mHasVREvents = false;
   mHasVRDisplayActivateEvents = false;
   mVRDisplays.Clear();
 
+  // This breaks a cycle between the window and the ClientSource object.
+  mClientSource.reset();
+
   if (mTabChild) {
     while (mBeforeUnloadListenerCount-- > 0) {
       mTabChild->BeforeUnloadRemoved();
     }
   }
 }
 
 //*****************************************************************************
@@ -1349,16 +1355,18 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell)
 
   tmp->UnlinkHostObjectURIs();
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleRequestExecutor)
   tmp->DisableIdleCallbackRequests();
 
+  tmp->mClientSource.reset();
+
   if (tmp->IsChromeWindow()) {
     NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeFields.mBrowserDOMWindow)
     if (tmp->mChromeFields.mMessageManager) {
       static_cast<nsFrameMessageManager*>(
         tmp->mChromeFields.mMessageManager.get())->Disconnect();
       NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeFields.mMessageManager)
     }
     tmp->DisconnectAndClearGroupMessageManagers();
@@ -1514,16 +1522,78 @@ nsGlobalWindowInner::InnerSetNewDocument
 
   Telemetry::Accumulate(Telemetry::INNERWINDOWS_WITH_MUTATION_LISTENERS,
                         mMutationBits ? 1 : 0);
 
   // Clear our mutation bitfield.
   mMutationBits = 0;
 }
 
+nsresult
+nsGlobalWindowInner::EnsureClientSource()
+{
+  MOZ_DIAGNOSTIC_ASSERT(mDoc);
+
+  nsCOMPtr<nsIChannel> channel = mDoc->GetChannel();
+  nsCOMPtr<nsILoadInfo> loadInfo = channel ? channel->GetLoadInfo() : nullptr;
+
+  // Try to get the reserved client from the LoadInfo.  A Client is
+  // reserved at the start of the channel load if there is not an
+  // initial about:blank document that will be reused.  It is also
+  // created if the channel load encounters a cross-origin redirect.
+  if (loadInfo) {
+    UniquePtr<ClientSource> reservedClient = loadInfo->TakeReservedClientSource();
+    if (reservedClient) {
+      mClientSource.reset();
+      mClientSource = Move(reservedClient);
+    }
+  }
+
+  // We don't have a LoadInfo reserved client, but maybe we should
+  // be inheriting an initial one from the docshell.  This means
+  // that the docshell started the channel load before creating the
+  // initial about:blank document.  This is an optimization, though,
+  // and it created an initial Client as a placeholder for the document.
+  // In this case we want to inherit this placeholder Client here.
+  if (!mClientSource) {
+    nsIDocShell* docshell = GetDocShell();
+    if (docshell) {
+      mClientSource = docshell->TakeInitialClientSource();
+    }
+  }
+
+  // If we don't have a reserved client or an initial client, then create
+  // one now.  This can happen in certain cases where we avoid preallocating
+  // the client in the docshell.  This mainly occurs in situations where
+  // the principal is not clearly inherited from the parent; e.g. sandboxed
+  // iframes, window.open(), etc.
+  if (!mClientSource) {
+    mClientSource = ClientManager::CreateSource(ClientType::Window,
+                                                EventTargetFor(TaskCategory::Other),
+                                                mDoc->NodePrincipal());
+    if (NS_WARN_IF(!mClientSource)) {
+      return NS_ERROR_FAILURE;
+    }
+  }
+
+  return NS_OK;
+}
+
+nsresult
+nsGlobalWindowInner::ExecutionReady()
+{
+  nsresult rv = EnsureClientSource();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mClientSource->WindowExecutionReady(AsInner());
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
 void
 nsGlobalWindowInner::SetOpenerWindow(nsPIDOMWindowOuter* aOpener,
                                      bool aOriginalOpener)
 {
   FORWARD_TO_OUTER_VOID(SetOpenerWindow, (aOpener, aOriginalOpener));
 }
 
 void
@@ -2005,16 +2075,22 @@ nsPIDOMWindowInner::Thaw()
 }
 
 void
 nsPIDOMWindowInner::SyncStateFromParentWindow()
 {
   nsGlobalWindowInner::Cast(this)->SyncStateFromParentWindow();
 }
 
+Maybe<ClientInfo>
+nsPIDOMWindowInner::GetClientInfo() const
+{
+  return Move(nsGlobalWindowInner::Cast(this)->GetClientInfo());
+}
+
 void
 nsGlobalWindowInner::UpdateTopInnerWindow()
 {
   if (!IsInnerWindow() || IsTopInnerWindow() || !mTopInnerWindow) {
     return;
   }
 
   mTopInnerWindow->UpdateWebSocketCount(-(int32_t)mNumOfOpenWebSockets);
@@ -6116,16 +6192,27 @@ nsGlobalWindowInner::CallOnChildren(Meth
     if (!mDoc || !frame || mDoc != frame->OwnerDoc() || !inner) {
       continue;
     }
 
     (inner->*aMethod)();
   }
 }
 
+Maybe<ClientInfo>
+nsGlobalWindowInner::GetClientInfo() const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  Maybe<ClientInfo> clientInfo;
+  if (mClientSource) {
+    clientInfo.emplace(mClientSource->Info());
+  }
+  return Move(clientInfo);
+}
+
 nsresult
 nsGlobalWindowInner::FireDelayedDOMEvents()
 {
   if (mApplicationCache) {
     static_cast<nsDOMOfflineResourceList*>(mApplicationCache.get())->FirePendingEvents();
   }
 
   // Fires an offline status event if the offline status has changed
--- a/dom/base/nsGlobalWindowInner.h
+++ b/dom/base/nsGlobalWindowInner.h
@@ -92,16 +92,17 @@ class DialogValueHolder;
 
 namespace mozilla {
 class AbstractThread;
 class DOMEventTargetHelper;
 class ThrottledEventQueue;
 namespace dom {
 class BarProp;
 struct ChannelPixelLayout;
+class ClientSource;
 class Console;
 class Crypto;
 class CustomElementRegistry;
 class DocGroup;
 class External;
 class Function;
 class Gamepad;
 enum class ImageBitmapFormat : uint8_t;
@@ -366,16 +367,18 @@ public:
   void Suspend();
   void Resume();
   virtual bool IsSuspended() const override;
   void Freeze();
   void Thaw();
   virtual bool IsFrozen() const override;
   void SyncStateFromParentWindow();
 
+  mozilla::Maybe<mozilla::dom::ClientInfo> GetClientInfo() const;
+
   virtual nsresult FireDelayedDOMEvents() override;
 
   virtual nsresult SetNewDocument(nsIDocument *aDocument,
                                   nsISupports *aState,
                                   bool aForceReuseInnerWindow) override;
 
   virtual void SetOpenerWindow(nsPIDOMWindowOuter* aOpener,
                                bool aOriginalOpener) override;
@@ -1160,16 +1163,19 @@ protected:
 
   void FreeInnerObjects();
   nsGlobalWindowInner *CallerInnerWindow();
 
   // Only to be called on an inner window.
   // aDocument must not be null.
   void InnerSetNewDocument(JSContext* aCx, nsIDocument* aDocument);
 
+  nsresult EnsureClientSource();
+  nsresult ExecutionReady();
+
   // Inner windows only.
   nsresult DefineArgumentsProperty(nsIArray *aArguments);
 
   // Get the parent, returns null if this is a toplevel window
   nsPIDOMWindowOuter* GetParentInternal();
 
 public:
   // popup tracking
@@ -1565,16 +1571,18 @@ protected:
   // When non-zero, the document should receive a vrdisplayactivate event
   // after loading.  The value is the ID of the VRDisplay that content should
   // begin presentation on.
   uint32_t mAutoActivateVRDisplayID; // Outer windows only
   int64_t mBeforeUnloadListenerCount; // Inner windows only
 
   RefPtr<mozilla::dom::IntlUtils> mIntlUtils;
 
+  mozilla::UniquePtr<mozilla::dom::ClientSource> mClientSource;
+
   static InnerWindowByIdTable* sInnerWindowsById;
 
   // Members in the mChromeFields member should only be used in chrome windows.
   // All accesses to this field should be guarded by a check of mIsChrome.
   struct ChromeFields {
     ChromeFields()
       : mGroupMessageManagers(1)
     {}
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -1927,24 +1927,32 @@ nsGlobalWindowOuter::SetNewDocument(nsID
         newInnerWindow->mDoc = aDocument;
 
         // The storage objects contain the URL of the window. We have to
         // recreate them when the innerWindow is reused.
         newInnerWindow->mLocalStorage = nullptr;
         newInnerWindow->mSessionStorage = nullptr;
 
         newInnerWindow->ClearDocumentDependentSlots(cx);
+
+        // When replacing an initial about:blank document we call
+        // ExecutionReady again to update the client creation URL.
+        rv = newInnerWindow->ExecutionReady();
+        NS_ENSURE_SUCCESS(rv, rv);
       }
     } else {
       newInnerWindow->InnerSetNewDocument(cx, aDocument);
 
       // Initialize DOM classes etc on the inner window.
       JS::Rooted<JSObject*> obj(cx, newInnerGlobal);
       rv = kungFuDeathGrip->InitClasses(obj);
       NS_ENSURE_SUCCESS(rv, rv);
+
+      rv = newInnerWindow->ExecutionReady();
+      NS_ENSURE_SUCCESS(rv, rv);
     }
 
     // If the document comes from a JAR, check if the channel was determined
     // to be unsafe. If so, permanently disable script on the compartment by
     // calling Block() and throwing away the key.
     nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(aDocument->GetChannel());
     if (jarChannel && jarChannel->GetIsUnsafe()) {
       xpc::Scriptability::Get(newInnerGlobal).Block();
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -41,16 +41,17 @@ class nsPIWindowRoot;
 class nsXBLPrototypeHandler;
 
 typedef uint32_t SuspendTypes;
 
 namespace mozilla {
 class ThrottledEventQueue;
 namespace dom {
 class AudioContext;
+class ClientInfo;
 class DocGroup;
 class TabGroup;
 class Element;
 class Performance;
 class ServiceWorkerRegistration;
 class Timeout;
 class TimeoutManager;
 class CustomElementRegistry;
@@ -926,16 +927,18 @@ public:
 
   // Increase/Decrease the number of open WebSockets.
   void UpdateWebSocketCount(int32_t aDelta);
 
   // Return true if there are any open WebSockets that could block
   // timeout-throttling.
   bool HasOpenWebSockets() const;
 
+  mozilla::Maybe<mozilla::dom::ClientInfo> GetClientInfo() const;
+
   mozilla::dom::TabGroup* TabGroup();
 protected:
   void CreatePerformanceObjectIfNeeded();
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsPIDOMWindowInner, NS_PIDOMWINDOWINNER_IID)
 
 // NB: It's very very important that these two classes have identical vtables