1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set sw=2 ts=8 et tw=80 : */
3 
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 
8 #include "DocumentLoadListener.h"
9 
10 #include "mozilla/AntiTrackingUtils.h"
11 #include "mozilla/LoadInfo.h"
12 #include "mozilla/MozPromiseInlines.h"  // For MozPromise::FromDomPromise
13 #include "mozilla/StaticPrefs_extensions.h"
14 #include "mozilla/StaticPrefs_fission.h"
15 #include "mozilla/StaticPrefs_security.h"
16 #include "mozilla/dom/CanonicalBrowsingContext.h"
17 #include "mozilla/dom/ClientChannelHelper.h"
18 #include "mozilla/dom/ContentParent.h"
19 #include "mozilla/dom/ContentProcessManager.h"
20 #include "mozilla/dom/SessionHistoryEntry.h"
21 #include "mozilla/dom/WindowGlobalParent.h"
22 #include "mozilla/dom/ipc/IdType.h"
23 #include "mozilla/net/CookieJarSettings.h"
24 #include "mozilla/net/HttpChannelParent.h"
25 #include "mozilla/net/RedirectChannelRegistrar.h"
26 #include "mozilla/net/UrlClassifierCommon.h"
27 #include "nsContentSecurityUtils.h"
28 #include "nsDocShell.h"
29 #include "nsDocShellLoadState.h"
30 #include "nsDocShellLoadTypes.h"
31 #include "nsExternalHelperAppService.h"
32 #include "nsHttpChannel.h"
33 #include "nsIHttpChannelInternal.h"
34 #include "nsIBrowser.h"
35 #include "nsIE10SUtils.h"
36 #include "nsIStreamConverterService.h"
37 #include "nsIViewSourceChannel.h"
38 #include "nsImportModule.h"
39 #include "nsMimeTypes.h"
40 #include "nsRedirectHistoryEntry.h"
41 #include "nsSandboxFlags.h"
42 #include "nsURILoader.h"
43 #include "nsWebNavigationInfo.h"
44 
45 #ifdef ANDROID
46 #  include "mozilla/widget/nsWindow.h"
47 #endif /* ANDROID */
48 
49 mozilla::LazyLogModule gDocumentChannelLog("DocumentChannel");
50 #define LOG(fmt) MOZ_LOG(gDocumentChannelLog, mozilla::LogLevel::Verbose, fmt)
51 
52 using namespace mozilla::dom;
53 
54 namespace mozilla {
55 namespace net {
56 
SetNeedToAddURIVisit(nsIChannel * aChannel,bool aNeedToAddURIVisit)57 static void SetNeedToAddURIVisit(nsIChannel* aChannel,
58                                  bool aNeedToAddURIVisit) {
59   nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(aChannel));
60   if (!props) {
61     return;
62   }
63 
64   props->SetPropertyAsBool(NS_LITERAL_STRING("docshell.needToAddURIVisit"),
65                            aNeedToAddURIVisit);
66 }
67 
68 /**
69  * An extension to nsDocumentOpenInfo that we run in the parent process, so
70  * that we can make the decision to retarget to content handlers or the external
71  * helper app, before we make process switching decisions.
72  *
73  * This modifies the behaviour of nsDocumentOpenInfo so that it can do
74  * retargeting, but doesn't do stream conversion (but confirms that we will be
75  * able to do so later).
76  *
77  * We still run nsDocumentOpenInfo in the content process, but disable
78  * retargeting, so that it can only apply stream conversion, and then send data
79  * to the docshell.
80  */
81 class ParentProcessDocumentOpenInfo final : public nsDocumentOpenInfo,
82                                             public nsIMultiPartChannelListener {
83  public:
ParentProcessDocumentOpenInfo(ParentChannelListener * aListener,uint32_t aFlags,mozilla::dom::BrowsingContext * aBrowsingContext)84   ParentProcessDocumentOpenInfo(ParentChannelListener* aListener,
85                                 uint32_t aFlags,
86                                 mozilla::dom::BrowsingContext* aBrowsingContext)
87       : nsDocumentOpenInfo(aFlags, false),
88         mBrowsingContext(aBrowsingContext),
89         mListener(aListener) {
90     LOG(("ParentProcessDocumentOpenInfo ctor [this=%p]", this));
91   }
92 
93   NS_DECL_ISUPPORTS_INHERITED
94 
95   // The default content listener is always a docshell, so this manually
96   // implements the same checks, and if it succeeds, uses the parent
97   // channel listener so that we forward onto DocumentLoadListener.
TryDefaultContentListener(nsIChannel * aChannel,const nsCString & aContentType)98   bool TryDefaultContentListener(nsIChannel* aChannel,
99                                  const nsCString& aContentType) {
100     uint32_t canHandle = nsWebNavigationInfo::IsTypeSupported(
101         aContentType, mBrowsingContext->GetAllowPlugins());
102     if (canHandle != nsIWebNavigationInfo::UNSUPPORTED) {
103       m_targetStreamListener = mListener;
104       nsLoadFlags loadFlags = 0;
105       aChannel->GetLoadFlags(&loadFlags);
106       aChannel->SetLoadFlags(loadFlags | nsIChannel::LOAD_TARGETED);
107       return true;
108     }
109     return false;
110   }
111 
TryDefaultContentListener(nsIChannel * aChannel)112   bool TryDefaultContentListener(nsIChannel* aChannel) override {
113     return TryDefaultContentListener(aChannel, mContentType);
114   }
115 
116   // Generally we only support stream converters that can tell
117   // use exactly what type they'll output. If we find one, then
118   // we just target to our default listener directly (without
119   // conversion), and the content process nsDocumentOpenInfo will
120   // run and do the actual conversion.
TryStreamConversion(nsIChannel * aChannel)121   nsresult TryStreamConversion(nsIChannel* aChannel) override {
122     // The one exception is nsUnknownDecoder, which works in the parent
123     // (and we need to know what the content type is before we can
124     // decide if it will be handled in the parent), so we run that here.
125     if (mContentType.LowerCaseEqualsASCII(UNKNOWN_CONTENT_TYPE)) {
126       return nsDocumentOpenInfo::TryStreamConversion(aChannel);
127     }
128 
129     nsresult rv;
130     nsCOMPtr<nsIStreamConverterService> streamConvService =
131         do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
132     nsAutoCString str;
133     rv = streamConvService->ConvertedType(mContentType, aChannel, str);
134     NS_ENSURE_SUCCESS(rv, rv);
135 
136     // We only support passing data to the default content listener
137     // (docshell), and we don't supported chaining converters.
138     if (TryDefaultContentListener(aChannel, str)) {
139       mContentType = str;
140       return NS_OK;
141     }
142     // This is the same result as nsStreamConverterService uses when it
143     // can't find a converter
144     return NS_ERROR_FAILURE;
145   }
146 
TryExternalHelperApp(nsIExternalHelperAppService * aHelperAppService,nsIChannel * aChannel)147   nsresult TryExternalHelperApp(nsIExternalHelperAppService* aHelperAppService,
148                                 nsIChannel* aChannel) override {
149     RefPtr<nsExternalAppHandler> handler;
150     nsresult rv = aHelperAppService->CreateListener(
151         mContentType, aChannel, mBrowsingContext, false, nullptr,
152         getter_AddRefs(handler));
153     if (NS_SUCCEEDED(rv)) {
154       m_targetStreamListener = handler;
155     }
156     return rv;
157   }
158 
Clone()159   nsDocumentOpenInfo* Clone() override {
160     mCloned = true;
161     return new ParentProcessDocumentOpenInfo(mListener, mFlags,
162                                              mBrowsingContext);
163   }
164 
OnStartRequest(nsIRequest * request)165   NS_IMETHOD OnStartRequest(nsIRequest* request) override {
166     LOG(("ParentProcessDocumentOpenInfo OnStartRequest [this=%p]", this));
167 
168     nsresult rv = nsDocumentOpenInfo::OnStartRequest(request);
169 
170     // If we didn't find a content handler,
171     // and we don't have a listener, then just forward to our
172     // default listener. This happens when the channel is in
173     // an error state, and we want to just forward that on to be
174     // handled in the content process.
175     if (!mUsedContentHandler && !m_targetStreamListener) {
176       m_targetStreamListener = mListener;
177       return m_targetStreamListener->OnStartRequest(request);
178     }
179     if (m_targetStreamListener != mListener) {
180       LOG(
181           ("ParentProcessDocumentOpenInfo targeted to non-default listener "
182            "[this=%p]",
183            this));
184       // If this is the only part, then we can immediately tell our listener
185       // that it won't be getting any content and disconnect it. For multipart
186       // channels we have to wait until we've handled all parts before we know.
187       // This does mean that the content process can still Cancel() a multipart
188       // response while the response is being handled externally, but this
189       // matches the single-process behaviour.
190       // If we got cloned, then we don't need to do this, as only the last link
191       // needs to do it.
192       // Multi-part channels are guaranteed to call OnAfterLastPart, which we
193       // forward to the listeners, so it will handle disconnection at that
194       // point.
195       nsCOMPtr<nsIMultiPartChannel> multiPartChannel =
196           do_QueryInterface(request);
197       if (!multiPartChannel && !mCloned) {
198         DisconnectChildListeners();
199       }
200     }
201     return rv;
202   }
203 
OnAfterLastPart(nsresult aStatus)204   NS_IMETHOD OnAfterLastPart(nsresult aStatus) override {
205     mListener->OnAfterLastPart(aStatus);
206     return NS_OK;
207   }
208 
209  private:
~ParentProcessDocumentOpenInfo()210   virtual ~ParentProcessDocumentOpenInfo() {
211     LOG(("ParentProcessDocumentOpenInfo dtor [this=%p]", this));
212   }
213 
DisconnectChildListeners()214   void DisconnectChildListeners() {
215     // Tell the DocumentLoadListener to notify the content process that it's
216     // been entirely retargeted, and to stop waiting.
217     // Clear mListener's pointer to the DocumentLoadListener to break the
218     // reference cycle.
219     RefPtr<DocumentLoadListener> doc = do_GetInterface(ToSupports(mListener));
220     MOZ_ASSERT(doc);
221     doc->DisconnectChildListeners(NS_BINDING_RETARGETED, NS_OK);
222     mListener->SetListenerAfterRedirect(nullptr);
223   }
224 
225   RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext;
226   RefPtr<ParentChannelListener> mListener;
227 
228   /**
229    * Set to true if we got cloned to create a chained listener.
230    */
231   bool mCloned = false;
232 };
233 
NS_IMPL_ADDREF_INHERITED(ParentProcessDocumentOpenInfo,nsDocumentOpenInfo)234 NS_IMPL_ADDREF_INHERITED(ParentProcessDocumentOpenInfo, nsDocumentOpenInfo)
235 NS_IMPL_RELEASE_INHERITED(ParentProcessDocumentOpenInfo, nsDocumentOpenInfo)
236 
237 NS_INTERFACE_MAP_BEGIN(ParentProcessDocumentOpenInfo)
238   NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannelListener)
239 NS_INTERFACE_MAP_END_INHERITING(nsDocumentOpenInfo)
240 
241 NS_IMPL_ADDREF(DocumentLoadListener)
242 NS_IMPL_RELEASE(DocumentLoadListener)
243 
244 NS_INTERFACE_MAP_BEGIN(DocumentLoadListener)
245   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInterfaceRequestor)
246   NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
247   NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
248   NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
249   NS_INTERFACE_MAP_ENTRY(nsIParentChannel)
250   NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectReadyCallback)
251   NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
252   NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannelListener)
253   NS_INTERFACE_MAP_ENTRY_CONCRETE(DocumentLoadListener)
254 NS_INTERFACE_MAP_END
255 
256 DocumentLoadListener::DocumentLoadListener(
257     CanonicalBrowsingContext* aBrowsingContext, ADocumentChannelBridge* aBridge)
258     : mDocumentChannelBridge(aBridge) {
259   MOZ_ASSERT(aBridge);
260   LOG(("DocumentLoadListener ctor [this=%p]", this));
261   mParentChannelListener = new ParentChannelListener(
262       this, aBrowsingContext, aBrowsingContext->UsePrivateBrowsing());
263 }
264 
DocumentLoadListener(CanonicalBrowsingContext * aBrowsingContext,base::ProcessId aPendingBridgeProcess)265 DocumentLoadListener::DocumentLoadListener(
266     CanonicalBrowsingContext* aBrowsingContext,
267     base::ProcessId aPendingBridgeProcess) {
268   LOG(("DocumentLoadListener ctor [this=%p]", this));
269   mParentChannelListener = new ParentChannelListener(
270       this, aBrowsingContext, aBrowsingContext->UsePrivateBrowsing());
271   mPendingDocumentChannelBridgeProcess = Some(aPendingBridgeProcess);
272 }
273 
~DocumentLoadListener()274 DocumentLoadListener::~DocumentLoadListener() {
275   LOG(("DocumentLoadListener dtor [this=%p]", this));
276 }
277 
AddURIVisit(nsIChannel * aChannel,uint32_t aLoadFlags)278 void DocumentLoadListener::AddURIVisit(nsIChannel* aChannel,
279                                        uint32_t aLoadFlags) {
280   if (mLoadStateLoadType == LOAD_ERROR_PAGE ||
281       mLoadStateLoadType == LOAD_BYPASS_HISTORY) {
282     return;
283   }
284 
285   nsCOMPtr<nsIURI> uri;
286   NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
287 
288   nsCOMPtr<nsIURI> previousURI;
289   uint32_t previousFlags = 0;
290   if (mLoadStateLoadType & nsIDocShell::LOAD_CMD_RELOAD) {
291     previousURI = uri;
292   } else {
293     nsDocShell::ExtractLastVisit(aChannel, getter_AddRefs(previousURI),
294                                  &previousFlags);
295   }
296 
297   // Get the HTTP response code, if available.
298   uint32_t responseStatus = 0;
299   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
300   if (httpChannel) {
301     Unused << httpChannel->GetResponseStatus(&responseStatus);
302   }
303 
304   RefPtr<CanonicalBrowsingContext> browsingContext =
305       mParentChannelListener->GetBrowsingContext();
306   nsCOMPtr<nsIWidget> widget =
307       browsingContext->GetParentProcessWidgetContaining();
308 
309   nsDocShell::InternalAddURIVisit(uri, previousURI, previousFlags,
310                                   responseStatus, browsingContext, widget,
311                                   mLoadStateLoadType);
312 }
313 
CreateLoadInfo(CanonicalBrowsingContext * aBrowsingContext,nsDocShellLoadState * aLoadState,uint64_t aOuterWindowId)314 already_AddRefed<LoadInfo> DocumentLoadListener::CreateLoadInfo(
315     CanonicalBrowsingContext* aBrowsingContext, nsDocShellLoadState* aLoadState,
316     uint64_t aOuterWindowId) {
317   // TODO: Block copied from nsDocShell::DoURILoad, refactor out somewhere
318   bool inheritPrincipal = false;
319 
320   if (aLoadState->PrincipalToInherit()) {
321     bool isSrcdoc =
322         aLoadState->HasLoadFlags(nsDocShell::INTERNAL_LOAD_FLAGS_IS_SRCDOC);
323     bool inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal(
324         aLoadState->PrincipalToInherit(), aLoadState->URI(),
325         true,  // aInheritForAboutBlank
326         isSrcdoc);
327 
328     bool isURIUniqueOrigin =
329         StaticPrefs::security_data_uri_unique_opaque_origin() &&
330         SchemeIsData(aLoadState->URI());
331     inheritPrincipal = inheritAttrs && !isURIUniqueOrigin;
332   }
333 
334   nsSecurityFlags securityFlags =
335       nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL;
336   uint32_t sandboxFlags = aBrowsingContext->GetSandboxFlags();
337 
338   if (aLoadState->LoadType() == LOAD_ERROR_PAGE) {
339     securityFlags |= nsILoadInfo::SEC_LOAD_ERROR_PAGE;
340   }
341 
342   if (inheritPrincipal) {
343     securityFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
344   }
345 
346   RefPtr<LoadInfo> loadInfo;
347   if (aBrowsingContext->GetParent()) {
348     // Build LoadInfo for TYPE_SUBDOCUMENT
349     loadInfo = new LoadInfo(aBrowsingContext, aLoadState->TriggeringPrincipal(),
350                             aOuterWindowId, securityFlags, sandboxFlags);
351   } else {
352     // Build LoadInfo for TYPE_DOCUMENT
353     OriginAttributes attrs;
354     aBrowsingContext->GetOriginAttributes(attrs);
355     loadInfo = new LoadInfo(aBrowsingContext, aLoadState->TriggeringPrincipal(),
356                             attrs, aOuterWindowId, securityFlags, sandboxFlags);
357   }
358 
359   loadInfo->SetHasValidUserGestureActivation(
360       aLoadState->HasValidUserGestureActivation());
361 
362   return loadInfo.forget();
363 }
364 
GetBrowsingContext()365 CanonicalBrowsingContext* DocumentLoadListener::GetBrowsingContext() {
366   if (!mParentChannelListener) {
367     return nullptr;
368   }
369   return mParentChannelListener->GetBrowsingContext();
370 }
371 
Open(nsDocShellLoadState * aLoadState,uint32_t aCacheKey,const Maybe<uint64_t> & aChannelId,const TimeStamp & aAsyncOpenTime,nsDOMNavigationTiming * aTiming,Maybe<ClientInfo> && aInfo,uint64_t aOuterWindowId,bool aHasGesture,Maybe<bool> aUriModified,Maybe<bool> aIsXFOError,nsresult * aRv)372 bool DocumentLoadListener::Open(
373     nsDocShellLoadState* aLoadState, uint32_t aCacheKey,
374     const Maybe<uint64_t>& aChannelId, const TimeStamp& aAsyncOpenTime,
375     nsDOMNavigationTiming* aTiming, Maybe<ClientInfo>&& aInfo,
376     uint64_t aOuterWindowId, bool aHasGesture, Maybe<bool> aUriModified,
377     Maybe<bool> aIsXFOError, nsresult* aRv) {
378   LOG(("DocumentLoadListener Open [this=%p, uri=%s]", this,
379        aLoadState->URI()->GetSpecOrDefault().get()));
380   RefPtr<CanonicalBrowsingContext> browsingContext =
381       mParentChannelListener->GetBrowsingContext();
382 
383   OriginAttributes attrs;
384   browsingContext->GetOriginAttributes(attrs);
385 
386   MOZ_DIAGNOSTIC_ASSERT_IF(browsingContext->GetParent(),
387                            browsingContext->GetParentWindowContext());
388 
389   // If this is a top-level load, then rebuild the LoadInfo from scratch,
390   // since the goal is to be able to initiate loads in the parent, where the
391   // content process won't have provided us with an existing one.
392   RefPtr<LoadInfo> loadInfo =
393       CreateLoadInfo(browsingContext, aLoadState, aOuterWindowId);
394 
395   nsLoadFlags loadFlags = aLoadState->CalculateChannelLoadFlags(
396       browsingContext, std::move(aUriModified), std::move(aIsXFOError));
397 
398   if (!nsDocShell::CreateAndConfigureRealChannelForLoadState(
399           browsingContext, aLoadState, loadInfo, mParentChannelListener,
400           nullptr, attrs, loadFlags, aCacheKey, *aRv,
401           getter_AddRefs(mChannel))) {
402     mParentChannelListener = nullptr;
403     return false;
404   }
405 
406   nsCOMPtr<nsIURI> uriBeingLoaded =
407       AntiTrackingUtils::MaybeGetDocumentURIBeingLoaded(mChannel);
408 
409   RefPtr<HttpBaseChannel> httpBaseChannel = do_QueryObject(mChannel, aRv);
410   if (httpBaseChannel) {
411     nsCOMPtr<nsIURI> topWindowURI;
412     if (browsingContext->IsTop()) {
413       // If this is for the top level loading, the top window URI should be the
414       // URI which we are loading.
415       topWindowURI = uriBeingLoaded;
416     } else if (RefPtr<WindowGlobalParent> topWindow = AntiTrackingUtils::
417                    GetTopWindowExcludingExtensionAccessibleContentFrames(
418                        browsingContext, uriBeingLoaded)) {
419       nsCOMPtr<nsIPrincipal> topWindowPrincipal =
420           topWindow->DocumentPrincipal();
421       if (topWindowPrincipal && !topWindowPrincipal->GetIsNullPrincipal()) {
422         auto* basePrin = BasePrincipal::Cast(topWindowPrincipal);
423         basePrin->GetURI(getter_AddRefs(topWindowURI));
424       }
425     }
426     httpBaseChannel->SetTopWindowURI(topWindowURI);
427   }
428 
429   nsCOMPtr<nsIIdentChannel> identChannel = do_QueryInterface(mChannel);
430   if (identChannel && aChannelId) {
431     Unused << identChannel->SetChannelId(*aChannelId);
432   }
433 
434   RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
435   if (httpChannelImpl) {
436     httpChannelImpl->SetWarningReporter(this);
437   }
438 
439   nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(mChannel);
440   if (timedChannel) {
441     timedChannel->SetAsyncOpen(aAsyncOpenTime);
442   }
443 
444   if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel)) {
445     Unused << httpChannel->SetRequestContextID(
446         browsingContext->GetRequestContextId());
447   }
448 
449   // nsViewSourceChannel normally replaces the nsIRequest passed to
450   // OnStart/StopRequest with itself. We don't need this, and instead
451   // we want the original request so that we get different ones for
452   // each part of a multipart channel.
453   nsCOMPtr<nsIViewSourceChannel> viewSourceChannel;
454   if (OtherPid() && (viewSourceChannel = do_QueryInterface(mChannel))) {
455     viewSourceChannel->SetReplaceRequest(false);
456   }
457 
458   // Setup a ClientChannelHelper to watch for redirects, and copy
459   // across any serviceworker related data between channels as needed.
460   AddClientChannelHelperInParent(mChannel, std::move(aInfo));
461 
462   // Recalculate the openFlags, matching the logic in use in Content process.
463   // NOTE: The only case not handled here to mirror Content process is
464   // redirecting to re-use the channel.
465   MOZ_ASSERT(!aLoadState->GetPendingRedirectedChannel());
466   uint32_t openFlags = nsDocShell::ComputeURILoaderFlags(
467       browsingContext, aLoadState->LoadType());
468 
469   RefPtr<ParentProcessDocumentOpenInfo> openInfo =
470       new ParentProcessDocumentOpenInfo(mParentChannelListener, openFlags,
471                                         browsingContext);
472   openInfo->Prepare();
473 
474 #ifdef ANDROID
475   RefPtr<MozPromise<bool, bool, false>> promise;
476   if (aLoadState->LoadType() != LOAD_ERROR_PAGE &&
477       !(aLoadState->HasLoadFlags(
478           nsDocShell::INTERNAL_LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE)) &&
479       !(aLoadState->LoadType() & LOAD_HISTORY)) {
480     nsCOMPtr<nsIWidget> widget =
481         browsingContext->GetParentProcessWidgetContaining();
482     RefPtr<nsWindow> window = nsWindow::From(widget);
483 
484     if (window) {
485       promise = window->OnLoadRequest(
486           aLoadState->URI(), nsIBrowserDOMWindow::OPEN_CURRENTWINDOW,
487           aLoadState->LoadFlags(), aLoadState->TriggeringPrincipal(),
488           aHasGesture, browsingContext->IsTopContent());
489     }
490   }
491 
492   if (promise) {
493     RefPtr<DocumentLoadListener> self = this;
494     promise->Then(
495         GetCurrentThreadSerialEventTarget(), __func__,
496         [=](const MozPromise<bool, bool, false>::ResolveOrRejectValue& aValue) {
497           if (aValue.IsResolve()) {
498             bool handled = aValue.ResolveValue();
499             if (handled) {
500               self->DisconnectChildListeners(NS_ERROR_ABORT, NS_ERROR_ABORT);
501               mParentChannelListener = nullptr;
502             } else {
503               nsresult rv = mChannel->AsyncOpen(openInfo);
504               if (NS_FAILED(rv)) {
505                 self->DisconnectChildListeners(NS_ERROR_ABORT, NS_ERROR_ABORT);
506                 mParentChannelListener = nullptr;
507               }
508             }
509           }
510         });
511   } else
512 #endif /* ANDROID */
513   {
514     *aRv = mChannel->AsyncOpen(openInfo);
515     if (NS_FAILED(*aRv)) {
516       mParentChannelListener = nullptr;
517       return false;
518     }
519   }
520 
521   mChannelCreationURI = aLoadState->URI();
522   mLoadStateLoadFlags = aLoadState->LoadFlags();
523   mLoadStateLoadType = aLoadState->LoadType();
524   mTiming = aTiming;
525   mSrcdocData = aLoadState->SrcdocData();
526   mBaseURI = aLoadState->BaseURI();
527   if (StaticPrefs::fission_sessionHistoryInParent() &&
528       browsingContext->GetSessionHistory()) {
529     mSessionHistoryInfo =
530         browsingContext->CreateSessionHistoryEntryForLoad(aLoadState, mChannel);
531   }
532 
533   if (auto* ctx = GetBrowsingContext()) {
534     ctx->StartDocumentLoad(this);
535   }
536   return true;
537 }
538 
539 /* static */
OpenFromParent(dom::CanonicalBrowsingContext * aBrowsingContext,nsDocShellLoadState * aLoadState,uint64_t aOuterWindowId,uint32_t * aOutIdent)540 bool DocumentLoadListener::OpenFromParent(
541     dom::CanonicalBrowsingContext* aBrowsingContext,
542     nsDocShellLoadState* aLoadState, uint64_t aOuterWindowId,
543     uint32_t* aOutIdent) {
544   LOG(("DocumentLoadListener::OpenFromParent"));
545 
546   // We currently only support passing nullptr for aLoadInfo for
547   // top level browsing contexts.
548   if (!aBrowsingContext->IsTopContent() ||
549       !aBrowsingContext->GetContentParent()) {
550     LOG(("DocumentLoadListener::OpenFromParent failed because of subdoc"));
551     return false;
552   }
553 
554   if (nsCOMPtr<nsIContentSecurityPolicy> csp = aLoadState->Csp()) {
555     // Check CSP navigate-to
556     bool allowsNavigateTo = false;
557     nsresult rv = csp->GetAllowsNavigateTo(aLoadState->URI(),
558                                            aLoadState->IsFormSubmission(),
559                                            false, /* aWasRedirected */
560                                            false, /* aEnforceWhitelist */
561                                            &allowsNavigateTo);
562     if (NS_FAILED(rv) || !allowsNavigateTo) {
563       return false;
564     }
565   }
566 
567   // Any sort of reload/history load would need the cacheKey, and session
568   // history data for load flags. We don't have those available in the parent
569   // yet, so don't support these load types.
570   auto loadType = aLoadState->LoadType();
571   if (loadType == LOAD_HISTORY || loadType == LOAD_RELOAD_NORMAL ||
572       loadType == LOAD_RELOAD_CHARSET_CHANGE ||
573       loadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE ||
574       loadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE) {
575     LOG(
576         ("DocumentLoadListener::OpenFromParent failed because of history "
577          "load"));
578     return false;
579   }
580 
581   // Clone because this mutates the load flags in the load state, which
582   // breaks nsDocShells expectations of being able to do it.
583   RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(*aLoadState);
584   loadState->CalculateLoadURIFlags();
585 
586   RefPtr<nsDOMNavigationTiming> timing = new nsDOMNavigationTiming(nullptr);
587   timing->NotifyNavigationStart(
588       aBrowsingContext->GetIsActive()
589           ? nsDOMNavigationTiming::DocShellState::eActive
590           : nsDOMNavigationTiming::DocShellState::eInactive);
591 
592   // We're not a history load or a reload, so we don't need this.
593   uint32_t cacheKey = 0;
594 
595   // Loads start in the content process might have exposed a channel id to
596   // observers, so we need to preserve the value in the parent. That can't have
597   // happened here, so Nothing() is fine.
598   Maybe<uint64_t> channelId = Nothing();
599 
600   // Initial client info is only relevant for subdocument loads, which we're
601   // not supporting yet.
602   Maybe<dom::ClientInfo> initialClientInfo;
603 
604   RefPtr<DocumentLoadListener> listener = new DocumentLoadListener(
605       aBrowsingContext, aBrowsingContext->GetContentParent()->OtherPid());
606 
607   nsresult rv;
608   bool result =
609       listener->Open(loadState, cacheKey, channelId, TimeStamp::Now(), timing,
610                      std::move(initialClientInfo), aOuterWindowId, false,
611                      Nothing(), Nothing(), &rv);
612   if (result) {
613     // Create an entry in the redirect channel registrar to
614     // allocate an identifier for this load.
615     nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
616         RedirectChannelRegistrar::GetOrCreate();
617     rv = registrar->RegisterChannel(nullptr, aOutIdent);
618     MOZ_ASSERT(NS_SUCCEEDED(rv));
619     // Register listener (as an nsIParentChannel) under our new identifier.
620     rv = registrar->LinkChannels(*aOutIdent, listener, nullptr);
621     MOZ_ASSERT(NS_SUCCEEDED(rv));
622   }
623   return result;
624 }
625 
CleanupParentLoadAttempt(uint32_t aLoadIdent)626 void DocumentLoadListener::CleanupParentLoadAttempt(uint32_t aLoadIdent) {
627   nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
628       RedirectChannelRegistrar::GetOrCreate();
629 
630   nsCOMPtr<nsIParentChannel> parentChannel;
631   registrar->GetParentChannel(aLoadIdent, getter_AddRefs(parentChannel));
632   RefPtr<DocumentLoadListener> loadListener = do_QueryObject(parentChannel);
633 
634   if (loadListener) {
635     // If the load listener is still registered, then we must have failed
636     // to connect DocumentChannel into it. Better cancel it!
637     loadListener->NotifyBridgeFailed();
638   }
639 
640   registrar->DeregisterChannels(aLoadIdent);
641 }
642 
ClaimParentLoad(uint32_t aLoadIdent,ADocumentChannelBridge * aBridge)643 already_AddRefed<DocumentLoadListener> DocumentLoadListener::ClaimParentLoad(
644     uint32_t aLoadIdent, ADocumentChannelBridge* aBridge) {
645   nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
646       RedirectChannelRegistrar::GetOrCreate();
647 
648   nsCOMPtr<nsIParentChannel> parentChannel;
649   registrar->GetParentChannel(aLoadIdent, getter_AddRefs(parentChannel));
650   RefPtr<DocumentLoadListener> loadListener = do_QueryObject(parentChannel);
651   registrar->DeregisterChannels(aLoadIdent);
652 
653   MOZ_ASSERT(loadListener);
654   if (loadListener) {
655     loadListener->NotifyBridgeConnected(aBridge);
656   }
657   return loadListener.forget();
658 }
659 
NotifyBridgeConnected(ADocumentChannelBridge * aBridge)660 void DocumentLoadListener::NotifyBridgeConnected(
661     ADocumentChannelBridge* aBridge) {
662   LOG(("DocumentLoadListener NotifyBridgeConnected [this=%p]", this));
663   MOZ_ASSERT(!mDocumentChannelBridge);
664   MOZ_ASSERT(mPendingDocumentChannelBridgeProcess);
665   MOZ_ASSERT(aBridge->OtherPid() == *mPendingDocumentChannelBridgeProcess);
666 
667   mDocumentChannelBridge = aBridge;
668   mPendingDocumentChannelBridgeProcess.reset();
669   mBridgePromise.ResolveIfExists(aBridge, __func__);
670 }
671 
NotifyBridgeFailed()672 void DocumentLoadListener::NotifyBridgeFailed() {
673   LOG(("DocumentLoadListener NotifyBridgeFailed [this=%p]", this));
674   MOZ_ASSERT(!mDocumentChannelBridge);
675   MOZ_ASSERT(mPendingDocumentChannelBridgeProcess);
676   mPendingDocumentChannelBridgeProcess.reset();
677 
678   Cancel(NS_BINDING_ABORTED);
679 
680   mBridgePromise.RejectIfExists(false, __func__);
681 }
682 
DocumentChannelBridgeDisconnected()683 void DocumentLoadListener::DocumentChannelBridgeDisconnected() {
684   LOG(("DocumentLoadListener DocumentChannelBridgeDisconnected [this=%p]",
685        this));
686   // The nsHttpChannel may have a reference to this parent, release it
687   // to avoid circular references.
688   RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
689   if (httpChannelImpl) {
690     httpChannelImpl->SetWarningReporter(nullptr);
691   }
692   mDocumentChannelBridge = nullptr;
693 
694   if (auto* ctx = GetBrowsingContext()) {
695     ctx->EndDocumentLoad(this);
696   }
697 }
698 
Cancel(const nsresult & aStatusCode)699 void DocumentLoadListener::Cancel(const nsresult& aStatusCode) {
700   LOG(
701       ("DocumentLoadListener Cancel [this=%p, "
702        "aStatusCode=%" PRIx32 " ]",
703        this, static_cast<uint32_t>(aStatusCode)));
704   mCancelled = true;
705 
706   if (mDoingProcessSwitch) {
707     // If we've already initiated process-switching
708     // then we can no longer be cancelled and we'll
709     // disconnect the old listeners when done.
710     return;
711   }
712 
713   if (mChannel) {
714     mChannel->Cancel(aStatusCode);
715   }
716 
717   DisconnectChildListeners(aStatusCode, aStatusCode);
718 }
719 
DisconnectChildListeners(nsresult aStatus,nsresult aLoadGroupStatus)720 void DocumentLoadListener::DisconnectChildListeners(nsresult aStatus,
721                                                     nsresult aLoadGroupStatus) {
722   LOG(
723       ("DocumentLoadListener DisconnectChildListener [this=%p, "
724        "aStatus=%" PRIx32 " aLoadGroupStatus=%" PRIx32 " ]",
725        this, static_cast<uint32_t>(aStatus),
726        static_cast<uint32_t>(aLoadGroupStatus)));
727   RefPtr<DocumentLoadListener> keepAlive(this);
728   if (mDocumentChannelBridge) {
729     // This will drop the bridge's reference to us, so we use keepAlive to
730     // make sure we don't get deleted until we exit the function.
731     mDocumentChannelBridge->DisconnectChildListeners(aStatus, aLoadGroupStatus);
732   } else if (mPendingDocumentChannelBridgeProcess) {
733     EnsureBridge()->Then(
734         GetCurrentThreadSerialEventTarget(), __func__,
735         [keepAlive, aStatus,
736          aLoadGroupStatus](ADocumentChannelBridge* aBridge) {
737           aBridge->DisconnectChildListeners(aStatus, aLoadGroupStatus);
738           keepAlive->mDocumentChannelBridge = nullptr;
739         },
740         [](bool aDummy) {});
741   }
742   DocumentChannelBridgeDisconnected();
743 
744   // If we're not going to send anything else to the content process, and
745   // we haven't yet consumed a stream filter promise, then we're never going
746   // to.
747   // TODO: This might be because we retargeted the stream to the download
748   // handler or similar. Do we need to attach a stream filter to that?
749   mStreamFilterRequests.Clear();
750 }
751 
RedirectToRealChannelFinished(nsresult aRv)752 void DocumentLoadListener::RedirectToRealChannelFinished(nsresult aRv) {
753   LOG(
754       ("DocumentLoadListener RedirectToRealChannelFinished [this=%p, "
755        "aRv=%" PRIx32 " ]",
756        this, static_cast<uint32_t>(aRv)));
757   if (NS_FAILED(aRv) || !mRedirectChannelId) {
758     FinishReplacementChannelSetup(aRv);
759     return;
760   }
761 
762   // Wait for background channel ready on target channel
763   nsCOMPtr<nsIRedirectChannelRegistrar> redirectReg =
764       RedirectChannelRegistrar::GetOrCreate();
765   MOZ_ASSERT(redirectReg);
766 
767   nsCOMPtr<nsIParentChannel> redirectParentChannel;
768   redirectReg->GetParentChannel(mRedirectChannelId,
769                                 getter_AddRefs(redirectParentChannel));
770   if (!redirectParentChannel) {
771     FinishReplacementChannelSetup(NS_ERROR_FAILURE);
772     return;
773   }
774 
775   nsCOMPtr<nsIParentRedirectingChannel> redirectingParent =
776       do_QueryInterface(redirectParentChannel);
777   if (!redirectingParent) {
778     // Continue verification procedure if redirecting to non-Http protocol
779     FinishReplacementChannelSetup(NS_OK);
780     return;
781   }
782 
783   // Ask redirected channel if verification can proceed.
784   // ReadyToVerify will be invoked when redirected channel is ready.
785   redirectingParent->ContinueVerification(this);
786 }
787 
788 NS_IMETHODIMP
ReadyToVerify(nsresult aResultCode)789 DocumentLoadListener::ReadyToVerify(nsresult aResultCode) {
790   FinishReplacementChannelSetup(aResultCode);
791   return NS_OK;
792 }
793 
FinishReplacementChannelSetup(nsresult aResult)794 void DocumentLoadListener::FinishReplacementChannelSetup(nsresult aResult) {
795   LOG(
796       ("DocumentLoadListener FinishReplacementChannelSetup [this=%p, "
797        "aResult=%x]",
798        this, int(aResult)));
799 
800   if (mDoingProcessSwitch) {
801     DisconnectChildListeners(NS_BINDING_ABORTED, NS_BINDING_ABORTED);
802   }
803 
804   if (!mRedirectChannelId) {
805     if (NS_FAILED(aResult)) {
806       mChannel->Cancel(aResult);
807       mChannel->Resume();
808       DisconnectChildListeners(aResult, aResult);
809       return;
810     }
811     ApplyPendingFunctions(mChannel);
812     // ResumeSuspendedChannel will be called later as RedirectToRealChannel
813     // continues, so we can return early.
814     return;
815   }
816 
817   nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
818       RedirectChannelRegistrar::GetOrCreate();
819   MOZ_ASSERT(registrar);
820 
821   nsCOMPtr<nsIParentChannel> redirectChannel;
822   nsresult rv = registrar->GetParentChannel(mRedirectChannelId,
823                                             getter_AddRefs(redirectChannel));
824   if (NS_FAILED(rv) || !redirectChannel) {
825     // Redirect might get canceled before we got AsyncOnChannelRedirect
826     nsCOMPtr<nsIChannel> newChannel;
827     rv = registrar->GetRegisteredChannel(mRedirectChannelId,
828                                          getter_AddRefs(newChannel));
829     MOZ_ASSERT(newChannel, "Already registered channel not found");
830 
831     if (NS_SUCCEEDED(rv)) {
832       newChannel->Cancel(NS_ERROR_FAILURE);
833     }
834     if (!redirectChannel) {
835       aResult = NS_ERROR_FAILURE;
836     }
837   }
838 
839   // Release all previously registered channels, they are no longer needed to
840   // be kept in the registrar from this moment.
841   registrar->DeregisterChannels(mRedirectChannelId);
842   mRedirectChannelId = 0;
843   if (NS_FAILED(aResult)) {
844     if (redirectChannel) {
845       redirectChannel->Delete();
846     }
847     mChannel->Cancel(aResult);
848     mChannel->Resume();
849     if (auto* ctx = GetBrowsingContext()) {
850       ctx->EndDocumentLoad(this);
851     }
852     return;
853   }
854 
855   MOZ_ASSERT(
856       !SameCOMIdentity(redirectChannel, static_cast<nsIParentChannel*>(this)));
857 
858   Delete();
859   redirectChannel->SetParentListener(mParentChannelListener);
860 
861   ApplyPendingFunctions(redirectChannel);
862 
863   ResumeSuspendedChannel(redirectChannel);
864 }
865 
ApplyPendingFunctions(nsISupports * aChannel) const866 void DocumentLoadListener::ApplyPendingFunctions(nsISupports* aChannel) const {
867   // We stored the values from all nsIParentChannel functions called since we
868   // couldn't handle them. Copy them across to the real channel since it
869   // should know what to do.
870 
871   nsCOMPtr<nsIParentChannel> parentChannel = do_QueryInterface(aChannel);
872   if (parentChannel) {
873     for (auto& variant : mIParentChannelFunctions) {
874       variant.match(
875           [parentChannel](const nsIHttpChannel::FlashPluginState& aState) {
876             parentChannel->NotifyFlashPluginStateChanged(aState);
877           },
878           [parentChannel](const ClassifierMatchedInfoParams& aParams) {
879             parentChannel->SetClassifierMatchedInfo(
880                 aParams.mList, aParams.mProvider, aParams.mFullHash);
881           },
882           [parentChannel](const ClassifierMatchedTrackingInfoParams& aParams) {
883             parentChannel->SetClassifierMatchedTrackingInfo(
884                 aParams.mLists, aParams.mFullHashes);
885           },
886           [parentChannel](const ClassificationFlagsParams& aParams) {
887             parentChannel->NotifyClassificationFlags(
888                 aParams.mClassificationFlags, aParams.mIsThirdParty);
889           });
890     }
891   } else {
892     for (auto& variant : mIParentChannelFunctions) {
893       variant.match(
894           [&](const nsIHttpChannel::FlashPluginState& aState) {
895             // For now, only HttpChannel use this attribute.
896             RefPtr<HttpBaseChannel> httpChannel = do_QueryObject(aChannel);
897             if (httpChannel) {
898               httpChannel->SetFlashPluginState(aState);
899             }
900           },
901           [&](const ClassifierMatchedInfoParams& aParams) {
902             nsCOMPtr<nsIClassifiedChannel> classifiedChannel =
903                 do_QueryInterface(aChannel);
904             if (classifiedChannel) {
905               classifiedChannel->SetMatchedInfo(
906                   aParams.mList, aParams.mProvider, aParams.mFullHash);
907             }
908           },
909           [&](const ClassifierMatchedTrackingInfoParams& aParams) {
910             nsCOMPtr<nsIClassifiedChannel> classifiedChannel =
911                 do_QueryInterface(aChannel);
912             if (classifiedChannel) {
913               nsTArray<nsCString> lists, fullhashes;
914               for (const nsACString& token : aParams.mLists.Split(',')) {
915                 lists.AppendElement(token);
916               }
917               for (const nsACString& token : aParams.mFullHashes.Split(',')) {
918                 fullhashes.AppendElement(token);
919               }
920               classifiedChannel->SetMatchedTrackingInfo(lists, fullhashes);
921             }
922           },
923           [&](const ClassificationFlagsParams& aParams) {
924             UrlClassifierCommon::SetClassificationFlagsHelper(
925                 static_cast<nsIChannel*>(aChannel),
926                 aParams.mClassificationFlags, aParams.mIsThirdParty);
927           });
928     }
929   }
930 
931   RefPtr<HttpChannelSecurityWarningReporter> reporter;
932   if (RefPtr<HttpChannelParent> httpParent = do_QueryObject(aChannel)) {
933     reporter = httpParent;
934   } else if (RefPtr<nsHttpChannel> httpChannel = do_QueryObject(aChannel)) {
935     reporter = httpChannel->GetWarningReporter();
936   }
937   if (reporter) {
938     for (auto& variant : mSecurityWarningFunctions) {
939       variant.match(
940           [reporter](const ReportSecurityMessageParams& aParams) {
941             Unused << reporter->ReportSecurityMessage(aParams.mMessageTag,
942                                                       aParams.mMessageCategory);
943           },
944           [reporter](const LogBlockedCORSRequestParams& aParams) {
945             Unused << reporter->LogBlockedCORSRequest(aParams.mMessage,
946                                                       aParams.mCategory);
947           },
948           [reporter](const LogMimeTypeMismatchParams& aParams) {
949             Unused << reporter->LogMimeTypeMismatch(
950                 aParams.mMessageName, aParams.mWarning, aParams.mURL,
951                 aParams.mContentType);
952           });
953     }
954   }
955 }
956 
ResumeSuspendedChannel(nsIStreamListener * aListener)957 bool DocumentLoadListener::ResumeSuspendedChannel(
958     nsIStreamListener* aListener) {
959   LOG(("DocumentLoadListener ResumeSuspendedChannel [this=%p]", this));
960   RefPtr<nsHttpChannel> httpChannel = do_QueryObject(mChannel);
961   if (httpChannel) {
962     httpChannel->SetApplyConversion(mOldApplyConversion);
963   }
964 
965   if (!mIsFinished) {
966     mParentChannelListener->SetListenerAfterRedirect(aListener);
967   }
968 
969   // If we failed to suspend the channel, then we might have received
970   // some messages while the redirected was being handled.
971   // Manually send them on now.
972   nsTArray<StreamListenerFunction> streamListenerFunctions =
973       std::move(mStreamListenerFunctions);
974   if (!aListener) {
975     streamListenerFunctions.Clear();
976   }
977   nsresult rv = NS_OK;
978   for (auto& variant : streamListenerFunctions) {
979     variant.match(
980         [&](const OnStartRequestParams& aParams) {
981           rv = aListener->OnStartRequest(aParams.request);
982           if (NS_FAILED(rv)) {
983             aParams.request->Cancel(rv);
984           }
985         },
986         [&](const OnDataAvailableParams& aParams) {
987           // Don't deliver OnDataAvailable if we've
988           // already failed.
989           if (NS_FAILED(rv)) {
990             return;
991           }
992           nsCOMPtr<nsIInputStream> stringStream;
993           rv = NS_NewByteInputStream(
994               getter_AddRefs(stringStream),
995               Span<const char>(aParams.data.get(), aParams.count),
996               NS_ASSIGNMENT_DEPEND);
997           if (NS_SUCCEEDED(rv)) {
998             rv = aListener->OnDataAvailable(aParams.request, stringStream,
999                                             aParams.offset, aParams.count);
1000           }
1001           if (NS_FAILED(rv)) {
1002             aParams.request->Cancel(rv);
1003           }
1004         },
1005         [&](const OnStopRequestParams& aParams) {
1006           if (NS_SUCCEEDED(rv)) {
1007             aListener->OnStopRequest(aParams.request, aParams.status);
1008           } else {
1009             aListener->OnStopRequest(aParams.request, rv);
1010           }
1011           rv = NS_OK;
1012         },
1013         [&](const OnAfterLastPartParams& aParams) {
1014           nsCOMPtr<nsIMultiPartChannelListener> multiListener =
1015               do_QueryInterface(aListener);
1016           if (multiListener) {
1017             if (NS_SUCCEEDED(rv)) {
1018               multiListener->OnAfterLastPart(aParams.status);
1019             } else {
1020               multiListener->OnAfterLastPart(rv);
1021             }
1022           }
1023         });
1024   }
1025   // We don't expect to get new stream listener functions added
1026   // via re-entrancy. If this ever happens, we should understand
1027   // exactly why before allowing it.
1028   NS_ASSERTION(mStreamListenerFunctions.IsEmpty(),
1029                "Should not have added new stream listener function!");
1030 
1031   mChannel->Resume();
1032 
1033   if (auto* ctx = GetBrowsingContext()) {
1034     ctx->EndDocumentLoad(this);
1035   }
1036 
1037   return !mIsFinished;
1038 }
1039 
SerializeRedirectData(RedirectToRealChannelArgs & aArgs,bool aIsCrossProcess,uint32_t aRedirectFlags,uint32_t aLoadFlags,ContentParent * aParent) const1040 void DocumentLoadListener::SerializeRedirectData(
1041     RedirectToRealChannelArgs& aArgs, bool aIsCrossProcess,
1042     uint32_t aRedirectFlags, uint32_t aLoadFlags,
1043     ContentParent* aParent) const {
1044   // Use the original URI of the current channel, as this is what
1045   // we'll use to construct the channel in the content process.
1046   aArgs.uri() = mChannelCreationURI;
1047   if (!aArgs.uri()) {
1048     mChannel->GetOriginalURI(getter_AddRefs(aArgs.uri()));
1049   }
1050 
1051   // I previously used HttpBaseChannel::CloneLoadInfoForRedirect, but that
1052   // clears the principal to inherit, which fails tests (probably because this
1053   // 'redirect' is usually just an implementation detail). It's also http
1054   // only, and mChannel can be anything that we redirected to.
1055   nsCOMPtr<nsILoadInfo> channelLoadInfo;
1056   mChannel->GetLoadInfo(getter_AddRefs(channelLoadInfo));
1057 
1058   nsCOMPtr<nsIPrincipal> principalToInherit;
1059   channelLoadInfo->GetPrincipalToInherit(getter_AddRefs(principalToInherit));
1060 
1061   const RefPtr<nsHttpChannel> baseChannel = do_QueryObject(mChannel.get());
1062 
1063   nsCOMPtr<nsILoadContext> loadContext;
1064   NS_QueryNotificationCallbacks(mChannel, loadContext);
1065   nsCOMPtr<nsILoadInfo> redirectLoadInfo;
1066 
1067   // Only use CloneLoadInfoForRedirect if we have a load context,
1068   // since it internally tries to pull OriginAttributes from the
1069   // the load context and asserts if they don't match the load info.
1070   // We can end up without a load context if the channel has been aborted
1071   // and the callbacks have been cleared.
1072   if (baseChannel && loadContext) {
1073     redirectLoadInfo = baseChannel->CloneLoadInfoForRedirect(
1074         aArgs.uri(), nsIChannelEventSink::REDIRECT_INTERNAL);
1075     redirectLoadInfo->SetResultPrincipalURI(aArgs.uri());
1076 
1077     // The clone process clears this, and then we fail tests..
1078     // docshell/test/mochitest/test_forceinheritprincipal_overrule_owner.html
1079     if (principalToInherit) {
1080       redirectLoadInfo->SetPrincipalToInherit(principalToInherit);
1081     }
1082   } else {
1083     redirectLoadInfo =
1084         static_cast<mozilla::net::LoadInfo*>(channelLoadInfo.get())->Clone();
1085 
1086     nsCOMPtr<nsIPrincipal> uriPrincipal;
1087     nsIScriptSecurityManager* sm = nsContentUtils::GetSecurityManager();
1088     sm->GetChannelURIPrincipal(mChannel, getter_AddRefs(uriPrincipal));
1089 
1090     nsCOMPtr<nsIRedirectHistoryEntry> entry =
1091         new nsRedirectHistoryEntry(uriPrincipal, nullptr, EmptyCString());
1092 
1093     redirectLoadInfo->AppendRedirectHistoryEntry(entry, true);
1094   }
1095 
1096   const Maybe<ClientInfo>& reservedClientInfo =
1097       channelLoadInfo->GetReservedClientInfo();
1098   if (reservedClientInfo) {
1099     redirectLoadInfo->SetReservedClientInfo(*reservedClientInfo);
1100   }
1101 
1102   aArgs.registrarId() = mRedirectChannelId;
1103 
1104   MOZ_ALWAYS_SUCCEEDS(
1105       ipc::LoadInfoToLoadInfoArgs(redirectLoadInfo, &aArgs.loadInfo()));
1106 
1107   mChannel->GetOriginalURI(getter_AddRefs(aArgs.originalURI()));
1108 
1109   // mChannel can be a nsHttpChannel as well as InterceptedHttpChannel so we
1110   // can't use baseChannel here.
1111   if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel)) {
1112     MOZ_ALWAYS_SUCCEEDS(httpChannel->GetChannelId(&aArgs.channelId()));
1113   }
1114 
1115   aArgs.redirectMode() = nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW;
1116   nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
1117       do_QueryInterface(mChannel);
1118   if (httpChannelInternal) {
1119     MOZ_ALWAYS_SUCCEEDS(
1120         httpChannelInternal->GetRedirectMode(&aArgs.redirectMode()));
1121   }
1122 
1123   if (baseChannel) {
1124     aArgs.init() =
1125         Some(baseChannel
1126                  ->CloneReplacementChannelConfig(
1127                      true, aRedirectFlags,
1128                      HttpBaseChannel::ReplacementReason::DocumentChannel)
1129                  .Serialize(aParent));
1130   }
1131 
1132   uint32_t contentDispositionTemp;
1133   nsresult rv = mChannel->GetContentDisposition(&contentDispositionTemp);
1134   if (NS_SUCCEEDED(rv)) {
1135     aArgs.contentDisposition() = Some(contentDispositionTemp);
1136   }
1137 
1138   nsString contentDispositionFilenameTemp;
1139   rv = mChannel->GetContentDispositionFilename(contentDispositionFilenameTemp);
1140   if (NS_SUCCEEDED(rv)) {
1141     aArgs.contentDispositionFilename() = Some(contentDispositionFilenameTemp);
1142   }
1143 
1144   SetNeedToAddURIVisit(mChannel, false);
1145 
1146   aArgs.newLoadFlags() = aLoadFlags;
1147   aArgs.redirectFlags() = aRedirectFlags;
1148   aArgs.redirectIdentifier() = mCrossProcessRedirectIdentifier;
1149   aArgs.properties() = do_QueryObject(mChannel.get());
1150   aArgs.srcdocData() = mSrcdocData;
1151   aArgs.baseUri() = mBaseURI;
1152   aArgs.loadStateLoadFlags() = mLoadStateLoadFlags;
1153   aArgs.loadStateLoadType() = mLoadStateLoadType;
1154   if (mSessionHistoryInfo) {
1155     aArgs.sessionHistoryInfo().emplace(
1156         mSessionHistoryInfo->mId, MakeUnique<mozilla::dom::SessionHistoryInfo>(
1157                                       *mSessionHistoryInfo->mInfo));
1158   }
1159 }
1160 
MaybeTriggerProcessSwitch(bool * aWillSwitchToRemote)1161 bool DocumentLoadListener::MaybeTriggerProcessSwitch(
1162     bool* aWillSwitchToRemote) {
1163   MOZ_ASSERT(XRE_IsParentProcess());
1164   MOZ_DIAGNOSTIC_ASSERT(!mDoingProcessSwitch,
1165                         "Already in the middle of switching?");
1166   MOZ_DIAGNOSTIC_ASSERT(mChannel);
1167   MOZ_DIAGNOSTIC_ASSERT(mParentChannelListener);
1168   MOZ_DIAGNOSTIC_ASSERT(aWillSwitchToRemote);
1169 
1170   LOG(("DocumentLoadListener MaybeTriggerProcessSwitch [this=%p]", this));
1171 
1172   // Get the BrowsingContext which will be switching processes.
1173   RefPtr<CanonicalBrowsingContext> browsingContext =
1174       mParentChannelListener->GetBrowsingContext();
1175   if (NS_WARN_IF(!browsingContext)) {
1176     LOG(("Process Switch Abort: no browsing context"));
1177     return false;
1178   }
1179   if (!browsingContext->IsContent()) {
1180     LOG(("Process Switch Abort: non-content browsing context"));
1181     return false;
1182   }
1183   if (browsingContext->GetParent() && !browsingContext->UseRemoteSubframes()) {
1184     LOG(("Process Switch Abort: remote subframes disabled"));
1185     return false;
1186   }
1187 
1188   if (browsingContext->GetParentWindowContext() &&
1189       browsingContext->GetParentWindowContext()->IsInProcess()) {
1190     LOG(("Process Switch Abort: Subframe with in-process parent"));
1191     return false;
1192   }
1193 
1194   // We currently can't switch processes for toplevel loads unless they're
1195   // loaded within a browser tab.
1196   // FIXME: Ideally we won't do this in the future.
1197   nsCOMPtr<nsIBrowser> browser;
1198   bool isPreloadSwitch = false;
1199   if (!browsingContext->GetParent()) {
1200     Element* browserElement = browsingContext->GetEmbedderElement();
1201     if (!browserElement) {
1202       LOG(("Process Switch Abort: cannot get browser element"));
1203       return false;
1204     }
1205     browser = browserElement->AsBrowser();
1206     if (!browser) {
1207       LOG(("Process Switch Abort: not loaded within nsIBrowser"));
1208       return false;
1209     }
1210     bool loadedInTab = false;
1211     if (NS_FAILED(browser->GetCanPerformProcessSwitch(&loadedInTab)) ||
1212         !loadedInTab) {
1213       LOG(("Process Switch Abort: browser is not loaded in a tab"));
1214       return false;
1215     }
1216 
1217     // Leaving about:newtab from a used to be preloaded browser should run the
1218     // process selecting algorithm again.
1219     nsAutoString isPreloadBrowserStr;
1220     if (browserElement->GetAttr(kNameSpaceID_None, nsGkAtoms::preloadedState,
1221                                 isPreloadBrowserStr)) {
1222       if (isPreloadBrowserStr.EqualsLiteral("consumed")) {
1223         nsCOMPtr<nsIURI> originalURI;
1224         if (NS_SUCCEEDED(
1225                 mChannel->GetOriginalURI(getter_AddRefs(originalURI)))) {
1226           if (!originalURI->GetSpecOrDefault().EqualsLiteral("about:newtab")) {
1227             LOG(("Process Switch: leaving preloaded browser"));
1228             isPreloadSwitch = true;
1229             browserElement->UnsetAttr(kNameSpaceID_None,
1230                                       nsGkAtoms::preloadedState, true);
1231           }
1232         }
1233       }
1234     }
1235   }
1236 
1237   // Get information about the current document loaded in our BrowsingContext.
1238   nsCOMPtr<nsIPrincipal> currentPrincipal;
1239   if (RefPtr<WindowGlobalParent> wgp =
1240           browsingContext->GetCurrentWindowGlobal()) {
1241     currentPrincipal = wgp->DocumentPrincipal();
1242   }
1243   RefPtr<ContentParent> contentParent = browsingContext->GetContentParent();
1244   MOZ_ASSERT(!OtherPid() || contentParent,
1245              "Only PPDC is allowed to not have an existing ContentParent");
1246 
1247   // Get the final principal, used to select which process to load into.
1248   nsCOMPtr<nsIPrincipal> resultPrincipal;
1249   nsresult rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
1250       mChannel, getter_AddRefs(resultPrincipal));
1251   if (NS_FAILED(rv)) {
1252     LOG(("Process Switch Abort: failed to get channel result principal"));
1253     return false;
1254   }
1255 
1256   // Determine our COOP status, which will be used to determine our preferred
1257   // remote type.
1258   bool isCOOPSwitch = HasCrossOriginOpenerPolicyMismatch();
1259   nsILoadInfo::CrossOriginOpenerPolicy coop =
1260       nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
1261   if (!browsingContext->IsTop()) {
1262     coop = browsingContext->Top()->GetOpenerPolicy();
1263   } else if (nsCOMPtr<nsIHttpChannelInternal> httpChannel =
1264                  do_QueryInterface(mChannel)) {
1265     MOZ_ALWAYS_SUCCEEDS(httpChannel->GetCrossOriginOpenerPolicy(&coop));
1266   }
1267 
1268   nsAutoString currentRemoteType;
1269   if (contentParent) {
1270     currentRemoteType = contentParent->GetRemoteType();
1271   } else {
1272     currentRemoteType = VoidString();
1273   }
1274   nsAutoString preferredRemoteType = currentRemoteType;
1275   if (coop ==
1276       nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP) {
1277     // We want documents with SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP COOP
1278     // policy to be loaded in a separate process in which we can enable
1279     // high-resolution timers.
1280     nsAutoCString siteOrigin;
1281     resultPrincipal->GetSiteOrigin(siteOrigin);
1282     preferredRemoteType.Assign(
1283         NS_LITERAL_STRING(WITH_COOP_COEP_REMOTE_TYPE_PREFIX));
1284     preferredRemoteType.Append(NS_ConvertUTF8toUTF16(siteOrigin));
1285   } else if (isCOOPSwitch) {
1286     // If we're doing a COOP switch, we do not need any affinity to the current
1287     // remote type. Clear it back to the default value.
1288     preferredRemoteType.Assign(NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE));
1289   }
1290   MOZ_DIAGNOSTIC_ASSERT(!contentParent || !preferredRemoteType.IsEmpty(),
1291                         "Unexpected empty remote type!");
1292 
1293   LOG(
1294       ("DocumentLoadListener GetRemoteTypeForPrincipal "
1295        "[this=%p, contentParent=%s, preferredRemoteType=%s]",
1296        this, NS_ConvertUTF16toUTF8(currentRemoteType).get(),
1297        NS_ConvertUTF16toUTF8(preferredRemoteType).get()));
1298 
1299   nsCOMPtr<nsIE10SUtils> e10sUtils =
1300       do_ImportModule("resource://gre/modules/E10SUtils.jsm", "E10SUtils");
1301   if (!e10sUtils) {
1302     LOG(("Process Switch Abort: Could not import E10SUtils"));
1303     return false;
1304   }
1305 
1306   nsAutoString remoteType;
1307   rv = e10sUtils->GetRemoteTypeForPrincipal(
1308       resultPrincipal, mChannelCreationURI, browsingContext->UseRemoteTabs(),
1309       browsingContext->UseRemoteSubframes(), preferredRemoteType,
1310       currentPrincipal, browsingContext->GetParent(), remoteType);
1311   if (NS_WARN_IF(NS_FAILED(rv))) {
1312     LOG(("Process Switch Abort: getRemoteTypeForPrincipal threw an exception"));
1313     return false;
1314   }
1315 
1316   LOG(("GetRemoteTypeForPrincipal -> current:%s remoteType:%s",
1317        NS_ConvertUTF16toUTF8(currentRemoteType).get(),
1318        NS_ConvertUTF16toUTF8(remoteType).get()));
1319 
1320   // Check if a process switch is needed.
1321   if (currentRemoteType == remoteType && !isCOOPSwitch && !isPreloadSwitch) {
1322     LOG(("Process Switch Abort: type (%s) is compatible",
1323          NS_ConvertUTF16toUTF8(remoteType).get()));
1324     return false;
1325   }
1326   if (NS_WARN_IF(remoteType.IsEmpty())) {
1327     LOG(("Process Switch Abort: non-remote target process"));
1328     return false;
1329   }
1330 
1331   *aWillSwitchToRemote = !remoteType.IsEmpty();
1332 
1333   LOG(("Process Switch: Changing Remoteness from '%s' to '%s'",
1334        NS_ConvertUTF16toUTF8(currentRemoteType).get(),
1335        NS_ConvertUTF16toUTF8(remoteType).get()));
1336 
1337   // XXX: This is super hacky, and we should be able to do something better.
1338   static uint64_t sNextCrossProcessRedirectIdentifier = 0;
1339   mCrossProcessRedirectIdentifier = ++sNextCrossProcessRedirectIdentifier;
1340   mDoingProcessSwitch = true;
1341 
1342   RefPtr<DocumentLoadListener> self = this;
1343   // At this point, we're actually going to perform a process switch, which
1344   // involves calling into other logic.
1345   if (browsingContext->GetParent()) {
1346     LOG(("Process Switch: Calling ChangeFrameRemoteness"));
1347     // If we're switching a subframe, ask BrowsingContext to do it for us.
1348     MOZ_ASSERT(!isCOOPSwitch);
1349     browsingContext
1350         ->ChangeFrameRemoteness(remoteType, mCrossProcessRedirectIdentifier)
1351         ->Then(
1352             GetMainThreadSerialEventTarget(), __func__,
1353             [self](BrowserParent* aBrowserParent) {
1354               MOZ_ASSERT(self->mChannel,
1355                          "Something went wrong, channel got cancelled");
1356               self->TriggerRedirectToRealChannel(
1357                   Some(aBrowserParent->Manager()->ChildID()));
1358             },
1359             [self](nsresult aStatusCode) {
1360               MOZ_ASSERT(NS_FAILED(aStatusCode), "Status should be error");
1361               self->RedirectToRealChannelFinished(aStatusCode);
1362             });
1363     return true;
1364   }
1365 
1366   LOG(("Process Switch: Calling nsIBrowser::PerformProcessSwitch"));
1367   // We're switching a toplevel BrowsingContext's process. This has to be done
1368   // using nsIBrowser.
1369   RefPtr<Promise> domPromise;
1370   browser->PerformProcessSwitch(remoteType, mCrossProcessRedirectIdentifier,
1371                                 isCOOPSwitch, getter_AddRefs(domPromise));
1372   MOZ_DIAGNOSTIC_ASSERT(domPromise,
1373                         "PerformProcessSwitch didn't return a promise");
1374 
1375   MozPromise<uint64_t, nsresult, true>::FromDomPromise(domPromise)
1376       ->Then(
1377           GetMainThreadSerialEventTarget(), __func__,
1378           [self](uint64_t aCpId) {
1379             MOZ_ASSERT(self->mChannel,
1380                        "Something went wrong, channel got cancelled");
1381             self->TriggerRedirectToRealChannel(Some(aCpId));
1382           },
1383           [self](nsresult aStatusCode) {
1384             MOZ_ASSERT(NS_FAILED(aStatusCode), "Status should be error");
1385             self->RedirectToRealChannelFinished(aStatusCode);
1386           });
1387   return true;
1388 }
1389 
EnsureBridge()1390 auto DocumentLoadListener::EnsureBridge() -> RefPtr<EnsureBridgePromise> {
1391   MOZ_ASSERT(mDocumentChannelBridge || mPendingDocumentChannelBridgeProcess);
1392   if (mDocumentChannelBridge) {
1393     MOZ_ASSERT(mBridgePromise.IsEmpty());
1394     return EnsureBridgePromise::CreateAndResolve(mDocumentChannelBridge,
1395                                                  __func__);
1396   }
1397 
1398   return mBridgePromise.Ensure(__func__);
1399 }
1400 
1401 RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
RedirectToRealChannel(uint32_t aRedirectFlags,uint32_t aLoadFlags,const Maybe<uint64_t> & aDestinationProcess,nsTArray<ParentEndpoint> && aStreamFilterEndpoints)1402 DocumentLoadListener::RedirectToRealChannel(
1403     uint32_t aRedirectFlags, uint32_t aLoadFlags,
1404     const Maybe<uint64_t>& aDestinationProcess,
1405     nsTArray<ParentEndpoint>&& aStreamFilterEndpoints) {
1406   LOG(
1407       ("DocumentLoadListener RedirectToRealChannel [this=%p] "
1408        "aRedirectFlags=%" PRIx32 ", aLoadFlags=%" PRIx32,
1409        this, aRedirectFlags, aLoadFlags));
1410 
1411   // TODO(djg): Add the last URI visit to history if success. Is there a better
1412   // place to handle this? Need access to the updated aLoadFlags.
1413   nsresult status = NS_OK;
1414   mChannel->GetStatus(&status);
1415   bool updateGHistory =
1416       nsDocShell::ShouldUpdateGlobalHistory(mLoadStateLoadType);
1417   if (NS_SUCCEEDED(status) && updateGHistory && !net::ChannelIsPost(mChannel)) {
1418     AddURIVisit(mChannel, aLoadFlags);
1419   }
1420 
1421   if (aDestinationProcess || OtherPid()) {
1422     // Register the new channel and obtain id for it
1423     nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
1424         RedirectChannelRegistrar::GetOrCreate();
1425     MOZ_ASSERT(registrar);
1426     MOZ_ALWAYS_SUCCEEDS(
1427         registrar->RegisterChannel(mChannel, &mRedirectChannelId));
1428   }
1429 
1430   if (aDestinationProcess) {
1431     dom::ContentParent* cp =
1432         dom::ContentProcessManager::GetSingleton()->GetContentProcessById(
1433             ContentParentId{*aDestinationProcess});
1434     if (!cp) {
1435       return PDocumentChannelParent::RedirectToRealChannelPromise::
1436           CreateAndReject(ipc::ResponseRejectReason::SendError, __func__);
1437     }
1438 
1439     RedirectToRealChannelArgs args;
1440     SerializeRedirectData(args, !!aDestinationProcess, aRedirectFlags,
1441                           aLoadFlags, cp);
1442     if (mTiming) {
1443       mTiming->Anonymize(args.uri());
1444       args.timing() = Some(std::move(mTiming));
1445     }
1446 
1447     auto loadInfo = args.loadInfo();
1448 
1449     if (loadInfo.isNothing()) {
1450       return PDocumentChannelParent::RedirectToRealChannelPromise::
1451           CreateAndReject(ipc::ResponseRejectReason::SendError, __func__);
1452     }
1453 
1454     auto triggeringPrincipalOrErr =
1455         PrincipalInfoToPrincipal(loadInfo.ref().triggeringPrincipalInfo());
1456 
1457     if (triggeringPrincipalOrErr.isOk()) {
1458       nsCOMPtr<nsIPrincipal> triggeringPrincipal =
1459           triggeringPrincipalOrErr.unwrap();
1460       cp->TransmitBlobDataIfBlobURL(args.uri(), triggeringPrincipal);
1461     }
1462 
1463     return cp->SendCrossProcessRedirect(args,
1464                                         std::move(aStreamFilterEndpoints));
1465   }
1466 
1467   return EnsureBridge()->Then(
1468       GetCurrentThreadSerialEventTarget(), __func__,
1469       [self = RefPtr<DocumentLoadListener>(this),
1470        endpoints = std::move(aStreamFilterEndpoints), aRedirectFlags,
1471        aLoadFlags](ADocumentChannelBridge* aBridge) mutable {
1472         if (self->mCancelled) {
1473           return PDocumentChannelParent::RedirectToRealChannelPromise::
1474               CreateAndResolve(NS_BINDING_ABORTED, __func__);
1475         }
1476         return aBridge->RedirectToRealChannel(std::move(endpoints),
1477                                               aRedirectFlags, aLoadFlags);
1478       },
1479       [](bool aDummy) {
1480         return PDocumentChannelParent::RedirectToRealChannelPromise::
1481             CreateAndReject(ipc::ResponseRejectReason::ActorDestroyed,
1482                             __func__);
1483       });
1484 }
1485 
TriggerRedirectToRealChannel(const Maybe<uint64_t> & aDestinationProcess)1486 void DocumentLoadListener::TriggerRedirectToRealChannel(
1487     const Maybe<uint64_t>& aDestinationProcess) {
1488   LOG((
1489       "DocumentLoadListener::TriggerRedirectToRealChannel [this=%p] "
1490       "aDestinationProcess=%" PRId64,
1491       this, aDestinationProcess ? int64_t(*aDestinationProcess) : int64_t(-1)));
1492   // This initiates replacing the current DocumentChannel with a
1493   // protocol specific 'real' channel, maybe in a different process than
1494   // the current DocumentChannelChild, if aDestinationProces is set.
1495   // It registers the current mChannel with the registrar to get an ID
1496   // so that the remote end can setup a new IPDL channel and lookup
1497   // the same underlying channel.
1498   // We expect this process to finish with FinishReplacementChannelSetup
1499   // (for both in-process and process switch cases), where we cleanup
1500   // the registrar and copy across any needed state to the replacing
1501   // IPDL parent object.
1502 
1503   nsTArray<ParentEndpoint> parentEndpoints(mStreamFilterRequests.Length());
1504   if (!mStreamFilterRequests.IsEmpty()) {
1505     base::ProcessId pid = OtherPid();
1506     if (aDestinationProcess) {
1507       dom::ContentParent* cp =
1508           dom::ContentProcessManager::GetSingleton()->GetContentProcessById(
1509               ContentParentId(*aDestinationProcess));
1510       if (cp) {
1511         pid = cp->OtherPid();
1512       }
1513     }
1514 
1515     for (StreamFilterRequest& request : mStreamFilterRequests) {
1516       ParentEndpoint parent;
1517       nsresult rv = extensions::PStreamFilter::CreateEndpoints(
1518           pid, request.mChildProcessId, &parent, &request.mChildEndpoint);
1519 
1520       if (NS_FAILED(rv)) {
1521         request.mPromise->Reject(false, __func__);
1522         request.mPromise = nullptr;
1523       } else {
1524         parentEndpoints.AppendElement(std::move(parent));
1525       }
1526     }
1527   }
1528 
1529   // If we didn't have any redirects, then we pass the REDIRECT_INTERNAL flag
1530   // for this channel switch so that it isn't recorded in session history etc.
1531   // If there were redirect(s), then we want this switch to be recorded as a
1532   // real one, since we have a new URI.
1533   uint32_t redirectFlags = 0;
1534   if (!mHaveVisibleRedirect) {
1535     redirectFlags = nsIChannelEventSink::REDIRECT_INTERNAL;
1536   }
1537 
1538   uint32_t newLoadFlags = nsIRequest::LOAD_NORMAL;
1539   MOZ_ALWAYS_SUCCEEDS(mChannel->GetLoadFlags(&newLoadFlags));
1540   // We're pulling our flags from the inner channel, which may not have this
1541   // flag set on it. This is the case when loading a 'view-source' channel.
1542   newLoadFlags |= nsIChannel::LOAD_DOCUMENT_URI;
1543   if (!aDestinationProcess) {
1544     newLoadFlags |= nsIChannel::LOAD_REPLACE;
1545   }
1546 
1547   // INHIBIT_PERSISTENT_CACHING is clearing during http redirects (from
1548   // both parent and content process channel instances), but only ever
1549   // re-added to the parent-side nsHttpChannel.
1550   // To match that behaviour, we want to explicitly avoid copying this flag
1551   // back to our newly created content side channel, otherwise it can
1552   // affect sub-resources loads in the same load group.
1553   nsCOMPtr<nsIURI> uri;
1554   mChannel->GetURI(getter_AddRefs(uri));
1555   if (uri && uri->SchemeIs("https")) {
1556     newLoadFlags &= ~nsIRequest::INHIBIT_PERSISTENT_CACHING;
1557   }
1558 
1559   RefPtr<DocumentLoadListener> self = this;
1560   RedirectToRealChannel(redirectFlags, newLoadFlags, aDestinationProcess,
1561                         std::move(parentEndpoints))
1562       ->Then(
1563           GetCurrentThreadSerialEventTarget(), __func__,
1564           [self, requests = std::move(mStreamFilterRequests)](
1565               const nsresult& aResponse) mutable {
1566             for (StreamFilterRequest& request : requests) {
1567               if (request.mPromise) {
1568                 request.mPromise->Resolve(std::move(request.mChildEndpoint),
1569                                           __func__);
1570                 request.mPromise = nullptr;
1571               }
1572             }
1573             self->RedirectToRealChannelFinished(aResponse);
1574           },
1575           [self](const mozilla::ipc::ResponseRejectReason) {
1576             self->RedirectToRealChannelFinished(NS_ERROR_FAILURE);
1577           });
1578 }
1579 
1580 NS_IMETHODIMP
OnStartRequest(nsIRequest * aRequest)1581 DocumentLoadListener::OnStartRequest(nsIRequest* aRequest) {
1582   LOG(("DocumentLoadListener OnStartRequest [this=%p]", this));
1583   nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest);
1584   if (multiPartChannel) {
1585     multiPartChannel->GetBaseChannel(getter_AddRefs(mChannel));
1586   } else {
1587     mChannel = do_QueryInterface(aRequest);
1588   }
1589   MOZ_DIAGNOSTIC_ASSERT(mChannel);
1590   RefPtr<nsHttpChannel> httpChannel = do_QueryObject(mChannel);
1591 
1592   if (!mDocumentChannelBridge && !mPendingDocumentChannelBridgeProcess) {
1593     return NS_ERROR_UNEXPECTED;
1594   }
1595 
1596   // Enforce CSP frame-ancestors and x-frame-options checks which
1597   // might cancel the channel.
1598   nsContentSecurityUtils::PerformCSPFrameAncestorAndXFOCheck(mChannel);
1599 
1600   // Generally we want to switch to a real channel even if the request failed,
1601   // since the listener might want to access protocol-specific data (like http
1602   // response headers) in its error handling.
1603   // An exception to this is when nsExtProtocolChannel handled the request and
1604   // returned NS_ERROR_NO_CONTENT, since creating a real one in the content
1605   // process will attempt to handle the URI a second time.
1606   nsresult status = NS_OK;
1607   aRequest->GetStatus(&status);
1608   if (status == NS_ERROR_NO_CONTENT) {
1609     DisconnectChildListeners(status, status);
1610     return NS_OK;
1611   }
1612 
1613   mStreamListenerFunctions.AppendElement(StreamListenerFunction{
1614       VariantIndex<0>{}, OnStartRequestParams{aRequest}});
1615 
1616   if (!mInitiatedRedirectToRealChannel) {
1617     mChannel->Suspend();
1618   } else {
1619     // This can be called multiple time if we have a multipart
1620     // decoder. Since we've already added the reqest to
1621     // mStreamListenerFunctions, we don't need to do anything else.
1622     return NS_OK;
1623   }
1624   mInitiatedRedirectToRealChannel = true;
1625 
1626   // Determine if a new process needs to be spawned. If it does, this will
1627   // trigger a cross process switch, and we should hold off on redirecting to
1628   // the real channel.
1629   bool willBeRemote = false;
1630   if (status == NS_BINDING_ABORTED ||
1631       !MaybeTriggerProcessSwitch(&willBeRemote)) {
1632     TriggerRedirectToRealChannel();
1633 
1634     // If we're not switching, then check if we're currently remote.
1635     if (GetBrowsingContext() && GetBrowsingContext()->GetContentParent()) {
1636       willBeRemote = true;
1637     }
1638   }
1639 
1640   // If we're going to be delivering this channel to a remote content
1641   // process, then we want to install any required content conversions
1642   // in the content process.
1643   // The caller of this OnStartRequest will install a conversion
1644   // helper after we return if we haven't disabled conversion. Normally
1645   // HttpChannelParent::OnStartRequest would disable conversion, but we're
1646   // defering calling that until later. Manually disable it now to prevent the
1647   // converter from being installed (since we want the child to do it), and
1648   // also save the value so that when we do call
1649   // HttpChannelParent::OnStartRequest, we can have the value as it originally
1650   // was.
1651   if (httpChannel) {
1652     Unused << httpChannel->GetApplyConversion(&mOldApplyConversion);
1653     if (willBeRemote) {
1654       httpChannel->SetApplyConversion(false);
1655     }
1656   }
1657 
1658   return NS_OK;
1659 }
1660 
1661 NS_IMETHODIMP
OnStopRequest(nsIRequest * aRequest,nsresult aStatusCode)1662 DocumentLoadListener::OnStopRequest(nsIRequest* aRequest,
1663                                     nsresult aStatusCode) {
1664   LOG(("DocumentLoadListener OnStopRequest [this=%p]", this));
1665   mStreamListenerFunctions.AppendElement(StreamListenerFunction{
1666       VariantIndex<2>{}, OnStopRequestParams{aRequest, aStatusCode}});
1667 
1668   // If we're not a multi-part channel, then we're finished and we don't
1669   // expect any further events. If we are, then this might be called again,
1670   // so wait for OnAfterLastPart instead.
1671   nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest);
1672   if (!multiPartChannel) {
1673     mIsFinished = true;
1674   }
1675 
1676   mStreamFilterRequests.Clear();
1677 
1678   return NS_OK;
1679 }
1680 
1681 NS_IMETHODIMP
OnDataAvailable(nsIRequest * aRequest,nsIInputStream * aInputStream,uint64_t aOffset,uint32_t aCount)1682 DocumentLoadListener::OnDataAvailable(nsIRequest* aRequest,
1683                                       nsIInputStream* aInputStream,
1684                                       uint64_t aOffset, uint32_t aCount) {
1685   LOG(("DocumentLoadListener OnDataAvailable [this=%p]", this));
1686   // This isn't supposed to happen, since we suspended the channel, but
1687   // sometimes Suspend just doesn't work. This can happen when we're routing
1688   // through nsUnknownDecoder to sniff the content type, and it doesn't handle
1689   // being suspended. Let's just store the data and manually forward it to our
1690   // redirected channel when it's ready.
1691   nsCString data;
1692   nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount);
1693   NS_ENSURE_SUCCESS(rv, rv);
1694 
1695   mStreamListenerFunctions.AppendElement(StreamListenerFunction{
1696       VariantIndex<1>{},
1697       OnDataAvailableParams{aRequest, data, aOffset, aCount}});
1698 
1699   return NS_OK;
1700 }
1701 
1702 //-----------------------------------------------------------------------------
1703 // DoucmentLoadListener::nsIMultiPartChannelListener
1704 //-----------------------------------------------------------------------------
1705 
1706 NS_IMETHODIMP
OnAfterLastPart(nsresult aStatus)1707 DocumentLoadListener::OnAfterLastPart(nsresult aStatus) {
1708   LOG(("DocumentLoadListener OnAfterLastPart [this=%p]", this));
1709   if (!mInitiatedRedirectToRealChannel) {
1710     // if we get here, and we haven't initiated a redirect to a real
1711     // channel, then it means we never got OnStartRequest (maybe a problem?)
1712     // and we retargeted everything.
1713     LOG(("DocumentLoadListener Disconnecting child"));
1714     DisconnectChildListeners(NS_BINDING_RETARGETED, NS_OK);
1715     return NS_OK;
1716   }
1717   mStreamListenerFunctions.AppendElement(StreamListenerFunction{
1718       VariantIndex<3>{}, OnAfterLastPartParams{aStatus}});
1719   mIsFinished = true;
1720   return NS_OK;
1721 }
1722 
1723 NS_IMETHODIMP
SetParentListener(mozilla::net::ParentChannelListener * listener)1724 DocumentLoadListener::SetParentListener(
1725     mozilla::net::ParentChannelListener* listener) {
1726   // We don't need this (do we?)
1727   return NS_OK;
1728 }
1729 
1730 NS_IMETHODIMP
GetInterface(const nsIID & aIID,void ** result)1731 DocumentLoadListener::GetInterface(const nsIID& aIID, void** result) {
1732   RefPtr<CanonicalBrowsingContext> browsingContext =
1733       mParentChannelListener->GetBrowsingContext();
1734   if (aIID.Equals(NS_GET_IID(nsILoadContext)) && browsingContext) {
1735     browsingContext.forget(result);
1736     return NS_OK;
1737   }
1738 
1739   return QueryInterface(aIID, result);
1740 }
1741 
1742 // Rather than forwarding all these nsIParentChannel functions to the child,
1743 // we cache a list of them, and then ask the 'real' channel to forward them
1744 // for us after it's created.
1745 NS_IMETHODIMP
NotifyFlashPluginStateChanged(nsIHttpChannel::FlashPluginState aState)1746 DocumentLoadListener::NotifyFlashPluginStateChanged(
1747     nsIHttpChannel::FlashPluginState aState) {
1748   mIParentChannelFunctions.AppendElement(
1749       IParentChannelFunction{VariantIndex<0>{}, aState});
1750   return NS_OK;
1751 }
1752 
1753 NS_IMETHODIMP
SetClassifierMatchedInfo(const nsACString & aList,const nsACString & aProvider,const nsACString & aFullHash)1754 DocumentLoadListener::SetClassifierMatchedInfo(const nsACString& aList,
1755                                                const nsACString& aProvider,
1756                                                const nsACString& aFullHash) {
1757   ClassifierMatchedInfoParams params;
1758   params.mList = aList;
1759   params.mProvider = aProvider;
1760   params.mFullHash = aFullHash;
1761 
1762   mIParentChannelFunctions.AppendElement(
1763       IParentChannelFunction{VariantIndex<1>{}, std::move(params)});
1764   return NS_OK;
1765 }
1766 
1767 NS_IMETHODIMP
SetClassifierMatchedTrackingInfo(const nsACString & aLists,const nsACString & aFullHash)1768 DocumentLoadListener::SetClassifierMatchedTrackingInfo(
1769     const nsACString& aLists, const nsACString& aFullHash) {
1770   ClassifierMatchedTrackingInfoParams params;
1771   params.mLists = aLists;
1772   params.mFullHashes = aFullHash;
1773 
1774   mIParentChannelFunctions.AppendElement(
1775       IParentChannelFunction{VariantIndex<2>{}, std::move(params)});
1776   return NS_OK;
1777 }
1778 
1779 NS_IMETHODIMP
NotifyClassificationFlags(uint32_t aClassificationFlags,bool aIsThirdParty)1780 DocumentLoadListener::NotifyClassificationFlags(uint32_t aClassificationFlags,
1781                                                 bool aIsThirdParty) {
1782   mIParentChannelFunctions.AppendElement(IParentChannelFunction{
1783       VariantIndex<3>{},
1784       ClassificationFlagsParams{aClassificationFlags, aIsThirdParty}});
1785   return NS_OK;
1786 }
1787 
1788 NS_IMETHODIMP
Delete()1789 DocumentLoadListener::Delete() {
1790   if (mDocumentChannelBridge) {
1791     mDocumentChannelBridge->Delete();
1792   }
1793   return NS_OK;
1794 }
1795 
1796 NS_IMETHODIMP
AsyncOnChannelRedirect(nsIChannel * aOldChannel,nsIChannel * aNewChannel,uint32_t aFlags,nsIAsyncVerifyRedirectCallback * aCallback)1797 DocumentLoadListener::AsyncOnChannelRedirect(
1798     nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
1799     nsIAsyncVerifyRedirectCallback* aCallback) {
1800   LOG(("DocumentLoadListener AsyncOnChannelRedirect [this=%p, aFlags=%" PRIx32
1801        "]",
1802        this, aFlags));
1803   // We generally don't want to notify the content process about redirects,
1804   // so just update our channel and tell the callback that we're good to go.
1805   mChannel = aNewChannel;
1806 
1807   // Since we're redirecting away from aOldChannel, we should check if it
1808   // had a COOP mismatch, since we want the final result for this to
1809   // include the state of all channels we redirected through.
1810   nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(aOldChannel);
1811   if (httpChannel) {
1812     bool isCOOPMismatch = false;
1813     Unused << NS_WARN_IF(NS_FAILED(
1814         httpChannel->HasCrossOriginOpenerPolicyMismatch(&isCOOPMismatch)));
1815     mHasCrossOriginOpenerPolicyMismatch |= isCOOPMismatch;
1816   }
1817 
1818   // We don't need to confirm internal redirects or record any
1819   // history for them, so just immediately verify and return.
1820   if (aFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
1821     LOG(
1822         ("DocumentLoadListener AsyncOnChannelRedirect [this=%p] "
1823          "flags=REDIRECT_INTERNAL",
1824          this));
1825     aCallback->OnRedirectVerifyCallback(NS_OK);
1826     return NS_OK;
1827   }
1828 
1829   if (!net::ChannelIsPost(aOldChannel)) {
1830     AddURIVisit(aOldChannel, 0);
1831 
1832     nsCOMPtr<nsIURI> oldURI;
1833     aOldChannel->GetURI(getter_AddRefs(oldURI));
1834     nsDocShell::SaveLastVisit(aNewChannel, oldURI, aFlags);
1835   }
1836   mHaveVisibleRedirect |= true;
1837 
1838   LOG(
1839       ("DocumentLoadListener AsyncOnChannelRedirect [this=%p] "
1840        "mHaveVisibleRedirect=%c",
1841        this, mHaveVisibleRedirect ? 'T' : 'F'));
1842 
1843   // If this is a cross-origin redirect, then we should no longer allow
1844   // mixed content. The destination docshell checks this in its redirect
1845   // handling, but if we deliver to a new docshell (with a process switch)
1846   // then this doesn't happen.
1847   // Manually remove the allow mixed content flags.
1848   nsresult rv = nsContentUtils::CheckSameOrigin(aOldChannel, aNewChannel);
1849   if (NS_FAILED(rv)) {
1850     if (mLoadStateLoadType == LOAD_NORMAL_ALLOW_MIXED_CONTENT) {
1851       mLoadStateLoadType = LOAD_NORMAL;
1852     } else if (mLoadStateLoadType == LOAD_RELOAD_ALLOW_MIXED_CONTENT) {
1853       mLoadStateLoadType = LOAD_RELOAD_NORMAL;
1854     }
1855     MOZ_ASSERT(!LOAD_TYPE_HAS_FLAGS(
1856         mLoadStateLoadType, nsIWebNavigation::LOAD_FLAGS_ALLOW_MIXED_CONTENT));
1857   }
1858 
1859   // We need the original URI of the current channel to use to open the real
1860   // channel in the content process. Unfortunately we overwrite the original
1861   // uri of the new channel with the original pre-redirect URI, so grab
1862   // a copy of it now.
1863   aNewChannel->GetOriginalURI(getter_AddRefs(mChannelCreationURI));
1864 
1865   // Clear out our nsIParentChannel functions, since a normal parent
1866   // channel would actually redirect and not have those values on the new one.
1867   // We expect the URI classifier to run on the redirected channel with
1868   // the new URI and set these again.
1869   mIParentChannelFunctions.Clear();
1870 
1871 #ifdef ANDROID
1872   nsCOMPtr<nsIURI> uriBeingLoaded =
1873       AntiTrackingUtils::MaybeGetDocumentURIBeingLoaded(mChannel);
1874   RefPtr<CanonicalBrowsingContext> bc =
1875       mParentChannelListener->GetBrowsingContext();
1876 
1877   RefPtr<MozPromise<bool, bool, false>> promise;
1878   nsCOMPtr<nsIWidget> widget = bc->GetParentProcessWidgetContaining();
1879   RefPtr<nsWindow> window = nsWindow::From(widget);
1880 
1881   if (window) {
1882     promise = window->OnLoadRequest(uriBeingLoaded,
1883                                     nsIBrowserDOMWindow::OPEN_CURRENTWINDOW,
1884                                     nsIWebNavigation::LOAD_FLAGS_IS_REDIRECT,
1885                                     nullptr, false, bc->IsTopContent());
1886   }
1887 
1888   if (promise) {
1889     RefPtr<nsIAsyncVerifyRedirectCallback> cb = aCallback;
1890     promise->Then(
1891         GetCurrentThreadSerialEventTarget(), __func__,
1892         [=](const MozPromise<bool, bool, false>::ResolveOrRejectValue& aValue) {
1893           if (aValue.IsResolve()) {
1894             bool handled = aValue.ResolveValue();
1895             if (handled) {
1896               cb->OnRedirectVerifyCallback(NS_ERROR_ABORT);
1897             } else {
1898               cb->OnRedirectVerifyCallback(NS_OK);
1899             }
1900           }
1901         });
1902   } else
1903 #endif /* ANDROID */
1904   {
1905     aCallback->OnRedirectVerifyCallback(NS_OK);
1906   }
1907   return NS_OK;
1908 }
1909 
1910 // This method returns the cached result of running the Cross-Origin-Opener
1911 // policy compare algorithm by calling ComputeCrossOriginOpenerPolicyMismatch
HasCrossOriginOpenerPolicyMismatch() const1912 bool DocumentLoadListener::HasCrossOriginOpenerPolicyMismatch() const {
1913   // If we found a COOP mismatch on an earlier channel and then
1914   // redirected away from that, we should use that result.
1915   if (mHasCrossOriginOpenerPolicyMismatch) {
1916     return true;
1917   }
1918 
1919   nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(mChannel);
1920   if (!httpChannel) {
1921     // Not an nsIHttpChannelInternal assume it's okay to switch.
1922     return false;
1923   }
1924 
1925   bool isCOOPMismatch = false;
1926   Unused << NS_WARN_IF(NS_FAILED(
1927       httpChannel->HasCrossOriginOpenerPolicyMismatch(&isCOOPMismatch)));
1928   return isCOOPMismatch;
1929 }
1930 
AttachStreamFilter(base::ProcessId aChildProcessId)1931 auto DocumentLoadListener::AttachStreamFilter(base::ProcessId aChildProcessId)
1932     -> RefPtr<ChildEndpointPromise> {
1933   LOG(("DocumentLoadListener AttachStreamFilter [this=%p]", this));
1934 
1935   StreamFilterRequest* request = mStreamFilterRequests.AppendElement();
1936   request->mPromise = new ChildEndpointPromise::Private(__func__);
1937   request->mChildProcessId = aChildProcessId;
1938   return request->mPromise;
1939 }
1940 
1941 }  // namespace net
1942 }  // namespace mozilla
1943 
1944 #undef LOG
1945