Bug 1607984 - P12-4. Start parent load via DocumentChannel. r=mayhemer,nika,mattwoodrow,necko-reviewers
authorJean-Yves Avenard <jyavenard@mozilla.com>
Fri, 24 Apr 2020 01:58:23 +0000
changeset 589267 e27e24312c2728385490c5e06f023ce837a27384
parent 589266 2896323daed4e290691e323c68df572e08d34eb5
child 589268 b507b6482b9d56d599384ca76e0836b48e117a6d
push id13072
push userffxbld-merge
push dateMon, 04 May 2020 14:22:08 +0000
treeherdermozilla-beta@8a4f45b9a244 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmayhemer, nika, mattwoodrow, necko-reviewers
bugs1607984
milestone77.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 1607984 - P12-4. Start parent load via DocumentChannel. r=mayhemer,nika,mattwoodrow,necko-reviewers Add ParentProcessDocumentChannel object. This object is a DocumentChannel that will start a channel load from the parent process via a DocumentChannel. The aim of this task is two-fold. 1- Be consistent on how we handle redirects before continuing the load on the final channel. 2- Prepare to initiate a process switch when needed without having to go via an intermediary content process, saving a process switch. This task will be done in a follow-up task. The behaviour of the ParentProcessDocumentChannel is similar in logic to the DocumentChannelChild/DocumentChannelParent pair. The ParentProcessDocumentChannel sets up a DocumentLoadListener, have it handle the redirects and upon completion continue the load on the final channel. Differential Revision: https://phabricator.services.mozilla.com/D70009
docshell/base/nsDocShell.cpp
docshell/test/chrome/test_bug909218.html
netwerk/ipc/ADocumentChannelBridge.h
netwerk/ipc/DocumentChannel.cpp
netwerk/ipc/DocumentChannelParent.h
netwerk/ipc/DocumentLoadListener.cpp
netwerk/ipc/ParentProcessDocumentChannel.cpp
netwerk/ipc/ParentProcessDocumentChannel.h
netwerk/ipc/moz.build
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -5707,17 +5707,18 @@ void nsDocShell::OnRedirectStateChange(n
   }
 
   // DocumentChannel only reports a single redirect via the normal
   // confirmation mechanism (when they replace themselves with a real
   // channel), but can have had an arbitrary number
   // of redirects handled in the parent process.
   // Query the full redirect chain directly, so that we can add history
   // entries for them.
