no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / netwerk / ipc / DocumentLoadListener.cpp
blob3cf3901f8a7d66aad94b5e109e41fee31ec698b8
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set sw=2 ts=8 et tw=80 : */
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/. */
8 #include "DocumentLoadListener.h"
10 #include "NeckoCommon.h"
11 #include "mozilla/AntiTrackingUtils.h"
12 #include "mozilla/DebugOnly.h"
13 #include "mozilla/LoadInfo.h"
14 #include "mozilla/NullPrincipal.h"
15 #include "mozilla/RefPtr.h"
16 #include "mozilla/ResultVariant.h"
17 #include "mozilla/ScopeExit.h"
18 #include "mozilla/StaticPrefs_extensions.h"
19 #include "mozilla/StaticPrefs_fission.h"
20 #include "mozilla/StaticPrefs_security.h"
21 #include "mozilla/dom/BrowserParent.h"
22 #include "mozilla/dom/BrowsingContextGroup.h"
23 #include "mozilla/dom/CanonicalBrowsingContext.h"
24 #include "mozilla/dom/ChildProcessChannelListener.h"
25 #include "mozilla/dom/ClientChannelHelper.h"
26 #include "mozilla/dom/ContentParent.h"
27 #include "mozilla/dom/ContentProcessManager.h"
28 #include "mozilla/dom/ProcessIsolation.h"
29 #include "mozilla/dom/SessionHistoryEntry.h"
30 #include "mozilla/dom/WindowGlobalParent.h"
31 #include "mozilla/dom/ipc/IdType.h"
32 #include "mozilla/net/CookieJarSettings.h"
33 #include "mozilla/net/HttpChannelParent.h"
34 #include "mozilla/net/RedirectChannelRegistrar.h"
35 #include "nsContentSecurityUtils.h"
36 #include "nsContentSecurityManager.h"
37 #include "nsDocShell.h"
38 #include "nsDocShellLoadState.h"
39 #include "nsDocShellLoadTypes.h"
40 #include "nsDOMNavigationTiming.h"
41 #include "nsDSURIContentListener.h"
42 #include "nsObjectLoadingContent.h"
43 #include "nsOpenWindowInfo.h"
44 #include "nsExternalHelperAppService.h"
45 #include "nsHttpChannel.h"
46 #include "nsIBrowser.h"
47 #include "nsIHttpChannelInternal.h"
48 #include "nsIStreamConverterService.h"
49 #include "nsIViewSourceChannel.h"
50 #include "nsImportModule.h"
51 #include "nsIXULRuntime.h"
52 #include "nsMimeTypes.h"
53 #include "nsQueryObject.h"
54 #include "nsRedirectHistoryEntry.h"
55 #include "nsSandboxFlags.h"
56 #include "nsSHistory.h"
57 #include "nsStringStream.h"
58 #include "nsURILoader.h"
59 #include "nsWebNavigationInfo.h"
60 #include "mozilla/dom/BrowserParent.h"
61 #include "mozilla/dom/Element.h"
62 #include "mozilla/dom/nsHTTPSOnlyUtils.h"
63 #include "mozilla/dom/ReferrerInfo.h"
64 #include "mozilla/dom/RemoteWebProgressRequest.h"
65 #include "mozilla/net/UrlClassifierFeatureFactory.h"
66 #include "mozilla/ExtensionPolicyService.h"
68 #ifdef ANDROID
69 # include "mozilla/widget/nsWindow.h"
70 #endif /* ANDROID */
72 mozilla::LazyLogModule gDocumentChannelLog("DocumentChannel");
73 #define LOG(fmt) MOZ_LOG(gDocumentChannelLog, mozilla::LogLevel::Verbose, fmt)
75 extern mozilla::LazyLogModule gSHIPBFCacheLog;
77 // Bug 136580: Limit to the number of nested content frames that can have the
78 // same URL. This is to stop content that is recursively loading
79 // itself. Note that "#foo" on the end of URL doesn't affect
80 // whether it's considered identical, but "?foo" or ";foo" are
81 // considered and compared.
82 // Limit this to 2, like chromium does.
83 static constexpr int kMaxSameURLContentFrames = 2;
85 using namespace mozilla::dom;
87 namespace mozilla {
88 namespace net {
90 static ContentParentId GetContentProcessId(ContentParent* aContentParent) {
91 return aContentParent ? aContentParent->ChildID() : ContentParentId{0};
94 static void SetNeedToAddURIVisit(nsIChannel* aChannel,
95 bool aNeedToAddURIVisit) {
96 nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(aChannel));
97 if (!props) {
98 return;
101 props->SetPropertyAsBool(u"docshell.needToAddURIVisit"_ns,
102 aNeedToAddURIVisit);
105 static auto SecurityFlagsForLoadInfo(nsDocShellLoadState* aLoadState)
106 -> nsSecurityFlags {
107 // TODO: Block copied from nsDocShell::DoURILoad, refactor out somewhere
108 nsSecurityFlags securityFlags =
109 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
111 if (aLoadState->LoadType() == LOAD_ERROR_PAGE) {
112 securityFlags |= nsILoadInfo::SEC_LOAD_ERROR_PAGE;
115 if (aLoadState->PrincipalToInherit()) {
116 bool isSrcdoc = aLoadState->HasInternalLoadFlags(
117 nsDocShell::INTERNAL_LOAD_FLAGS_IS_SRCDOC);
118 bool inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal(
119 aLoadState->PrincipalToInherit(), aLoadState->URI(),
120 true, // aInheritForAboutBlank
121 isSrcdoc);
123 bool isData = SchemeIsData(aLoadState->URI());
124 if (inheritAttrs && !isData) {
125 securityFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
129 return securityFlags;
132 // Construct a LoadInfo object to use when creating the internal channel for a
133 // Document/SubDocument load.
134 static auto CreateDocumentLoadInfo(CanonicalBrowsingContext* aBrowsingContext,
135 nsDocShellLoadState* aLoadState)
136 -> already_AddRefed<LoadInfo> {
137 uint32_t sandboxFlags = aBrowsingContext->GetSandboxFlags();
138 RefPtr<LoadInfo> loadInfo;
140 auto securityFlags = SecurityFlagsForLoadInfo(aLoadState);
142 if (aBrowsingContext->GetParent()) {
143 loadInfo = LoadInfo::CreateForFrame(
144 aBrowsingContext, aLoadState->TriggeringPrincipal(),
145 aLoadState->GetEffectiveTriggeringRemoteType(), securityFlags,
146 sandboxFlags);
147 } else {
148 OriginAttributes attrs;
149 aBrowsingContext->GetOriginAttributes(attrs);
150 loadInfo = LoadInfo::CreateForDocument(
151 aBrowsingContext, aLoadState->URI(), aLoadState->TriggeringPrincipal(),
152 aLoadState->GetEffectiveTriggeringRemoteType(), attrs, securityFlags,
153 sandboxFlags);
156 if (aLoadState->IsExemptFromHTTPSFirstMode()) {
157 uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
158 httpsOnlyStatus |= nsILoadInfo::HTTPS_FIRST_EXEMPT_NEXT_LOAD;
159 loadInfo->SetHttpsOnlyStatus(httpsOnlyStatus);
162 loadInfo->SetWasSchemelessInput(aLoadState->GetWasSchemelessInput());
164 loadInfo->SetTriggeringSandboxFlags(aLoadState->TriggeringSandboxFlags());
165 loadInfo->SetTriggeringWindowId(aLoadState->TriggeringWindowId());
166 loadInfo->SetTriggeringStorageAccess(aLoadState->TriggeringStorageAccess());
167 loadInfo->SetHasValidUserGestureActivation(
168 aLoadState->HasValidUserGestureActivation());
169 loadInfo->SetIsMetaRefresh(aLoadState->IsMetaRefresh());
171 return loadInfo.forget();
174 // Construct a LoadInfo object to use when creating the internal channel for an
175 // Object/Embed load.
176 static auto CreateObjectLoadInfo(nsDocShellLoadState* aLoadState,
177 uint64_t aInnerWindowId,
178 nsContentPolicyType aContentPolicyType,
179 uint32_t aSandboxFlags)
180 -> already_AddRefed<LoadInfo> {
181 RefPtr<WindowGlobalParent> wgp =
182 WindowGlobalParent::GetByInnerWindowId(aInnerWindowId);
183 MOZ_RELEASE_ASSERT(wgp);
185 auto securityFlags = SecurityFlagsForLoadInfo(aLoadState);
187 RefPtr<LoadInfo> loadInfo = LoadInfo::CreateForNonDocument(
188 wgp, wgp->DocumentPrincipal(), aContentPolicyType, securityFlags,
189 aSandboxFlags);
191 loadInfo->SetHasValidUserGestureActivation(
192 aLoadState->HasValidUserGestureActivation());
193 loadInfo->SetTriggeringSandboxFlags(aLoadState->TriggeringSandboxFlags());
194 loadInfo->SetTriggeringWindowId(aLoadState->TriggeringWindowId());
195 loadInfo->SetTriggeringStorageAccess(aLoadState->TriggeringStorageAccess());
196 loadInfo->SetIsMetaRefresh(aLoadState->IsMetaRefresh());
198 return loadInfo.forget();
202 * An extension to nsDocumentOpenInfo that we run in the parent process, so
203 * that we can make the decision to retarget to content handlers or the external
204 * helper app, before we make process switching decisions.
206 * This modifies the behaviour of nsDocumentOpenInfo so that it can do
207 * retargeting, but doesn't do stream conversion (but confirms that we will be
208 * able to do so later).
210 * We still run nsDocumentOpenInfo in the content process, but disable
211 * retargeting, so that it can only apply stream conversion, and then send data
212 * to the docshell.
214 class ParentProcessDocumentOpenInfo final : public nsDocumentOpenInfo,
215 public nsIMultiPartChannelListener {
216 public:
217 ParentProcessDocumentOpenInfo(ParentChannelListener* aListener,
218 uint32_t aFlags,
219 mozilla::dom::BrowsingContext* aBrowsingContext,
220 bool aIsDocumentLoad)
221 : nsDocumentOpenInfo(aFlags, false),
222 mBrowsingContext(aBrowsingContext),
223 mListener(aListener),
224 mIsDocumentLoad(aIsDocumentLoad) {
225 LOG(("ParentProcessDocumentOpenInfo ctor [this=%p]", this));
228 NS_DECL_ISUPPORTS_INHERITED
230 // The default content listener is always a docshell, so this manually
231 // implements the same checks, and if it succeeds, uses the parent
232 // channel listener so that we forward onto DocumentLoadListener.
233 bool TryDefaultContentListener(nsIChannel* aChannel,
234 const nsCString& aContentType) {
235 uint32_t canHandle = nsWebNavigationInfo::IsTypeSupported(aContentType);
236 if (canHandle != nsIWebNavigationInfo::UNSUPPORTED) {
237 m_targetStreamListener = mListener;
238 nsLoadFlags loadFlags = 0;
239 aChannel->GetLoadFlags(&loadFlags);
240 aChannel->SetLoadFlags(loadFlags | nsIChannel::LOAD_TARGETED);
241 return true;
243 return false;
246 bool TryDefaultContentListener(nsIChannel* aChannel) override {
247 return TryDefaultContentListener(aChannel, mContentType);
250 // Generally we only support stream converters that can tell
251 // use exactly what type they'll output. If we find one, then
252 // we just target to our default listener directly (without
253 // conversion), and the content process nsDocumentOpenInfo will
254 // run and do the actual conversion.
255 nsresult TryStreamConversion(nsIChannel* aChannel) override {
256 // The one exception is nsUnknownDecoder, which works in the parent
257 // (and we need to know what the content type is before we can
258 // decide if it will be handled in the parent), so we run that here.
259 if (mContentType.LowerCaseEqualsASCII(UNKNOWN_CONTENT_TYPE) ||
260 mContentType.IsEmpty()) {
261 return nsDocumentOpenInfo::TryStreamConversion(aChannel);
264 nsresult rv;
265 nsCOMPtr<nsIStreamConverterService> streamConvService =
266 do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
267 nsAutoCString str;
268 rv = streamConvService->ConvertedType(mContentType, aChannel, str);
269 NS_ENSURE_SUCCESS(rv, rv);
271 // We only support passing data to the default content listener
272 // (docshell), and we don't supported chaining converters.
273 if (TryDefaultContentListener(aChannel, str)) {
274 mContentType = str;
275 return NS_OK;
277 // This is the same result as nsStreamConverterService uses when it
278 // can't find a converter
279 return NS_ERROR_FAILURE;
282 nsresult TryExternalHelperApp(nsIExternalHelperAppService* aHelperAppService,
283 nsIChannel* aChannel) override {
284 RefPtr<nsIStreamListener> listener;
285 nsresult rv = aHelperAppService->CreateListener(
286 mContentType, aChannel, mBrowsingContext, false, nullptr,
287 getter_AddRefs(listener));
288 if (NS_SUCCEEDED(rv)) {
289 m_targetStreamListener = listener;
291 return rv;
294 nsDocumentOpenInfo* Clone() override {
295 mCloned = true;
296 return new ParentProcessDocumentOpenInfo(mListener, mFlags,
297 mBrowsingContext, mIsDocumentLoad);
300 nsresult OnDocumentStartRequest(nsIRequest* request) {
301 LOG(("ParentProcessDocumentOpenInfo OnDocumentStartRequest [this=%p]",
302 this));
304 nsresult rv = nsDocumentOpenInfo::OnStartRequest(request);
306 // If we didn't find a content handler,
307 // and we don't have a listener, then just forward to our
308 // default listener. This happens when the channel is in
309 // an error state, and we want to just forward that on to be
310 // handled in the content process.
311 if (NS_SUCCEEDED(rv) && !mUsedContentHandler && !m_targetStreamListener) {
312 m_targetStreamListener = mListener;
313 return m_targetStreamListener->OnStartRequest(request);
315 if (m_targetStreamListener != mListener) {
316 LOG(
317 ("ParentProcessDocumentOpenInfo targeted to non-default listener "
318 "[this=%p]",
319 this));
320 // If this is the only part, then we can immediately tell our listener
321 // that it won't be getting any content and disconnect it. For multipart
322 // channels we have to wait until we've handled all parts before we know.
323 // This does mean that the content process can still Cancel() a multipart
324 // response while the response is being handled externally, but this
325 // matches the single-process behaviour.
326 // If we got cloned, then we don't need to do this, as only the last link
327 // needs to do it.
328 // Multi-part channels are guaranteed to call OnAfterLastPart, which we
329 // forward to the listeners, so it will handle disconnection at that
330 // point.
331 nsCOMPtr<nsIMultiPartChannel> multiPartChannel =
332 do_QueryInterface(request);
333 if (!multiPartChannel && !mCloned) {
334 DisconnectChildListeners(NS_FAILED(rv) ? rv : NS_BINDING_RETARGETED,
335 rv);
338 return rv;
341 nsresult OnObjectStartRequest(nsIRequest* request) {
342 LOG(("ParentProcessDocumentOpenInfo OnObjectStartRequest [this=%p]", this));
343 // Just redirect to the nsObjectLoadingContent in the content process.
344 m_targetStreamListener = mListener;
345 return m_targetStreamListener->OnStartRequest(request);
348 NS_IMETHOD OnStartRequest(nsIRequest* request) override {
349 LOG(("ParentProcessDocumentOpenInfo OnStartRequest [this=%p]", this));
351 if (mIsDocumentLoad) {
352 return OnDocumentStartRequest(request);
355 return OnObjectStartRequest(request);
358 NS_IMETHOD OnAfterLastPart(nsresult aStatus) override {
359 mListener->OnAfterLastPart(aStatus);
360 return NS_OK;
363 private:
364 virtual ~ParentProcessDocumentOpenInfo() {
365 LOG(("ParentProcessDocumentOpenInfo dtor [this=%p]", this));
368 void DisconnectChildListeners(nsresult aStatus, nsresult aLoadGroupStatus) {
369 // Tell the DocumentLoadListener to notify the content process that it's
370 // been entirely retargeted, and to stop waiting.
371 // Clear mListener's pointer to the DocumentLoadListener to break the
372 // reference cycle.
373 RefPtr<DocumentLoadListener> doc = do_GetInterface(ToSupports(mListener));
374 MOZ_ASSERT(doc);
375 doc->DisconnectListeners(aStatus, aLoadGroupStatus);
376 mListener->SetListenerAfterRedirect(nullptr);
379 RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext;
380 RefPtr<ParentChannelListener> mListener;
381 const bool mIsDocumentLoad;
384 * Set to true if we got cloned to create a chained listener.
386 bool mCloned = false;
389 NS_IMPL_ADDREF_INHERITED(ParentProcessDocumentOpenInfo, nsDocumentOpenInfo)
390 NS_IMPL_RELEASE_INHERITED(ParentProcessDocumentOpenInfo, nsDocumentOpenInfo)
392 NS_INTERFACE_MAP_BEGIN(ParentProcessDocumentOpenInfo)
393 NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannelListener)
394 NS_INTERFACE_MAP_END_INHERITING(nsDocumentOpenInfo)
396 NS_IMPL_ADDREF(DocumentLoadListener)
397 NS_IMPL_RELEASE(DocumentLoadListener)
399 NS_INTERFACE_MAP_BEGIN(DocumentLoadListener)
400 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInterfaceRequestor)
401 NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
402 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
403 NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
404 NS_INTERFACE_MAP_ENTRY(nsIParentChannel)
405 NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectReadyCallback)
406 NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
407 NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannelListener)
408 NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
409 NS_INTERFACE_MAP_ENTRY(nsIEarlyHintObserver)
410 NS_INTERFACE_MAP_ENTRY_CONCRETE(DocumentLoadListener)
411 NS_INTERFACE_MAP_END
413 DocumentLoadListener::DocumentLoadListener(
414 CanonicalBrowsingContext* aLoadingBrowsingContext, bool aIsDocumentLoad)
415 : mIsDocumentLoad(aIsDocumentLoad) {
416 LOG(("DocumentLoadListener ctor [this=%p]", this));
417 mParentChannelListener =
418 new ParentChannelListener(this, aLoadingBrowsingContext);
421 DocumentLoadListener::~DocumentLoadListener() {
422 LOG(("DocumentLoadListener dtor [this=%p]", this));
425 void DocumentLoadListener::AddURIVisit(nsIChannel* aChannel,
426 uint32_t aLoadFlags) {
427 if (mLoadStateLoadType == LOAD_ERROR_PAGE ||
428 mLoadStateLoadType == LOAD_BYPASS_HISTORY) {
429 return;
432 nsCOMPtr<nsIURI> uri;
433 NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
435 nsCOMPtr<nsIURI> previousURI;
436 uint32_t previousFlags = 0;
437 if (mLoadStateLoadType & nsIDocShell::LOAD_CMD_RELOAD) {
438 previousURI = uri;
439 } else {
440 nsDocShell::ExtractLastVisit(aChannel, getter_AddRefs(previousURI),
441 &previousFlags);
444 // Get the HTTP response code, if available.
445 uint32_t responseStatus = 0;
446 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
447 if (httpChannel) {
448 Unused << httpChannel->GetResponseStatus(&responseStatus);
451 RefPtr<CanonicalBrowsingContext> browsingContext =
452 GetDocumentBrowsingContext();
453 nsCOMPtr<nsIWidget> widget =
454 browsingContext->GetParentProcessWidgetContaining();
456 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
458 // Check if the URI has a http scheme and if either HSTS is enabled for this
459 // host, or if we were upgraded by HTTPS-Only/First. If this is the case, we
460 // want to mark this URI specially, as it will be followed shortly by an
461 // almost identical https history entry. That way the global history
462 // implementation can handle the visit appropriately (e.g. by marking it as
463 // `hidden`, so only the https url appears in default history views).
464 bool wasUpgraded =
465 uri->SchemeIs("http") &&
466 (loadInfo->GetHstsStatus() ||
467 (loadInfo->GetHttpsOnlyStatus() &
468 (nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST |
469 nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_NOT_REGISTERED |
470 nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_REGISTERED)));
472 nsDocShell::InternalAddURIVisit(uri, previousURI, previousFlags,
473 responseStatus, browsingContext, widget,
474 mLoadStateLoadType, wasUpgraded);
477 CanonicalBrowsingContext* DocumentLoadListener::GetLoadingBrowsingContext()
478 const {
479 return mParentChannelListener ? mParentChannelListener->GetBrowsingContext()
480 : nullptr;
483 CanonicalBrowsingContext* DocumentLoadListener::GetDocumentBrowsingContext()
484 const {
485 return mIsDocumentLoad ? GetLoadingBrowsingContext() : nullptr;
488 CanonicalBrowsingContext* DocumentLoadListener::GetTopBrowsingContext() const {
489 auto* loadingContext = GetLoadingBrowsingContext();
490 return loadingContext ? loadingContext->Top() : nullptr;
493 WindowGlobalParent* DocumentLoadListener::GetParentWindowContext() const {
494 return mParentWindowContext;
497 bool CheckRecursiveLoad(CanonicalBrowsingContext* aLoadingContext,
498 nsDocShellLoadState* aLoadState, bool aIsDocumentLoad) {
499 // Bug 136580: Check for recursive frame loading excluding about:srcdoc URIs.
500 // srcdoc URIs require their contents to be specified inline, so it isn't
501 // possible for undesirable recursion to occur without the aid of a
502 // non-srcdoc URI, which this method will block normally.
503 // Besides, URI is not enough to guarantee uniqueness of srcdoc documents.
504 nsAutoCString buffer;
505 if (aLoadState->URI()->SchemeIs("about")) {
506 nsresult rv = aLoadState->URI()->GetPathQueryRef(buffer);
507 if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("srcdoc")) {
508 // Duplicates allowed up to depth limits
509 return true;
513 RefPtr<WindowGlobalParent> parent;
514 if (!aIsDocumentLoad) { // object load
515 parent = aLoadingContext->GetCurrentWindowGlobal();
516 } else {
517 parent = aLoadingContext->GetParentWindowContext();
520 int matchCount = 0;
521 CanonicalBrowsingContext* ancestorBC;
522 for (WindowGlobalParent* ancestorWGP = parent; ancestorWGP;
523 ancestorWGP = ancestorBC->GetParentWindowContext()) {
524 ancestorBC = ancestorWGP->BrowsingContext();
525 MOZ_ASSERT(ancestorBC);
526 if (nsCOMPtr<nsIURI> parentURI = ancestorWGP->GetDocumentURI()) {
527 bool equal;
528 nsresult rv = aLoadState->URI()->EqualsExceptRef(parentURI, &equal);
529 NS_ENSURE_SUCCESS(rv, false);
531 if (equal) {
532 matchCount++;
533 if (matchCount >= kMaxSameURLContentFrames) {
534 NS_WARNING(
535 "Too many nested content frames/objects have the same url "
536 "(recursion?) "
537 "so giving up");
538 return false;
543 return true;
546 // Check that the load state, potentially received from a child process, appears
547 // to be performing a load of the specified LoadingSessionHistoryInfo.
548 // Returns a Result<…> containing the SessionHistoryEntry found for the
549 // LoadingSessionHistoryInfo as success value if the validation succeeded, or a
550 // static (telemetry-safe) string naming what did not match as a failure value
551 // if the validation failed.
552 static Result<SessionHistoryEntry*, const char*> ValidateHistoryLoad(
553 CanonicalBrowsingContext* aLoadingContext,
554 nsDocShellLoadState* aLoadState) {
555 MOZ_ASSERT(SessionHistoryInParent());
556 MOZ_ASSERT(aLoadState->LoadIsFromSessionHistory());
558 if (!aLoadState->GetLoadingSessionHistoryInfo()) {
559 return Err("Missing LoadingSessionHistoryInfo");
562 SessionHistoryEntry::LoadingEntry* loading = SessionHistoryEntry::GetByLoadId(
563 aLoadState->GetLoadingSessionHistoryInfo()->mLoadId);
564 if (!loading) {
565 return Err("Missing SessionHistoryEntry");
568 SessionHistoryInfo* snapshot = loading->mInfoSnapshotForValidation.get();
569 // History loads do not inherit principal.
570 if (aLoadState->HasInternalLoadFlags(
571 nsDocShell::INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL)) {
572 return Err("LOAD_FLAGS_INHERIT_PRINCIPAL");
575 auto uriEq = [](nsIURI* a, nsIURI* b) -> bool {
576 bool eq = false;
577 return a == b || (a && b && NS_SUCCEEDED(a->Equals(b, &eq)) && eq);
579 auto principalEq = [](nsIPrincipal* a, nsIPrincipal* b) -> bool {
580 return a == b || (a && b && a->Equals(b));
583 // XXX: Needing to do all of this validation manually is kinda gross.
584 if (!uriEq(snapshot->GetURI(), aLoadState->URI())) {
585 return Err("URI");
587 if (!uriEq(snapshot->GetOriginalURI(), aLoadState->OriginalURI())) {
588 return Err("OriginalURI");
590 if (!aLoadState->ResultPrincipalURIIsSome() ||
591 !uriEq(snapshot->GetResultPrincipalURI(),
592 aLoadState->ResultPrincipalURI())) {
593 return Err("ResultPrincipalURI");
595 if (!uriEq(snapshot->GetUnstrippedURI(), aLoadState->GetUnstrippedURI())) {
596 return Err("UnstrippedURI");
598 if (!principalEq(snapshot->GetTriggeringPrincipal(),
599 aLoadState->TriggeringPrincipal())) {
600 return Err("TriggeringPrincipal");
602 if (!principalEq(snapshot->GetPrincipalToInherit(),
603 aLoadState->PrincipalToInherit())) {
604 return Err("PrincipalToInherit");
606 if (!principalEq(snapshot->GetPartitionedPrincipalToInherit(),
607 aLoadState->PartitionedPrincipalToInherit())) {
608 return Err("PartitionedPrincipalToInherit");
611 // Everything matches!
612 return loading->mEntry;
615 auto DocumentLoadListener::Open(nsDocShellLoadState* aLoadState,
616 LoadInfo* aLoadInfo, nsLoadFlags aLoadFlags,
617 uint32_t aCacheKey,
618 const Maybe<uint64_t>& aChannelId,
619 const TimeStamp& aAsyncOpenTime,
620 nsDOMNavigationTiming* aTiming,
621 Maybe<ClientInfo>&& aInfo, bool aUrgentStart,
622 dom::ContentParent* aContentParent,
623 nsresult* aRv) -> RefPtr<OpenPromise> {
624 auto* loadingContext = GetLoadingBrowsingContext();
626 MOZ_DIAGNOSTIC_ASSERT_IF(loadingContext->GetParent(),
627 loadingContext->GetParentWindowContext());
629 OriginAttributes attrs;
630 loadingContext->GetOriginAttributes(attrs);
632 mLoadIdentifier = aLoadState->GetLoadIdentifier();
633 // See description of mFileName in nsDocShellLoadState.h
634 mIsDownload = !aLoadState->FileName().IsVoid();
635 mIsLoadingJSURI = net::SchemeIsJavascript(aLoadState->URI());
637 // Check for infinite recursive object or iframe loads
638 if (aLoadState->OriginalFrameSrc() || !mIsDocumentLoad) {
639 if (!CheckRecursiveLoad(loadingContext, aLoadState, mIsDocumentLoad)) {
640 *aRv = NS_ERROR_RECURSIVE_DOCUMENT_LOAD;
641 mParentChannelListener = nullptr;
642 return nullptr;
646 auto* documentContext = GetDocumentBrowsingContext();
648 // If we are using SHIP and this load is from session history, validate that
649 // the load matches our local copy of the loading history entry.
651 // NOTE: Keep this check in-sync with the check in
652 // `nsDocShellLoadState::GetEffectiveTriggeringRemoteType()`!
653 RefPtr<SessionHistoryEntry> existingEntry;
654 if (SessionHistoryInParent() && aLoadState->LoadIsFromSessionHistory() &&
655 aLoadState->LoadType() != LOAD_ERROR_PAGE) {
656 Result<SessionHistoryEntry*, const char*> result =
657 ValidateHistoryLoad(loadingContext, aLoadState);
658 if (result.isErr()) {
659 const char* mismatch = result.unwrapErr();
660 LOG(
661 ("DocumentLoadListener::Open with invalid loading history entry "
662 "[this=%p, mismatch=%s]",
663 this, mismatch));
664 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
665 MOZ_CRASH_UNSAFE_PRINTF(
666 "DocumentLoadListener::Open for invalid history entry due to "
667 "mismatch of '%s'",
668 mismatch);
669 #endif
670 *aRv = NS_ERROR_DOM_SECURITY_ERR;
671 mParentChannelListener = nullptr;
672 return nullptr;
675 existingEntry = result.unwrap();
676 if (!existingEntry->IsInSessionHistory() &&
677 !documentContext->HasLoadingHistoryEntry(existingEntry)) {
678 SessionHistoryEntry::RemoveLoadId(
679 aLoadState->GetLoadingSessionHistoryInfo()->mLoadId);
680 LOG(
681 ("DocumentLoadListener::Open with disconnected history entry "
682 "[this=%p]",
683 this));
685 *aRv = NS_BINDING_ABORTED;
686 mParentChannelListener = nullptr;
687 mChannel = nullptr;
688 return nullptr;
692 if (aLoadState->GetRemoteTypeOverride()) {
693 if (!mIsDocumentLoad || !NS_IsAboutBlank(aLoadState->URI()) ||
694 !loadingContext->IsTopContent()) {
695 LOG(
696 ("DocumentLoadListener::Open with invalid remoteTypeOverride "
697 "[this=%p]",
698 this));
699 *aRv = NS_ERROR_DOM_SECURITY_ERR;
700 mParentChannelListener = nullptr;
701 return nullptr;
704 mRemoteTypeOverride = aLoadState->GetRemoteTypeOverride();
707 if (NS_WARN_IF(!loadingContext->IsOwnedByProcess(
708 GetContentProcessId(aContentParent)))) {
709 LOG(
710 ("DocumentLoadListener::Open called from non-current content process "
711 "[this=%p, current=%" PRIu64 ", caller=%" PRIu64 "]",
712 this, loadingContext->OwnerProcessId(),
713 uint64_t(GetContentProcessId(aContentParent))));
714 *aRv = NS_BINDING_ABORTED;
715 mParentChannelListener = nullptr;
716 return nullptr;
719 if (mIsDocumentLoad && loadingContext->IsContent() &&
720 NS_WARN_IF(loadingContext->IsReplaced())) {
721 LOG(
722 ("DocumentLoadListener::Open called from replaced BrowsingContext "
723 "[this=%p, browserid=%" PRIx64 ", bcid=%" PRIx64 "]",
724 this, loadingContext->BrowserId(), loadingContext->Id()));
725 *aRv = NS_BINDING_ABORTED;
726 mParentChannelListener = nullptr;
727 return nullptr;
730 if (!nsDocShell::CreateAndConfigureRealChannelForLoadState(
731 loadingContext, aLoadState, aLoadInfo, mParentChannelListener,
732 nullptr, attrs, aLoadFlags, aCacheKey, *aRv,
733 getter_AddRefs(mChannel))) {
734 LOG(("DocumentLoadListener::Open failed to create channel [this=%p]",
735 this));
736 mParentChannelListener = nullptr;
737 return nullptr;
740 if (documentContext && aLoadState->LoadType() != LOAD_ERROR_PAGE &&
741 mozilla::SessionHistoryInParent()) {
742 // It's hard to know at this point whether session history will be enabled
743 // in the browsing context, so we always create an entry for a load here.
744 mLoadingSessionHistoryInfo =
745 documentContext->CreateLoadingSessionHistoryEntryForLoad(
746 aLoadState, existingEntry, mChannel);
747 MOZ_ASSERT(mLoadingSessionHistoryInfo);
750 nsCOMPtr<nsIURI> uriBeingLoaded;
751 Unused << NS_WARN_IF(
752 NS_FAILED(mChannel->GetURI(getter_AddRefs(uriBeingLoaded))));
754 RefPtr<HttpBaseChannel> httpBaseChannel = do_QueryObject(mChannel, aRv);
755 if (uriBeingLoaded && httpBaseChannel) {
756 nsCOMPtr<nsIURI> topWindowURI;
757 if (mIsDocumentLoad && loadingContext->IsTop()) {
758 // If this is for the top level loading, the top window URI should be the
759 // URI which we are loading.
760 topWindowURI = uriBeingLoaded;
761 } else if (RefPtr<WindowGlobalParent> topWindow = AntiTrackingUtils::
762 GetTopWindowExcludingExtensionAccessibleContentFrames(
763 loadingContext, uriBeingLoaded)) {
764 nsCOMPtr<nsIPrincipal> topWindowPrincipal =
765 topWindow->DocumentPrincipal();
766 if (topWindowPrincipal && !topWindowPrincipal->GetIsNullPrincipal()) {
767 auto* basePrin = BasePrincipal::Cast(topWindowPrincipal);
768 basePrin->GetURI(getter_AddRefs(topWindowURI));
771 httpBaseChannel->SetTopWindowURI(topWindowURI);
774 nsCOMPtr<nsIIdentChannel> identChannel = do_QueryInterface(mChannel);
775 if (identChannel && aChannelId) {
776 Unused << identChannel->SetChannelId(*aChannelId);
778 mDocumentChannelId = aChannelId;
780 RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
781 if (httpChannelImpl) {
782 httpChannelImpl->SetWarningReporter(this);
784 if (mIsDocumentLoad && loadingContext->IsTop()) {
785 httpChannelImpl->SetEarlyHintObserver(this);
789 nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(mChannel);
790 if (timedChannel) {
791 timedChannel->SetAsyncOpen(aAsyncOpenTime);
794 if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel)) {
795 Unused << httpChannel->SetRequestContextID(
796 loadingContext->GetRequestContextId());
798 nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(httpChannel));
799 if (cos && aUrgentStart) {
800 cos->AddClassFlags(nsIClassOfService::UrgentStart);
804 // Setup a ClientChannelHelper to watch for redirects, and copy
805 // across any serviceworker related data between channels as needed.
806 AddClientChannelHelperInParent(mChannel, std::move(aInfo));
808 if (documentContext && !documentContext->StartDocumentLoad(this)) {
809 LOG(("DocumentLoadListener::Open failed StartDocumentLoad [this=%p]",
810 this));
811 *aRv = NS_BINDING_ABORTED;
812 mParentChannelListener = nullptr;
813 mChannel = nullptr;
814 return nullptr;
817 // Recalculate the openFlags, matching the logic in use in Content process.
818 // NOTE: The only case not handled here to mirror Content process is
819 // redirecting to re-use the channel.
820 MOZ_ASSERT(!aLoadState->GetPendingRedirectedChannel());
821 uint32_t openFlags =
822 nsDocShell::ComputeURILoaderFlags(loadingContext, aLoadState->LoadType());
824 RefPtr<ParentProcessDocumentOpenInfo> openInfo =
825 new ParentProcessDocumentOpenInfo(mParentChannelListener, openFlags,
826 loadingContext, mIsDocumentLoad);
827 openInfo->Prepare();
829 #ifdef ANDROID
830 RefPtr<MozPromise<bool, bool, false>> promise;
831 if (documentContext && aLoadState->LoadType() != LOAD_ERROR_PAGE &&
832 !(aLoadState->HasInternalLoadFlags(
833 nsDocShell::INTERNAL_LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE)) &&
834 !(aLoadState->LoadType() & LOAD_HISTORY)) {
835 nsCOMPtr<nsIWidget> widget =
836 documentContext->GetParentProcessWidgetContaining();
837 RefPtr<nsWindow> window = nsWindow::From(widget);
839 if (window) {
840 promise = window->OnLoadRequest(
841 aLoadState->URI(), nsIBrowserDOMWindow::OPEN_CURRENTWINDOW,
842 aLoadState->InternalLoadFlags(), aLoadState->TriggeringPrincipal(),
843 aLoadState->HasValidUserGestureActivation(),
844 documentContext->IsTopContent());
848 if (promise) {
849 RefPtr<DocumentLoadListener> self = this;
850 promise->Then(
851 GetCurrentSerialEventTarget(), __func__,
852 [=](const MozPromise<bool, bool, false>::ResolveOrRejectValue& aValue) {
853 if (aValue.IsResolve()) {
854 bool handled = aValue.ResolveValue();
855 if (handled) {
856 self->DisconnectListeners(NS_ERROR_ABORT, NS_ERROR_ABORT);
857 mParentChannelListener = nullptr;
858 } else {
859 nsresult rv = mChannel->AsyncOpen(openInfo);
860 if (NS_FAILED(rv)) {
861 self->DisconnectListeners(rv, rv);
862 mParentChannelListener = nullptr;
867 } else
868 #endif /* ANDROID */
870 *aRv = mChannel->AsyncOpen(openInfo);
871 if (NS_FAILED(*aRv)) {
872 LOG(("DocumentLoadListener::Open failed AsyncOpen [this=%p rv=%" PRIx32
873 "]",
874 this, static_cast<uint32_t>(*aRv)));
875 if (documentContext) {
876 documentContext->EndDocumentLoad(false);
878 mParentChannelListener = nullptr;
879 return nullptr;
883 // HTTPS-Only Mode fights potential timeouts caused by upgrades. Instantly
884 // after opening the document channel we have to kick off countermeasures.
885 nsHTTPSOnlyUtils::PotentiallyFireHttpRequestToShortenTimout(this);
887 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
888 loadInfo->SetChannelCreationOriginalURI(aLoadState->URI());
890 mContentParent = aContentParent;
891 mLoadStateExternalLoadFlags = aLoadState->LoadFlags();
892 mLoadStateInternalLoadFlags = aLoadState->InternalLoadFlags();
893 mLoadStateLoadType = aLoadState->LoadType();
894 mTiming = aTiming;
895 mSrcdocData = aLoadState->SrcdocData();
896 mBaseURI = aLoadState->BaseURI();
897 mOriginalUriString = aLoadState->GetOriginalURIString();
898 if (documentContext) {
899 mParentWindowContext = documentContext->GetParentWindowContext();
900 } else {
901 mParentWindowContext =
902 WindowGlobalParent::GetByInnerWindowId(aLoadInfo->GetInnerWindowID());
903 MOZ_RELEASE_ASSERT(mParentWindowContext->GetBrowsingContext() ==
904 GetLoadingBrowsingContext(),
905 "mismatched parent window context?");
908 // For content-initiated loads, this flag is set in nsDocShell::LoadURI.
909 // For parent-initiated loads, we have to set it here.
910 // Below comment is copied from nsDocShell::LoadURI -
911 // If we have a system triggering principal, we can assume that this load was
912 // triggered by some UI in the browser chrome, such as the URL bar or
913 // bookmark bar. This should count as a user interaction for the current sh
914 // entry, so that the user may navigate back to the current entry, from the
915 // entry that is going to be added as part of this load.
916 if (!mSupportsRedirectToRealChannel && aLoadState->TriggeringPrincipal() &&
917 aLoadState->TriggeringPrincipal()->IsSystemPrincipal()) {
918 WindowContext* topWc = loadingContext->GetTopWindowContext();
919 if (topWc && !topWc->IsDiscarded()) {
920 MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(true));
924 *aRv = NS_OK;
925 mOpenPromise = new OpenPromise::Private(__func__);
926 // We make the promise use direct task dispatch in order to reduce the number
927 // of event loops iterations.
928 mOpenPromise->UseDirectTaskDispatch(__func__);
929 return mOpenPromise;
932 auto DocumentLoadListener::OpenDocument(
933 nsDocShellLoadState* aLoadState, uint32_t aCacheKey,
934 const Maybe<uint64_t>& aChannelId, const TimeStamp& aAsyncOpenTime,
935 nsDOMNavigationTiming* aTiming, Maybe<dom::ClientInfo>&& aInfo,
936 Maybe<bool> aUriModified, Maybe<bool> aIsEmbeddingBlockedError,
937 dom::ContentParent* aContentParent, nsresult* aRv) -> RefPtr<OpenPromise> {
938 LOG(("DocumentLoadListener [%p] OpenDocument [uri=%s]", this,
939 aLoadState->URI()->GetSpecOrDefault().get()));
941 MOZ_ASSERT(mIsDocumentLoad);
943 RefPtr<CanonicalBrowsingContext> browsingContext =
944 GetDocumentBrowsingContext();
946 // If this is a top-level load, then rebuild the LoadInfo from scratch,
947 // since the goal is to be able to initiate loads in the parent, where the
948 // content process won't have provided us with an existing one.
949 RefPtr<LoadInfo> loadInfo =
950 CreateDocumentLoadInfo(browsingContext, aLoadState);
952 nsLoadFlags loadFlags = aLoadState->CalculateChannelLoadFlags(
953 browsingContext, std::move(aUriModified),
954 std::move(aIsEmbeddingBlockedError));
956 // Keep track of navigation for the Bounce Tracking Protection.
957 if (browsingContext->IsTopContent()) {
958 RefPtr<BounceTrackingState> bounceTrackingState =
959 browsingContext->GetBounceTrackingState();
961 // Not every browsing context has a BounceTrackingState. It's also null when
962 // the feature is disabled.
963 if (bounceTrackingState) {
964 nsCOMPtr<nsIPrincipal> triggeringPrincipal;
965 nsresult rv =
966 loadInfo->GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal));
968 if (!NS_WARN_IF(NS_FAILED(rv))) {
969 DebugOnly<nsresult> rv = bounceTrackingState->OnStartNavigation(
970 triggeringPrincipal, loadInfo->GetHasValidUserGestureActivation());
971 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
972 "BounceTrackingState::OnStartNavigation failed");
977 return Open(aLoadState, loadInfo, loadFlags, aCacheKey, aChannelId,
978 aAsyncOpenTime, aTiming, std::move(aInfo), false, aContentParent,
979 aRv);
982 auto DocumentLoadListener::OpenObject(
983 nsDocShellLoadState* aLoadState, uint32_t aCacheKey,
984 const Maybe<uint64_t>& aChannelId, const TimeStamp& aAsyncOpenTime,
985 nsDOMNavigationTiming* aTiming, Maybe<dom::ClientInfo>&& aInfo,
986 uint64_t aInnerWindowId, nsLoadFlags aLoadFlags,
987 nsContentPolicyType aContentPolicyType, bool aUrgentStart,
988 dom::ContentParent* aContentParent,
989 ObjectUpgradeHandler* aObjectUpgradeHandler, nsresult* aRv)
990 -> RefPtr<OpenPromise> {
991 LOG(("DocumentLoadListener [%p] OpenObject [uri=%s]", this,
992 aLoadState->URI()->GetSpecOrDefault().get()));
994 MOZ_ASSERT(!mIsDocumentLoad);
996 auto sandboxFlags = aLoadState->TriggeringSandboxFlags();
998 RefPtr<LoadInfo> loadInfo = CreateObjectLoadInfo(
999 aLoadState, aInnerWindowId, aContentPolicyType, sandboxFlags);
1001 mObjectUpgradeHandler = aObjectUpgradeHandler;
1003 return Open(aLoadState, loadInfo, aLoadFlags, aCacheKey, aChannelId,
1004 aAsyncOpenTime, aTiming, std::move(aInfo), aUrgentStart,
1005 aContentParent, aRv);
1008 auto DocumentLoadListener::OpenInParent(nsDocShellLoadState* aLoadState,
1009 bool aSupportsRedirectToRealChannel)
1010 -> RefPtr<OpenPromise> {
1011 MOZ_ASSERT(mIsDocumentLoad);
1013 // We currently only support passing nullptr for aLoadInfo for
1014 // top level browsing contexts.
1015 auto* browsingContext = GetDocumentBrowsingContext();
1016 if (!browsingContext->IsTopContent() ||
1017 !browsingContext->GetContentParent()) {
1018 LOG(("DocumentLoadListener::OpenInParent failed because of subdoc"));
1019 return nullptr;
1022 if (nsCOMPtr<nsIContentSecurityPolicy> csp = aLoadState->Csp()) {
1023 // Check CSP navigate-to
1024 bool allowsNavigateTo = false;
1025 nsresult rv = csp->GetAllowsNavigateTo(aLoadState->URI(),
1026 aLoadState->IsFormSubmission(),
1027 false, /* aWasRedirected */
1028 false, /* aEnforceWhitelist */
1029 &allowsNavigateTo);
1030 if (NS_FAILED(rv) || !allowsNavigateTo) {
1031 return nullptr;
1035 // Clone because this mutates the load flags in the load state, which
1036 // breaks nsDocShells expectations of being able to do it.
1037 RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(*aLoadState);
1038 loadState->CalculateLoadURIFlags();
1040 RefPtr<nsDOMNavigationTiming> timing = new nsDOMNavigationTiming(nullptr);
1041 timing->NotifyNavigationStart(
1042 browsingContext->IsActive()
1043 ? nsDOMNavigationTiming::DocShellState::eActive
1044 : nsDOMNavigationTiming::DocShellState::eInactive);
1046 const mozilla::dom::LoadingSessionHistoryInfo* loadingInfo =
1047 loadState->GetLoadingSessionHistoryInfo();
1049 uint32_t cacheKey = 0;
1050 auto loadType = aLoadState->LoadType();
1051 if (loadType == LOAD_HISTORY || loadType == LOAD_RELOAD_NORMAL ||
1052 loadType == LOAD_RELOAD_CHARSET_CHANGE ||
1053 loadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE ||
1054 loadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE) {
1055 if (loadingInfo) {
1056 cacheKey = loadingInfo->mInfo.GetCacheKey();
1060 // Loads start in the content process might have exposed a channel id to
1061 // observers, so we need to preserve the value in the parent. That can't have
1062 // happened here, so Nothing() is fine.
1063 Maybe<uint64_t> channelId = Nothing();
1065 // Initial client info is only relevant for subdocument loads, which we're
1066 // not supporting yet.
1067 Maybe<dom::ClientInfo> initialClientInfo;
1069 mSupportsRedirectToRealChannel = aSupportsRedirectToRealChannel;
1071 // This is a top-level load, so rebuild the LoadInfo from scratch,
1072 // since in the parent the
1073 // content process won't have provided us with an existing one.
1074 RefPtr<LoadInfo> loadInfo =
1075 CreateDocumentLoadInfo(browsingContext, aLoadState);
1077 nsLoadFlags loadFlags = loadState->CalculateChannelLoadFlags(
1078 browsingContext,
1079 Some(loadingInfo && loadingInfo->mInfo.GetURIWasModified()), Nothing());
1081 nsresult rv;
1082 return Open(loadState, loadInfo, loadFlags, cacheKey, channelId,
1083 TimeStamp::Now(), timing, std::move(initialClientInfo), false,
1084 browsingContext->GetContentParent(), &rv);
1087 base::ProcessId DocumentLoadListener::OtherPid() const {
1088 return mContentParent ? mContentParent->OtherPid() : base::ProcessId{0};
1091 void DocumentLoadListener::FireStateChange(uint32_t aStateFlags,
1092 nsresult aStatus) {
1093 nsCOMPtr<nsIChannel> request = GetChannel();
1095 RefPtr<BrowsingContextWebProgress> webProgress =
1096 GetLoadingBrowsingContext()->GetWebProgress();
1098 if (webProgress) {
1099 NS_DispatchToMainThread(
1100 NS_NewRunnableFunction("DocumentLoadListener::FireStateChange", [=]() {
1101 webProgress->OnStateChange(webProgress, request, aStateFlags,
1102 aStatus);
1103 }));
1107 static void SetNavigating(CanonicalBrowsingContext* aBrowsingContext,
1108 bool aNavigating) {
1109 nsCOMPtr<nsIBrowser> browser;
1110 if (RefPtr<Element> currentElement = aBrowsingContext->GetEmbedderElement()) {
1111 browser = currentElement->AsBrowser();
1114 if (!browser) {
1115 return;
1118 NS_DispatchToMainThread(NS_NewRunnableFunction(
1119 "DocumentLoadListener::SetNavigating",
1120 [browser, aNavigating]() { browser->SetIsNavigating(aNavigating); }));
1123 /* static */ bool DocumentLoadListener::LoadInParent(
1124 CanonicalBrowsingContext* aBrowsingContext, nsDocShellLoadState* aLoadState,
1125 bool aSetNavigating) {
1126 SetNavigating(aBrowsingContext, aSetNavigating);
1128 RefPtr<DocumentLoadListener> load =
1129 new DocumentLoadListener(aBrowsingContext, true);
1130 RefPtr<DocumentLoadListener::OpenPromise> promise = load->OpenInParent(
1131 aLoadState, /* aSupportsRedirectToRealChannel */ false);
1132 if (!promise) {
1133 SetNavigating(aBrowsingContext, false);
1134 return false;
1137 // We passed false for aSupportsRedirectToRealChannel, so we should always
1138 // take the process switching path, and reject this promise.
1139 promise->Then(
1140 GetCurrentSerialEventTarget(), __func__,
1141 [load](DocumentLoadListener::OpenPromise::ResolveOrRejectValue&& aValue) {
1142 MOZ_ASSERT(aValue.IsReject());
1143 DocumentLoadListener::OpenPromiseFailedType& rejectValue =
1144 aValue.RejectValue();
1145 if (!rejectValue.mContinueNavigating) {
1146 // If we're not switching the load to a new process, then it is
1147 // finished (and failed), and we should fire a state change to notify
1148 // observers. Normally the docshell would fire this, and it would get
1149 // filtered out by BrowserParent if needed.
1150 load->FireStateChange(nsIWebProgressListener::STATE_STOP |
1151 nsIWebProgressListener::STATE_IS_WINDOW |
1152 nsIWebProgressListener::STATE_IS_NETWORK,
1153 rejectValue.mStatus);
1157 load->FireStateChange(nsIWebProgressListener::STATE_START |
1158 nsIWebProgressListener::STATE_IS_DOCUMENT |
1159 nsIWebProgressListener::STATE_IS_REQUEST |
1160 nsIWebProgressListener::STATE_IS_WINDOW |
1161 nsIWebProgressListener::STATE_IS_NETWORK,
1162 NS_OK);
1163 SetNavigating(aBrowsingContext, false);
1164 return true;
1167 /* static */
1168 bool DocumentLoadListener::SpeculativeLoadInParent(
1169 dom::CanonicalBrowsingContext* aBrowsingContext,
1170 nsDocShellLoadState* aLoadState) {
1171 LOG(("DocumentLoadListener::OpenFromParent"));
1173 RefPtr<DocumentLoadListener> listener =
1174 new DocumentLoadListener(aBrowsingContext, true);
1176 auto promise = listener->OpenInParent(aLoadState, true);
1177 if (promise) {
1178 // Create an entry in the redirect channel registrar to
1179 // allocate an identifier for this load.
1180 nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
1181 RedirectChannelRegistrar::GetOrCreate();
1182 uint64_t loadIdentifier = aLoadState->GetLoadIdentifier();
1183 DebugOnly<nsresult> rv =
1184 registrar->RegisterChannel(nullptr, loadIdentifier);
1185 MOZ_ASSERT(NS_SUCCEEDED(rv));
1186 // Register listener (as an nsIParentChannel) under our new identifier.
1187 rv = registrar->LinkChannels(loadIdentifier, listener, nullptr);
1188 MOZ_ASSERT(NS_SUCCEEDED(rv));
1190 return !!promise;
1193 void DocumentLoadListener::CleanupParentLoadAttempt(uint64_t aLoadIdent) {
1194 nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
1195 RedirectChannelRegistrar::GetOrCreate();
1197 nsCOMPtr<nsIParentChannel> parentChannel;
1198 registrar->GetParentChannel(aLoadIdent, getter_AddRefs(parentChannel));
1199 RefPtr<DocumentLoadListener> loadListener = do_QueryObject(parentChannel);
1201 if (loadListener) {
1202 // If the load listener is still registered, then we must have failed
1203 // to connect DocumentChannel into it. Better cancel it!
1204 loadListener->NotifyDocumentChannelFailed();
1207 registrar->DeregisterChannels(aLoadIdent);
1210 auto DocumentLoadListener::ClaimParentLoad(DocumentLoadListener** aListener,
1211 uint64_t aLoadIdent,
1212 Maybe<uint64_t> aChannelId)
1213 -> RefPtr<OpenPromise> {
1214 nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
1215 RedirectChannelRegistrar::GetOrCreate();
1217 nsCOMPtr<nsIParentChannel> parentChannel;
1218 registrar->GetParentChannel(aLoadIdent, getter_AddRefs(parentChannel));
1219 RefPtr<DocumentLoadListener> loadListener = do_QueryObject(parentChannel);
1220 registrar->DeregisterChannels(aLoadIdent);
1222 if (!loadListener) {
1223 // The parent went away unexpectedly.
1224 *aListener = nullptr;
1225 return nullptr;
1228 loadListener->mDocumentChannelId = aChannelId;
1230 MOZ_DIAGNOSTIC_ASSERT(loadListener->mOpenPromise);
1231 loadListener.forget(aListener);
1233 return (*aListener)->mOpenPromise;
1236 void DocumentLoadListener::NotifyDocumentChannelFailed() {
1237 LOG(("DocumentLoadListener NotifyDocumentChannelFailed [this=%p]", this));
1238 // There's been no calls to ClaimParentLoad, and so no listeners have been
1239 // attached to mOpenPromise yet. As such we can run Then() on it.
1240 mOpenPromise->Then(
1241 GetMainThreadSerialEventTarget(), __func__,
1242 [](DocumentLoadListener::OpenPromiseSucceededType&& aResolveValue) {
1243 aResolveValue.mPromise->Resolve(NS_BINDING_ABORTED, __func__);
1245 []() {});
1247 Cancel(NS_BINDING_ABORTED,
1248 "DocumentLoadListener::NotifyDocumentChannelFailed"_ns);
1251 void DocumentLoadListener::Disconnect(bool aContinueNavigating) {
1252 LOG(("DocumentLoadListener Disconnect [this=%p, aContinueNavigating=%d]",
1253 this, aContinueNavigating));
1254 // The nsHttpChannel may have a reference to this parent, release it
1255 // to avoid circular references.
1256 RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
1257 if (httpChannelImpl) {
1258 httpChannelImpl->SetWarningReporter(nullptr);
1259 httpChannelImpl->SetEarlyHintObserver(nullptr);
1262 // Don't cancel ongoing early hints when continuing to load the web page.
1263 // Early hints are loaded earlier in the code and shouldn't get cancelled
1264 // here. See also: Bug 1765652
1265 if (!aContinueNavigating) {
1266 mEarlyHintsService.Cancel("DocumentLoadListener::Disconnect"_ns);
1269 if (auto* ctx = GetDocumentBrowsingContext()) {
1270 ctx->EndDocumentLoad(aContinueNavigating);
1274 void DocumentLoadListener::Cancel(const nsresult& aStatusCode,
1275 const nsACString& aReason) {
1276 LOG(
1277 ("DocumentLoadListener Cancel [this=%p, "
1278 "aStatusCode=%" PRIx32 " ]",
1279 this, static_cast<uint32_t>(aStatusCode)));
1280 if (mOpenPromiseResolved) {
1281 return;
1283 if (mChannel) {
1284 mChannel->CancelWithReason(aStatusCode, aReason);
1287 DisconnectListeners(aStatusCode, aStatusCode);
1290 void DocumentLoadListener::DisconnectListeners(nsresult aStatus,
1291 nsresult aLoadGroupStatus,
1292 bool aContinueNavigating) {
1293 LOG(
1294 ("DocumentLoadListener DisconnectListener [this=%p, "
1295 "aStatus=%" PRIx32 ", aLoadGroupStatus=%" PRIx32
1296 ", aContinueNavigating=%d]",
1297 this, static_cast<uint32_t>(aStatus),
1298 static_cast<uint32_t>(aLoadGroupStatus), aContinueNavigating));
1300 RejectOpenPromise(aStatus, aLoadGroupStatus, aContinueNavigating, __func__);
1302 Disconnect(aContinueNavigating);
1304 // Clear any pending stream filter requests. If we're going to be sending a
1305 // response to the content process due to a navigation, our caller will have
1306 // already stashed the array to be passed to `TriggerRedirectToRealChannel`,
1307 // so it's safe for us to clear here.
1308 // TODO: If we retargeted the stream to a non-default handler (e.g. to trigger
1309 // a download), we currently never attach a stream filter. Should we attach a
1310 // stream filter in those situations as well?
1311 mStreamFilterRequests.Clear();
1314 void DocumentLoadListener::RedirectToRealChannelFinished(nsresult aRv) {
1315 LOG(
1316 ("DocumentLoadListener RedirectToRealChannelFinished [this=%p, "
1317 "aRv=%" PRIx32 " ]",
1318 this, static_cast<uint32_t>(aRv)));
1319 if (NS_FAILED(aRv)) {
1320 FinishReplacementChannelSetup(aRv);
1321 return;
1324 // Wait for background channel ready on target channel
1325 nsCOMPtr<nsIRedirectChannelRegistrar> redirectReg =
1326 RedirectChannelRegistrar::GetOrCreate();
1327 MOZ_ASSERT(redirectReg);
1329 nsCOMPtr<nsIParentChannel> redirectParentChannel;
1330 redirectReg->GetParentChannel(mRedirectChannelId,
1331 getter_AddRefs(redirectParentChannel));
1332 if (!redirectParentChannel) {
1333 FinishReplacementChannelSetup(NS_ERROR_FAILURE);
1334 return;
1337 nsCOMPtr<nsIParentRedirectingChannel> redirectingParent =
1338 do_QueryInterface(redirectParentChannel);
1339 if (!redirectingParent) {
1340 // Continue verification procedure if redirecting to non-Http protocol
1341 FinishReplacementChannelSetup(NS_OK);
1342 return;
1345 // Ask redirected channel if verification can proceed.
1346 // ReadyToVerify will be invoked when redirected channel is ready.
1347 redirectingParent->ContinueVerification(this);
1350 NS_IMETHODIMP
1351 DocumentLoadListener::ReadyToVerify(nsresult aResultCode) {
1352 FinishReplacementChannelSetup(aResultCode);
1353 return NS_OK;
1356 void DocumentLoadListener::FinishReplacementChannelSetup(nsresult aResult) {
1357 LOG(
1358 ("DocumentLoadListener FinishReplacementChannelSetup [this=%p, "
1359 "aResult=%x]",
1360 this, int(aResult)));
1362 auto endDocumentLoad = MakeScopeExit([&]() {
1363 if (auto* ctx = GetDocumentBrowsingContext()) {
1364 ctx->EndDocumentLoad(false);
1367 mStreamFilterRequests.Clear();
1369 nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
1370 RedirectChannelRegistrar::GetOrCreate();
1371 MOZ_ASSERT(registrar);
1373 nsCOMPtr<nsIParentChannel> redirectChannel;
1374 nsresult rv = registrar->GetParentChannel(mRedirectChannelId,
1375 getter_AddRefs(redirectChannel));
1376 if (NS_FAILED(rv) || !redirectChannel) {
1377 aResult = NS_ERROR_FAILURE;
1380 // Release all previously registered channels, they are no longer needed to
1381 // be kept in the registrar from this moment.
1382 registrar->DeregisterChannels(mRedirectChannelId);
1383 mRedirectChannelId = 0;
1384 if (NS_FAILED(aResult)) {
1385 if (redirectChannel) {
1386 redirectChannel->Delete();
1388 mChannel->Cancel(aResult);
1389 mChannel->Resume();
1390 return;
1393 MOZ_ASSERT(
1394 !SameCOMIdentity(redirectChannel, static_cast<nsIParentChannel*>(this)));
1396 redirectChannel->SetParentListener(mParentChannelListener);
1398 ApplyPendingFunctions(redirectChannel);
1400 if (!ResumeSuspendedChannel(redirectChannel)) {
1401 nsCOMPtr<nsILoadGroup> loadGroup;
1402 mChannel->GetLoadGroup(getter_AddRefs(loadGroup));
1403 if (loadGroup) {
1404 // We added ourselves to the load group, but attempting
1405 // to resume has notified us that the channel is already
1406 // finished. Better remove ourselves from the loadgroup
1407 // again. The only time the channel will be in a loadgroup
1408 // is if we're connected to the parent process.
1409 nsresult status = NS_OK;
1410 mChannel->GetStatus(&status);
1411 loadGroup->RemoveRequest(mChannel, nullptr, status);
1416 void DocumentLoadListener::ApplyPendingFunctions(
1417 nsIParentChannel* aChannel) const {
1418 // We stored the values from all nsIParentChannel functions called since we
1419 // couldn't handle them. Copy them across to the real channel since it
1420 // should know what to do.
1422 nsCOMPtr<nsIParentChannel> parentChannel = aChannel;
1423 for (const auto& variant : mIParentChannelFunctions) {
1424 variant.match(
1425 [parentChannel](const ClassifierMatchedInfoParams& aParams) {
1426 parentChannel->SetClassifierMatchedInfo(
1427 aParams.mList, aParams.mProvider, aParams.mFullHash);
1429 [parentChannel](const ClassifierMatchedTrackingInfoParams& aParams) {
1430 parentChannel->SetClassifierMatchedTrackingInfo(aParams.mLists,
1431 aParams.mFullHashes);
1433 [parentChannel](const ClassificationFlagsParams& aParams) {
1434 parentChannel->NotifyClassificationFlags(aParams.mClassificationFlags,
1435 aParams.mIsThirdParty);
1439 RefPtr<HttpChannelSecurityWarningReporter> reporter;
1440 if (RefPtr<HttpChannelParent> httpParent = do_QueryObject(aChannel)) {
1441 reporter = httpParent;
1442 } else if (RefPtr<nsHttpChannel> httpChannel = do_QueryObject(aChannel)) {
1443 reporter = httpChannel->GetWarningReporter();
1445 if (reporter) {
1446 for (const auto& variant : mSecurityWarningFunctions) {
1447 variant.match(
1448 [reporter](const ReportSecurityMessageParams& aParams) {
1449 Unused << reporter->ReportSecurityMessage(aParams.mMessageTag,
1450 aParams.mMessageCategory);
1452 [reporter](const LogBlockedCORSRequestParams& aParams) {
1453 Unused << reporter->LogBlockedCORSRequest(
1454 aParams.mMessage, aParams.mCategory, aParams.mIsWarning);
1456 [reporter](const LogMimeTypeMismatchParams& aParams) {
1457 Unused << reporter->LogMimeTypeMismatch(
1458 aParams.mMessageName, aParams.mWarning, aParams.mURL,
1459 aParams.mContentType);
1465 bool DocumentLoadListener::ResumeSuspendedChannel(
1466 nsIStreamListener* aListener) {
1467 LOG(("DocumentLoadListener ResumeSuspendedChannel [this=%p]", this));
1468 RefPtr<nsHttpChannel> httpChannel = do_QueryObject(mChannel);
1469 if (httpChannel) {
1470 httpChannel->SetApplyConversion(mOldApplyConversion);
1473 if (!mIsFinished) {
1474 mParentChannelListener->SetListenerAfterRedirect(aListener);
1477 // If we failed to suspend the channel, then we might have received
1478 // some messages while the redirected was being handled.
1479 // Manually send them on now.
1480 nsTArray<StreamListenerFunction> streamListenerFunctions =
1481 std::move(mStreamListenerFunctions);
1482 if (!aListener) {
1483 streamListenerFunctions.Clear();
1486 ForwardStreamListenerFunctions(streamListenerFunctions, aListener);
1488 // We don't expect to get new stream listener functions added
1489 // via re-entrancy. If this ever happens, we should understand
1490 // exactly why before allowing it.
1491 NS_ASSERTION(mStreamListenerFunctions.IsEmpty(),
1492 "Should not have added new stream listener function!");
1494 mChannel->Resume();
1496 // Our caller will invoke `EndDocumentLoad` for us.
1498 return !mIsFinished;
1501 void DocumentLoadListener::CancelEarlyHintPreloads() {
1502 mEarlyHintsService.Cancel("DocumentLoadListener::CancelEarlyHintPreloads"_ns);
1505 void DocumentLoadListener::RegisterEarlyHintLinksAndGetConnectArgs(
1506 dom::ContentParentId aCpId, nsTArray<EarlyHintConnectArgs>& aOutLinks) {
1507 mEarlyHintsService.RegisterLinksAndGetConnectArgs(aCpId, aOutLinks);
1510 void DocumentLoadListener::SerializeRedirectData(
1511 RedirectToRealChannelArgs& aArgs, bool aIsCrossProcess,
1512 uint32_t aRedirectFlags, uint32_t aLoadFlags,
1513 nsTArray<EarlyHintConnectArgs>&& aEarlyHints,
1514 uint32_t aEarlyHintLinkType) const {
1515 aArgs.uri() = GetChannelCreationURI();
1516 aArgs.loadIdentifier() = mLoadIdentifier;
1517 aArgs.earlyHints() = std::move(aEarlyHints);
1518 aArgs.earlyHintLinkType() = aEarlyHintLinkType;
1520 // I previously used HttpBaseChannel::CloneLoadInfoForRedirect, but that
1521 // clears the principal to inherit, which fails tests (probably because this
1522 // 'redirect' is usually just an implementation detail). It's also http
1523 // only, and mChannel can be anything that we redirected to.
1524 nsCOMPtr<nsILoadInfo> channelLoadInfo = mChannel->LoadInfo();
1525 nsCOMPtr<nsIPrincipal> principalToInherit;
1526 channelLoadInfo->GetPrincipalToInherit(getter_AddRefs(principalToInherit));
1528 const RefPtr<nsHttpChannel> baseChannel = do_QueryObject(mChannel);
1530 nsCOMPtr<nsILoadContext> loadContext;
1531 NS_QueryNotificationCallbacks(mChannel, loadContext);
1532 nsCOMPtr<nsILoadInfo> redirectLoadInfo;
1534 // Only use CloneLoadInfoForRedirect if we have a load context,
1535 // since it internally tries to pull OriginAttributes from the
1536 // the load context and asserts if they don't match the load info.
1537 // We can end up without a load context if the channel has been aborted
1538 // and the callbacks have been cleared.
1539 if (baseChannel && loadContext) {
1540 redirectLoadInfo = baseChannel->CloneLoadInfoForRedirect(
1541 aArgs.uri(), nsIChannelEventSink::REDIRECT_INTERNAL);
1542 redirectLoadInfo->SetResultPrincipalURI(aArgs.uri());
1544 // The clone process clears this, and then we fail tests..
1545 // docshell/test/mochitest/test_forceinheritprincipal_overrule_owner.html
1546 if (principalToInherit) {
1547 redirectLoadInfo->SetPrincipalToInherit(principalToInherit);
1549 } else {
1550 redirectLoadInfo =
1551 static_cast<mozilla::net::LoadInfo*>(channelLoadInfo.get())->Clone();
1553 redirectLoadInfo->AppendRedirectHistoryEntry(mChannel, true);
1556 const Maybe<ClientInfo>& reservedClientInfo =
1557 channelLoadInfo->GetReservedClientInfo();
1558 if (reservedClientInfo) {
1559 redirectLoadInfo->SetReservedClientInfo(*reservedClientInfo);
1562 aArgs.registrarId() = mRedirectChannelId;
1564 #ifdef DEBUG
1565 // We only set the granularFingerprintingProtection field when opening http
1566 // channels. So, we mark the field as set here if the channel is not a
1567 // nsHTTPChannel to pass the assertion check for getting this field in below
1568 // LoadInfoToLoadInfoArgs() call.
1569 if (!baseChannel) {
1570 static_cast<mozilla::net::LoadInfo*>(redirectLoadInfo.get())
1571 ->MarkOverriddenFingerprintingSettingsAsSet();
1573 #endif
1575 MOZ_ALWAYS_SUCCEEDS(
1576 ipc::LoadInfoToLoadInfoArgs(redirectLoadInfo, &aArgs.loadInfo()));
1578 mChannel->GetOriginalURI(getter_AddRefs(aArgs.originalURI()));
1580 // mChannel can be a nsHttpChannel as well as InterceptedHttpChannel so we
1581 // can't use baseChannel here.
1582 if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel)) {
1583 MOZ_ALWAYS_SUCCEEDS(httpChannel->GetChannelId(&aArgs.channelId()));
1586 aArgs.redirectMode() = nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW;
1587 nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
1588 do_QueryInterface(mChannel);
1589 if (httpChannelInternal) {
1590 MOZ_ALWAYS_SUCCEEDS(
1591 httpChannelInternal->GetRedirectMode(&aArgs.redirectMode()));
1594 if (baseChannel) {
1595 aArgs.init() =
1596 Some(baseChannel
1597 ->CloneReplacementChannelConfig(
1598 true, aRedirectFlags,
1599 HttpBaseChannel::ReplacementReason::DocumentChannel)
1600 .Serialize());
1603 uint32_t contentDispositionTemp;
1604 nsresult rv = mChannel->GetContentDisposition(&contentDispositionTemp);
1605 if (NS_SUCCEEDED(rv)) {
1606 aArgs.contentDisposition() = Some(contentDispositionTemp);
1609 nsString contentDispositionFilenameTemp;
1610 rv = mChannel->GetContentDispositionFilename(contentDispositionFilenameTemp);
1611 if (NS_SUCCEEDED(rv)) {
1612 aArgs.contentDispositionFilename() = Some(contentDispositionFilenameTemp);
1615 SetNeedToAddURIVisit(mChannel, false);
1617 aArgs.newLoadFlags() = aLoadFlags;
1618 aArgs.redirectFlags() = aRedirectFlags;
1619 aArgs.properties() = do_QueryObject(mChannel);
1620 aArgs.srcdocData() = mSrcdocData;
1621 aArgs.baseUri() = mBaseURI;
1622 aArgs.loadStateExternalLoadFlags() = mLoadStateExternalLoadFlags;
1623 aArgs.loadStateInternalLoadFlags() = mLoadStateInternalLoadFlags;
1624 aArgs.loadStateLoadType() = mLoadStateLoadType;
1625 aArgs.originalUriString() = mOriginalUriString;
1626 if (mLoadingSessionHistoryInfo) {
1627 aArgs.loadingSessionHistoryInfo().emplace(*mLoadingSessionHistoryInfo);
1631 static bool IsFirstLoadInWindow(nsIChannel* aChannel) {
1632 if (nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aChannel)) {
1633 bool tmp = false;
1634 nsresult rv =
1635 props->GetPropertyAsBool(u"docshell.newWindowTarget"_ns, &tmp);
1636 return NS_SUCCEEDED(rv) && tmp;
1638 return false;
1641 // Get where the document loaded by this nsIChannel should be rendered. This
1642 // will be `OPEN_CURRENTWINDOW` unless we're loading an attachment which would
1643 // normally open in an external program, but we're instead choosing to render
1644 // internally.
1645 static int32_t GetWhereToOpen(nsIChannel* aChannel, bool aIsDocumentLoad) {
1646 // Ignore content disposition for loads from an object or embed element.
1647 if (!aIsDocumentLoad) {
1648 return nsIBrowserDOMWindow::OPEN_CURRENTWINDOW;
1651 // Always continue in the same window if we're not loading an attachment.
1652 uint32_t disposition = nsIChannel::DISPOSITION_INLINE;
1653 if (NS_FAILED(aChannel->GetContentDisposition(&disposition)) ||
1654 disposition != nsIChannel::DISPOSITION_ATTACHMENT) {
1655 return nsIBrowserDOMWindow::OPEN_CURRENTWINDOW;
1658 // If the channel is for a new window target, continue in the same window.
1659 if (IsFirstLoadInWindow(aChannel)) {
1660 return nsIBrowserDOMWindow::OPEN_CURRENTWINDOW;
1663 // Respect the user's preferences with browser.link.open_newwindow
1664 // FIXME: There should probably be a helper for this, as the logic is
1665 // duplicated in a few places.
1666 int32_t where = Preferences::GetInt("browser.link.open_newwindow",
1667 nsIBrowserDOMWindow::OPEN_NEWTAB);
1668 if (where == nsIBrowserDOMWindow::OPEN_CURRENTWINDOW ||
1669 where == nsIBrowserDOMWindow::OPEN_NEWWINDOW ||
1670 where == nsIBrowserDOMWindow::OPEN_NEWTAB) {
1671 return where;
1673 return nsIBrowserDOMWindow::OPEN_NEWTAB;
1676 static DocumentLoadListener::ProcessBehavior GetProcessSwitchBehavior(
1677 Element* aBrowserElement) {
1678 if (aBrowserElement->HasAttribute(u"maychangeremoteness"_ns)) {
1679 return DocumentLoadListener::ProcessBehavior::PROCESS_BEHAVIOR_STANDARD;
1681 nsCOMPtr<nsIBrowser> browser = aBrowserElement->AsBrowser();
1682 bool isRemoteBrowser = false;
1683 browser->GetIsRemoteBrowser(&isRemoteBrowser);
1684 if (isRemoteBrowser) {
1685 return DocumentLoadListener::ProcessBehavior::
1686 PROCESS_BEHAVIOR_SUBFRAME_ONLY;
1688 return DocumentLoadListener::ProcessBehavior::PROCESS_BEHAVIOR_DISABLED;
1691 static bool ContextCanProcessSwitch(CanonicalBrowsingContext* aBrowsingContext,
1692 WindowGlobalParent* aParentWindow,
1693 bool aSwitchToNewTab) {
1694 if (NS_WARN_IF(!aBrowsingContext)) {
1695 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
1696 ("Process Switch Abort: no browsing context"));
1697 return false;
1699 if (!aBrowsingContext->IsContent()) {
1700 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
1701 ("Process Switch Abort: non-content browsing context"));
1702 return false;
1705 // If we're switching into a new tab, we can skip the remaining checks, as
1706 // we're not actually changing the process of aBrowsingContext, so whether or
1707 // not it is allowed to process switch isn't relevant.
1708 if (aSwitchToNewTab) {
1709 return true;
1712 if (aParentWindow && !aBrowsingContext->UseRemoteSubframes()) {
1713 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
1714 ("Process Switch Abort: remote subframes disabled"));
1715 return false;
1718 if (aParentWindow && aParentWindow->IsInProcess()) {
1719 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
1720 ("Process Switch Abort: Subframe with in-process parent"));
1721 return false;
1724 // Determine what process switching behaviour is being requested by the root
1725 // <browser> element.
1726 Element* browserElement = aBrowsingContext->Top()->GetEmbedderElement();
1727 if (!browserElement) {
1728 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
1729 ("Process Switch Abort: cannot get embedder element"));
1730 return false;
1732 nsCOMPtr<nsIBrowser> browser = browserElement->AsBrowser();
1733 if (!browser) {
1734 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
1735 ("Process Switch Abort: not loaded within nsIBrowser"));
1736 return false;
1739 DocumentLoadListener::ProcessBehavior processBehavior =
1740 GetProcessSwitchBehavior(browserElement);
1742 // Check if the process switch we're considering is disabled by the
1743 // <browser>'s process behavior.
1744 if (processBehavior ==
1745 DocumentLoadListener::ProcessBehavior::PROCESS_BEHAVIOR_DISABLED) {
1746 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
1747 ("Process Switch Abort: switch disabled by <browser>"));
1748 return false;
1750 if (!aParentWindow && processBehavior ==
1751 DocumentLoadListener::ProcessBehavior::
1752 PROCESS_BEHAVIOR_SUBFRAME_ONLY) {
1753 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
1754 ("Process Switch Abort: toplevel switch disabled by <browser>"));
1755 return false;
1758 return true;
1761 static RefPtr<dom::BrowsingContextCallbackReceivedPromise> SwitchToNewTab(
1762 CanonicalBrowsingContext* aLoadingBrowsingContext, int32_t aWhere) {
1763 MOZ_ASSERT(aWhere == nsIBrowserDOMWindow::OPEN_NEWTAB ||
1764 aWhere == nsIBrowserDOMWindow::OPEN_NEWWINDOW,
1765 "Unsupported open location");
1767 auto promise =
1768 MakeRefPtr<dom::BrowsingContextCallbackReceivedPromise::Private>(
1769 __func__);
1771 // Get the nsIBrowserDOMWindow for the given BrowsingContext's tab.
1772 nsCOMPtr<nsIBrowserDOMWindow> browserDOMWindow =
1773 aLoadingBrowsingContext->GetBrowserDOMWindow();
1774 if (NS_WARN_IF(!browserDOMWindow)) {
1775 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
1776 ("Process Switch Abort: Unable to get nsIBrowserDOMWindow"));
1777 promise->Reject(NS_ERROR_FAILURE, __func__);
1778 return promise;
1781 // Open a new content tab by calling into frontend. We don't need to worry
1782 // about the triggering principal or CSP, as createContentWindow doesn't
1783 // actually start loading anything, but use a null principal anyway in case
1784 // something changes.
1785 nsCOMPtr<nsIPrincipal> triggeringPrincipal =
1786 NullPrincipal::Create(aLoadingBrowsingContext->OriginAttributesRef());
1788 RefPtr<nsOpenWindowInfo> openInfo = new nsOpenWindowInfo();
1789 openInfo->mBrowsingContextReadyCallback =
1790 new nsBrowsingContextReadyCallback(promise);
1791 openInfo->mOriginAttributes = aLoadingBrowsingContext->OriginAttributesRef();
1792 openInfo->mParent = aLoadingBrowsingContext;
1793 openInfo->mForceNoOpener = true;
1794 openInfo->mIsRemote = true;
1796 // Do the actual work to open a new tab or window async.
1797 nsresult rv = NS_DispatchToMainThread(NS_NewRunnableFunction(
1798 "DocumentLoadListener::SwitchToNewTab",
1799 [browserDOMWindow, openInfo, aWhere, triggeringPrincipal, promise] {
1800 RefPtr<BrowsingContext> bc;
1801 nsresult rv = browserDOMWindow->CreateContentWindow(
1802 /* uri */ nullptr, openInfo, aWhere,
1803 nsIBrowserDOMWindow::OPEN_NO_REFERRER, triggeringPrincipal,
1804 /* csp */ nullptr, getter_AddRefs(bc));
1805 if (NS_WARN_IF(NS_FAILED(rv))) {
1806 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
1807 ("Process Switch Abort: CreateContentWindow threw"));
1808 promise->Reject(rv, __func__);
1810 if (bc) {
1811 promise->Resolve(bc, __func__);
1813 }));
1814 if (NS_WARN_IF(NS_FAILED(rv))) {
1815 promise->Reject(NS_ERROR_UNEXPECTED, __func__);
1817 return promise;
1820 bool DocumentLoadListener::MaybeTriggerProcessSwitch(
1821 bool* aWillSwitchToRemote) {
1822 MOZ_ASSERT(XRE_IsParentProcess());
1823 MOZ_DIAGNOSTIC_ASSERT(mChannel);
1824 MOZ_DIAGNOSTIC_ASSERT(mParentChannelListener);
1825 MOZ_DIAGNOSTIC_ASSERT(aWillSwitchToRemote);
1827 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
1828 ("DocumentLoadListener MaybeTriggerProcessSwitch [this=%p, uri=%s, "
1829 "browserid=%" PRIx64 "]",
1830 this, GetChannelCreationURI()->GetSpecOrDefault().get(),
1831 GetLoadingBrowsingContext()->Top()->BrowserId()));
1833 // If we're doing an <object>/<embed> load, we may be doing a document load at
1834 // this point. We never need to do a process switch for a non-document
1835 // <object> or <embed> load.
1836 if (!mIsDocumentLoad) {
1837 if (!mChannel->IsDocument()) {
1838 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
1839 ("Process Switch Abort: non-document load"));
1840 return false;
1842 nsresult status;
1843 if (!nsObjectLoadingContent::IsSuccessfulRequest(mChannel, &status)) {
1844 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
1845 ("Process Switch Abort: error page"));
1846 return false;
1850 // Check if we should handle this load in a different tab or window.
1851 int32_t where = GetWhereToOpen(mChannel, mIsDocumentLoad);
1852 bool switchToNewTab = where != nsIBrowserDOMWindow::OPEN_CURRENTWINDOW;
1854 // Get the loading BrowsingContext. This may not be the context which will be
1855 // switching processes when switching to a new tab, and in the case of an
1856 // <object> or <embed> element, as we don't create the final context until
1857 // after process selection.
1859 // - /!\ WARNING /!\ -
1860 // Don't use `browsingContext->IsTop()` in this method! It will behave
1861 // incorrectly for non-document loads such as `<object>` or `<embed>`.
1862 // Instead, check whether or not `parentWindow` is null.
1863 RefPtr<CanonicalBrowsingContext> browsingContext =
1864 GetLoadingBrowsingContext();
1865 // If switching to a new tab, the final BC isn't a frame.
1866 RefPtr<WindowGlobalParent> parentWindow =
1867 switchToNewTab ? nullptr : GetParentWindowContext();
1868 if (!ContextCanProcessSwitch(browsingContext, parentWindow, switchToNewTab)) {
1869 return false;
1872 if (!browsingContext->IsOwnedByProcess(GetContentProcessId(mContentParent))) {
1873 MOZ_LOG(gProcessIsolationLog, LogLevel::Error,
1874 ("Process Switch Abort: context no longer owned by creator"));
1875 Cancel(NS_BINDING_ABORTED,
1876 "Process Switch Abort: context no longer owned by creator"_ns);
1877 return false;
1880 if (browsingContext->IsReplaced()) {
1881 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
1882 ("Process Switch Abort: replaced browsing context"));
1883 Cancel(NS_BINDING_ABORTED,
1884 "Process Switch Abort: replaced browsing context"_ns);
1885 return false;
1888 nsAutoCString currentRemoteType(NOT_REMOTE_TYPE);
1889 if (mContentParent) {
1890 currentRemoteType = mContentParent->GetRemoteType();
1893 auto optionsResult = IsolationOptionsForNavigation(
1894 browsingContext->Top(), switchToNewTab ? nullptr : parentWindow.get(),
1895 GetChannelCreationURI(), mChannel, currentRemoteType,
1896 HasCrossOriginOpenerPolicyMismatch(), switchToNewTab, mLoadStateLoadType,
1897 mDocumentChannelId, mRemoteTypeOverride);
1898 if (optionsResult.isErr()) {
1899 MOZ_LOG(gProcessIsolationLog, LogLevel::Error,
1900 ("Process Switch Abort: CheckIsolationForNavigation Failed with %s",
1901 GetStaticErrorName(optionsResult.inspectErr())));
1902 Cancel(optionsResult.unwrapErr(),
1903 "Process Switch Abort: CheckIsolationForNavigation Failed"_ns);
1904 return false;
1907 NavigationIsolationOptions options = optionsResult.unwrap();
1909 if (options.mTryUseBFCache) {
1910 MOZ_ASSERT(!parentWindow, "Can only BFCache toplevel windows");
1911 MOZ_ASSERT(!switchToNewTab, "Can't BFCache for a tab switch");
1912 bool sameOrigin = false;
1913 if (auto* wgp = browsingContext->GetCurrentWindowGlobal()) {
1914 nsCOMPtr<nsIPrincipal> resultPrincipal;
1915 MOZ_ALWAYS_SUCCEEDS(
1916 nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
1917 mChannel, getter_AddRefs(resultPrincipal)));
1918 sameOrigin =
1919 wgp->DocumentPrincipal()->EqualsConsideringDomain(resultPrincipal);
1922 // We only reset the window name for content.
1923 mLoadingSessionHistoryInfo->mForceMaybeResetName.emplace(
1924 StaticPrefs::privacy_window_name_update_enabled() &&
1925 browsingContext->IsContent() && !sameOrigin);
1928 MOZ_LOG(
1929 gProcessIsolationLog, LogLevel::Verbose,
1930 ("CheckIsolationForNavigation -> current:(%s) remoteType:(%s) replace:%d "
1931 "group:%" PRIx64 " bfcache:%d shentry:%p newTab:%d",
1932 currentRemoteType.get(), options.mRemoteType.get(),
1933 options.mReplaceBrowsingContext, options.mSpecificGroupId,
1934 options.mTryUseBFCache, options.mActiveSessionHistoryEntry.get(),
1935 switchToNewTab));
1937 // Check if a process switch is needed.
1938 if (currentRemoteType == options.mRemoteType &&
1939 !options.mReplaceBrowsingContext && !switchToNewTab) {
1940 MOZ_LOG(gProcessIsolationLog, LogLevel::Info,
1941 ("Process Switch Abort: type (%s) is compatible",
1942 options.mRemoteType.get()));
1943 return false;
1946 if (NS_WARN_IF(parentWindow && options.mRemoteType.IsEmpty())) {
1947 MOZ_LOG(gProcessIsolationLog, LogLevel::Error,
1948 ("Process Switch Abort: non-remote target process for subframe"));
1949 return false;
1952 *aWillSwitchToRemote = !options.mRemoteType.IsEmpty();
1954 // If we've decided to re-target this load into a new tab or window (see
1955 // `GetWhereToOpen`), do so before performing a process switch. This will
1956 // require creating the new <browser> to load in, which may be performed
1957 // async.
1958 if (switchToNewTab) {
1959 SwitchToNewTab(browsingContext, where)
1960 ->Then(
1961 GetMainThreadSerialEventTarget(), __func__,
1962 [self = RefPtr{this},
1963 options](const RefPtr<BrowsingContext>& aBrowsingContext) mutable {
1964 if (aBrowsingContext->IsDiscarded()) {
1965 MOZ_LOG(
1966 gProcessIsolationLog, LogLevel::Error,
1967 ("Process Switch: Got invalid new-tab BrowsingContext"));
1968 self->RedirectToRealChannelFinished(NS_ERROR_FAILURE);
1969 return;
1972 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
1973 ("Process Switch: Redirected load to new tab"));
1974 self->TriggerProcessSwitch(aBrowsingContext->Canonical(), options,
1975 /* aIsNewTab */ true);
1977 [self = RefPtr{this}](const CopyableErrorResult&) {
1978 MOZ_LOG(gProcessIsolationLog, LogLevel::Error,
1979 ("Process Switch: SwitchToNewTab failed"));
1980 self->RedirectToRealChannelFinished(NS_ERROR_FAILURE);
1982 return true;
1985 // If we're doing a document load, we can immediately perform a process
1986 // switch.
1987 if (mIsDocumentLoad) {
1988 TriggerProcessSwitch(browsingContext, options);
1989 return true;
1992 // We're not doing a document load, which means we must be performing an
1993 // object load. We need a BrowsingContext to perform the switch in, so will
1994 // trigger an upgrade.
1995 if (!mObjectUpgradeHandler) {
1996 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
1997 ("Process Switch Abort: no object upgrade handler"));
1998 return false;
2001 if (!StaticPrefs::fission_remoteObjectEmbed()) {
2002 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
2003 ("Process Switch Abort: remote <object>/<embed> disabled"));
2004 return false;
2007 mObjectUpgradeHandler->UpgradeObjectLoad()->Then(
2008 GetMainThreadSerialEventTarget(), __func__,
2009 [self = RefPtr{this}, options, parentWindow](
2010 const RefPtr<CanonicalBrowsingContext>& aBrowsingContext) mutable {
2011 if (aBrowsingContext->IsDiscarded() ||
2012 parentWindow != aBrowsingContext->GetParentWindowContext()) {
2013 MOZ_LOG(gProcessIsolationLog, LogLevel::Error,
2014 ("Process Switch: Got invalid BrowsingContext from object "
2015 "upgrade!"));
2016 self->RedirectToRealChannelFinished(NS_ERROR_FAILURE);
2017 return;
2020 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
2021 ("Process Switch: Upgraded Object to Document Load"));
2022 self->TriggerProcessSwitch(aBrowsingContext, options);
2024 [self = RefPtr{this}](nsresult aStatusCode) {
2025 MOZ_ASSERT(NS_FAILED(aStatusCode), "Status should be error");
2026 self->RedirectToRealChannelFinished(aStatusCode);
2028 return true;
2031 void DocumentLoadListener::TriggerProcessSwitch(
2032 CanonicalBrowsingContext* aContext,
2033 const NavigationIsolationOptions& aOptions, bool aIsNewTab) {
2034 MOZ_DIAGNOSTIC_ASSERT(aIsNewTab || aContext->IsOwnedByProcess(
2035 GetContentProcessId(mContentParent)),
2036 "not owned by creator process anymore?");
2037 if (MOZ_LOG_TEST(gProcessIsolationLog, LogLevel::Info)) {
2038 nsCString currentRemoteType = "INVALID"_ns;
2039 aContext->GetCurrentRemoteType(currentRemoteType, IgnoreErrors());
2041 MOZ_LOG(gProcessIsolationLog, LogLevel::Info,
2042 ("Process Switch: Changing Remoteness from '%s' to '%s'",
2043 currentRemoteType.get(), aOptions.mRemoteType.get()));
2046 // Stash our stream filter requests to pass to TriggerRedirectToRealChannel,
2047 // as the call to `DisconnectListeners` will clear our list.
2048 nsTArray<StreamFilterRequest> streamFilterRequests =
2049 std::move(mStreamFilterRequests);
2051 // We're now committing to a process switch, so we can disconnect from
2052 // the listeners in the old process.
2053 // As the navigation is continuing, we don't actually want to cancel the
2054 // request in the old process unless we're redirecting the load into a new
2055 // tab.
2056 DisconnectListeners(NS_BINDING_ABORTED, NS_BINDING_ABORTED, !aIsNewTab);
2058 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
2059 ("Process Switch: Calling ChangeRemoteness"));
2060 aContext->ChangeRemoteness(aOptions, mLoadIdentifier)
2061 ->Then(
2062 GetMainThreadSerialEventTarget(), __func__,
2063 [self = RefPtr{this}, requests = std::move(streamFilterRequests)](
2064 BrowserParent* aBrowserParent) mutable {
2065 MOZ_ASSERT(self->mChannel,
2066 "Something went wrong, channel got cancelled");
2067 self->TriggerRedirectToRealChannel(
2068 Some(aBrowserParent ? aBrowserParent->Manager() : nullptr),
2069 std::move(requests));
2071 [self = RefPtr{this}](nsresult aStatusCode) {
2072 MOZ_ASSERT(NS_FAILED(aStatusCode), "Status should be error");
2073 self->RedirectToRealChannelFinished(aStatusCode);
2077 RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
2078 DocumentLoadListener::RedirectToParentProcess(uint32_t aRedirectFlags,
2079 uint32_t aLoadFlags) {
2080 // This is largely the same as ContentChild::RecvCrossProcessRedirect,
2081 // except without needing to deserialize or create an nsIChildChannel.
2083 RefPtr<nsDocShellLoadState> loadState;
2084 nsDocShellLoadState::CreateFromPendingChannel(
2085 mChannel, mLoadIdentifier, mRedirectChannelId, getter_AddRefs(loadState));
2087 loadState->SetLoadFlags(mLoadStateExternalLoadFlags);
2088 loadState->SetInternalLoadFlags(mLoadStateInternalLoadFlags);
2089 loadState->SetLoadType(mLoadStateLoadType);
2090 if (mLoadingSessionHistoryInfo) {
2091 loadState->SetLoadingSessionHistoryInfo(*mLoadingSessionHistoryInfo);
2094 // This is poorly named now.
2095 RefPtr<ChildProcessChannelListener> processListener =
2096 ChildProcessChannelListener::GetSingleton();
2098 auto promise =
2099 MakeRefPtr<PDocumentChannelParent::RedirectToRealChannelPromise::Private>(
2100 __func__);
2101 promise->UseDirectTaskDispatch(__func__);
2102 auto resolve = [promise](nsresult aResult) {
2103 promise->Resolve(aResult, __func__);
2106 nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>> endpoints;
2107 processListener->OnChannelReady(loadState, mLoadIdentifier,
2108 std::move(endpoints), mTiming,
2109 std::move(resolve));
2111 return promise;
2114 RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
2115 DocumentLoadListener::RedirectToRealChannel(
2116 uint32_t aRedirectFlags, uint32_t aLoadFlags,
2117 const Maybe<ContentParent*>& aDestinationProcess,
2118 nsTArray<ParentEndpoint>&& aStreamFilterEndpoints) {
2119 LOG(
2120 ("DocumentLoadListener RedirectToRealChannel [this=%p] "
2121 "aRedirectFlags=%" PRIx32 ", aLoadFlags=%" PRIx32,
2122 this, aRedirectFlags, aLoadFlags));
2124 if (mIsDocumentLoad) {
2125 // TODO(djg): Add the last URI visit to history if success. Is there a
2126 // better place to handle this? Need access to the updated aLoadFlags.
2127 nsresult status = NS_OK;
2128 mChannel->GetStatus(&status);
2129 bool updateGHistory =
2130 nsDocShell::ShouldUpdateGlobalHistory(mLoadStateLoadType);
2131 if (NS_SUCCEEDED(status) && updateGHistory &&
2132 !net::ChannelIsPost(mChannel)) {
2133 AddURIVisit(mChannel, aLoadFlags);
2137 // Register the new channel and obtain id for it
2138 nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
2139 RedirectChannelRegistrar::GetOrCreate();
2140 MOZ_ASSERT(registrar);
2141 nsCOMPtr<nsIChannel> chan = mChannel;
2142 if (nsCOMPtr<nsIViewSourceChannel> vsc = do_QueryInterface(chan)) {
2143 chan = vsc->GetInnerChannel();
2145 mRedirectChannelId = nsContentUtils::GenerateLoadIdentifier();
2146 MOZ_ALWAYS_SUCCEEDS(registrar->RegisterChannel(chan, mRedirectChannelId));
2148 if (aDestinationProcess) {
2149 RefPtr<ContentParent> cp = *aDestinationProcess;
2150 if (!cp) {
2151 MOZ_ASSERT(aStreamFilterEndpoints.IsEmpty());
2152 return RedirectToParentProcess(aRedirectFlags, aLoadFlags);
2155 if (!cp->CanSend()) {
2156 return PDocumentChannelParent::RedirectToRealChannelPromise::
2157 CreateAndReject(ipc::ResponseRejectReason::SendError, __func__);
2160 nsTArray<EarlyHintConnectArgs> ehArgs;
2161 mEarlyHintsService.RegisterLinksAndGetConnectArgs(cp->ChildID(), ehArgs);
2163 RedirectToRealChannelArgs args;
2164 SerializeRedirectData(args, /* aIsCrossProcess */ true, aRedirectFlags,
2165 aLoadFlags, std::move(ehArgs),
2166 mEarlyHintsService.LinkType());
2167 if (mTiming) {
2168 mTiming->Anonymize(args.uri());
2169 args.timing() = std::move(mTiming);
2172 cp->TransmitBlobDataIfBlobURL(args.uri());
2174 if (CanonicalBrowsingContext* bc = GetDocumentBrowsingContext()) {
2175 if (bc->IsTop() && bc->IsActive()) {
2176 nsContentUtils::RequestGeckoTaskBurst();
2180 return cp->SendCrossProcessRedirect(args,
2181 std::move(aStreamFilterEndpoints));
2184 if (mOpenPromiseResolved) {
2185 LOG(
2186 ("DocumentLoadListener RedirectToRealChannel [this=%p] "
2187 "promise already resolved. Aborting.",
2188 this));
2189 // The promise has already been resolved or aborted, so we have no way to
2190 // return a promise again to the listener which would cancel the operation.
2191 // Reject the promise immediately.
2192 return PDocumentChannelParent::RedirectToRealChannelPromise::
2193 CreateAndResolve(NS_BINDING_ABORTED, __func__);
2196 // This promise will be passed on the promise listener which will
2197 // resolve this promise for us.
2198 auto promise =
2199 MakeRefPtr<PDocumentChannelParent::RedirectToRealChannelPromise::Private>(
2200 __func__);
2202 mOpenPromise->Resolve(
2203 OpenPromiseSucceededType({std::move(aStreamFilterEndpoints),
2204 aRedirectFlags, aLoadFlags,
2205 mEarlyHintsService.LinkType(), promise}),
2206 __func__);
2208 // There is no way we could come back here if the promise had been resolved
2209 // previously. But for clarity and to avoid all doubt, we set this boolean to
2210 // true.
2211 mOpenPromiseResolved = true;
2213 return promise;
2216 void DocumentLoadListener::TriggerRedirectToRealChannel(
2217 const Maybe<ContentParent*>& aDestinationProcess,
2218 nsTArray<StreamFilterRequest> aStreamFilterRequests) {
2219 LOG((
2220 "DocumentLoadListener::TriggerRedirectToRealChannel [this=%p] "
2221 "aDestinationProcess=%" PRId64,
2222 this, aDestinationProcess ? int64_t(*aDestinationProcess) : int64_t(-1)));
2223 // This initiates replacing the current DocumentChannel with a
2224 // protocol specific 'real' channel, maybe in a different process than
2225 // the current DocumentChannelChild, if aDestinationProces is set.
2226 // It registers the current mChannel with the registrar to get an ID
2227 // so that the remote end can setup a new IPDL channel and lookup
2228 // the same underlying channel.
2229 // We expect this process to finish with FinishReplacementChannelSetup
2230 // (for both in-process and process switch cases), where we cleanup
2231 // the registrar and copy across any needed state to the replacing
2232 // IPDL parent object.
2234 nsTArray<ParentEndpoint> parentEndpoints(aStreamFilterRequests.Length());
2235 if (!aStreamFilterRequests.IsEmpty()) {
2236 ContentParent* cp = aDestinationProcess.valueOr(mContentParent);
2237 base::ProcessId pid = cp ? cp->OtherPid() : base::ProcessId{0};
2239 for (StreamFilterRequest& request : aStreamFilterRequests) {
2240 if (!pid) {
2241 request.mPromise->Reject(false, __func__);
2242 request.mPromise = nullptr;
2243 continue;
2245 ParentEndpoint parent;
2246 nsresult rv = extensions::PStreamFilter::CreateEndpoints(
2247 &parent, &request.mChildEndpoint);
2249 if (NS_FAILED(rv)) {
2250 request.mPromise->Reject(false, __func__);
2251 request.mPromise = nullptr;
2252 } else {
2253 parentEndpoints.AppendElement(std::move(parent));
2258 // If we didn't have any redirects, then we pass the REDIRECT_INTERNAL flag
2259 // for this channel switch so that it isn't recorded in session history etc.
2260 // If there were redirect(s), then we want this switch to be recorded as a
2261 // real one, since we have a new URI.
2262 uint32_t redirectFlags = 0;
2263 if (!mHaveVisibleRedirect) {
2264 redirectFlags = nsIChannelEventSink::REDIRECT_INTERNAL;
2267 uint32_t newLoadFlags = nsIRequest::LOAD_NORMAL;
2268 MOZ_ALWAYS_SUCCEEDS(mChannel->GetLoadFlags(&newLoadFlags));
2269 // We're pulling our flags from the inner channel, which may not have this
2270 // flag set on it. This is the case when loading a 'view-source' channel.
2271 if (mIsDocumentLoad || aDestinationProcess) {
2272 newLoadFlags |= nsIChannel::LOAD_DOCUMENT_URI;
2274 if (!aDestinationProcess) {
2275 newLoadFlags |= nsIChannel::LOAD_REPLACE;
2278 // INHIBIT_PERSISTENT_CACHING is clearing during http redirects (from
2279 // both parent and content process channel instances), but only ever
2280 // re-added to the parent-side nsHttpChannel.
2281 // To match that behaviour, we want to explicitly avoid copying this flag
2282 // back to our newly created content side channel, otherwise it can
2283 // affect sub-resources loads in the same load group.
2284 nsCOMPtr<nsIURI> uri;
2285 mChannel->GetURI(getter_AddRefs(uri));
2286 if (uri && uri->SchemeIs("https")) {
2287 newLoadFlags &= ~nsIRequest::INHIBIT_PERSISTENT_CACHING;
2290 RefPtr<DocumentLoadListener> self = this;
2291 RedirectToRealChannel(redirectFlags, newLoadFlags, aDestinationProcess,
2292 std::move(parentEndpoints))
2293 ->Then(
2294 GetCurrentSerialEventTarget(), __func__,
2295 [self, requests = std::move(aStreamFilterRequests)](
2296 const nsresult& aResponse) mutable {
2297 for (StreamFilterRequest& request : requests) {
2298 if (request.mPromise) {
2299 request.mPromise->Resolve(std::move(request.mChildEndpoint),
2300 __func__);
2301 request.mPromise = nullptr;
2304 self->RedirectToRealChannelFinished(aResponse);
2306 [self](const mozilla::ipc::ResponseRejectReason) {
2307 self->RedirectToRealChannelFinished(NS_ERROR_FAILURE);
2311 void DocumentLoadListener::MaybeReportBlockedByURLClassifier(nsresult aStatus) {
2312 auto* browsingContext = GetDocumentBrowsingContext();
2313 if (!browsingContext || browsingContext->IsTop() ||
2314 !StaticPrefs::privacy_trackingprotection_testing_report_blocked_node()) {
2315 return;
2318 if (!UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aStatus)) {
2319 return;
2322 RefPtr<WindowGlobalParent> parent = browsingContext->GetParentWindowContext();
2323 if (parent) {
2324 Unused << parent->SendAddBlockedFrameNodeByClassifier(browsingContext);
2328 bool DocumentLoadListener::DocShellWillDisplayContent(nsresult aStatus) {
2329 if (NS_SUCCEEDED(aStatus)) {
2330 return true;
2333 // Always return errored loads to the <object> or <embed> element's process,
2334 // as load errors will not be rendered as documents.
2335 if (!mIsDocumentLoad) {
2336 return false;
2339 // nsDocShell attempts urifixup on some failure types,
2340 // but also of those also display an error page if we don't
2341 // succeed with fixup, so we don't need to check for it
2342 // here.
2344 auto* loadingContext = GetLoadingBrowsingContext();
2346 bool isInitialDocument = true;
2347 if (WindowGlobalParent* currentWindow =
2348 loadingContext->GetCurrentWindowGlobal()) {
2349 isInitialDocument = currentWindow->IsInitialDocument();
2352 nsresult rv = nsDocShell::FilterStatusForErrorPage(
2353 aStatus, mChannel, mLoadStateLoadType, loadingContext->IsTop(),
2354 loadingContext->GetUseErrorPages(), isInitialDocument, nullptr);
2356 if (NS_SUCCEEDED(rv)) {
2357 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
2358 ("Skipping process switch, as DocShell will not display content "
2359 "(status: %s) %s",
2360 GetStaticErrorName(aStatus),
2361 GetChannelCreationURI()->GetSpecOrDefault().get()));
2364 // If filtering returned a failure code, then an error page will
2365 // be display for that code, so return true;
2366 return NS_FAILED(rv);
2369 bool DocumentLoadListener::MaybeHandleLoadErrorWithURIFixup(nsresult aStatus) {
2370 RefPtr<CanonicalBrowsingContext> bc = GetDocumentBrowsingContext();
2371 if (!bc) {
2372 return false;
2375 nsCOMPtr<nsIInputStream> newPostData;
2376 bool wasSchemelessInput = false;
2377 nsCOMPtr<nsIURI> newURI = nsDocShell::AttemptURIFixup(
2378 mChannel, aStatus, mOriginalUriString, mLoadStateLoadType, bc->IsTop(),
2379 mLoadStateInternalLoadFlags &
2380 nsDocShell::INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP,
2381 bc->UsePrivateBrowsing(), true, getter_AddRefs(newPostData),
2382 &wasSchemelessInput);
2384 // Since aStatus will be NS_OK for 4xx and 5xx error codes we
2385 // have to check each request which was upgraded by https-first.
2386 // If an error (including 4xx and 5xx) occured, then let's check if
2387 // we can downgrade the scheme to HTTP again.
2388 bool isHTTPSFirstFixup = false;
2389 if (!newURI) {
2390 newURI = nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(mChannel,
2391 aStatus);
2392 isHTTPSFirstFixup = true;
2395 if (!newURI) {
2396 return false;
2399 // If we got a new URI, then we should initiate a load with that.
2400 // Notify the listeners that this load is complete (with a code that
2401 // won't trigger an error page), and then start the new one.
2402 DisconnectListeners(NS_BINDING_ABORTED, NS_BINDING_ABORTED);
2404 RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(newURI);
2405 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
2407 nsCOMPtr<nsIContentSecurityPolicy> cspToInherit = loadInfo->GetCspToInherit();
2408 loadState->SetCsp(cspToInherit);
2410 nsCOMPtr<nsIPrincipal> triggeringPrincipal = loadInfo->TriggeringPrincipal();
2411 loadState->SetTriggeringPrincipal(triggeringPrincipal);
2413 loadState->SetPostDataStream(newPostData);
2415 // Record whether the protocol was added through a fixup.
2416 loadState->SetWasSchemelessInput(wasSchemelessInput);
2418 if (isHTTPSFirstFixup) {
2419 // We have to exempt the load from HTTPS-First to prevent a
2420 // upgrade-downgrade loop.
2421 loadState->SetIsExemptFromHTTPSFirstMode(true);
2424 // Ensure to set referrer information in the fallback channel equally to the
2425 // not-upgraded original referrer info.
2427 // A simply copy of the referrer info from the upgraded one leads to problems.
2428 // For example:
2429 // 1. https://some-site.com redirects to http://other-site.com with referrer
2430 // policy
2431 // "no-referrer-when-downgrade".
2432 // 2. https-first upgrades the redirection, so redirects to
2433 // https://other-site.com,
2434 // according to referrer policy the referrer will be send (https-> https)
2435 // 3. Assume other-site.com is not supporting https, https-first performs
2436 // fall-
2437 // back.
2438 // If the referrer info from the upgraded channel gets copied into the
2439 // http fallback channel, the referrer info would contain the referrer
2440 // (https://some-site.com). That would violate the policy
2441 // "no-referrer-when-downgrade". A recreation of the original referrer info
2442 // would ensure us that the referrer is set according to the referrer policy.
2443 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
2444 if (httpChannel) {
2445 nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo();
2446 if (referrerInfo) {
2447 ReferrerPolicy referrerPolicy = referrerInfo->ReferrerPolicy();
2448 nsCOMPtr<nsIURI> originalReferrer = referrerInfo->GetOriginalReferrer();
2449 if (originalReferrer) {
2450 // Create new ReferrerInfo with the original referrer and the referrer
2451 // policy.
2452 nsCOMPtr<nsIReferrerInfo> newReferrerInfo =
2453 new ReferrerInfo(originalReferrer, referrerPolicy);
2454 loadState->SetReferrerInfo(newReferrerInfo);
2459 bc->LoadURI(loadState, false);
2460 return true;
2463 NS_IMETHODIMP
2464 DocumentLoadListener::OnStartRequest(nsIRequest* aRequest) {
2465 LOG(("DocumentLoadListener OnStartRequest [this=%p]", this));
2467 nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest);
2468 if (multiPartChannel) {
2469 multiPartChannel->GetBaseChannel(getter_AddRefs(mChannel));
2470 } else {
2471 mChannel = do_QueryInterface(aRequest);
2473 MOZ_DIAGNOSTIC_ASSERT(mChannel);
2475 if (mHaveVisibleRedirect && GetDocumentBrowsingContext() &&
2476 mLoadingSessionHistoryInfo) {
2477 mLoadingSessionHistoryInfo =
2478 GetDocumentBrowsingContext()->ReplaceLoadingSessionHistoryEntryForLoad(
2479 mLoadingSessionHistoryInfo.get(), mChannel);
2482 RefPtr<nsHttpChannel> httpChannel = do_QueryObject(mChannel);
2484 // Enforce CSP frame-ancestors and x-frame-options checks which
2485 // might cancel the channel.
2486 nsContentSecurityUtils::PerformCSPFrameAncestorAndXFOCheck(mChannel);
2488 // HTTPS-Only Mode tries to upgrade connections to https. Once loading
2489 // is in progress we set that flag so that timeout counter measures
2490 // do not kick in.
2491 if (httpChannel) {
2492 nsCOMPtr<nsILoadInfo> loadInfo = httpChannel->LoadInfo();
2493 bool isPrivateWin = loadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
2494 if (nsHTTPSOnlyUtils::IsHttpsOnlyModeEnabled(isPrivateWin)) {
2495 uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
2496 httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_TOP_LEVEL_LOAD_IN_PROGRESS;
2497 loadInfo->SetHttpsOnlyStatus(httpsOnlyStatus);
2500 if (mLoadingSessionHistoryInfo &&
2501 nsDocShell::ShouldDiscardLayoutState(httpChannel)) {
2502 mLoadingSessionHistoryInfo->mInfo.SetSaveLayoutStateFlag(false);
2506 auto* loadingContext = GetLoadingBrowsingContext();
2507 if (!loadingContext || loadingContext->IsDiscarded()) {
2508 Cancel(NS_ERROR_UNEXPECTED, "No valid LoadingBrowsingContext."_ns);
2509 return NS_ERROR_UNEXPECTED;
2512 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
2513 Cancel(NS_ERROR_ILLEGAL_DURING_SHUTDOWN,
2514 "Aborting OnStartRequest after shutdown started."_ns);
2515 return NS_OK;
2518 // Block top-level data URI navigations if triggered by the web. Logging is
2519 // performed in AllowTopLevelNavigationToDataURI.
2520 if (!nsContentSecurityManager::AllowTopLevelNavigationToDataURI(mChannel)) {
2521 mChannel->Cancel(NS_ERROR_DOM_BAD_URI);
2522 if (loadingContext) {
2523 RefPtr<MaybeCloseWindowHelper> maybeCloseWindowHelper =
2524 new MaybeCloseWindowHelper(loadingContext);
2525 // If a new window was opened specifically for this request, close it
2526 // after blocking the navigation.
2527 maybeCloseWindowHelper->SetShouldCloseWindow(
2528 IsFirstLoadInWindow(mChannel));
2529 Unused << maybeCloseWindowHelper->MaybeCloseWindow();
2531 DisconnectListeners(NS_ERROR_DOM_BAD_URI, NS_ERROR_DOM_BAD_URI);
2532 return NS_OK;
2535 // Generally we want to switch to a real channel even if the request failed,
2536 // since the listener might want to access protocol-specific data (like http
2537 // response headers) in its error handling.
2538 // An exception to this is when nsExtProtocolChannel handled the request and
2539 // returned NS_ERROR_NO_CONTENT, since creating a real one in the content
2540 // process will attempt to handle the URI a second time.
2541 nsresult status = NS_OK;
2542 aRequest->GetStatus(&status);
2543 if (status == NS_ERROR_NO_CONTENT) {
2544 DisconnectListeners(status, status);
2545 return NS_OK;
2548 // PerformCSPFrameAncestorAndXFOCheck may cancel a moz-extension request that
2549 // needs to be handled here. Without this, the resource would be loaded and
2550 // not blocked when the real channel is created in the content process.
2551 if (status == NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION && !httpChannel) {
2552 DisconnectListeners(status, status);
2553 return NS_OK;
2556 // If this was a failed load and we want to try fixing the uri, then
2557 // this will initiate a new load (and disconnect this one), and we don't
2558 // need to do anything else.
2559 if (MaybeHandleLoadErrorWithURIFixup(status)) {
2560 return NS_OK;
2563 mStreamListenerFunctions.AppendElement(StreamListenerFunction{
2564 VariantIndex<0>{}, OnStartRequestParams{aRequest}});
2566 if (mOpenPromiseResolved || mInitiatedRedirectToRealChannel) {
2567 // I we have already resolved the promise, there's no point to continue
2568 // attempting a process switch or redirecting to the real channel.
2569 // We can also have multiple calls to OnStartRequest when dealing with
2570 // multi-part content, but only want to redirect once.
2571 return NS_OK;
2574 // Keep track of server responses resulting in a document for the Bounce
2575 // Tracking Protection.
2576 if (mIsDocumentLoad && GetParentWindowContext() == nullptr &&
2577 loadingContext->IsTopContent()) {
2578 RefPtr<BounceTrackingState> bounceTrackingState =
2579 loadingContext->GetBounceTrackingState();
2581 // Not every browsing context has a BounceTrackingState. It's also null when
2582 // the feature is disabled.
2583 if (bounceTrackingState) {
2584 DebugOnly<nsresult> rv =
2585 bounceTrackingState->OnDocumentStartRequest(mChannel);
2586 NS_WARNING_ASSERTION(
2587 NS_SUCCEEDED(rv),
2588 "BounceTrackingState::OnDocumentStartRequest failed.");
2592 mChannel->Suspend();
2594 mInitiatedRedirectToRealChannel = true;
2596 MaybeReportBlockedByURLClassifier(status);
2598 // Determine if a new process needs to be spawned. If it does, this will
2599 // trigger a cross process switch, and we should hold off on redirecting to
2600 // the real channel.
2601 // If the channel has failed, and the docshell isn't going to display an
2602 // error page for that failure, then don't allow process switching, since
2603 // we just want to keep our existing document.
2604 bool willBeRemote = false;
2605 if (!DocShellWillDisplayContent(status) ||
2606 !MaybeTriggerProcessSwitch(&willBeRemote)) {
2607 // We're not going to be doing a process switch, so redirect to the real
2608 // channel within our current process.
2609 nsTArray<StreamFilterRequest> streamFilterRequests =
2610 std::move(mStreamFilterRequests);
2611 if (!mSupportsRedirectToRealChannel) {
2612 RefPtr<BrowserParent> browserParent = loadingContext->GetBrowserParent();
2613 if (browserParent->Manager() != mContentParent) {
2614 LOG(
2615 ("DocumentLoadListener::RedirectToRealChannel failed because "
2616 "browsingContext no longer owned by creator"));
2617 Cancel(NS_BINDING_ABORTED,
2618 "DocumentLoadListener::RedirectToRealChannel failed because "
2619 "browsingContext no longer owned by creator"_ns);
2620 return NS_OK;
2622 MOZ_DIAGNOSTIC_ASSERT(
2623 browserParent->GetBrowsingContext() == loadingContext,
2624 "make sure the load is going to the right place");
2626 // If the existing process is right for this load, but the bridge doesn't
2627 // support redirects, then we need to do it manually, by faking a process
2628 // switch.
2629 DisconnectListeners(NS_BINDING_ABORTED, NS_BINDING_ABORTED,
2630 /* aContinueNavigating */ true);
2632 // Notify the docshell that it should load using the newly connected
2633 // channel
2634 browserParent->ResumeLoad(mLoadIdentifier);
2636 // Use the current process ID to run the 'process switch' path and connect
2637 // the channel into the current process.
2638 TriggerRedirectToRealChannel(Some(mContentParent),
2639 std::move(streamFilterRequests));
2640 } else {
2641 TriggerRedirectToRealChannel(Nothing(), std::move(streamFilterRequests));
2644 // If we're not switching, then check if we're currently remote.
2645 if (mContentParent) {
2646 willBeRemote = true;
2650 if (httpChannel) {
2651 uint32_t responseStatus = 0;
2652 Unused << httpChannel->GetResponseStatus(&responseStatus);
2653 mEarlyHintsService.FinalResponse(responseStatus);
2654 } else {
2655 mEarlyHintsService.Cancel(
2656 "DocumentLoadListener::OnStartRequest: no httpChannel"_ns);
2659 // If we're going to be delivering this channel to a remote content
2660 // process, then we want to install any required content conversions
2661 // in the content process.
2662 // The caller of this OnStartRequest will install a conversion
2663 // helper after we return if we haven't disabled conversion. Normally
2664 // HttpChannelParent::OnStartRequest would disable conversion, but we're
2665 // defering calling that until later. Manually disable it now to prevent the
2666 // converter from being installed (since we want the child to do it), and
2667 // also save the value so that when we do call
2668 // HttpChannelParent::OnStartRequest, we can have the value as it originally
2669 // was.
2670 if (httpChannel) {
2671 Unused << httpChannel->GetApplyConversion(&mOldApplyConversion);
2672 if (willBeRemote) {
2673 httpChannel->SetApplyConversion(false);
2677 return NS_OK;
2680 NS_IMETHODIMP
2681 DocumentLoadListener::OnStopRequest(nsIRequest* aRequest,
2682 nsresult aStatusCode) {
2683 LOG(("DocumentLoadListener OnStopRequest [this=%p]", this));
2684 mStreamListenerFunctions.AppendElement(StreamListenerFunction{
2685 VariantIndex<2>{}, OnStopRequestParams{aRequest, aStatusCode}});
2687 // If we're not a multi-part channel, then we're finished and we don't
2688 // expect any further events. If we are, then this might be called again,
2689 // so wait for OnAfterLastPart instead.
2690 nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest);
2691 if (!multiPartChannel) {
2692 mIsFinished = true;
2695 mStreamFilterRequests.Clear();
2697 return NS_OK;
2700 NS_IMETHODIMP
2701 DocumentLoadListener::OnDataAvailable(nsIRequest* aRequest,
2702 nsIInputStream* aInputStream,
2703 uint64_t aOffset, uint32_t aCount) {
2704 LOG(("DocumentLoadListener OnDataAvailable [this=%p]", this));
2705 // This isn't supposed to happen, since we suspended the channel, but
2706 // sometimes Suspend just doesn't work. This can happen when we're routing
2707 // through nsUnknownDecoder to sniff the content type, and it doesn't handle
2708 // being suspended. Let's just store the data and manually forward it to our
2709 // redirected channel when it's ready.
2710 nsCString data;
2711 nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount);
2712 NS_ENSURE_SUCCESS(rv, rv);
2714 mStreamListenerFunctions.AppendElement(StreamListenerFunction{
2715 VariantIndex<1>{},
2716 OnDataAvailableParams{aRequest, data, aOffset, aCount}});
2718 return NS_OK;
2721 //-----------------------------------------------------------------------------
2722 // DoucmentLoadListener::nsIMultiPartChannelListener
2723 //-----------------------------------------------------------------------------
2725 NS_IMETHODIMP
2726 DocumentLoadListener::OnAfterLastPart(nsresult aStatus) {
2727 LOG(("DocumentLoadListener OnAfterLastPart [this=%p]", this));
2728 if (!mInitiatedRedirectToRealChannel) {
2729 // if we get here, and we haven't initiated a redirect to a real
2730 // channel, then it means we never got OnStartRequest (maybe a problem?)
2731 // and we retargeted everything.
2732 LOG(("DocumentLoadListener Disconnecting child"));
2733 DisconnectListeners(NS_BINDING_RETARGETED, NS_OK);
2734 return NS_OK;
2736 mStreamListenerFunctions.AppendElement(StreamListenerFunction{
2737 VariantIndex<3>{}, OnAfterLastPartParams{aStatus}});
2738 mIsFinished = true;
2739 return NS_OK;
2742 NS_IMETHODIMP
2743 DocumentLoadListener::GetInterface(const nsIID& aIID, void** result) {
2744 RefPtr<CanonicalBrowsingContext> browsingContext =
2745 GetLoadingBrowsingContext();
2746 if (aIID.Equals(NS_GET_IID(nsILoadContext)) && browsingContext) {
2747 browsingContext.forget(result);
2748 return NS_OK;
2751 return QueryInterface(aIID, result);
2754 ////////////////////////////////////////////////////////////////////////////////
2755 // nsIParentChannel
2756 ////////////////////////////////////////////////////////////////////////////////
2758 NS_IMETHODIMP
2759 DocumentLoadListener::SetParentListener(
2760 mozilla::net::ParentChannelListener* listener) {
2761 // We don't need this (do we?)
2762 return NS_OK;
2765 NS_IMETHODIMP
2766 DocumentLoadListener::SetClassifierMatchedInfo(const nsACString& aList,
2767 const nsACString& aProvider,
2768 const nsACString& aFullHash) {
2769 ClassifierMatchedInfoParams params;
2770 params.mList = aList;
2771 params.mProvider = aProvider;
2772 params.mFullHash = aFullHash;
2774 mIParentChannelFunctions.AppendElement(
2775 IParentChannelFunction{VariantIndex<0>{}, std::move(params)});
2776 return NS_OK;
2779 NS_IMETHODIMP
2780 DocumentLoadListener::SetClassifierMatchedTrackingInfo(
2781 const nsACString& aLists, const nsACString& aFullHash) {
2782 ClassifierMatchedTrackingInfoParams params;
2783 params.mLists = aLists;
2784 params.mFullHashes = aFullHash;
2786 mIParentChannelFunctions.AppendElement(
2787 IParentChannelFunction{VariantIndex<1>{}, std::move(params)});
2788 return NS_OK;
2791 NS_IMETHODIMP
2792 DocumentLoadListener::NotifyClassificationFlags(uint32_t aClassificationFlags,
2793 bool aIsThirdParty) {
2794 mIParentChannelFunctions.AppendElement(IParentChannelFunction{
2795 VariantIndex<2>{},
2796 ClassificationFlagsParams{aClassificationFlags, aIsThirdParty}});
2797 return NS_OK;
2800 NS_IMETHODIMP
2801 DocumentLoadListener::Delete() {
2802 MOZ_ASSERT_UNREACHABLE("This method is unused");
2803 return NS_OK;
2806 NS_IMETHODIMP
2807 DocumentLoadListener::GetRemoteType(nsACString& aRemoteType) {
2808 // FIXME: The remote type here should be pulled from the remote process used
2809 // to create this DLL, not from the current `browsingContext`.
2810 RefPtr<CanonicalBrowsingContext> browsingContext =
2811 GetDocumentBrowsingContext();
2812 if (!browsingContext) {
2813 return NS_ERROR_UNEXPECTED;
2816 ErrorResult error;
2817 browsingContext->GetCurrentRemoteType(aRemoteType, error);
2818 if (error.Failed()) {
2819 aRemoteType = NOT_REMOTE_TYPE;
2821 return NS_OK;
2824 ////////////////////////////////////////////////////////////////////////////////
2825 // nsIChannelEventSink
2826 ////////////////////////////////////////////////////////////////////////////////
2828 NS_IMETHODIMP
2829 DocumentLoadListener::AsyncOnChannelRedirect(
2830 nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
2831 nsIAsyncVerifyRedirectCallback* aCallback) {
2832 LOG(("DocumentLoadListener::AsyncOnChannelRedirect [this=%p flags=%" PRIu32
2833 "]",
2834 this, aFlags));
2835 // We generally don't want to notify the content process about redirects,
2836 // so just update our channel and tell the callback that we're good to go.
2837 mChannel = aNewChannel;
2839 // We need the original URI of the current channel to use to open the real
2840 // channel in the content process. Unfortunately we overwrite the original
2841 // uri of the new channel with the original pre-redirect URI, so grab
2842 // a copy of it now and save it on the loadInfo corresponding to the
2843 // new channel.
2844 nsCOMPtr<nsILoadInfo> loadInfoFromChannel = mChannel->LoadInfo();
2845 MOZ_ASSERT(loadInfoFromChannel);
2846 nsCOMPtr<nsIURI> uri;
2847 mChannel->GetOriginalURI(getter_AddRefs(uri));
2848 loadInfoFromChannel->SetChannelCreationOriginalURI(uri);
2850 // Since we're redirecting away from aOldChannel, we should check if it
2851 // had a COOP mismatch, since we want the final result for this to
2852 // include the state of all channels we redirected through.
2853 nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(aOldChannel);
2854 if (httpChannel) {
2855 bool isCOOPMismatch = false;
2856 Unused << NS_WARN_IF(NS_FAILED(
2857 httpChannel->HasCrossOriginOpenerPolicyMismatch(&isCOOPMismatch)));
2858 mHasCrossOriginOpenerPolicyMismatch |= isCOOPMismatch;
2861 // If HTTPS-Only mode is enabled, we need to check whether the exception-flag
2862 // needs to be removed or set, by asking the PermissionManager.
2863 nsHTTPSOnlyUtils::TestSitePermissionAndPotentiallyAddExemption(mChannel);
2865 // We don't need to confirm internal redirects or record any
2866 // history for them, so just immediately verify and return.
2867 if (aFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
2868 LOG(
2869 ("DocumentLoadListener AsyncOnChannelRedirect [this=%p] "
2870 "flags=REDIRECT_INTERNAL",
2871 this));
2872 aCallback->OnRedirectVerifyCallback(NS_OK);
2873 return NS_OK;
2876 // Cancel cross origin redirects as described by whatwg:
2877 // > Note: [The early hint reponse] is discarded if it is succeeded by a
2878 // > cross-origin redirect.
2879 // https://html.spec.whatwg.org/multipage/semantics.html#early-hints
2880 nsCOMPtr<nsIURI> oldURI;
2881 aOldChannel->GetURI(getter_AddRefs(oldURI));
2882 nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
2883 nsresult rv = ssm->CheckSameOriginURI(oldURI, uri, false, false);
2884 if (NS_FAILED(rv)) {
2885 mEarlyHintsService.Cancel(
2886 "DocumentLoadListener::AsyncOnChannelRedirect: cors redirect"_ns);
2889 if (GetDocumentBrowsingContext()) {
2890 if (!net::ChannelIsPost(aOldChannel)) {
2891 AddURIVisit(aOldChannel, 0);
2892 nsDocShell::SaveLastVisit(aNewChannel, oldURI, aFlags);
2895 mHaveVisibleRedirect |= true;
2897 LOG(
2898 ("DocumentLoadListener AsyncOnChannelRedirect [this=%p] "
2899 "mHaveVisibleRedirect=%c",
2900 this, mHaveVisibleRedirect ? 'T' : 'F'));
2902 // Clear out our nsIParentChannel functions, since a normal parent
2903 // channel would actually redirect and not have those values on the new one.
2904 // We expect the URI classifier to run on the redirected channel with
2905 // the new URI and set these again.
2906 mIParentChannelFunctions.Clear();
2908 // If we had a remote type override, ensure it's been cleared after a
2909 // redirect, as it can't apply anymore.
2910 mRemoteTypeOverride.reset();
2912 #ifdef ANDROID
2913 nsCOMPtr<nsIURI> uriBeingLoaded =
2914 AntiTrackingUtils::MaybeGetDocumentURIBeingLoaded(mChannel);
2916 RefPtr<MozPromise<bool, bool, false>> promise;
2917 RefPtr<CanonicalBrowsingContext> bc =
2918 mParentChannelListener->GetBrowsingContext();
2919 nsCOMPtr<nsIWidget> widget =
2920 bc ? bc->GetParentProcessWidgetContaining() : nullptr;
2921 RefPtr<nsWindow> window = nsWindow::From(widget);
2923 if (window) {
2924 promise = window->OnLoadRequest(uriBeingLoaded,
2925 nsIBrowserDOMWindow::OPEN_CURRENTWINDOW,
2926 nsIWebNavigation::LOAD_FLAGS_IS_REDIRECT,
2927 nullptr, false, bc->IsTopContent());
2930 if (promise) {
2931 RefPtr<nsIAsyncVerifyRedirectCallback> cb = aCallback;
2932 promise->Then(
2933 GetCurrentSerialEventTarget(), __func__,
2934 [=](const MozPromise<bool, bool, false>::ResolveOrRejectValue& aValue) {
2935 if (aValue.IsResolve()) {
2936 bool handled = aValue.ResolveValue();
2937 if (handled) {
2938 cb->OnRedirectVerifyCallback(NS_ERROR_ABORT);
2939 } else {
2940 cb->OnRedirectVerifyCallback(NS_OK);
2944 } else
2945 #endif /* ANDROID */
2947 aCallback->OnRedirectVerifyCallback(NS_OK);
2949 return NS_OK;
2952 nsIURI* DocumentLoadListener::GetChannelCreationURI() const {
2953 nsCOMPtr<nsILoadInfo> channelLoadInfo = mChannel->LoadInfo();
2955 nsCOMPtr<nsIURI> uri;
2956 channelLoadInfo->GetChannelCreationOriginalURI(getter_AddRefs(uri));
2957 if (uri) {
2958 // See channelCreationOriginalURI for more info. We use this instead of the
2959 // originalURI of the channel to help us avoid the situation when we use
2960 // the URI of a redirect that has failed to happen.
2961 return uri;
2964 // Otherwise, get the original URI from the channel.
2965 mChannel->GetOriginalURI(getter_AddRefs(uri));
2966 return uri;
2969 // This method returns the cached result of running the Cross-Origin-Opener
2970 // policy compare algorithm by calling ComputeCrossOriginOpenerPolicyMismatch
2971 bool DocumentLoadListener::HasCrossOriginOpenerPolicyMismatch() const {
2972 // If we found a COOP mismatch on an earlier channel and then
2973 // redirected away from that, we should use that result.
2974 if (mHasCrossOriginOpenerPolicyMismatch) {
2975 return true;
2978 nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(mChannel);
2979 if (!httpChannel) {
2980 // Not an nsIHttpChannelInternal assume it's okay to switch.
2981 return false;
2984 bool isCOOPMismatch = false;
2985 Unused << NS_WARN_IF(NS_FAILED(
2986 httpChannel->HasCrossOriginOpenerPolicyMismatch(&isCOOPMismatch)));
2987 return isCOOPMismatch;
2990 auto DocumentLoadListener::AttachStreamFilter()
2991 -> RefPtr<ChildEndpointPromise> {
2992 LOG(("DocumentLoadListener AttachStreamFilter [this=%p]", this));
2994 StreamFilterRequest* request = mStreamFilterRequests.AppendElement();
2995 request->mPromise = new ChildEndpointPromise::Private(__func__);
2996 return request->mPromise;
2999 NS_IMETHODIMP DocumentLoadListener::OnProgress(nsIRequest* aRequest,
3000 int64_t aProgress,
3001 int64_t aProgressMax) {
3002 return NS_OK;
3005 NS_IMETHODIMP DocumentLoadListener::OnStatus(nsIRequest* aRequest,
3006 nsresult aStatus,
3007 const char16_t* aStatusArg) {
3008 nsCOMPtr<nsIChannel> channel = mChannel;
3010 RefPtr<BrowsingContextWebProgress> webProgress =
3011 GetLoadingBrowsingContext()->GetWebProgress();
3012 const nsString message(aStatusArg);
3014 if (webProgress) {
3015 NS_DispatchToMainThread(
3016 NS_NewRunnableFunction("DocumentLoadListener::OnStatus", [=]() {
3017 webProgress->OnStatusChange(webProgress, channel, aStatus,
3018 message.get());
3019 }));
3021 return NS_OK;
3024 NS_IMETHODIMP DocumentLoadListener::EarlyHint(const nsACString& aLinkHeader,
3025 const nsACString& aReferrerPolicy,
3026 const nsACString& aCSPHeader) {
3027 LOG(("DocumentLoadListener::EarlyHint.\n"));
3028 mEarlyHintsService.EarlyHint(aLinkHeader, GetChannelCreationURI(), mChannel,
3029 aReferrerPolicy, aCSPHeader, this);
3030 return NS_OK;
3033 } // namespace net
3034 } // namespace mozilla
3036 #undef LOG