-  if (RefPtr<DocumentChannel> docChannel = do_QueryObject(aOldChannel)) {
+  RefPtr<DocumentChannel> docChannel = do_QueryObject(aOldChannel);
+  if (docChannel) {
     nsCOMPtr<nsIURI> previousURI;
     uint32_t previousFlags = 0;
     docChannel->GetLastVisit(getter_AddRefs(previousURI), &previousFlags);
     SavePreviousRedirectsAndLastVisit(aNewChannel, previousURI, previousFlags,
                                       docChannel->GetRedirectChain());
   } else {
     // Below a URI visit is saved (see AddURIVisit method doc).
     // The visit chain looks something like:
@@ -5755,17 +5756,17 @@ void nsDocShell::OnRedirectStateChange(n
       // here.  OnNewURI will do that, so we will cache it.
       SaveLastVisit(aNewChannel, oldURI, aRedirectFlags);
     }
   }
 
   // check if the new load should go through the application cache.
   nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
       do_QueryInterface(aNewChannel);
-  if (appCacheChannel) {
+  if (appCacheChannel && !docChannel) {
     if (GeckoProcessType_Default != XRE_GetProcessType()) {
       // Permission will be checked in the parent process.
       appCacheChannel->SetChooseApplicationCache(true);
     } else {
       nsCOMPtr<nsIScriptSecurityManager> secMan =
           do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
 
       if (secMan) {
--- a/docshell/test/chrome/test_bug909218.html
+++ b/docshell/test/chrome/test_bug909218.html
@@ -90,17 +90,17 @@ var RequestWatcher = {
     }
     // We will usually see requests for 'about:document-onload-blocker' not
     // have the flag, so we just ignore them.
     // We also see, eg, resource://gre-resources/loading-image.png, so
     // skip resource:// URLs too.
     // We may also see, eg, chrome://global/skin/icons/resizer.svg, so
     // skip chrome:// URLs too.
     if (req.name.startsWith("about:") || req.name.startsWith("resource:") ||
-      req.name.startsWith("chrome:")) {
+        req.name.startsWith("chrome:") || req.name.startsWith("documentchannel:")) {
       return;
     }
     is(req.loadFlags & TEST_FLAGS, TEST_FLAGS, "request " + req.name + " has the expected flags");
     this.requestCounts[req.name] += 1;
     var stopFlags = Ci.nsIWebProgressListener.STATE_STOP |
                     Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
     if (req.name == TEST_URL && (flags & stopFlags) == stopFlags) {
       this.finalize();
--- a/netwerk/ipc/ADocumentChannelBridge.h
+++ b/netwerk/ipc/ADocumentChannelBridge.h
@@ -42,16 +42,17 @@ class ADocumentChannelBridge {
   // real channel.
   virtual RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
   RedirectToRealChannel(
       nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>>&&
           aStreamFilterEndpoints,
       uint32_t aRedirectFlags, uint32_t aLoadFlags) = 0;
 
   // Returns the process id that this bridge is connected to.
+  // If 0 indicates that the load is started from the parent process.
   virtual base::ProcessId OtherPid() const = 0;
 
  protected:
   virtual ~ADocumentChannelBridge() = default;
 };
 
 }  // namespace net
 }  // namespace mozilla
--- a/netwerk/ipc/DocumentChannel.cpp
+++ b/netwerk/ipc/DocumentChannel.cpp
@@ -15,16 +15,17 @@
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/nsCSPContext.h"
 #include "mozilla/extensions/StreamFilterParent.h"
 #include "mozilla/ipc/IPCStreamUtils.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "mozilla/net/DocumentChannelChild.h"
 #include "mozilla/net/HttpChannelChild.h"
 #include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/ParentProcessDocumentChannel.h"
 #include "mozilla/net/UrlClassifierCommon.h"
 #include "nsContentSecurityManager.h"
 #include "nsDocShell.h"
 #include "nsDocShellLoadState.h"
 #include "nsHttpHandler.h"
 #include "nsIInputStreamChannel.h"
 #include "nsNetUtil.h"
 #include "nsQueryObject.h"
@@ -153,29 +154,33 @@ static bool URIUsesDocChannel(nsIURI* aU
 bool DocumentChannel::CanUseDocumentChannel(nsDocShellLoadState* aLoadState) {
   MOZ_ASSERT(aLoadState);
   // We want to use DocumentChannel if we're using a supported scheme. Sandboxed
   // srcdoc loads break due to failing assertions after changing processes, and
   // non-sandboxed srcdoc loads need to share the same principal object as their
   // outer document (and must load in the same process), which breaks if we
   // serialize to the parent process.
   return StaticPrefs::browser_tabs_documentchannel() &&
-         XRE_IsContentProcess() &&
          !aLoadState->HasLoadFlags(nsDocShell::INTERNAL_LOAD_FLAGS_IS_SRCDOC) &&
          URIUsesDocChannel(aLoadState->URI());
 }
 
 /* static */
 already_AddRefed<DocumentChannel> DocumentChannel::CreateDocumentChannel(
     nsDocShellLoadState* aLoadState, class LoadInfo* aLoadInfo,
     nsLoadFlags aLoadFlags, nsIInterfaceRequestor* aNotificationCallbacks,
     uint32_t aCacheKey) {
-  MOZ_ASSERT(XRE_IsContentProcess());
-  RefPtr<DocumentChannel> channel =
-      new DocumentChannelChild(aLoadState, aLoadInfo, aLoadFlags, aCacheKey);
+  RefPtr<DocumentChannel> channel;
+  if (XRE_IsContentProcess()) {
+    channel =
+        new DocumentChannelChild(aLoadState, aLoadInfo, aLoadFlags, aCacheKey);
+  } else {
+    channel = new ParentProcessDocumentChannel(aLoadState, aLoadInfo,
+                                               aLoadFlags, aCacheKey);
+  }
   channel->SetNotificationCallbacks(aNotificationCallbacks);
   return channel.forget();
 }
 
 //-----------------------------------------------------------------------------
 // DocumentChannel::nsITraceableChannel
 //-----------------------------------------------------------------------------
 
--- a/netwerk/ipc/DocumentChannelParent.h
+++ b/netwerk/ipc/DocumentChannelParent.h
@@ -56,25 +56,25 @@ class DocumentChannelParent final : publ
   }
 
   void Delete() override {
     if (CanSend()) {
       Unused << SendDeleteSelf();
     }
   }
 
-  virtual ProcessId OtherPid() const override { return IProtocol::OtherPid(); }
+  ProcessId OtherPid() const override { return IProtocol::OtherPid(); }
 
   RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
   RedirectToRealChannel(
       nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>>&&
           aStreamFilterEndpoints,
       uint32_t aRedirectFlags, uint32_t aLoadFlags) override;
 
-  ~DocumentChannelParent();
+  virtual ~DocumentChannelParent();
 
   RefPtr<DocumentLoadListener> mParent;
 };
 
 }  // namespace net
 }  // namespace mozilla
 
 #endif  // mozilla_net_DocumentChannelParent_h
--- a/netwerk/ipc/DocumentLoadListener.cpp
+++ b/netwerk/ipc/DocumentLoadListener.cpp
@@ -426,18 +426,18 @@ bool DocumentLoadListener::Open(
   if (timedChannel) {
     timedChannel->SetAsyncOpen(aAsyncOpenTime);
   }
 
   // nsViewSourceChannel normally replaces the nsIRequest passed to
   // OnStart/StopRequest with itself. We don't need this, and instead
   // we want the original request so that we get different ones for
   // each part of a multipart channel.
-  if (nsCOMPtr<nsIViewSourceChannel> viewSourceChannel =
-          do_QueryInterface(mChannel)) {
+  nsCOMPtr<nsIViewSourceChannel> viewSourceChannel;
+  if (OtherPid() && (viewSourceChannel = do_QueryInterface(mChannel))) {
     viewSourceChannel->SetReplaceRequest(false);
   }
 
   // Setup a ClientChannelHelper to watch for redirects, and copy
   // across any serviceworker related data between channels as needed.
   AddClientChannelHelperInParent(mChannel, std::move(aInfo));
 
   // Recalculate the openFlags, matching the logic in use in Content process.
@@ -1134,22 +1134,24 @@ bool DocumentLoadListener::MaybeTriggerP
 
 RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
 DocumentLoadListener::RedirectToRealChannel(
     uint32_t aRedirectFlags, uint32_t aLoadFlags,
     const Maybe<uint64_t>& aDestinationProcess,
     nsTArray<ParentEndpoint>&& aStreamFilterEndpoints) {
   LOG(("DocumentLoadListener RedirectToRealChannel [this=%p]", this));
 
-  // Register the new channel and obtain id for it
-  nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
-      RedirectChannelRegistrar::GetOrCreate();
-  MOZ_ASSERT(registrar);
-  MOZ_ALWAYS_SUCCEEDS(
-      registrar->RegisterChannel(mChannel, &mRedirectChannelId));
+  if (aDestinationProcess || OtherPid()) {
+    // Register the new channel and obtain id for it
+    nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
+        RedirectChannelRegistrar::GetOrCreate();
+    MOZ_ASSERT(registrar);
+    MOZ_ALWAYS_SUCCEEDS(
+        registrar->RegisterChannel(mChannel, &mRedirectChannelId));
+  }
 
   if (aDestinationProcess) {
     dom::ContentParent* cp =
         dom::ContentProcessManager::GetSingleton()->GetContentProcessById(
             ContentParentId{*aDestinationProcess});
     if (!cp) {
       return PDocumentChannelParent::RedirectToRealChannelPromise::
           CreateAndReject(ipc::ResponseRejectReason::SendError, __func__);
new file mode 100644
--- /dev/null
+++ b/netwerk/ipc/ParentProcessDocumentChannel.cpp
@@ -0,0 +1,159 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et 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 "ParentProcessDocumentChannel.h"
+
+extern mozilla::LazyLogModule gDocumentChannelLog;
+#define LOG(fmt) MOZ_LOG(gDocumentChannelLog, mozilla::LogLevel::Verbose, fmt)
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS_INHERITED(ParentProcessDocumentChannel, DocumentChannel,
+                            nsIAsyncVerifyRedirectCallback)
+
+ParentProcessDocumentChannel::ParentProcessDocumentChannel(
+    nsDocShellLoadState* aLoadState, class LoadInfo* aLoadInfo,
+    nsLoadFlags aLoadFlags, uint32_t aCacheKey)
+    : DocumentChannel(aLoadState, aLoadInfo, aLoadFlags, aCacheKey) {
+  LOG(("ParentProcessDocumentChannel ctor [this=%p]", this));
+}
+
+ParentProcessDocumentChannel::~ParentProcessDocumentChannel() {
+  LOG(("ParentProcessDocumentChannel dtor [this=%p]", this));
+}
+
+RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
+ParentProcessDocumentChannel::RedirectToRealChannel(
+    nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>>&&
+        aStreamFilterEndpoints,
+    uint32_t aRedirectFlags, uint32_t aLoadFlags) {
+  LOG(("ParentProcessDocumentChannel RedirectToRealChannel [this=%p]", this));
+  nsCOMPtr<nsIChannel> channel = mDocumentLoadListener->GetChannel();
+  channel->SetLoadFlags(aLoadFlags);
+  channel->SetNotificationCallbacks(mCallbacks);
+
+  if (mLoadGroup) {
+    channel->SetLoadGroup(mLoadGroup);
+  }
+
+  mLastVisitInfo = mDocumentLoadListener->LastVisitInfo();
+  mRedirects = mDocumentLoadListener->Redirects();
+  mStreamFilterEndpoints = std::move(aStreamFilterEndpoints);
+
+  RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise> p =
+      mPromise.Ensure(__func__);
+
+  nsresult rv =
+      gHttpHandler->AsyncOnChannelRedirect(this, channel, aRedirectFlags);
+  if (NS_FAILED(rv)) {
+    OnRedirectVerifyCallback(rv);
+  }
+
+  return p;
+}
+
+NS_IMETHODIMP
+ParentProcessDocumentChannel::OnRedirectVerifyCallback(nsresult aResult) {
+  LOG(
+      ("ParentProcessDocumentChannel OnRedirectVerifyCallback [this=%p "
+       "aResult=%d]",
+       this, int(aResult)));
+  MOZ_ASSERT(mDocumentLoadListener);
+
+  if (NS_FAILED(aResult)) {
+    Cancel(aResult);
+  } else if (mCanceled && NS_SUCCEEDED(aResult)) {
+    aResult = NS_BINDING_ABORTED;
+  } else {
+    const nsCOMPtr<nsIChannel> channel = mDocumentLoadListener->GetChannel();
+    mLoadGroup->AddRequest(channel, nullptr);
+    // Adding the channel to the loadgroup could have triggered a status
+    // change with an observer being called destroying the docShell, resulting
+    // in the PPDC to be canceled.
+    if (!mCanceled) {
+      mLoadGroup->RemoveRequest(this, nullptr, NS_BINDING_REDIRECTED);
+      for (auto& endpoint : mStreamFilterEndpoints) {
+        extensions::StreamFilterParent::Attach(channel, std::move(endpoint));
+      }
+      if (!mDocumentLoadListener->ResumeSuspendedChannel(mListener)) {
+        // We added ourselves to the load group, but attempting
+        // to resume has notified us that the channel is already
+        // finished. Better remove ourselves from the loadgroup
+        // again.
+        nsresult status = NS_OK;
+        channel->GetStatus(&status);
+        mLoadGroup->RemoveRequest(channel, nullptr, status);
+      }
+    }
+  }
+
+  mLoadGroup = nullptr;
+  mListener = nullptr;
+  mCallbacks = nullptr;
+  mDocumentLoadListener->DocumentChannelBridgeDisconnected();
+  mDocumentLoadListener = nullptr;
+
+  mPromise.ResolveIfExists(aResult, __func__);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP ParentProcessDocumentChannel::AsyncOpen(
+    nsIStreamListener* aListener) {
+  LOG(("ParentProcessDocumentChannel AsyncOpen [this=%p]", this));
+  nsCOMPtr<nsILoadContext> loadContext;
+  NS_QueryNotificationCallbacks(this, loadContext);
+
+  mDocumentLoadListener = new DocumentLoadListener(
+      GetDocShell()->GetBrowsingContext()->Canonical(), loadContext, this);
+  LOG(("Created PPDocumentChannel with listener=%p",
+       mDocumentLoadListener.get()));
+
+  gHttpHandler->OnOpeningDocumentRequest(this);
+
+  nsresult rv = NS_OK;
+  Maybe<dom::ClientInfo> initialClientInfo = mInitialClientInfo;
+  if (!mDocumentLoadListener->Open(
+          mLoadState, mLoadFlags, mCacheKey, mChannelId, mAsyncOpenTime,
+          mTiming, std::move(initialClientInfo),
+          GetDocShell()->GetOuterWindowID(),
+          GetDocShell()
+              ->GetBrowsingContext()
+              ->HasValidTransientUserGestureActivation(),
+          &rv)) {
+    MOZ_ASSERT(NS_FAILED(rv));
+    mDocumentLoadListener->DocumentChannelBridgeDisconnected();
+    mDocumentLoadListener = nullptr;
+    return rv;
+  }
+
+  mListener = aListener;
+  if (mLoadGroup) {
+    mLoadGroup->AddRequest(this, nullptr);
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP ParentProcessDocumentChannel::Cancel(nsresult aStatus) {
+  LOG(("ParentProcessDocumentChannel Cancel [this=%p]", this));
+  if (mCanceled) {
+    return NS_OK;
+  }
+
+  mCanceled = true;
+  mDocumentLoadListener->Cancel(aStatus);
+
+  ShutdownListeners(aStatus);
+
+  return NS_OK;
+}
+
+}  // namespace net
+}  // namespace mozilla
+
+#undef LOG
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/netwerk/ipc/ParentProcessDocumentChannel.h
@@ -0,0 +1,62 @@
+/* vim: set sw=2 ts=8 et 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_net_ParentProcessDocumentChannel_h
+#define mozilla_net_ParentProcessDocumentChannel_h
+
+#include "ProtocolUtils.h"
+#include "mozilla/net/ADocumentChannelBridge.h"
+#include "mozilla/net/DocumentChannel.h"
+#include "mozilla/net/DocumentLoadListener.h"
+
+namespace mozilla {
+namespace net {
+
+class ParentProcessDocumentChannel : public DocumentChannel,
+                                     public nsIAsyncVerifyRedirectCallback,
+                                     public ADocumentChannelBridge {
+ public:
+  ParentProcessDocumentChannel(nsDocShellLoadState* aLoadState,
+                               class LoadInfo* aLoadInfo,
+                               nsLoadFlags aLoadFlags, uint32_t aCacheKey);
+
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
+
+  NS_IMETHOD AsyncOpen(nsIStreamListener* aListener) override;
+  NS_IMETHOD Cancel(nsresult aStatusCode) override;
+
+  RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
+  RedirectToRealChannel(
+      nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>>&&
+          aStreamFilterEndpoints,
+      uint32_t aRedirectFlags, uint32_t aLoadFlags) override;
+
+  void DisconnectChildListeners(nsresult aStatus,
+                                nsresult aLoadGroupStatus) override {
+    DocumentChannel::DisconnectChildListeners(aStatus, aLoadGroupStatus);
+    mDocumentLoadListener = nullptr;
+  }
+  void Delete() override {}
+  void DeleteIPDL() override {
+    mPromise.ResolveIfExists(NS_BINDING_ABORTED, __func__);
+  }
+  base::ProcessId OtherPid() const override { return 0; }
+
+ private:
+  virtual ~ParentProcessDocumentChannel();
+
+  RefPtr<DocumentLoadListener> mDocumentLoadListener;
+  nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>>
+      mStreamFilterEndpoints;
+  MozPromiseHolder<PDocumentChannelParent::RedirectToRealChannelPromise>
+      mPromise;
+};
+
+}  // namespace net
+}  // namespace mozilla
+
+#endif  // mozilla_net_ParentProcessDocumentChannel_h
--- a/netwerk/ipc/moz.build
+++ b/netwerk/ipc/moz.build
@@ -13,16 +13,17 @@ EXPORTS.mozilla.net += [
     'DocumentLoadListener.h',
     'InputChannelThrottleQueueChild.h',
     'InputChannelThrottleQueueParent.h',
     'NeckoChild.h',
     'NeckoCommon.h',
     'NeckoMessageUtils.h',
     'NeckoParent.h',
     'NeckoTargetHolder.h',
+    'ParentProcessDocumentChannel.h',
     'SocketProcessBridgeChild.h',
     'SocketProcessBridgeParent.h',
     'SocketProcessChild.h',
     'SocketProcessHost.h',
     'SocketProcessImpl.h',
     'SocketProcessParent.h',
 ]
 
@@ -33,16 +34,17 @@ UNIFIED_SOURCES += [
     'DocumentChannelParent.cpp',
     'DocumentLoadListener.cpp',
     'InputChannelThrottleQueueChild.cpp',
     'InputChannelThrottleQueueParent.cpp',
     'NeckoChild.cpp',
     'NeckoCommon.cpp',
     'NeckoParent.cpp',
     'NeckoTargetHolder.cpp',
+    'ParentProcessDocumentChannel.cpp',
     'SocketProcessBridgeChild.cpp',
     'SocketProcessBridgeParent.cpp',
     'SocketProcessChild.cpp',
     'SocketProcessHost.cpp',
     'SocketProcessImpl.cpp',
     'SocketProcessParent.cpp',
 ]