Backed out 6 changesets (bug 1835907) for causing multiple failures. CLOSED TREE
[gecko.git] / netwerk / ipc / DocumentLoadListener.cpp
blobe465c421bc2891eb074570c6043d80aaeaf63884
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->IsExemptFromHTTPSOnlyMode()) {
157 uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
158 httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_EXEMPT_NEXT_LOAD;
159 loadInfo->SetHttpsOnlyStatus(httpsOnlyStatus);
162 loadInfo->SetTriggeringSandboxFlags(aLoadState->TriggeringSandboxFlags());
163 loadInfo->SetHasValidUserGestureActivation(
164 aLoadState->HasValidUserGestureActivation());
165 loadInfo->SetIsMetaRefresh(aLoadState->IsMetaRefresh());
167 return loadInfo.forget();
170 // Construct a LoadInfo object to use when creating the internal channel for an
171 // Object/Embed load.
172 static auto CreateObjectLoadInfo(nsDocShellLoadState* aLoadState,
173 uint64_t aInnerWindowId,
174 nsContentPolicyType aContentPolicyType,
175 uint32_t aSandboxFlags)
176 -> already_AddRefed<LoadInfo> {
177 RefPtr<WindowGlobalParent> wgp =
178 WindowGlobalParent::GetByInnerWindowId(aInnerWindowId);
179 MOZ_RELEASE_ASSERT(wgp);
181 auto securityFlags = SecurityFlagsForLoadInfo(aLoadState);
183 RefPtr<LoadInfo> loadInfo = LoadInfo::CreateForNonDocument(
184 wgp, wgp->DocumentPrincipal(), aContentPolicyType, securityFlags,
185 aSandboxFlags);
187 loadInfo->SetHasValidUserGestureActivation(
188 aLoadState->HasValidUserGestureActivation());
189 loadInfo->SetTriggeringSandboxFlags(aLoadState->TriggeringSandboxFlags());
190 loadInfo->SetIsMetaRefresh(aLoadState->IsMetaRefresh());
192 return loadInfo.forget();
196 * An extension to nsDocumentOpenInfo that we run in the parent process, so
197 * that we can make the decision to retarget to content handlers or the external
198 * helper app, before we make process switching decisions.
200 * This modifies the behaviour of nsDocumentOpenInfo so that it can do
201 * retargeting, but doesn't do stream conversion (but confirms that we will be
202 * able to do so later).
204 * We still run nsDocumentOpenInfo in the content process, but disable
205 * retargeting, so that it can only apply stream conversion, and then send data
206 * to the docshell.
208 class ParentProcessDocumentOpenInfo final : public nsDocumentOpenInfo,
209 public nsIMultiPartChannelListener {
210 public:
211 ParentProcessDocumentOpenInfo(ParentChannelListener* aListener,
212 uint32_t aFlags,
213 mozilla::dom::BrowsingContext* aBrowsingContext,
214 bool aIsDocumentLoad)
215 : nsDocumentOpenInfo(aFlags, false),
216 mBrowsingContext(aBrowsingContext),
217 mListener(aListener),
218 mIsDocumentLoad(aIsDocumentLoad) {
219 LOG(("ParentProcessDocumentOpenInfo ctor [this=%p]", this));
222 NS_DECL_ISUPPORTS_INHERITED
224 // The default content listener is always a docshell, so this manually
225 // implements the same checks, and if it succeeds, uses the parent
226 // channel listener so that we forward onto DocumentLoadListener.
227 bool TryDefaultContentListener(nsIChannel* aChannel,
228 const nsCString& aContentType) {
229 uint32_t canHandle = nsWebNavigationInfo::IsTypeSupported(aContentType);
230 if (canHandle != nsIWebNavigationInfo::UNSUPPORTED) {
231 m_targetStreamListener = mListener;
232 nsLoadFlags loadFlags = 0;
233 aChannel->GetLoadFlags(&loadFlags);
234 aChannel->SetLoadFlags(loadFlags | nsIChannel::LOAD_TARGETED);
235 return true;
237 return false;
240 bool TryDefaultContentListener(nsIChannel* aChannel) override {
241 return TryDefaultContentListener(aChannel, mContentType);
244 // Generally we only support stream converters that can tell
245 // use exactly what type they'll output. If we find one, then
246 // we just target to our default listener directly (without
247 // conversion), and the content process nsDocumentOpenInfo will
248 // run and do the actual conversion.
249 nsresult TryStreamConversion(nsIChannel* aChannel) override {
250 // The one exception is nsUnknownDecoder, which works in the parent
251 // (and we need to know what the content type is before we can
252 // decide if it will be handled in the parent), so we run that here.
253 if (mContentType.LowerCaseEqualsASCII(UNKNOWN_CONTENT_TYPE) ||
254 mContentType.IsEmpty()) {
255 return nsDocumentOpenInfo::TryStreamConversion(aChannel);
258 nsresult rv;
259 nsCOMPtr<nsIStreamConverterService> streamConvService =
260 do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
261 nsAutoCString str;
262 rv = streamConvService->ConvertedType(mContentType, aChannel, str);
263 NS_ENSURE_SUCCESS(rv, rv);
265 // We only support passing data to the default content listener
266 // (docshell), and we don't supported chaining converters.
267 if (TryDefaultContentListener(aChannel, str)) {
268 mContentType = str;
269 return NS_OK;
271 // This is the same result as nsStreamConverterService uses when it
272 // can't find a converter
273 return NS_ERROR_FAILURE;
276 nsresult TryExternalHelperApp(nsIExternalHelperAppService* aHelperAppService,
277 nsIChannel* aChannel) override {
278 RefPtr<nsIStreamListener> listener;
279 nsresult rv = aHelperAppService->CreateListener(
280 mContentType, aChannel, mBrowsingContext, false, nullptr,
281 getter_AddRefs(listener));
282 if (NS_SUCCEEDED(rv)) {
283 m_targetStreamListener = listener;
285 return rv;
288 nsDocumentOpenInfo* Clone() override {
289 mCloned = true;
290 return new ParentProcessDocumentOpenInfo(mListener, mFlags,
291 mBrowsingContext, mIsDocumentLoad);
294 nsresult OnDocumentStartRequest(nsIRequest* request) {
295 LOG(("ParentProcessDocumentOpenInfo OnDocumentStartRequest [this=%p]",
296 this));
298 nsresult rv = nsDocumentOpenInfo::OnStartRequest(request);
300 // If we didn't find a content handler,
301 // and we don't have a listener, then just forward to our
302 // default listener. This happens when the channel is in
303 // an error state, and we want to just forward that on to be
304 // handled in the content process.
305 if (NS_SUCCEEDED(rv) && !mUsedContentHandler && !m_targetStreamListener) {
306 m_targetStreamListener = mListener;
307 return m_targetStreamListener->OnStartRequest(request);
309 if (m_targetStreamListener != mListener) {
310 LOG(
311 ("ParentProcessDocumentOpenInfo targeted to non-default listener "
312 "[this=%p]",
313 this));
314 // If this is the only part, then we can immediately tell our listener
315 // that it won't be getting any content and disconnect it. For multipart
316 // channels we have to wait until we've handled all parts before we know.
317 // This does mean that the content process can still Cancel() a multipart
318 // response while the response is being handled externally, but this
319 // matches the single-process behaviour.
320 // If we got cloned, then we don't need to do this, as only the last link
321 // needs to do it.
322 // Multi-part channels are guaranteed to call OnAfterLastPart, which we
323 // forward to the listeners, so it will handle disconnection at that
324 // point.
325 nsCOMPtr<nsIMultiPartChannel> multiPartChannel =
326 do_QueryInterface(request);
327 if (!multiPartChannel && !mCloned) {
328 DisconnectChildListeners(NS_FAILED(rv) ? rv : NS_BINDING_RETARGETED,
329 rv);
332 return rv;
335 nsresult OnObjectStartRequest(nsIRequest* request) {
336 LOG(("ParentProcessDocumentOpenInfo OnObjectStartRequest [this=%p]", this));
337 // Just redirect to the nsObjectLoadingContent in the content process.
338 m_targetStreamListener = mListener;
339 return m_targetStreamListener->OnStartRequest(request);
342 NS_IMETHOD OnStartRequest(nsIRequest* request) override {
343 LOG(("ParentProcessDocumentOpenInfo OnStartRequest [this=%p]", this));
345 if (mIsDocumentLoad) {
346 return OnDocumentStartRequest(request);
349 return OnObjectStartRequest(request);
352 NS_IMETHOD OnAfterLastPart(nsresult aStatus) override {
353 mListener->OnAfterLastPart(aStatus);
354 return NS_OK;
357 private:
358 virtual ~ParentProcessDocumentOpenInfo() {
359 LOG(("ParentProcessDocumentOpenInfo dtor [this=%p]", this));
362 void DisconnectChildListeners(nsresult aStatus, nsresult aLoadGroupStatus) {
363 // Tell the DocumentLoadListener to notify the content process that it's
364 // been entirely retargeted, and to stop waiting.
365 // Clear mListener's pointer to the DocumentLoadListener to break the
366 // reference cycle.
367 RefPtr<DocumentLoadListener> doc = do_GetInterface(ToSupports(mListener));
368 MOZ_ASSERT(doc);
369 doc->DisconnectListeners(aStatus, aLoadGroupStatus);
370 mListener->SetListenerAfterRedirect(nullptr);
373 RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext;
374 RefPtr<ParentChannelListener> mListener;
375 const bool mIsDocumentLoad;
378 * Set to true if we got cloned to create a chained listener.
380 bool mCloned = false;
383 NS_IMPL_ADDREF_INHERITED(ParentProcessDocumentOpenInfo, nsDocumentOpenInfo)
384 NS_IMPL_RELEASE_INHERITED(ParentProcessDocumentOpenInfo, nsDocumentOpenInfo)
386 NS_INTERFACE_MAP_BEGIN(ParentProcessDocumentOpenInfo)
387 NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannelListener)
388 NS_INTERFACE_MAP_END_INHERITING(nsDocumentOpenInfo)
390 NS_IMPL_ADDREF(DocumentLoadListener)
391 NS_IMPL_RELEASE(DocumentLoadListener)
393 NS_INTERFACE_MAP_BEGIN(DocumentLoadListener)
394 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInterfaceRequestor)
395 NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
396 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
397 NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
398 NS_INTERFACE_MAP_ENTRY(nsIParentChannel)
399 NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectReadyCallback)
400 NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
401 NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannelListener)
402 NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
403 NS_INTERFACE_MAP_ENTRY(nsIEarlyHintObserver)
404 NS_INTERFACE_MAP_ENTRY_CONCRETE(DocumentLoadListener)
405 NS_INTERFACE_MAP_END
407 DocumentLoadListener::DocumentLoadListener(
408 CanonicalBrowsingContext* aLoadingBrowsingContext, bool aIsDocumentLoad)
409 : mIsDocumentLoad(aIsDocumentLoad) {
410 LOG(("DocumentLoadListener ctor [this=%p]", this));
411 mParentChannelListener =
412 new ParentChannelListener(this, aLoadingBrowsingContext);
415 DocumentLoadListener::~DocumentLoadListener() {
416 LOG(("DocumentLoadListener dtor [this=%p]", this));
419 void DocumentLoadListener::AddURIVisit(nsIChannel* aChannel,
420 uint32_t aLoadFlags) {
421 if (mLoadStateLoadType == LOAD_ERROR_PAGE ||
422 mLoadStateLoadType == LOAD_BYPASS_HISTORY) {
423 return;
426 nsCOMPtr<nsIURI> uri;
427 NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
429 nsCOMPtr<nsIURI> previousURI;
430 uint32_t previousFlags = 0;
431 if (mLoadStateLoadType & nsIDocShell::LOAD_CMD_RELOAD) {
432 previousURI = uri;
433 } else {
434 nsDocShell::ExtractLastVisit(aChannel, getter_AddRefs(previousURI),
435 &previousFlags);
438 // Get the HTTP response code, if available.
439 uint32_t responseStatus = 0;
440 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
441 if (httpChannel) {
442 Unused << httpChannel->GetResponseStatus(&responseStatus);
445 RefPtr<CanonicalBrowsingContext> browsingContext =
446 GetDocumentBrowsingContext();
447 nsCOMPtr<nsIWidget> widget =
448 browsingContext->GetParentProcessWidgetContaining();
450 nsDocShell::InternalAddURIVisit(uri, previousURI, previousFlags,
451 responseStatus, browsingContext, widget,
452 mLoadStateLoadType);
455 CanonicalBrowsingContext* DocumentLoadListener::GetLoadingBrowsingContext()
456 const {
457 return mParentChannelListener ? mParentChannelListener->GetBrowsingContext()
458 : nullptr;
461 CanonicalBrowsingContext* DocumentLoadListener::GetDocumentBrowsingContext()
462 const {
463 return mIsDocumentLoad ? GetLoadingBrowsingContext() : nullptr;
466 CanonicalBrowsingContext* DocumentLoadListener::GetTopBrowsingContext() const {
467 auto* loadingContext = GetLoadingBrowsingContext();
468 return loadingContext ? loadingContext->Top() : nullptr;
471 WindowGlobalParent* DocumentLoadListener::GetParentWindowContext() const {
472 return mParentWindowContext;
475 bool CheckRecursiveLoad(CanonicalBrowsingContext* aLoadingContext,
476 nsDocShellLoadState* aLoadState, bool aIsDocumentLoad) {
477 // Bug 136580: Check for recursive frame loading excluding about:srcdoc URIs.
478 // srcdoc URIs require their contents to be specified inline, so it isn't
479 // possible for undesirable recursion to occur without the aid of a
480 // non-srcdoc URI, which this method will block normally.
481 // Besides, URI is not enough to guarantee uniqueness of srcdoc documents.
482 nsAutoCString buffer;
483 if (aLoadState->URI()->SchemeIs("about")) {
484 nsresult rv = aLoadState->URI()->GetPathQueryRef(buffer);
485 if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("srcdoc")) {
486 // Duplicates allowed up to depth limits
487 return true;
491 RefPtr<WindowGlobalParent> parent;
492 if (!aIsDocumentLoad) { // object load
493 parent = aLoadingContext->GetCurrentWindowGlobal();
494 } else {
495 parent = aLoadingContext->GetParentWindowContext();
498 int matchCount = 0;
499 CanonicalBrowsingContext* ancestorBC;
500 for (WindowGlobalParent* ancestorWGP = parent; ancestorWGP;
501 ancestorWGP = ancestorBC->GetParentWindowContext()) {
502 ancestorBC = ancestorWGP->BrowsingContext();
503 MOZ_ASSERT(ancestorBC);
504 if (nsCOMPtr<nsIURI> parentURI = ancestorWGP->GetDocumentURI()) {
505 bool equal;
506 nsresult rv = aLoadState->URI()->EqualsExceptRef(parentURI, &equal);
507 NS_ENSURE_SUCCESS(rv, false);
509 if (equal) {
510 matchCount++;
511 if (matchCount >= kMaxSameURLContentFrames) {
512 NS_WARNING(
513 "Too many nested content frames/objects have the same url "
514 "(recursion?) "
515 "so giving up");
516 return false;
521 return true;
524 // Check that the load state, potentially received from a child process, appears
525 // to be performing a load of the specified LoadingSessionHistoryInfo.
526 // Returns a Result<…> containing the SessionHistoryEntry found for the
527 // LoadingSessionHistoryInfo as success value if the validation succeeded, or a
528 // static (telemetry-safe) string naming what did not match as a failure value
529 // if the validation failed.
530 static Result<SessionHistoryEntry*, const char*> ValidateHistoryLoad(
531 CanonicalBrowsingContext* aLoadingContext,
532 nsDocShellLoadState* aLoadState) {
533 MOZ_ASSERT(SessionHistoryInParent());
534 MOZ_ASSERT(aLoadState->LoadIsFromSessionHistory());
536 if (!aLoadState->GetLoadingSessionHistoryInfo()) {
537 return Err("Missing LoadingSessionHistoryInfo");
540 SessionHistoryEntry::LoadingEntry* loading = SessionHistoryEntry::GetByLoadId(
541 aLoadState->GetLoadingSessionHistoryInfo()->mLoadId);
542 if (!loading) {
543 return Err("Missing SessionHistoryEntry");
546 SessionHistoryInfo* snapshot = loading->mInfoSnapshotForValidation.get();
547 // History loads do not inherit principal.
548 if (aLoadState->HasInternalLoadFlags(
549 nsDocShell::INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL)) {
550 return Err("LOAD_FLAGS_INHERIT_PRINCIPAL");
553 auto uriEq = [](nsIURI* a, nsIURI* b) -> bool {
554 bool eq = false;
555 return a == b || (a && b && NS_SUCCEEDED(a->Equals(b, &eq)) && eq);
557 auto principalEq = [](nsIPrincipal* a, nsIPrincipal* b) -> bool {
558 return a == b || (a && b && a->Equals(b));
561 // XXX: Needing to do all of this validation manually is kinda gross.
562 if (!uriEq(snapshot->GetURI(), aLoadState->URI())) {
563 return Err("URI");
565 if (!uriEq(snapshot->GetOriginalURI(), aLoadState->OriginalURI())) {
566 return Err("OriginalURI");
568 if (!aLoadState->ResultPrincipalURIIsSome() ||
569 !uriEq(snapshot->GetResultPrincipalURI(),
570 aLoadState->ResultPrincipalURI())) {
571 return Err("ResultPrincipalURI");
573 if (!uriEq(snapshot->GetUnstrippedURI(), aLoadState->GetUnstrippedURI())) {
574 return Err("UnstrippedURI");
576 if (!principalEq(snapshot->GetTriggeringPrincipal(),
577 aLoadState->TriggeringPrincipal())) {
578 return Err("TriggeringPrincipal");
580 if (!principalEq(snapshot->GetPrincipalToInherit(),
581 aLoadState->PrincipalToInherit())) {
582 return Err("PrincipalToInherit");
584 if (!principalEq(snapshot->GetPartitionedPrincipalToInherit(),
585 aLoadState->PartitionedPrincipalToInherit())) {
586 return Err("PartitionedPrincipalToInherit");
589 // Everything matches!
590 return loading->mEntry;
593 auto DocumentLoadListener::Open(nsDocShellLoadState* aLoadState,
594 LoadInfo* aLoadInfo, nsLoadFlags aLoadFlags,
595 uint32_t aCacheKey,
596 const Maybe<uint64_t>& aChannelId,
597 const TimeStamp& aAsyncOpenTime,
598 nsDOMNavigationTiming* aTiming,
599 Maybe<ClientInfo>&& aInfo, bool aUrgentStart,
600 dom::ContentParent* aContentParent,
601 nsresult* aRv) -> RefPtr<OpenPromise> {
602 auto* loadingContext = GetLoadingBrowsingContext();
604 MOZ_DIAGNOSTIC_ASSERT_IF(loadingContext->GetParent(),
605 loadingContext->GetParentWindowContext());
607 OriginAttributes attrs;
608 loadingContext->GetOriginAttributes(attrs);
610 mLoadIdentifier = aLoadState->GetLoadIdentifier();
611 // See description of mFileName in nsDocShellLoadState.h
612 mIsDownload = !aLoadState->FileName().IsVoid();
613 mIsLoadingJSURI = net::SchemeIsJavascript(aLoadState->URI());
615 // Check for infinite recursive object or iframe loads
616 if (aLoadState->OriginalFrameSrc() || !mIsDocumentLoad) {
617 if (!CheckRecursiveLoad(loadingContext, aLoadState, mIsDocumentLoad)) {
618 *aRv = NS_ERROR_RECURSIVE_DOCUMENT_LOAD;
619 mParentChannelListener = nullptr;
620 return nullptr;
624 auto* documentContext = GetDocumentBrowsingContext();
626 // If we are using SHIP and this load is from session history, validate that
627 // the load matches our local copy of the loading history entry.
629 // NOTE: Keep this check in-sync with the check in
630 // `nsDocShellLoadState::GetEffectiveTriggeringRemoteType()`!
631 RefPtr<SessionHistoryEntry> existingEntry;
632 if (SessionHistoryInParent() && aLoadState->LoadIsFromSessionHistory() &&
633 aLoadState->LoadType() != LOAD_ERROR_PAGE) {
634 Result<SessionHistoryEntry*, const char*> result =
635 ValidateHistoryLoad(loadingContext, aLoadState);
636 if (result.isErr()) {
637 const char* mismatch = result.unwrapErr();
638 LOG(
639 ("DocumentLoadListener::Open with invalid loading history entry "
640 "[this=%p, mismatch=%s]",
641 this, mismatch));
642 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
643 MOZ_CRASH_UNSAFE_PRINTF(
644 "DocumentLoadListener::Open for invalid history entry due to "
645 "mismatch of '%s'",
646 mismatch);
647 #endif
648 *aRv = NS_ERROR_DOM_SECURITY_ERR;
649 mParentChannelListener = nullptr;
650 return nullptr;
653 existingEntry = result.unwrap();
654 if (!existingEntry->IsInSessionHistory() &&
655 !documentContext->HasLoadingHistoryEntry(existingEntry)) {
656 SessionHistoryEntry::RemoveLoadId(
657 aLoadState->GetLoadingSessionHistoryInfo()->mLoadId);
658 LOG(
659 ("DocumentLoadListener::Open with disconnected history entry "
660 "[this=%p]",
661 this));
663 *aRv = NS_BINDING_ABORTED;
664 mParentChannelListener = nullptr;
665 mChannel = nullptr;
666 return nullptr;
670 if (aLoadState->GetRemoteTypeOverride()) {
671 if (!mIsDocumentLoad || !NS_IsAboutBlank(aLoadState->URI()) ||
672 !loadingContext->IsTopContent()) {
673 LOG(
674 ("DocumentLoadListener::Open with invalid remoteTypeOverride "
675 "[this=%p]",
676 this));
677 *aRv = NS_ERROR_DOM_SECURITY_ERR;
678 mParentChannelListener = nullptr;
679 return nullptr;
682 mRemoteTypeOverride = aLoadState->GetRemoteTypeOverride();
685 if (NS_WARN_IF(!loadingContext->IsOwnedByProcess(
686 GetContentProcessId(aContentParent)))) {
687 LOG(
688 ("DocumentLoadListener::Open called from non-current content process "
689 "[this=%p, current=%" PRIu64 ", caller=%" PRIu64 "]",
690 this, loadingContext->OwnerProcessId(),
691 uint64_t(GetContentProcessId(aContentParent))));
692 *aRv = NS_BINDING_ABORTED;
693 mParentChannelListener = nullptr;
694 return nullptr;
697 if (mIsDocumentLoad && loadingContext->IsContent() &&
698 NS_WARN_IF(loadingContext->IsReplaced())) {
699 LOG(
700 ("DocumentLoadListener::Open called from replaced BrowsingContext "
701 "[this=%p, browserid=%" PRIx64 ", bcid=%" PRIx64 "]",
702 this, loadingContext->BrowserId(), loadingContext->Id()));
703 *aRv = NS_BINDING_ABORTED;
704 mParentChannelListener = nullptr;
705 return nullptr;
708 if (!nsDocShell::CreateAndConfigureRealChannelForLoadState(
709 loadingContext, aLoadState, aLoadInfo, mParentChannelListener,
710 nullptr, attrs, aLoadFlags, aCacheKey, *aRv,
711 getter_AddRefs(mChannel))) {
712 LOG(("DocumentLoadListener::Open failed to create channel [this=%p]",
713 this));
714 mParentChannelListener = nullptr;
715 return nullptr;
718 if (documentContext && aLoadState->LoadType() != LOAD_ERROR_PAGE &&
719 mozilla::SessionHistoryInParent()) {
720 // It's hard to know at this point whether session history will be enabled
721 // in the browsing context, so we always create an entry for a load here.
722 mLoadingSessionHistoryInfo =
723 documentContext->CreateLoadingSessionHistoryEntryForLoad(
724 aLoadState, existingEntry, mChannel);
725 MOZ_ASSERT(mLoadingSessionHistoryInfo);
728 nsCOMPtr<nsIURI> uriBeingLoaded;
729 Unused << NS_WARN_IF(
730 NS_FAILED(mChannel->GetURI(getter_AddRefs(uriBeingLoaded))));
732 RefPtr<HttpBaseChannel> httpBaseChannel = do_QueryObject(mChannel, aRv);
733 if (uriBeingLoaded && httpBaseChannel) {
734 nsCOMPtr<nsIURI> topWindowURI;
735 if (mIsDocumentLoad && loadingContext->IsTop()) {
736 // If this is for the top level loading, the top window URI should be the
737 // URI which we are loading.
738 topWindowURI = uriBeingLoaded;
739 } else if (RefPtr<WindowGlobalParent> topWindow = AntiTrackingUtils::
740 GetTopWindowExcludingExtensionAccessibleContentFrames(
741 loadingContext, uriBeingLoaded)) {
742 nsCOMPtr<nsIPrincipal> topWindowPrincipal =
743 topWindow->DocumentPrincipal();
744 if (topWindowPrincipal && !topWindowPrincipal->GetIsNullPrincipal()) {
745 auto* basePrin = BasePrincipal::Cast(topWindowPrincipal);
746 basePrin->GetURI(getter_AddRefs(topWindowURI));
749 httpBaseChannel->SetTopWindowURI(topWindowURI);
752 nsCOMPtr<nsIIdentChannel> identChannel = do_QueryInterface(mChannel);
753 if (identChannel && aChannelId) {
754 Unused << identChannel->SetChannelId(*aChannelId);
756 mDocumentChannelId = aChannelId;
758 RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
759 if (httpChannelImpl) {
760 httpChannelImpl->SetWarningReporter(this);
762 if (mIsDocumentLoad && loadingContext->IsTop()) {
763 httpChannelImpl->SetEarlyHintObserver(this);
767 nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(mChannel);
768 if (timedChannel) {
769 timedChannel->SetAsyncOpen(aAsyncOpenTime);
772 if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel)) {
773 Unused << httpChannel->SetRequestContextID(
774 loadingContext->GetRequestContextId());
776 nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(httpChannel));
777 if (cos && aUrgentStart) {
778 cos->AddClassFlags(nsIClassOfService::UrgentStart);
782 // Setup a ClientChannelHelper to watch for redirects, and copy
783 // across any serviceworker related data between channels as needed.
784 AddClientChannelHelperInParent(mChannel, std::move(aInfo));
786 if (documentContext && !documentContext->StartDocumentLoad(this)) {
787 LOG(("DocumentLoadListener::Open failed StartDocumentLoad [this=%p]",
788 this));
789 *aRv = NS_BINDING_ABORTED;
790 mParentChannelListener = nullptr;
791 mChannel = nullptr;
792 return nullptr;
795 // Recalculate the openFlags, matching the logic in use in Content process.
796 // NOTE: The only case not handled here to mirror Content process is
797 // redirecting to re-use the channel.
798 MOZ_ASSERT(!aLoadState->GetPendingRedirectedChannel());
799 uint32_t openFlags =
800 nsDocShell::ComputeURILoaderFlags(loadingContext, aLoadState->LoadType());
802 RefPtr<ParentProcessDocumentOpenInfo> openInfo =
803 new ParentProcessDocumentOpenInfo(mParentChannelListener, openFlags,
804 loadingContext, mIsDocumentLoad);
805 openInfo->Prepare();
807 #ifdef ANDROID
808 RefPtr<MozPromise<bool, bool, false>> promise;
809 if (documentContext && aLoadState->LoadType() != LOAD_ERROR_PAGE &&
810 !(aLoadState->HasInternalLoadFlags(
811 nsDocShell::INTERNAL_LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE)) &&
812 !(aLoadState->LoadType() & LOAD_HISTORY)) {
813 nsCOMPtr<nsIWidget> widget =
814 documentContext->GetParentProcessWidgetContaining();
815 RefPtr<nsWindow> window = nsWindow::From(widget);
817 if (window) {
818 promise = window->OnLoadRequest(
819 aLoadState->URI(), nsIBrowserDOMWindow::OPEN_CURRENTWINDOW,
820 aLoadState->InternalLoadFlags(), aLoadState->TriggeringPrincipal(),
821 aLoadState->HasValidUserGestureActivation(),
822 documentContext->IsTopContent());
826 if (promise) {
827 RefPtr<DocumentLoadListener> self = this;
828 promise->Then(
829 GetCurrentSerialEventTarget(), __func__,
830 [=](const MozPromise<bool, bool, false>::ResolveOrRejectValue& aValue) {
831 if (aValue.IsResolve()) {
832 bool handled = aValue.ResolveValue();
833 if (handled) {
834 self->DisconnectListeners(NS_ERROR_ABORT, NS_ERROR_ABORT);
835 mParentChannelListener = nullptr;
836 } else {
837 nsresult rv = mChannel->AsyncOpen(openInfo);
838 if (NS_FAILED(rv)) {
839 self->DisconnectListeners(rv, rv);
840 mParentChannelListener = nullptr;
845 } else
846 #endif /* ANDROID */
848 *aRv = mChannel->AsyncOpen(openInfo);
849 if (NS_FAILED(*aRv)) {
850 LOG(("DocumentLoadListener::Open failed AsyncOpen [this=%p rv=%" PRIx32
851 "]",
852 this, static_cast<uint32_t>(*aRv)));
853 if (documentContext) {
854 documentContext->EndDocumentLoad(false);
856 mParentChannelListener = nullptr;
857 return nullptr;
861 // HTTPS-Only Mode fights potential timeouts caused by upgrades. Instantly
862 // after opening the document channel we have to kick off countermeasures.
863 nsHTTPSOnlyUtils::PotentiallyFireHttpRequestToShortenTimout(this);
865 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
866 loadInfo->SetChannelCreationOriginalURI(aLoadState->URI());
868 mContentParent = aContentParent;
869 mLoadStateExternalLoadFlags = aLoadState->LoadFlags();
870 mLoadStateInternalLoadFlags = aLoadState->InternalLoadFlags();
871 mLoadStateLoadType = aLoadState->LoadType();
872 mTiming = aTiming;
873 mSrcdocData = aLoadState->SrcdocData();
874 mBaseURI = aLoadState->BaseURI();
875 mOriginalUriString = aLoadState->GetOriginalURIString();
876 if (documentContext) {
877 mParentWindowContext = documentContext->GetParentWindowContext();
878 } else {
879 mParentWindowContext =
880 WindowGlobalParent::GetByInnerWindowId(aLoadInfo->GetInnerWindowID());
881 MOZ_RELEASE_ASSERT(mParentWindowContext->GetBrowsingContext() ==
882 GetLoadingBrowsingContext(),
883 "mismatched parent window context?");
886 // For content-initiated loads, this flag is set in nsDocShell::LoadURI.
887 // For parent-initiated loads, we have to set it here.
888 // Below comment is copied from nsDocShell::LoadURI -
889 // If we have a system triggering principal, we can assume that this load was
890 // triggered by some UI in the browser chrome, such as the URL bar or
891 // bookmark bar. This should count as a user interaction for the current sh
892 // entry, so that the user may navigate back to the current entry, from the
893 // entry that is going to be added as part of this load.
894 if (!mSupportsRedirectToRealChannel && aLoadState->TriggeringPrincipal() &&
895 aLoadState->TriggeringPrincipal()->IsSystemPrincipal()) {
896 WindowContext* topWc = loadingContext->GetTopWindowContext();
897 if (topWc && !topWc->IsDiscarded()) {
898 MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(true));
902 *aRv = NS_OK;
903 mOpenPromise = new OpenPromise::Private(__func__);
904 // We make the promise use direct task dispatch in order to reduce the number
905 // of event loops iterations.
906 mOpenPromise->UseDirectTaskDispatch(__func__);
907 return mOpenPromise;
910 auto DocumentLoadListener::OpenDocument(
911 nsDocShellLoadState* aLoadState, uint32_t aCacheKey,
912 const Maybe<uint64_t>& aChannelId, const TimeStamp& aAsyncOpenTime,
913 nsDOMNavigationTiming* aTiming, Maybe<dom::ClientInfo>&& aInfo,
914 Maybe<bool> aUriModified, Maybe<bool> aIsXFOError,
915 dom::ContentParent* aContentParent, nsresult* aRv) -> RefPtr<OpenPromise> {
916 LOG(("DocumentLoadListener [%p] OpenDocument [uri=%s]", this,
917 aLoadState->URI()->GetSpecOrDefault().get()));
919 MOZ_ASSERT(mIsDocumentLoad);
921 RefPtr<CanonicalBrowsingContext> browsingContext =
922 GetDocumentBrowsingContext();
924 // If this is a top-level load, then rebuild the LoadInfo from scratch,
925 // since the goal is to be able to initiate loads in the parent, where the
926 // content process won't have provided us with an existing one.
927 RefPtr<LoadInfo> loadInfo =
928 CreateDocumentLoadInfo(browsingContext, aLoadState);
930 nsLoadFlags loadFlags = aLoadState->CalculateChannelLoadFlags(
931 browsingContext, std::move(aUriModified), std::move(aIsXFOError));
933 // Keep track of navigation for the Bounce Tracking Protection.
934 if (browsingContext->IsTopContent()) {
935 RefPtr<BounceTrackingState> bounceTrackingState =
936 browsingContext->GetBounceTrackingState();
938 // Not every browsing context has a BounceTrackingState. It's also null when
939 // the feature is disabled.
940 if (bounceTrackingState) {
941 nsCOMPtr<nsIPrincipal> triggeringPrincipal;
942 nsresult rv =
943 loadInfo->GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal));
945 if (!NS_WARN_IF(NS_FAILED(rv))) {
946 DebugOnly<nsresult> rv = bounceTrackingState->OnStartNavigation(
947 triggeringPrincipal, loadInfo->GetHasValidUserGestureActivation());
948 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
949 "BounceTrackingState::OnStartNavigation failed");
954 return Open(aLoadState, loadInfo, loadFlags, aCacheKey, aChannelId,
955 aAsyncOpenTime, aTiming, std::move(aInfo), false, aContentParent,
956 aRv);
959 auto DocumentLoadListener::OpenObject(
960 nsDocShellLoadState* aLoadState, uint32_t aCacheKey,
961 const Maybe<uint64_t>& aChannelId, const TimeStamp& aAsyncOpenTime,
962 nsDOMNavigationTiming* aTiming, Maybe<dom::ClientInfo>&& aInfo,
963 uint64_t aInnerWindowId, nsLoadFlags aLoadFlags,
964 nsContentPolicyType aContentPolicyType, bool aUrgentStart,
965 dom::ContentParent* aContentParent,
966 ObjectUpgradeHandler* aObjectUpgradeHandler, nsresult* aRv)
967 -> RefPtr<OpenPromise> {
968 LOG(("DocumentLoadListener [%p] OpenObject [uri=%s]", this,
969 aLoadState->URI()->GetSpecOrDefault().get()));
971 MOZ_ASSERT(!mIsDocumentLoad);
973 auto sandboxFlags = aLoadState->TriggeringSandboxFlags();
975 RefPtr<LoadInfo> loadInfo = CreateObjectLoadInfo(
976 aLoadState, aInnerWindowId, aContentPolicyType, sandboxFlags);
978 mObjectUpgradeHandler = aObjectUpgradeHandler;
980 return Open(aLoadState, loadInfo, aLoadFlags, aCacheKey, aChannelId,
981 aAsyncOpenTime, aTiming, std::move(aInfo), aUrgentStart,
982 aContentParent, aRv);
985 auto DocumentLoadListener::OpenInParent(nsDocShellLoadState* aLoadState,
986 bool aSupportsRedirectToRealChannel)
987 -> RefPtr<OpenPromise> {
988 MOZ_ASSERT(mIsDocumentLoad);
990 // We currently only support passing nullptr for aLoadInfo for
991 // top level browsing contexts.
992 auto* browsingContext = GetDocumentBrowsingContext();
993 if (!browsingContext->IsTopContent() ||
994 !browsingContext->GetContentParent()) {
995 LOG(("DocumentLoadListener::OpenInParent failed because of subdoc"));
996 return nullptr;
999 if (nsCOMPtr<nsIContentSecurityPolicy> csp = aLoadState->Csp()) {
1000 // Check CSP navigate-to
1001 bool allowsNavigateTo = false;
1002 nsresult rv = csp->GetAllowsNavigateTo(aLoadState->URI(),
1003 aLoadState->IsFormSubmission(),
1004 false, /* aWasRedirected */
1005 false, /* aEnforceWhitelist */
1006 &allowsNavigateTo);
1007 if (NS_FAILED(rv) || !allowsNavigateTo) {
1008 return nullptr;
1012 // Clone because this mutates the load flags in the load state, which
1013 // breaks nsDocShells expectations of being able to do it.
1014 RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(*aLoadState);
1015 loadState->CalculateLoadURIFlags();
1017 RefPtr<nsDOMNavigationTiming> timing = new nsDOMNavigationTiming(nullptr);
1018 timing->NotifyNavigationStart(
1019 browsingContext->IsActive()
1020 ? nsDOMNavigationTiming::DocShellState::eActive
1021 : nsDOMNavigationTiming::DocShellState::eInactive);
1023 const mozilla::dom::LoadingSessionHistoryInfo* loadingInfo =
1024 loadState->GetLoadingSessionHistoryInfo();
1026 uint32_t cacheKey = 0;
1027 auto loadType = aLoadState->LoadType();
1028 if (loadType == LOAD_HISTORY || loadType == LOAD_RELOAD_NORMAL ||
1029 loadType == LOAD_RELOAD_CHARSET_CHANGE ||
1030 loadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE ||
1031 loadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE) {
1032 if (loadingInfo) {
1033 cacheKey = loadingInfo->mInfo.GetCacheKey();
1037 // Loads start in the content process might have exposed a channel id to
1038 // observers, so we need to preserve the value in the parent. That can't have
1039 // happened here, so Nothing() is fine.
1040 Maybe<uint64_t> channelId = Nothing();
1042 // Initial client info is only relevant for subdocument loads, which we're
1043 // not supporting yet.
1044 Maybe<dom::ClientInfo> initialClientInfo;
1046 mSupportsRedirectToRealChannel = aSupportsRedirectToRealChannel;
1048 // This is a top-level load, so rebuild the LoadInfo from scratch,
1049 // since in the parent the
1050 // content process won't have provided us with an existing one.
1051 RefPtr<LoadInfo> loadInfo =
1052 CreateDocumentLoadInfo(browsingContext, aLoadState);
1054 nsLoadFlags loadFlags = loadState->CalculateChannelLoadFlags(
1055 browsingContext,
1056 Some(loadingInfo && loadingInfo->mInfo.GetURIWasModified()), Nothing());
1058 nsresult rv;
1059 return Open(loadState, loadInfo, loadFlags, cacheKey, channelId,
1060 TimeStamp::Now(), timing, std::move(initialClientInfo), false,
1061 browsingContext->GetContentParent(), &rv);
1064 base::ProcessId DocumentLoadListener::OtherPid() const {
1065 return mContentParent ? mContentParent->OtherPid() : base::ProcessId{0};
1068 void DocumentLoadListener::FireStateChange(uint32_t aStateFlags,
1069 nsresult aStatus) {
1070 nsCOMPtr<nsIChannel> request = GetChannel();
1072 RefPtr<BrowsingContextWebProgress> webProgress =
1073 GetLoadingBrowsingContext()->GetWebProgress();
1075 if (webProgress) {
1076 NS_DispatchToMainThread(
1077 NS_NewRunnableFunction("DocumentLoadListener::FireStateChange", [=]() {
1078 webProgress->OnStateChange(webProgress, request, aStateFlags,
1079 aStatus);
1080 }));
1084 static void SetNavigating(CanonicalBrowsingContext* aBrowsingContext,
1085 bool aNavigating) {
1086 nsCOMPtr<nsIBrowser> browser;
1087 if (RefPtr<Element> currentElement = aBrowsingContext->GetEmbedderElement()) {
1088 browser = currentElement->AsBrowser();
1091 if (!browser) {
1092 return;
1095 NS_DispatchToMainThread(NS_NewRunnableFunction(
1096 "DocumentLoadListener::SetNavigating",
1097 [browser, aNavigating]() { browser->SetIsNavigating(aNavigating); }));
1100 /* static */ bool DocumentLoadListener::LoadInParent(
1101 CanonicalBrowsingContext* aBrowsingContext, nsDocShellLoadState* aLoadState,
1102 bool aSetNavigating) {
1103 SetNavigating(aBrowsingContext, aSetNavigating);
1105 RefPtr<DocumentLoadListener> load =
1106 new DocumentLoadListener(aBrowsingContext, true);
1107 RefPtr<DocumentLoadListener::OpenPromise> promise = load->OpenInParent(
1108 aLoadState, /* aSupportsRedirectToRealChannel */ false);
1109 if (!promise) {
1110 SetNavigating(aBrowsingContext, false);
1111 return false;
1114 // We passed false for aSupportsRedirectToRealChannel, so we should always
1115 // take the process switching path, and reject this promise.
1116 promise->Then(
1117 GetCurrentSerialEventTarget(), __func__,
1118 [load](DocumentLoadListener::OpenPromise::ResolveOrRejectValue&& aValue) {
1119 MOZ_ASSERT(aValue.IsReject());
1120 DocumentLoadListener::OpenPromiseFailedType& rejectValue =
1121 aValue.RejectValue();
1122 if (!rejectValue.mContinueNavigating) {
1123 // If we're not switching the load to a new process, then it is
1124 // finished (and failed), and we should fire a state change to notify
1125 // observers. Normally the docshell would fire this, and it would get
1126 // filtered out by BrowserParent if needed.
1127 load->FireStateChange(nsIWebProgressListener::STATE_STOP |
1128 nsIWebProgressListener::STATE_IS_WINDOW |
1129 nsIWebProgressListener::STATE_IS_NETWORK,
1130 rejectValue.mStatus);
1134 load->FireStateChange(nsIWebProgressListener::STATE_START |
1135 nsIWebProgressListener::STATE_IS_DOCUMENT |
1136 nsIWebProgressListener::STATE_IS_REQUEST |
1137 nsIWebProgressListener::STATE_IS_WINDOW |
1138 nsIWebProgressListener::STATE_IS_NETWORK,
1139 NS_OK);
1140 SetNavigating(aBrowsingContext, false);
1141 return true;
1144 /* static */
1145 bool DocumentLoadListener::SpeculativeLoadInParent(
1146 dom::CanonicalBrowsingContext* aBrowsingContext,
1147 nsDocShellLoadState* aLoadState) {
1148 LOG(("DocumentLoadListener::OpenFromParent"));
1150 RefPtr<DocumentLoadListener> listener =
1151 new DocumentLoadListener(aBrowsingContext, true);
1153 auto promise = listener->OpenInParent(aLoadState, true);
1154 if (promise) {
1155 // Create an entry in the redirect channel registrar to
1156 // allocate an identifier for this load.
1157 nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
1158 RedirectChannelRegistrar::GetOrCreate();
1159 uint64_t loadIdentifier = aLoadState->GetLoadIdentifier();
1160 DebugOnly<nsresult> rv =
1161 registrar->RegisterChannel(nullptr, loadIdentifier);
1162 MOZ_ASSERT(NS_SUCCEEDED(rv));
1163 // Register listener (as an nsIParentChannel) under our new identifier.
1164 rv = registrar->LinkChannels(loadIdentifier, listener, nullptr);
1165 MOZ_ASSERT(NS_SUCCEEDED(rv));
1167 return !!promise;
1170 void DocumentLoadListener::CleanupParentLoadAttempt(uint64_t aLoadIdent) {
1171 nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
1172 RedirectChannelRegistrar::GetOrCreate();
1174 nsCOMPtr<nsIParentChannel> parentChannel;
1175 registrar->GetParentChannel(aLoadIdent, getter_AddRefs(parentChannel));
1176 RefPtr<DocumentLoadListener> loadListener = do_QueryObject(parentChannel);
1178 if (loadListener) {
1179 // If the load listener is still registered, then we must have failed
1180 // to connect DocumentChannel into it. Better cancel it!
1181 loadListener->NotifyDocumentChannelFailed();
1184 registrar->DeregisterChannels(aLoadIdent);
1187 auto DocumentLoadListener::ClaimParentLoad(DocumentLoadListener** aListener,
1188 uint64_t aLoadIdent,
1189 Maybe<uint64_t> aChannelId)
1190 -> RefPtr<OpenPromise> {
1191 nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
1192 RedirectChannelRegistrar::GetOrCreate();
1194 nsCOMPtr<nsIParentChannel> parentChannel;
1195 registrar->GetParentChannel(aLoadIdent, getter_AddRefs(parentChannel));
1196 RefPtr<DocumentLoadListener> loadListener = do_QueryObject(parentChannel);
1197 registrar->DeregisterChannels(aLoadIdent);
1199 if (!loadListener) {
1200 // The parent went away unexpectedly.
1201 *aListener = nullptr;
1202 return nullptr;
1205 loadListener->mDocumentChannelId = aChannelId;
1207 MOZ_DIAGNOSTIC_ASSERT(loadListener->mOpenPromise);
1208 loadListener.forget(aListener);
1210 return (*aListener)->mOpenPromise;
1213 void DocumentLoadListener::NotifyDocumentChannelFailed() {
1214 LOG(("DocumentLoadListener NotifyDocumentChannelFailed [this=%p]", this));
1215 // There's been no calls to ClaimParentLoad, and so no listeners have been
1216 // attached to mOpenPromise yet. As such we can run Then() on it.
1217 mOpenPromise->Then(
1218 GetMainThreadSerialEventTarget(), __func__,
1219 [](DocumentLoadListener::OpenPromiseSucceededType&& aResolveValue) {
1220 aResolveValue.mPromise->Resolve(NS_BINDING_ABORTED, __func__);
1222 []() {});
1224 Cancel(NS_BINDING_ABORTED,
1225 "DocumentLoadListener::NotifyDocumentChannelFailed"_ns);
1228 void DocumentLoadListener::Disconnect(bool aContinueNavigating) {
1229 LOG(("DocumentLoadListener Disconnect [this=%p, aContinueNavigating=%d]",
1230 this, aContinueNavigating));
1231 // The nsHttpChannel may have a reference to this parent, release it
1232 // to avoid circular references.
1233 RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
1234 if (httpChannelImpl) {
1235 httpChannelImpl->SetWarningReporter(nullptr);
1236 httpChannelImpl->SetEarlyHintObserver(nullptr);
1239 // Don't cancel ongoing early hints when continuing to load the web page.
1240 // Early hints are loaded earlier in the code and shouldn't get cancelled
1241 // here. See also: Bug 1765652
1242 if (!aContinueNavigating) {
1243 mEarlyHintsService.Cancel("DocumentLoadListener::Disconnect"_ns);
1246 if (auto* ctx = GetDocumentBrowsingContext()) {
1247 ctx->EndDocumentLoad(aContinueNavigating);
1251 void DocumentLoadListener::Cancel(const nsresult& aStatusCode,
1252 const nsACString& aReason) {
1253 LOG(
1254 ("DocumentLoadListener Cancel [this=%p, "
1255 "aStatusCode=%" PRIx32 " ]",
1256 this, static_cast<uint32_t>(aStatusCode)));
1257 if (mOpenPromiseResolved) {
1258 return;
1260 if (mChannel) {
1261 mChannel->CancelWithReason(aStatusCode, aReason);
1264 DisconnectListeners(aStatusCode, aStatusCode);
1267 void DocumentLoadListener::DisconnectListeners(nsresult aStatus,
1268 nsresult aLoadGroupStatus,
1269 bool aContinueNavigating) {
1270 LOG(
1271 ("DocumentLoadListener DisconnectListener [this=%p, "
1272 "aStatus=%" PRIx32 ", aLoadGroupStatus=%" PRIx32
1273 ", aContinueNavigating=%d]",
1274 this, static_cast<uint32_t>(aStatus),
1275 static_cast<uint32_t>(aLoadGroupStatus), aContinueNavigating));
1277 RejectOpenPromise(aStatus, aLoadGroupStatus, aContinueNavigating, __func__);
1279 Disconnect(aContinueNavigating);
1281 // Clear any pending stream filter requests. If we're going to be sending a
1282 // response to the content process due to a navigation, our caller will have
1283 // already stashed the array to be passed to `TriggerRedirectToRealChannel`,
1284 // so it's safe for us to clear here.
1285 // TODO: If we retargeted the stream to a non-default handler (e.g. to trigger
1286 // a download), we currently never attach a stream filter. Should we attach a
1287 // stream filter in those situations as well?
1288 mStreamFilterRequests.Clear();
1291 void DocumentLoadListener::RedirectToRealChannelFinished(nsresult aRv) {
1292 LOG(
1293 ("DocumentLoadListener RedirectToRealChannelFinished [this=%p, "
1294 "aRv=%" PRIx32 " ]",
1295 this, static_cast<uint32_t>(aRv)));
1296 if (NS_FAILED(aRv)) {
1297 FinishReplacementChannelSetup(aRv);
1298 return;
1301 // Wait for background channel ready on target channel
1302 nsCOMPtr<nsIRedirectChannelRegistrar> redirectReg =
1303 RedirectChannelRegistrar::GetOrCreate();
1304 MOZ_ASSERT(redirectReg);
1306 nsCOMPtr<nsIParentChannel> redirectParentChannel;
1307 redirectReg->GetParentChannel(mRedirectChannelId,
1308 getter_AddRefs(redirectParentChannel));
1309 if (!redirectParentChannel) {
1310 FinishReplacementChannelSetup(NS_ERROR_FAILURE);
1311 return;
1314 nsCOMPtr<nsIParentRedirectingChannel> redirectingParent =
1315 do_QueryInterface(redirectParentChannel);
1316 if (!redirectingParent) {
1317 // Continue verification procedure if redirecting to non-Http protocol
1318 FinishReplacementChannelSetup(NS_OK);
1319 return;
1322 // Ask redirected channel if verification can proceed.
1323 // ReadyToVerify will be invoked when redirected channel is ready.
1324 redirectingParent->ContinueVerification(this);
1327 NS_IMETHODIMP
1328 DocumentLoadListener::ReadyToVerify(nsresult aResultCode) {
1329 FinishReplacementChannelSetup(aResultCode);
1330 return NS_OK;
1333 void DocumentLoadListener::FinishReplacementChannelSetup(nsresult aResult) {
1334 LOG(
1335 ("DocumentLoadListener FinishReplacementChannelSetup [this=%p, "
1336 "aResult=%x]",
1337 this, int(aResult)));
1339 auto endDocumentLoad = MakeScopeExit([&]() {
1340 if (auto* ctx = GetDocumentBrowsingContext()) {
1341 ctx->EndDocumentLoad(false);
1344 mStreamFilterRequests.Clear();
1346 nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
1347 RedirectChannelRegistrar::GetOrCreate();
1348 MOZ_ASSERT(registrar);
1350 nsCOMPtr<nsIParentChannel> redirectChannel;
1351 nsresult rv = registrar->GetParentChannel(mRedirectChannelId,
1352 getter_AddRefs(redirectChannel));
1353 if (NS_FAILED(rv) || !redirectChannel) {
1354 aResult = NS_ERROR_FAILURE;
1357 // Release all previously registered channels, they are no longer needed to
1358 // be kept in the registrar from this moment.
1359 registrar->DeregisterChannels(mRedirectChannelId);
1360 mRedirectChannelId = 0;
1361 if (NS_FAILED(aResult)) {
1362 if (redirectChannel) {
1363 redirectChannel->Delete();
1365 mChannel->Cancel(aResult);
1366 mChannel->Resume();
1367 return;
1370 MOZ_ASSERT(
1371 !SameCOMIdentity(redirectChannel, static_cast<nsIParentChannel*>(this)));
1373 redirectChannel->SetParentListener(mParentChannelListener);
1375 ApplyPendingFunctions(redirectChannel);
1377 if (!ResumeSuspendedChannel(redirectChannel)) {
1378 nsCOMPtr<nsILoadGroup> loadGroup;
1379 mChannel->GetLoadGroup(getter_AddRefs(loadGroup));
1380 if (loadGroup) {
1381 // We added ourselves to the load group, but attempting
1382 // to resume has notified us that the channel is already
1383 // finished. Better remove ourselves from the loadgroup
1384 // again. The only time the channel will be in a loadgroup
1385 // is if we're connected to the parent process.
1386 nsresult status = NS_OK;
1387 mChannel->GetStatus(&status);
1388 loadGroup->RemoveRequest(mChannel, nullptr, status);
1393 void DocumentLoadListener::ApplyPendingFunctions(
1394 nsIParentChannel* aChannel) const {
1395 // We stored the values from all nsIParentChannel functions called since we
1396 // couldn't handle them. Copy them across to the real channel since it
1397 // should know what to do.
1399 nsCOMPtr<nsIParentChannel> parentChannel = aChannel;
1400 for (const auto& variant : mIParentChannelFunctions) {
1401 variant.match(
1402 [parentChannel](const ClassifierMatchedInfoParams& aParams) {
1403 parentChannel->SetClassifierMatchedInfo(
1404 aParams.mList, aParams.mProvider, aParams.mFullHash);
1406 [parentChannel](const ClassifierMatchedTrackingInfoParams& aParams) {
1407 parentChannel->SetClassifierMatchedTrackingInfo(aParams.mLists,
1408 aParams.mFullHashes);
1410 [parentChannel](const ClassificationFlagsParams& aParams) {
1411 parentChannel->NotifyClassificationFlags(aParams.mClassificationFlags,
1412 aParams.mIsThirdParty);
1416 RefPtr<HttpChannelSecurityWarningReporter> reporter;
1417 if (RefPtr<HttpChannelParent> httpParent = do_QueryObject(aChannel)) {
1418 reporter = httpParent;
1419 } else if (RefPtr<nsHttpChannel> httpChannel = do_QueryObject(aChannel)) {
1420 reporter = httpChannel->GetWarningReporter();
1422 if (reporter) {
1423 for (const auto& variant : mSecurityWarningFunctions) {
1424 variant.match(
1425 [reporter](const ReportSecurityMessageParams& aParams) {
1426 Unused << reporter->ReportSecurityMessage(aParams.mMessageTag,
1427 aParams.mMessageCategory);
1429 [reporter](const LogBlockedCORSRequestParams& aParams) {
1430 Unused << reporter->LogBlockedCORSRequest(
1431 aParams.mMessage, aParams.mCategory, aParams.mIsWarning);
1433 [reporter](const LogMimeTypeMismatchParams& aParams) {
1434 Unused << reporter->LogMimeTypeMismatch(
1435 aParams.mMessageName, aParams.mWarning, aParams.mURL,
1436 aParams.mContentType);
1442 bool DocumentLoadListener::ResumeSuspendedChannel(
1443 nsIStreamListener* aListener) {
1444 LOG(("DocumentLoadListener ResumeSuspendedChannel [this=%p]", this));
1445 RefPtr<nsHttpChannel> httpChannel = do_QueryObject(mChannel);
1446 if (httpChannel) {
1447 httpChannel->SetApplyConversion(mOldApplyConversion);
1450 if (!mIsFinished) {
1451 mParentChannelListener->SetListenerAfterRedirect(aListener);
1454 // If we failed to suspend the channel, then we might have received
1455 // some messages while the redirected was being handled.
1456 // Manually send them on now.
1457 nsTArray<StreamListenerFunction> streamListenerFunctions =
1458 std::move(mStreamListenerFunctions);
1459 if (!aListener) {
1460 streamListenerFunctions.Clear();
1463 ForwardStreamListenerFunctions(streamListenerFunctions, aListener);
1465 // We don't expect to get new stream listener functions added
1466 // via re-entrancy. If this ever happens, we should understand
1467 // exactly why before allowing it.
1468 NS_ASSERTION(mStreamListenerFunctions.IsEmpty(),
1469 "Should not have added new stream listener function!");
1471 mChannel->Resume();
1473 // Our caller will invoke `EndDocumentLoad` for us.
1475 return !mIsFinished;
1478 void DocumentLoadListener::CancelEarlyHintPreloads() {
1479 mEarlyHintsService.Cancel("DocumentLoadListener::CancelEarlyHintPreloads"_ns);
1482 void DocumentLoadListener::RegisterEarlyHintLinksAndGetConnectArgs(
1483 dom::ContentParentId aCpId, nsTArray<EarlyHintConnectArgs>& aOutLinks) {
1484 mEarlyHintsService.RegisterLinksAndGetConnectArgs(aCpId, aOutLinks);
1487 void DocumentLoadListener::SerializeRedirectData(
1488 RedirectToRealChannelArgs& aArgs, bool aIsCrossProcess,
1489 uint32_t aRedirectFlags, uint32_t aLoadFlags,
1490 nsTArray<EarlyHintConnectArgs>&& aEarlyHints,
1491 uint32_t aEarlyHintLinkType) const {
1492 aArgs.uri() = GetChannelCreationURI();
1493 aArgs.loadIdentifier() = mLoadIdentifier;
1494 aArgs.earlyHints() = std::move(aEarlyHints);
1495 aArgs.earlyHintLinkType() = aEarlyHintLinkType;
1497 // I previously used HttpBaseChannel::CloneLoadInfoForRedirect, but that
1498 // clears the principal to inherit, which fails tests (probably because this
1499 // 'redirect' is usually just an implementation detail). It's also http
1500 // only, and mChannel can be anything that we redirected to.
1501 nsCOMPtr<nsILoadInfo> channelLoadInfo = mChannel->LoadInfo();
1502 nsCOMPtr<nsIPrincipal> principalToInherit;
1503 channelLoadInfo->GetPrincipalToInherit(getter_AddRefs(principalToInherit));
1505 const RefPtr<nsHttpChannel> baseChannel = do_QueryObject(mChannel);
1507 nsCOMPtr<nsILoadContext> loadContext;
1508 NS_QueryNotificationCallbacks(mChannel, loadContext);
1509 nsCOMPtr<nsILoadInfo> redirectLoadInfo;
1511 // Only use CloneLoadInfoForRedirect if we have a load context,
1512 // since it internally tries to pull OriginAttributes from the
1513 // the load context and asserts if they don't match the load info.
1514 // We can end up without a load context if the channel has been aborted
1515 // and the callbacks have been cleared.
1516 if (baseChannel && loadContext) {
1517 redirectLoadInfo = baseChannel->CloneLoadInfoForRedirect(
1518 aArgs.uri(), nsIChannelEventSink::REDIRECT_INTERNAL);
1519 redirectLoadInfo->SetResultPrincipalURI(aArgs.uri());
1521 // The clone process clears this, and then we fail tests..
1522 // docshell/test/mochitest/test_forceinheritprincipal_overrule_owner.html
1523 if (principalToInherit) {
1524 redirectLoadInfo->SetPrincipalToInherit(principalToInherit);
1526 } else {
1527 redirectLoadInfo =
1528 static_cast<mozilla::net::LoadInfo*>(channelLoadInfo.get())->Clone();
1530 redirectLoadInfo->AppendRedirectHistoryEntry(mChannel, true);
1533 const Maybe<ClientInfo>& reservedClientInfo =
1534 channelLoadInfo->GetReservedClientInfo();
1535 if (reservedClientInfo) {
1536 redirectLoadInfo->SetReservedClientInfo(*reservedClientInfo);
1539 aArgs.registrarId() = mRedirectChannelId;
1541 MOZ_ALWAYS_SUCCEEDS(
1542 ipc::LoadInfoToLoadInfoArgs(redirectLoadInfo, &aArgs.loadInfo()));
1544 mChannel->GetOriginalURI(getter_AddRefs(aArgs.originalURI()));
1546 // mChannel can be a nsHttpChannel as well as InterceptedHttpChannel so we
1547 // can't use baseChannel here.
1548 if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel)) {
1549 MOZ_ALWAYS_SUCCEEDS(httpChannel->GetChannelId(&aArgs.channelId()));
1552 aArgs.redirectMode() = nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW;
1553 nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
1554 do_QueryInterface(mChannel);
1555 if (httpChannelInternal) {
1556 MOZ_ALWAYS_SUCCEEDS(
1557 httpChannelInternal->GetRedirectMode(&aArgs.redirectMode()));
1560 if (baseChannel) {
1561 aArgs.init() =
1562 Some(baseChannel
1563 ->CloneReplacementChannelConfig(
1564 true, aRedirectFlags,
1565 HttpBaseChannel::ReplacementReason::DocumentChannel)
1566 .Serialize());
1569 uint32_t contentDispositionTemp;
1570 nsresult rv = mChannel->GetContentDisposition(&contentDispositionTemp);
1571 if (NS_SUCCEEDED(rv)) {
1572 aArgs.contentDisposition() = Some(contentDispositionTemp);
1575 nsString contentDispositionFilenameTemp;
1576 rv = mChannel->GetContentDispositionFilename(contentDispositionFilenameTemp);
1577 if (NS_SUCCEEDED(rv)) {
1578 aArgs.contentDispositionFilename() = Some(contentDispositionFilenameTemp);
1581 SetNeedToAddURIVisit(mChannel, false);
1583 aArgs.newLoadFlags() = aLoadFlags;
1584 aArgs.redirectFlags() = aRedirectFlags;
1585 aArgs.properties() = do_QueryObject(mChannel);
1586 aArgs.srcdocData() = mSrcdocData;
1587 aArgs.baseUri() = mBaseURI;
1588 aArgs.loadStateExternalLoadFlags() = mLoadStateExternalLoadFlags;
1589 aArgs.loadStateInternalLoadFlags() = mLoadStateInternalLoadFlags;
1590 aArgs.loadStateLoadType() = mLoadStateLoadType;
1591 aArgs.originalUriString() = mOriginalUriString;
1592 if (mLoadingSessionHistoryInfo) {
1593 aArgs.loadingSessionHistoryInfo().emplace(*mLoadingSessionHistoryInfo);
1597 static bool IsFirstLoadInWindow(nsIChannel* aChannel) {
1598 if (nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aChannel)) {
1599 bool tmp = false;
1600 nsresult rv =
1601 props->GetPropertyAsBool(u"docshell.newWindowTarget"_ns, &tmp);
1602 return NS_SUCCEEDED(rv) && tmp;
1604 return false;
1607 // Get where the document loaded by this nsIChannel should be rendered. This
1608 // will be `OPEN_CURRENTWINDOW` unless we're loading an attachment which would
1609 // normally open in an external program, but we're instead choosing to render
1610 // internally.
1611 static int32_t GetWhereToOpen(nsIChannel* aChannel, bool aIsDocumentLoad) {
1612 // Ignore content disposition for loads from an object or embed element.
1613 if (!aIsDocumentLoad) {
1614 return nsIBrowserDOMWindow::OPEN_CURRENTWINDOW;
1617 // Always continue in the same window if we're not loading an attachment.
1618 uint32_t disposition = nsIChannel::DISPOSITION_INLINE;
1619 if (NS_FAILED(aChannel->GetContentDisposition(&disposition)) ||
1620 disposition != nsIChannel::DISPOSITION_ATTACHMENT) {
1621 return nsIBrowserDOMWindow::OPEN_CURRENTWINDOW;
1624 // If the channel is for a new window target, continue in the same window.
1625 if (IsFirstLoadInWindow(aChannel)) {
1626 return nsIBrowserDOMWindow::OPEN_CURRENTWINDOW;
1629 // Respect the user's preferences with browser.link.open_newwindow
1630 // FIXME: There should probably be a helper for this, as the logic is
1631 // duplicated in a few places.
1632 int32_t where = Preferences::GetInt("browser.link.open_newwindow",
1633 nsIBrowserDOMWindow::OPEN_NEWTAB);
1634 if (where == nsIBrowserDOMWindow::OPEN_CURRENTWINDOW ||
1635 where == nsIBrowserDOMWindow::OPEN_NEWWINDOW ||
1636 where == nsIBrowserDOMWindow::OPEN_NEWTAB) {
1637 return where;
1639 return nsIBrowserDOMWindow::OPEN_NEWTAB;
1642 static DocumentLoadListener::ProcessBehavior GetProcessSwitchBehavior(
1643 Element* aBrowserElement) {
1644 if (aBrowserElement->HasAttribute(u"maychangeremoteness"_ns)) {
1645 return DocumentLoadListener::ProcessBehavior::PROCESS_BEHAVIOR_STANDARD;
1647 nsCOMPtr<nsIBrowser> browser = aBrowserElement->AsBrowser();
1648 bool isRemoteBrowser = false;
1649 browser->GetIsRemoteBrowser(&isRemoteBrowser);
1650 if (isRemoteBrowser) {
1651 return DocumentLoadListener::ProcessBehavior::
1652 PROCESS_BEHAVIOR_SUBFRAME_ONLY;
1654 return DocumentLoadListener::ProcessBehavior::PROCESS_BEHAVIOR_DISABLED;
1657 static bool ContextCanProcessSwitch(CanonicalBrowsingContext* aBrowsingContext,
1658 WindowGlobalParent* aParentWindow,
1659 bool aSwitchToNewTab) {
1660 if (NS_WARN_IF(!aBrowsingContext)) {
1661 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
1662 ("Process Switch Abort: no browsing context"));
1663 return false;
1665 if (!aBrowsingContext->IsContent()) {
1666 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
1667 ("Process Switch Abort: non-content browsing context"));
1668 return false;
1671 // If we're switching into a new tab, we can skip the remaining checks, as
1672 // we're not actually changing the process of aBrowsingContext, so whether or
1673 // not it is allowed to process switch isn't relevant.
1674 if (aSwitchToNewTab) {
1675 return true;
1678 if (aParentWindow && !aBrowsingContext->UseRemoteSubframes()) {
1679 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
1680 ("Process Switch Abort: remote subframes disabled"));
1681 return false;
1684 if (aParentWindow && aParentWindow->IsInProcess()) {
1685 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
1686 ("Process Switch Abort: Subframe with in-process parent"));
1687 return false;
1690 // Determine what process switching behaviour is being requested by the root
1691 // <browser> element.
1692 Element* browserElement = aBrowsingContext->Top()->GetEmbedderElement();
1693 if (!browserElement) {
1694 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
1695 ("Process Switch Abort: cannot get embedder element"));
1696 return false;
1698 nsCOMPtr<nsIBrowser> browser = browserElement->AsBrowser();
1699 if (!browser) {
1700 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
1701 ("Process Switch Abort: not loaded within nsIBrowser"));
1702 return false;
1705 DocumentLoadListener::ProcessBehavior processBehavior =
1706 GetProcessSwitchBehavior(browserElement);
1708 // Check if the process switch we're considering is disabled by the
1709 // <browser>'s process behavior.
1710 if (processBehavior ==
1711 DocumentLoadListener::ProcessBehavior::PROCESS_BEHAVIOR_DISABLED) {
1712 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
1713 ("Process Switch Abort: switch disabled by <browser>"));
1714 return false;
1716 if (!aParentWindow && processBehavior ==
1717 DocumentLoadListener::ProcessBehavior::
1718 PROCESS_BEHAVIOR_SUBFRAME_ONLY) {
1719 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
1720 ("Process Switch Abort: toplevel switch disabled by <browser>"));
1721 return false;
1724 return true;
1727 static RefPtr<dom::BrowsingContextCallbackReceivedPromise> SwitchToNewTab(
1728 CanonicalBrowsingContext* aLoadingBrowsingContext, int32_t aWhere) {
1729 MOZ_ASSERT(aWhere == nsIBrowserDOMWindow::OPEN_NEWTAB ||
1730 aWhere == nsIBrowserDOMWindow::OPEN_NEWWINDOW,
1731 "Unsupported open location");
1733 auto promise =
1734 MakeRefPtr<dom::BrowsingContextCallbackReceivedPromise::Private>(
1735 __func__);
1737 // Get the nsIBrowserDOMWindow for the given BrowsingContext's tab.
1738 nsCOMPtr<nsIBrowserDOMWindow> browserDOMWindow =
1739 aLoadingBrowsingContext->GetBrowserDOMWindow();
1740 if (NS_WARN_IF(!browserDOMWindow)) {
1741 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
1742 ("Process Switch Abort: Unable to get nsIBrowserDOMWindow"));
1743 promise->Reject(NS_ERROR_FAILURE, __func__);
1744 return promise;
1747 // Open a new content tab by calling into frontend. We don't need to worry
1748 // about the triggering principal or CSP, as createContentWindow doesn't
1749 // actually start loading anything, but use a null principal anyway in case
1750 // something changes.
1751 nsCOMPtr<nsIPrincipal> triggeringPrincipal =
1752 NullPrincipal::Create(aLoadingBrowsingContext->OriginAttributesRef());
1754 RefPtr<nsOpenWindowInfo> openInfo = new nsOpenWindowInfo();
1755 openInfo->mBrowsingContextReadyCallback =
1756 new nsBrowsingContextReadyCallback(promise);
1757 openInfo->mOriginAttributes = aLoadingBrowsingContext->OriginAttributesRef();
1758 openInfo->mParent = aLoadingBrowsingContext;
1759 openInfo->mForceNoOpener = true;
1760 openInfo->mIsRemote = true;
1762 // Do the actual work to open a new tab or window async.
1763 nsresult rv = NS_DispatchToMainThread(NS_NewRunnableFunction(
1764 "DocumentLoadListener::SwitchToNewTab",
1765 [browserDOMWindow, openInfo, aWhere, triggeringPrincipal, promise] {
1766 RefPtr<BrowsingContext> bc;
1767 nsresult rv = browserDOMWindow->CreateContentWindow(
1768 /* uri */ nullptr, openInfo, aWhere,
1769 nsIBrowserDOMWindow::OPEN_NO_REFERRER, triggeringPrincipal,
1770 /* csp */ nullptr, getter_AddRefs(bc));
1771 if (NS_WARN_IF(NS_FAILED(rv))) {
1772 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
1773 ("Process Switch Abort: CreateContentWindow threw"));
1774 promise->Reject(rv, __func__);
1776 if (bc) {
1777 promise->Resolve(bc, __func__);
1779 }));
1780 if (NS_WARN_IF(NS_FAILED(rv))) {
1781 promise->Reject(NS_ERROR_UNEXPECTED, __func__);
1783 return promise;
1786 bool DocumentLoadListener::MaybeTriggerProcessSwitch(
1787 bool* aWillSwitchToRemote) {
1788 MOZ_ASSERT(XRE_IsParentProcess());
1789 MOZ_DIAGNOSTIC_ASSERT(mChannel);
1790 MOZ_DIAGNOSTIC_ASSERT(mParentChannelListener);
1791 MOZ_DIAGNOSTIC_ASSERT(aWillSwitchToRemote);
1793 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
1794 ("DocumentLoadListener MaybeTriggerProcessSwitch [this=%p, uri=%s, "
1795 "browserid=%" PRIx64 "]",
1796 this, GetChannelCreationURI()->GetSpecOrDefault().get(),
1797 GetLoadingBrowsingContext()->Top()->BrowserId()));
1799 // If we're doing an <object>/<embed> load, we may be doing a document load at
1800 // this point. We never need to do a process switch for a non-document
1801 // <object> or <embed> load.
1802 if (!mIsDocumentLoad) {
1803 if (!mChannel->IsDocument()) {
1804 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
1805 ("Process Switch Abort: non-document load"));
1806 return false;
1808 nsresult status;
1809 if (!nsObjectLoadingContent::IsSuccessfulRequest(mChannel, &status)) {
1810 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
1811 ("Process Switch Abort: error page"));
1812 return false;
1816 // Check if we should handle this load in a different tab or window.
1817 int32_t where = GetWhereToOpen(mChannel, mIsDocumentLoad);
1818 bool switchToNewTab = where != nsIBrowserDOMWindow::OPEN_CURRENTWINDOW;
1820 // Get the loading BrowsingContext. This may not be the context which will be
1821 // switching processes when switching to a new tab, and in the case of an
1822 // <object> or <embed> element, as we don't create the final context until
1823 // after process selection.
1825 // - /!\ WARNING /!\ -
1826 // Don't use `browsingContext->IsTop()` in this method! It will behave
1827 // incorrectly for non-document loads such as `<object>` or `<embed>`.
1828 // Instead, check whether or not `parentWindow` is null.
1829 RefPtr<CanonicalBrowsingContext> browsingContext =
1830 GetLoadingBrowsingContext();
1831 // If switching to a new tab, the final BC isn't a frame.
1832 RefPtr<WindowGlobalParent> parentWindow =
1833 switchToNewTab ? nullptr : GetParentWindowContext();
1834 if (!ContextCanProcessSwitch(browsingContext, parentWindow, switchToNewTab)) {
1835 return false;
1838 if (!browsingContext->IsOwnedByProcess(GetContentProcessId(mContentParent))) {
1839 MOZ_LOG(gProcessIsolationLog, LogLevel::Error,
1840 ("Process Switch Abort: context no longer owned by creator"));
1841 Cancel(NS_BINDING_ABORTED,
1842 "Process Switch Abort: context no longer owned by creator"_ns);
1843 return false;
1846 if (browsingContext->IsReplaced()) {
1847 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
1848 ("Process Switch Abort: replaced browsing context"));
1849 Cancel(NS_BINDING_ABORTED,
1850 "Process Switch Abort: replaced browsing context"_ns);
1851 return false;
1854 nsAutoCString currentRemoteType(NOT_REMOTE_TYPE);
1855 if (mContentParent) {
1856 currentRemoteType = mContentParent->GetRemoteType();
1859 auto optionsResult = IsolationOptionsForNavigation(
1860 browsingContext->Top(), switchToNewTab ? nullptr : parentWindow.get(),
1861 GetChannelCreationURI(), mChannel, currentRemoteType,
1862 HasCrossOriginOpenerPolicyMismatch(), switchToNewTab, mLoadStateLoadType,
1863 mDocumentChannelId, mRemoteTypeOverride);
1864 if (optionsResult.isErr()) {
1865 MOZ_LOG(gProcessIsolationLog, LogLevel::Error,
1866 ("Process Switch Abort: CheckIsolationForNavigation Failed with %s",
1867 GetStaticErrorName(optionsResult.inspectErr())));
1868 Cancel(optionsResult.unwrapErr(),
1869 "Process Switch Abort: CheckIsolationForNavigation Failed"_ns);
1870 return false;
1873 NavigationIsolationOptions options = optionsResult.unwrap();
1875 if (options.mTryUseBFCache) {
1876 MOZ_ASSERT(!parentWindow, "Can only BFCache toplevel windows");
1877 MOZ_ASSERT(!switchToNewTab, "Can't BFCache for a tab switch");
1878 bool sameOrigin = false;
1879 if (auto* wgp = browsingContext->GetCurrentWindowGlobal()) {
1880 nsCOMPtr<nsIPrincipal> resultPrincipal;
1881 MOZ_ALWAYS_SUCCEEDS(
1882 nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
1883 mChannel, getter_AddRefs(resultPrincipal)));
1884 sameOrigin =
1885 wgp->DocumentPrincipal()->EqualsConsideringDomain(resultPrincipal);
1888 // We only reset the window name for content.
1889 mLoadingSessionHistoryInfo->mForceMaybeResetName.emplace(
1890 StaticPrefs::privacy_window_name_update_enabled() &&
1891 browsingContext->IsContent() && !sameOrigin);
1894 MOZ_LOG(
1895 gProcessIsolationLog, LogLevel::Verbose,
1896 ("CheckIsolationForNavigation -> current:(%s) remoteType:(%s) replace:%d "
1897 "group:%" PRIx64 " bfcache:%d shentry:%p newTab:%d",
1898 currentRemoteType.get(), options.mRemoteType.get(),
1899 options.mReplaceBrowsingContext, options.mSpecificGroupId,
1900 options.mTryUseBFCache, options.mActiveSessionHistoryEntry.get(),
1901 switchToNewTab));
1903 // Check if a process switch is needed.
1904 if (currentRemoteType == options.mRemoteType &&
1905 !options.mReplaceBrowsingContext && !switchToNewTab) {
1906 MOZ_LOG(gProcessIsolationLog, LogLevel::Info,
1907 ("Process Switch Abort: type (%s) is compatible",
1908 options.mRemoteType.get()));
1909 return false;
1912 if (NS_WARN_IF(parentWindow && options.mRemoteType.IsEmpty())) {
1913 MOZ_LOG(gProcessIsolationLog, LogLevel::Error,
1914 ("Process Switch Abort: non-remote target process for subframe"));
1915 return false;
1918 *aWillSwitchToRemote = !options.mRemoteType.IsEmpty();
1920 // If we've decided to re-target this load into a new tab or window (see
1921 // `GetWhereToOpen`), do so before performing a process switch. This will
1922 // require creating the new <browser> to load in, which may be performed
1923 // async.
1924 if (switchToNewTab) {
1925 SwitchToNewTab(browsingContext, where)
1926 ->Then(
1927 GetMainThreadSerialEventTarget(), __func__,
1928 [self = RefPtr{this},
1929 options](const RefPtr<BrowsingContext>& aBrowsingContext) mutable {
1930 if (aBrowsingContext->IsDiscarded()) {
1931 MOZ_LOG(
1932 gProcessIsolationLog, LogLevel::Error,
1933 ("Process Switch: Got invalid new-tab BrowsingContext"));
1934 self->RedirectToRealChannelFinished(NS_ERROR_FAILURE);
1935 return;
1938 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
1939 ("Process Switch: Redirected load to new tab"));
1940 self->TriggerProcessSwitch(aBrowsingContext->Canonical(), options,
1941 /* aIsNewTab */ true);
1943 [self = RefPtr{this}](const CopyableErrorResult&) {
1944 MOZ_LOG(gProcessIsolationLog, LogLevel::Error,
1945 ("Process Switch: SwitchToNewTab failed"));
1946 self->RedirectToRealChannelFinished(NS_ERROR_FAILURE);
1948 return true;
1951 // If we're doing a document load, we can immediately perform a process
1952 // switch.
1953 if (mIsDocumentLoad) {
1954 TriggerProcessSwitch(browsingContext, options);
1955 return true;
1958 // We're not doing a document load, which means we must be performing an
1959 // object load. We need a BrowsingContext to perform the switch in, so will
1960 // trigger an upgrade.
1961 if (!mObjectUpgradeHandler) {
1962 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning,
1963 ("Process Switch Abort: no object upgrade handler"));
1964 return false;
1967 if (!StaticPrefs::fission_remoteObjectEmbed()) {
1968 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
1969 ("Process Switch Abort: remote <object>/<embed> disabled"));
1970 return false;
1973 mObjectUpgradeHandler->UpgradeObjectLoad()->Then(
1974 GetMainThreadSerialEventTarget(), __func__,
1975 [self = RefPtr{this}, options, parentWindow](
1976 const RefPtr<CanonicalBrowsingContext>& aBrowsingContext) mutable {
1977 if (aBrowsingContext->IsDiscarded() ||
1978 parentWindow != aBrowsingContext->GetParentWindowContext()) {
1979 MOZ_LOG(gProcessIsolationLog, LogLevel::Error,
1980 ("Process Switch: Got invalid BrowsingContext from object "
1981 "upgrade!"));
1982 self->RedirectToRealChannelFinished(NS_ERROR_FAILURE);
1983 return;
1986 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
1987 ("Process Switch: Upgraded Object to Document Load"));
1988 self->TriggerProcessSwitch(aBrowsingContext, options);
1990 [self = RefPtr{this}](nsresult aStatusCode) {
1991 MOZ_ASSERT(NS_FAILED(aStatusCode), "Status should be error");
1992 self->RedirectToRealChannelFinished(aStatusCode);
1994 return true;
1997 void DocumentLoadListener::TriggerProcessSwitch(
1998 CanonicalBrowsingContext* aContext,
1999 const NavigationIsolationOptions& aOptions, bool aIsNewTab) {
2000 MOZ_DIAGNOSTIC_ASSERT(aIsNewTab || aContext->IsOwnedByProcess(
2001 GetContentProcessId(mContentParent)),
2002 "not owned by creator process anymore?");
2003 if (MOZ_LOG_TEST(gProcessIsolationLog, LogLevel::Info)) {
2004 nsCString currentRemoteType = "INVALID"_ns;
2005 aContext->GetCurrentRemoteType(currentRemoteType, IgnoreErrors());
2007 MOZ_LOG(gProcessIsolationLog, LogLevel::Info,
2008 ("Process Switch: Changing Remoteness from '%s' to '%s'",
2009 currentRemoteType.get(), aOptions.mRemoteType.get()));
2012 // Stash our stream filter requests to pass to TriggerRedirectToRealChannel,
2013 // as the call to `DisconnectListeners` will clear our list.
2014 nsTArray<StreamFilterRequest> streamFilterRequests =
2015 std::move(mStreamFilterRequests);
2017 // We're now committing to a process switch, so we can disconnect from
2018 // the listeners in the old process.
2019 // As the navigation is continuing, we don't actually want to cancel the
2020 // request in the old process unless we're redirecting the load into a new
2021 // tab.
2022 DisconnectListeners(NS_BINDING_ABORTED, NS_BINDING_ABORTED, !aIsNewTab);
2024 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
2025 ("Process Switch: Calling ChangeRemoteness"));
2026 aContext->ChangeRemoteness(aOptions, mLoadIdentifier)
2027 ->Then(
2028 GetMainThreadSerialEventTarget(), __func__,
2029 [self = RefPtr{this}, requests = std::move(streamFilterRequests)](
2030 BrowserParent* aBrowserParent) mutable {
2031 MOZ_ASSERT(self->mChannel,
2032 "Something went wrong, channel got cancelled");
2033 self->TriggerRedirectToRealChannel(
2034 Some(aBrowserParent ? aBrowserParent->Manager() : nullptr),
2035 std::move(requests));
2037 [self = RefPtr{this}](nsresult aStatusCode) {
2038 MOZ_ASSERT(NS_FAILED(aStatusCode), "Status should be error");
2039 self->RedirectToRealChannelFinished(aStatusCode);
2043 RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
2044 DocumentLoadListener::RedirectToParentProcess(uint32_t aRedirectFlags,
2045 uint32_t aLoadFlags) {
2046 // This is largely the same as ContentChild::RecvCrossProcessRedirect,
2047 // except without needing to deserialize or create an nsIChildChannel.
2049 RefPtr<nsDocShellLoadState> loadState;
2050 nsDocShellLoadState::CreateFromPendingChannel(
2051 mChannel, mLoadIdentifier, mRedirectChannelId, getter_AddRefs(loadState));
2053 loadState->SetLoadFlags(mLoadStateExternalLoadFlags);
2054 loadState->SetInternalLoadFlags(mLoadStateInternalLoadFlags);
2055 loadState->SetLoadType(mLoadStateLoadType);
2056 if (mLoadingSessionHistoryInfo) {
2057 loadState->SetLoadingSessionHistoryInfo(*mLoadingSessionHistoryInfo);
2060 // This is poorly named now.
2061 RefPtr<ChildProcessChannelListener> processListener =
2062 ChildProcessChannelListener::GetSingleton();
2064 auto promise =
2065 MakeRefPtr<PDocumentChannelParent::RedirectToRealChannelPromise::Private>(
2066 __func__);
2067 promise->UseDirectTaskDispatch(__func__);
2068 auto resolve = [promise](nsresult aResult) {
2069 promise->Resolve(aResult, __func__);
2072 nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>> endpoints;
2073 processListener->OnChannelReady(loadState, mLoadIdentifier,
2074 std::move(endpoints), mTiming,
2075 std::move(resolve));
2077 return promise;
2080 RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
2081 DocumentLoadListener::RedirectToRealChannel(
2082 uint32_t aRedirectFlags, uint32_t aLoadFlags,
2083 const Maybe<ContentParent*>& aDestinationProcess,
2084 nsTArray<ParentEndpoint>&& aStreamFilterEndpoints) {
2085 LOG(
2086 ("DocumentLoadListener RedirectToRealChannel [this=%p] "
2087 "aRedirectFlags=%" PRIx32 ", aLoadFlags=%" PRIx32,
2088 this, aRedirectFlags, aLoadFlags));
2090 if (mIsDocumentLoad) {
2091 // TODO(djg): Add the last URI visit to history if success. Is there a
2092 // better place to handle this? Need access to the updated aLoadFlags.
2093 nsresult status = NS_OK;
2094 mChannel->GetStatus(&status);
2095 bool updateGHistory =
2096 nsDocShell::ShouldUpdateGlobalHistory(mLoadStateLoadType);
2097 if (NS_SUCCEEDED(status) && updateGHistory &&
2098 !net::ChannelIsPost(mChannel)) {
2099 AddURIVisit(mChannel, aLoadFlags);
2103 // Register the new channel and obtain id for it
2104 nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
2105 RedirectChannelRegistrar::GetOrCreate();
2106 MOZ_ASSERT(registrar);
2107 nsCOMPtr<nsIChannel> chan = mChannel;
2108 if (nsCOMPtr<nsIViewSourceChannel> vsc = do_QueryInterface(chan)) {
2109 chan = vsc->GetInnerChannel();
2111 mRedirectChannelId = nsContentUtils::GenerateLoadIdentifier();
2112 MOZ_ALWAYS_SUCCEEDS(registrar->RegisterChannel(chan, mRedirectChannelId));
2114 if (aDestinationProcess) {
2115 RefPtr<ContentParent> cp = *aDestinationProcess;
2116 if (!cp) {
2117 MOZ_ASSERT(aStreamFilterEndpoints.IsEmpty());
2118 return RedirectToParentProcess(aRedirectFlags, aLoadFlags);
2121 if (!cp->CanSend()) {
2122 return PDocumentChannelParent::RedirectToRealChannelPromise::
2123 CreateAndReject(ipc::ResponseRejectReason::SendError, __func__);
2126 nsTArray<EarlyHintConnectArgs> ehArgs;
2127 mEarlyHintsService.RegisterLinksAndGetConnectArgs(cp->ChildID(), ehArgs);
2129 RedirectToRealChannelArgs args;
2130 SerializeRedirectData(args, /* aIsCrossProcess */ true, aRedirectFlags,
2131 aLoadFlags, std::move(ehArgs),
2132 mEarlyHintsService.LinkType());
2133 if (mTiming) {
2134 mTiming->Anonymize(args.uri());
2135 args.timing() = std::move(mTiming);
2138 cp->TransmitBlobDataIfBlobURL(args.uri());
2140 if (CanonicalBrowsingContext* bc = GetDocumentBrowsingContext()) {
2141 if (bc->IsTop() && bc->IsActive()) {
2142 nsContentUtils::RequestGeckoTaskBurst();
2146 return cp->SendCrossProcessRedirect(args,
2147 std::move(aStreamFilterEndpoints));
2150 if (mOpenPromiseResolved) {
2151 LOG(
2152 ("DocumentLoadListener RedirectToRealChannel [this=%p] "
2153 "promise already resolved. Aborting.",
2154 this));
2155 // The promise has already been resolved or aborted, so we have no way to
2156 // return a promise again to the listener which would cancel the operation.
2157 // Reject the promise immediately.
2158 return PDocumentChannelParent::RedirectToRealChannelPromise::
2159 CreateAndResolve(NS_BINDING_ABORTED, __func__);
2162 // This promise will be passed on the promise listener which will
2163 // resolve this promise for us.
2164 auto promise =
2165 MakeRefPtr<PDocumentChannelParent::RedirectToRealChannelPromise::Private>(
2166 __func__);
2168 mOpenPromise->Resolve(
2169 OpenPromiseSucceededType({std::move(aStreamFilterEndpoints),
2170 aRedirectFlags, aLoadFlags,
2171 mEarlyHintsService.LinkType(), promise}),
2172 __func__);
2174 // There is no way we could come back here if the promise had been resolved
2175 // previously. But for clarity and to avoid all doubt, we set this boolean to
2176 // true.
2177 mOpenPromiseResolved = true;
2179 return promise;
2182 void DocumentLoadListener::TriggerRedirectToRealChannel(
2183 const Maybe<ContentParent*>& aDestinationProcess,
2184 nsTArray<StreamFilterRequest> aStreamFilterRequests) {
2185 LOG((
2186 "DocumentLoadListener::TriggerRedirectToRealChannel [this=%p] "
2187 "aDestinationProcess=%" PRId64,
2188 this, aDestinationProcess ? int64_t(*aDestinationProcess) : int64_t(-1)));
2189 // This initiates replacing the current DocumentChannel with a
2190 // protocol specific 'real' channel, maybe in a different process than
2191 // the current DocumentChannelChild, if aDestinationProces is set.
2192 // It registers the current mChannel with the registrar to get an ID
2193 // so that the remote end can setup a new IPDL channel and lookup
2194 // the same underlying channel.
2195 // We expect this process to finish with FinishReplacementChannelSetup
2196 // (for both in-process and process switch cases), where we cleanup
2197 // the registrar and copy across any needed state to the replacing
2198 // IPDL parent object.
2200 nsTArray<ParentEndpoint> parentEndpoints(aStreamFilterRequests.Length());
2201 if (!aStreamFilterRequests.IsEmpty()) {
2202 ContentParent* cp = aDestinationProcess.valueOr(mContentParent);
2203 base::ProcessId pid = cp ? cp->OtherPid() : base::ProcessId{0};
2205 for (StreamFilterRequest& request : aStreamFilterRequests) {
2206 if (!pid) {
2207 request.mPromise->Reject(false, __func__);
2208 request.mPromise = nullptr;
2209 continue;
2211 ParentEndpoint parent;
2212 nsresult rv = extensions::PStreamFilter::CreateEndpoints(
2213 &parent, &request.mChildEndpoint);
2215 if (NS_FAILED(rv)) {
2216 request.mPromise->Reject(false, __func__);
2217 request.mPromise = nullptr;
2218 } else {
2219 parentEndpoints.AppendElement(std::move(parent));
2224 // If we didn't have any redirects, then we pass the REDIRECT_INTERNAL flag
2225 // for this channel switch so that it isn't recorded in session history etc.
2226 // If there were redirect(s), then we want this switch to be recorded as a
2227 // real one, since we have a new URI.
2228 uint32_t redirectFlags = 0;
2229 if (!mHaveVisibleRedirect) {
2230 redirectFlags = nsIChannelEventSink::REDIRECT_INTERNAL;
2233 uint32_t newLoadFlags = nsIRequest::LOAD_NORMAL;
2234 MOZ_ALWAYS_SUCCEEDS(mChannel->GetLoadFlags(&newLoadFlags));
2235 // We're pulling our flags from the inner channel, which may not have this
2236 // flag set on it. This is the case when loading a 'view-source' channel.
2237 if (mIsDocumentLoad || aDestinationProcess) {
2238 newLoadFlags |= nsIChannel::LOAD_DOCUMENT_URI;
2240 if (!aDestinationProcess) {
2241 newLoadFlags |= nsIChannel::LOAD_REPLACE;
2244 // INHIBIT_PERSISTENT_CACHING is clearing during http redirects (from
2245 // both parent and content process channel instances), but only ever
2246 // re-added to the parent-side nsHttpChannel.
2247 // To match that behaviour, we want to explicitly avoid copying this flag
2248 // back to our newly created content side channel, otherwise it can
2249 // affect sub-resources loads in the same load group.
2250 nsCOMPtr<nsIURI> uri;
2251 mChannel->GetURI(getter_AddRefs(uri));
2252 if (uri && uri->SchemeIs("https")) {
2253 newLoadFlags &= ~nsIRequest::INHIBIT_PERSISTENT_CACHING;
2256 RefPtr<DocumentLoadListener> self = this;
2257 RedirectToRealChannel(redirectFlags, newLoadFlags, aDestinationProcess,
2258 std::move(parentEndpoints))
2259 ->Then(
2260 GetCurrentSerialEventTarget(), __func__,
2261 [self, requests = std::move(aStreamFilterRequests)](
2262 const nsresult& aResponse) mutable {
2263 for (StreamFilterRequest& request : requests) {
2264 if (request.mPromise) {
2265 request.mPromise->Resolve(std::move(request.mChildEndpoint),
2266 __func__);
2267 request.mPromise = nullptr;
2270 self->RedirectToRealChannelFinished(aResponse);
2272 [self](const mozilla::ipc::ResponseRejectReason) {
2273 self->RedirectToRealChannelFinished(NS_ERROR_FAILURE);
2277 void DocumentLoadListener::MaybeReportBlockedByURLClassifier(nsresult aStatus) {
2278 auto* browsingContext = GetDocumentBrowsingContext();
2279 if (!browsingContext || browsingContext->IsTop() ||
2280 !StaticPrefs::privacy_trackingprotection_testing_report_blocked_node()) {
2281 return;
2284 if (!UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aStatus)) {
2285 return;
2288 RefPtr<WindowGlobalParent> parent = browsingContext->GetParentWindowContext();
2289 if (parent) {
2290 Unused << parent->SendAddBlockedFrameNodeByClassifier(browsingContext);
2294 bool DocumentLoadListener::DocShellWillDisplayContent(nsresult aStatus) {
2295 if (NS_SUCCEEDED(aStatus)) {
2296 return true;
2299 // Always return errored loads to the <object> or <embed> element's process,
2300 // as load errors will not be rendered as documents.
2301 if (!mIsDocumentLoad) {
2302 return false;
2305 // nsDocShell attempts urifixup on some failure types,
2306 // but also of those also display an error page if we don't
2307 // succeed with fixup, so we don't need to check for it
2308 // here.
2310 auto* loadingContext = GetLoadingBrowsingContext();
2312 bool isInitialDocument = true;
2313 if (WindowGlobalParent* currentWindow =
2314 loadingContext->GetCurrentWindowGlobal()) {
2315 isInitialDocument = currentWindow->IsInitialDocument();
2318 nsresult rv = nsDocShell::FilterStatusForErrorPage(
2319 aStatus, mChannel, mLoadStateLoadType, loadingContext->IsTop(),
2320 loadingContext->GetUseErrorPages(), isInitialDocument, nullptr);
2322 if (NS_SUCCEEDED(rv)) {
2323 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose,
2324 ("Skipping process switch, as DocShell will not display content "
2325 "(status: %s) %s",
2326 GetStaticErrorName(aStatus),
2327 GetChannelCreationURI()->GetSpecOrDefault().get()));
2330 // If filtering returned a failure code, then an error page will
2331 // be display for that code, so return true;
2332 return NS_FAILED(rv);
2335 bool DocumentLoadListener::MaybeHandleLoadErrorWithURIFixup(nsresult aStatus) {
2336 RefPtr<CanonicalBrowsingContext> bc = GetDocumentBrowsingContext();
2337 if (!bc) {
2338 return false;
2341 nsCOMPtr<nsIInputStream> newPostData;
2342 nsCOMPtr<nsIURI> newURI = nsDocShell::AttemptURIFixup(
2343 mChannel, aStatus, mOriginalUriString, mLoadStateLoadType, bc->IsTop(),
2344 mLoadStateInternalLoadFlags &
2345 nsDocShell::INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP,
2346 bc->UsePrivateBrowsing(), true, getter_AddRefs(newPostData));
2348 // Since aStatus will be NS_OK for 4xx and 5xx error codes we
2349 // have to check each request which was upgraded by https-first.
2350 // If an error (including 4xx and 5xx) occured, then let's check if
2351 // we can downgrade the scheme to HTTP again.
2352 bool isHTTPSFirstFixup = false;
2353 if (!newURI) {
2354 newURI = nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(mChannel,
2355 aStatus);
2356 isHTTPSFirstFixup = true;
2359 if (!newURI) {
2360 return false;
2363 // If we got a new URI, then we should initiate a load with that.
2364 // Notify the listeners that this load is complete (with a code that
2365 // won't trigger an error page), and then start the new one.
2366 DisconnectListeners(NS_BINDING_ABORTED, NS_BINDING_ABORTED);
2368 RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(newURI);
2369 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
2371 nsCOMPtr<nsIContentSecurityPolicy> cspToInherit = loadInfo->GetCspToInherit();
2372 loadState->SetCsp(cspToInherit);
2374 nsCOMPtr<nsIPrincipal> triggeringPrincipal = loadInfo->TriggeringPrincipal();
2375 loadState->SetTriggeringPrincipal(triggeringPrincipal);
2377 loadState->SetPostDataStream(newPostData);
2379 if (isHTTPSFirstFixup) {
2380 // We have to exempt the load from HTTPS-First to prevent a
2381 // upgrade-downgrade loop.
2382 loadState->SetIsExemptFromHTTPSOnlyMode(true);
2385 // Ensure to set referrer information in the fallback channel equally to the
2386 // not-upgraded original referrer info.
2388 // A simply copy of the referrer info from the upgraded one leads to problems.
2389 // For example:
2390 // 1. https://some-site.com redirects to http://other-site.com with referrer
2391 // policy
2392 // "no-referrer-when-downgrade".
2393 // 2. https-first upgrades the redirection, so redirects to
2394 // https://other-site.com,
2395 // according to referrer policy the referrer will be send (https-> https)
2396 // 3. Assume other-site.com is not supporting https, https-first performs
2397 // fall-
2398 // back.
2399 // If the referrer info from the upgraded channel gets copied into the
2400 // http fallback channel, the referrer info would contain the referrer
2401 // (https://some-site.com). That would violate the policy
2402 // "no-referrer-when-downgrade". A recreation of the original referrer info
2403 // would ensure us that the referrer is set according to the referrer policy.
2404 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
2405 if (httpChannel) {
2406 nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo();
2407 if (referrerInfo) {
2408 ReferrerPolicy referrerPolicy = referrerInfo->ReferrerPolicy();
2409 nsCOMPtr<nsIURI> originalReferrer = referrerInfo->GetOriginalReferrer();
2410 if (originalReferrer) {
2411 // Create new ReferrerInfo with the original referrer and the referrer
2412 // policy.
2413 nsCOMPtr<nsIReferrerInfo> newReferrerInfo =
2414 new ReferrerInfo(originalReferrer, referrerPolicy);
2415 loadState->SetReferrerInfo(newReferrerInfo);
2420 bc->LoadURI(loadState, false);
2421 return true;
2424 NS_IMETHODIMP
2425 DocumentLoadListener::OnStartRequest(nsIRequest* aRequest) {
2426 LOG(("DocumentLoadListener OnStartRequest [this=%p]", this));
2428 nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest);
2429 if (multiPartChannel) {
2430 multiPartChannel->GetBaseChannel(getter_AddRefs(mChannel));
2431 } else {
2432 mChannel = do_QueryInterface(aRequest);
2434 MOZ_DIAGNOSTIC_ASSERT(mChannel);
2436 if (mHaveVisibleRedirect && GetDocumentBrowsingContext() &&
2437 mLoadingSessionHistoryInfo) {
2438 mLoadingSessionHistoryInfo =
2439 GetDocumentBrowsingContext()->ReplaceLoadingSessionHistoryEntryForLoad(
2440 mLoadingSessionHistoryInfo.get(), mChannel);
2443 RefPtr<nsHttpChannel> httpChannel = do_QueryObject(mChannel);
2445 // Enforce CSP frame-ancestors and x-frame-options checks which
2446 // might cancel the channel.
2447 nsContentSecurityUtils::PerformCSPFrameAncestorAndXFOCheck(mChannel);
2449 // HTTPS-Only Mode tries to upgrade connections to https. Once loading
2450 // is in progress we set that flag so that timeout counter measures
2451 // do not kick in.
2452 if (httpChannel) {
2453 nsCOMPtr<nsILoadInfo> loadInfo = httpChannel->LoadInfo();
2454 bool isPrivateWin = loadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
2455 if (nsHTTPSOnlyUtils::IsHttpsOnlyModeEnabled(isPrivateWin)) {
2456 uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
2457 httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_TOP_LEVEL_LOAD_IN_PROGRESS;
2458 loadInfo->SetHttpsOnlyStatus(httpsOnlyStatus);
2461 if (mLoadingSessionHistoryInfo &&
2462 nsDocShell::ShouldDiscardLayoutState(httpChannel)) {
2463 mLoadingSessionHistoryInfo->mInfo.SetSaveLayoutStateFlag(false);
2467 auto* loadingContext = GetLoadingBrowsingContext();
2468 if (!loadingContext || loadingContext->IsDiscarded()) {
2469 Cancel(NS_ERROR_UNEXPECTED, "No valid LoadingBrowsingContext."_ns);
2470 return NS_ERROR_UNEXPECTED;
2473 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
2474 Cancel(NS_ERROR_ILLEGAL_DURING_SHUTDOWN,
2475 "Aborting OnStartRequest after shutdown started."_ns);
2476 return NS_OK;
2479 // Block top-level data URI navigations if triggered by the web. Logging is
2480 // performed in AllowTopLevelNavigationToDataURI.
2481 if (!nsContentSecurityManager::AllowTopLevelNavigationToDataURI(mChannel)) {
2482 mChannel->Cancel(NS_ERROR_DOM_BAD_URI);
2483 if (loadingContext) {
2484 RefPtr<MaybeCloseWindowHelper> maybeCloseWindowHelper =
2485 new MaybeCloseWindowHelper(loadingContext);
2486 // If a new window was opened specifically for this request, close it
2487 // after blocking the navigation.
2488 maybeCloseWindowHelper->SetShouldCloseWindow(
2489 IsFirstLoadInWindow(mChannel));
2490 Unused << maybeCloseWindowHelper->MaybeCloseWindow();
2492 DisconnectListeners(NS_ERROR_DOM_BAD_URI, NS_ERROR_DOM_BAD_URI);
2493 return NS_OK;
2496 // Generally we want to switch to a real channel even if the request failed,
2497 // since the listener might want to access protocol-specific data (like http
2498 // response headers) in its error handling.
2499 // An exception to this is when nsExtProtocolChannel handled the request and
2500 // returned NS_ERROR_NO_CONTENT, since creating a real one in the content
2501 // process will attempt to handle the URI a second time.
2502 nsresult status = NS_OK;
2503 aRequest->GetStatus(&status);
2504 if (status == NS_ERROR_NO_CONTENT) {
2505 DisconnectListeners(status, status);
2506 return NS_OK;
2509 // PerformCSPFrameAncestorAndXFOCheck may cancel a moz-extension request that
2510 // needs to be handled here. Without this, the resource would be loaded and
2511 // not blocked when the real channel is created in the content process.
2512 if (status == NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION && !httpChannel) {
2513 DisconnectListeners(status, status);
2514 return NS_OK;
2517 // If this was a failed load and we want to try fixing the uri, then
2518 // this will initiate a new load (and disconnect this one), and we don't
2519 // need to do anything else.
2520 if (MaybeHandleLoadErrorWithURIFixup(status)) {
2521 return NS_OK;
2524 mStreamListenerFunctions.AppendElement(StreamListenerFunction{
2525 VariantIndex<0>{}, OnStartRequestParams{aRequest}});
2527 if (mOpenPromiseResolved || mInitiatedRedirectToRealChannel) {
2528 // I we have already resolved the promise, there's no point to continue
2529 // attempting a process switch or redirecting to the real channel.
2530 // We can also have multiple calls to OnStartRequest when dealing with
2531 // multi-part content, but only want to redirect once.
2532 return NS_OK;
2535 // Keep track of server responses resulting in a document for the Bounce
2536 // Tracking Protection.
2537 if (mIsDocumentLoad && GetParentWindowContext() == nullptr &&
2538 loadingContext->IsTopContent()) {
2539 RefPtr<BounceTrackingState> bounceTrackingState =
2540 loadingContext->GetBounceTrackingState();
2542 // Not every browsing context has a BounceTrackingState. It's also null when
2543 // the feature is disabled.
2544 if (bounceTrackingState) {
2545 DebugOnly<nsresult> rv =
2546 bounceTrackingState->OnDocumentStartRequest(mChannel);
2547 NS_WARNING_ASSERTION(
2548 NS_SUCCEEDED(rv),
2549 "BounceTrackingState::OnDocumentStartRequest failed.");
2553 mChannel->Suspend();
2555 mInitiatedRedirectToRealChannel = true;
2557 MaybeReportBlockedByURLClassifier(status);
2559 // Determine if a new process needs to be spawned. If it does, this will
2560 // trigger a cross process switch, and we should hold off on redirecting to
2561 // the real channel.
2562 // If the channel has failed, and the docshell isn't going to display an
2563 // error page for that failure, then don't allow process switching, since
2564 // we just want to keep our existing document.
2565 bool willBeRemote = false;
2566 if (!DocShellWillDisplayContent(status) ||
2567 !MaybeTriggerProcessSwitch(&willBeRemote)) {
2568 // We're not going to be doing a process switch, so redirect to the real
2569 // channel within our current process.
2570 nsTArray<StreamFilterRequest> streamFilterRequests =
2571 std::move(mStreamFilterRequests);
2572 if (!mSupportsRedirectToRealChannel) {
2573 RefPtr<BrowserParent> browserParent = loadingContext->GetBrowserParent();
2574 if (browserParent->Manager() != mContentParent) {
2575 LOG(
2576 ("DocumentLoadListener::RedirectToRealChannel failed because "
2577 "browsingContext no longer owned by creator"));
2578 Cancel(NS_BINDING_ABORTED,
2579 "DocumentLoadListener::RedirectToRealChannel failed because "
2580 "browsingContext no longer owned by creator"_ns);
2581 return NS_OK;
2583 MOZ_DIAGNOSTIC_ASSERT(
2584 browserParent->GetBrowsingContext() == loadingContext,
2585 "make sure the load is going to the right place");
2587 // If the existing process is right for this load, but the bridge doesn't
2588 // support redirects, then we need to do it manually, by faking a process
2589 // switch.
2590 DisconnectListeners(NS_BINDING_ABORTED, NS_BINDING_ABORTED,
2591 /* aContinueNavigating */ true);
2593 // Notify the docshell that it should load using the newly connected
2594 // channel
2595 browserParent->ResumeLoad(mLoadIdentifier);
2597 // Use the current process ID to run the 'process switch' path and connect
2598 // the channel into the current process.
2599 TriggerRedirectToRealChannel(Some(mContentParent),
2600 std::move(streamFilterRequests));
2601 } else {
2602 TriggerRedirectToRealChannel(Nothing(), std::move(streamFilterRequests));
2605 // If we're not switching, then check if we're currently remote.
2606 if (mContentParent) {
2607 willBeRemote = true;
2611 if (httpChannel) {
2612 uint32_t responseStatus = 0;
2613 Unused << httpChannel->GetResponseStatus(&responseStatus);
2614 mEarlyHintsService.FinalResponse(responseStatus);
2615 } else {
2616 mEarlyHintsService.Cancel(
2617 "DocumentLoadListener::OnStartRequest: no httpChannel"_ns);
2620 // If we're going to be delivering this channel to a remote content
2621 // process, then we want to install any required content conversions
2622 // in the content process.
2623 // The caller of this OnStartRequest will install a conversion
2624 // helper after we return if we haven't disabled conversion. Normally
2625 // HttpChannelParent::OnStartRequest would disable conversion, but we're
2626 // defering calling that until later. Manually disable it now to prevent the
2627 // converter from being installed (since we want the child to do it), and
2628 // also save the value so that when we do call
2629 // HttpChannelParent::OnStartRequest, we can have the value as it originally
2630 // was.
2631 if (httpChannel) {
2632 Unused << httpChannel->GetApplyConversion(&mOldApplyConversion);
2633 if (willBeRemote) {
2634 httpChannel->SetApplyConversion(false);
2638 return NS_OK;
2641 NS_IMETHODIMP
2642 DocumentLoadListener::OnStopRequest(nsIRequest* aRequest,
2643 nsresult aStatusCode) {
2644 LOG(("DocumentLoadListener OnStopRequest [this=%p]", this));
2645 mStreamListenerFunctions.AppendElement(StreamListenerFunction{
2646 VariantIndex<2>{}, OnStopRequestParams{aRequest, aStatusCode}});
2648 // If we're not a multi-part channel, then we're finished and we don't
2649 // expect any further events. If we are, then this might be called again,
2650 // so wait for OnAfterLastPart instead.
2651 nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest);
2652 if (!multiPartChannel) {
2653 mIsFinished = true;
2656 mStreamFilterRequests.Clear();
2658 return NS_OK;
2661 NS_IMETHODIMP
2662 DocumentLoadListener::OnDataAvailable(nsIRequest* aRequest,
2663 nsIInputStream* aInputStream,
2664 uint64_t aOffset, uint32_t aCount) {
2665 LOG(("DocumentLoadListener OnDataAvailable [this=%p]", this));
2666 // This isn't supposed to happen, since we suspended the channel, but
2667 // sometimes Suspend just doesn't work. This can happen when we're routing
2668 // through nsUnknownDecoder to sniff the content type, and it doesn't handle
2669 // being suspended. Let's just store the data and manually forward it to our
2670 // redirected channel when it's ready.
2671 nsCString data;
2672 nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount);
2673 NS_ENSURE_SUCCESS(rv, rv);
2675 mStreamListenerFunctions.AppendElement(StreamListenerFunction{
2676 VariantIndex<1>{},
2677 OnDataAvailableParams{aRequest, data, aOffset, aCount}});
2679 return NS_OK;
2682 //-----------------------------------------------------------------------------
2683 // DoucmentLoadListener::nsIMultiPartChannelListener
2684 //-----------------------------------------------------------------------------
2686 NS_IMETHODIMP
2687 DocumentLoadListener::OnAfterLastPart(nsresult aStatus) {
2688 LOG(("DocumentLoadListener OnAfterLastPart [this=%p]", this));
2689 if (!mInitiatedRedirectToRealChannel) {
2690 // if we get here, and we haven't initiated a redirect to a real
2691 // channel, then it means we never got OnStartRequest (maybe a problem?)
2692 // and we retargeted everything.
2693 LOG(("DocumentLoadListener Disconnecting child"));
2694 DisconnectListeners(NS_BINDING_RETARGETED, NS_OK);
2695 return NS_OK;
2697 mStreamListenerFunctions.AppendElement(StreamListenerFunction{
2698 VariantIndex<3>{}, OnAfterLastPartParams{aStatus}});
2699 mIsFinished = true;
2700 return NS_OK;
2703 NS_IMETHODIMP
2704 DocumentLoadListener::GetInterface(const nsIID& aIID, void** result) {
2705 RefPtr<CanonicalBrowsingContext> browsingContext =
2706 GetLoadingBrowsingContext();
2707 if (aIID.Equals(NS_GET_IID(nsILoadContext)) && browsingContext) {
2708 browsingContext.forget(result);
2709 return NS_OK;
2712 return QueryInterface(aIID, result);
2715 ////////////////////////////////////////////////////////////////////////////////
2716 // nsIParentChannel
2717 ////////////////////////////////////////////////////////////////////////////////
2719 NS_IMETHODIMP
2720 DocumentLoadListener::SetParentListener(
2721 mozilla::net::ParentChannelListener* listener) {
2722 // We don't need this (do we?)
2723 return NS_OK;
2726 NS_IMETHODIMP
2727 DocumentLoadListener::SetClassifierMatchedInfo(const nsACString& aList,
2728 const nsACString& aProvider,
2729 const nsACString& aFullHash) {
2730 ClassifierMatchedInfoParams params;
2731 params.mList = aList;
2732 params.mProvider = aProvider;
2733 params.mFullHash = aFullHash;
2735 mIParentChannelFunctions.AppendElement(
2736 IParentChannelFunction{VariantIndex<0>{}, std::move(params)});
2737 return NS_OK;
2740 NS_IMETHODIMP
2741 DocumentLoadListener::SetClassifierMatchedTrackingInfo(
2742 const nsACString& aLists, const nsACString& aFullHash) {
2743 ClassifierMatchedTrackingInfoParams params;
2744 params.mLists = aLists;
2745 params.mFullHashes = aFullHash;
2747 mIParentChannelFunctions.AppendElement(
2748 IParentChannelFunction{VariantIndex<1>{}, std::move(params)});
2749 return NS_OK;
2752 NS_IMETHODIMP
2753 DocumentLoadListener::NotifyClassificationFlags(uint32_t aClassificationFlags,
2754 bool aIsThirdParty) {
2755 mIParentChannelFunctions.AppendElement(IParentChannelFunction{
2756 VariantIndex<2>{},
2757 ClassificationFlagsParams{aClassificationFlags, aIsThirdParty}});
2758 return NS_OK;
2761 NS_IMETHODIMP
2762 DocumentLoadListener::Delete() {
2763 MOZ_ASSERT_UNREACHABLE("This method is unused");
2764 return NS_OK;
2767 NS_IMETHODIMP
2768 DocumentLoadListener::GetRemoteType(nsACString& aRemoteType) {
2769 // FIXME: The remote type here should be pulled from the remote process used
2770 // to create this DLL, not from the current `browsingContext`.
2771 RefPtr<CanonicalBrowsingContext> browsingContext =
2772 GetDocumentBrowsingContext();
2773 if (!browsingContext) {
2774 return NS_ERROR_UNEXPECTED;
2777 ErrorResult error;
2778 browsingContext->GetCurrentRemoteType(aRemoteType, error);
2779 if (error.Failed()) {
2780 aRemoteType = NOT_REMOTE_TYPE;
2782 return NS_OK;
2785 ////////////////////////////////////////////////////////////////////////////////
2786 // nsIChannelEventSink
2787 ////////////////////////////////////////////////////////////////////////////////
2789 NS_IMETHODIMP
2790 DocumentLoadListener::AsyncOnChannelRedirect(
2791 nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
2792 nsIAsyncVerifyRedirectCallback* aCallback) {
2793 LOG(("DocumentLoadListener::AsyncOnChannelRedirect [this=%p flags=%" PRIu32
2794 "]",
2795 this, aFlags));
2796 // We generally don't want to notify the content process about redirects,
2797 // so just update our channel and tell the callback that we're good to go.
2798 mChannel = aNewChannel;
2800 // We need the original URI of the current channel to use to open the real
2801 // channel in the content process. Unfortunately we overwrite the original
2802 // uri of the new channel with the original pre-redirect URI, so grab
2803 // a copy of it now and save it on the loadInfo corresponding to the
2804 // new channel.
2805 nsCOMPtr<nsILoadInfo> loadInfoFromChannel = mChannel->LoadInfo();
2806 MOZ_ASSERT(loadInfoFromChannel);
2807 nsCOMPtr<nsIURI> uri;
2808 mChannel->GetOriginalURI(getter_AddRefs(uri));
2809 loadInfoFromChannel->SetChannelCreationOriginalURI(uri);
2811 // Since we're redirecting away from aOldChannel, we should check if it
2812 // had a COOP mismatch, since we want the final result for this to
2813 // include the state of all channels we redirected through.
2814 nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(aOldChannel);
2815 if (httpChannel) {
2816 bool isCOOPMismatch = false;
2817 Unused << NS_WARN_IF(NS_FAILED(
2818 httpChannel->HasCrossOriginOpenerPolicyMismatch(&isCOOPMismatch)));
2819 mHasCrossOriginOpenerPolicyMismatch |= isCOOPMismatch;
2822 // If HTTPS-Only mode is enabled, we need to check whether the exception-flag
2823 // needs to be removed or set, by asking the PermissionManager.
2824 nsHTTPSOnlyUtils::TestSitePermissionAndPotentiallyAddExemption(mChannel);
2826 // We don't need to confirm internal redirects or record any
2827 // history for them, so just immediately verify and return.
2828 if (aFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
2829 LOG(
2830 ("DocumentLoadListener AsyncOnChannelRedirect [this=%p] "
2831 "flags=REDIRECT_INTERNAL",
2832 this));
2833 aCallback->OnRedirectVerifyCallback(NS_OK);
2834 return NS_OK;
2837 // Cancel cross origin redirects as described by whatwg:
2838 // > Note: [The early hint reponse] is discarded if it is succeeded by a
2839 // > cross-origin redirect.
2840 // https://html.spec.whatwg.org/multipage/semantics.html#early-hints
2841 nsCOMPtr<nsIURI> oldURI;
2842 aOldChannel->GetURI(getter_AddRefs(oldURI));
2843 nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
2844 nsresult rv = ssm->CheckSameOriginURI(oldURI, uri, false, false);
2845 if (NS_FAILED(rv)) {
2846 mEarlyHintsService.Cancel(
2847 "DocumentLoadListener::AsyncOnChannelRedirect: cors redirect"_ns);
2850 if (GetDocumentBrowsingContext()) {
2851 if (!net::ChannelIsPost(aOldChannel)) {
2852 AddURIVisit(aOldChannel, 0);
2853 nsDocShell::SaveLastVisit(aNewChannel, oldURI, aFlags);
2856 mHaveVisibleRedirect |= true;
2858 LOG(
2859 ("DocumentLoadListener AsyncOnChannelRedirect [this=%p] "
2860 "mHaveVisibleRedirect=%c",
2861 this, mHaveVisibleRedirect ? 'T' : 'F'));
2863 // Clear out our nsIParentChannel functions, since a normal parent
2864 // channel would actually redirect and not have those values on the new one.
2865 // We expect the URI classifier to run on the redirected channel with
2866 // the new URI and set these again.
2867 mIParentChannelFunctions.Clear();
2869 // If we had a remote type override, ensure it's been cleared after a
2870 // redirect, as it can't apply anymore.
2871 mRemoteTypeOverride.reset();
2873 #ifdef ANDROID
2874 nsCOMPtr<nsIURI> uriBeingLoaded =
2875 AntiTrackingUtils::MaybeGetDocumentURIBeingLoaded(mChannel);
2877 RefPtr<MozPromise<bool, bool, false>> promise;
2878 RefPtr<CanonicalBrowsingContext> bc =
2879 mParentChannelListener->GetBrowsingContext();
2880 nsCOMPtr<nsIWidget> widget =
2881 bc ? bc->GetParentProcessWidgetContaining() : nullptr;
2882 RefPtr<nsWindow> window = nsWindow::From(widget);
2884 if (window) {
2885 promise = window->OnLoadRequest(uriBeingLoaded,
2886 nsIBrowserDOMWindow::OPEN_CURRENTWINDOW,
2887 nsIWebNavigation::LOAD_FLAGS_IS_REDIRECT,
2888 nullptr, false, bc->IsTopContent());
2891 if (promise) {
2892 RefPtr<nsIAsyncVerifyRedirectCallback> cb = aCallback;
2893 promise->Then(
2894 GetCurrentSerialEventTarget(), __func__,
2895 [=](const MozPromise<bool, bool, false>::ResolveOrRejectValue& aValue) {
2896 if (aValue.IsResolve()) {
2897 bool handled = aValue.ResolveValue();
2898 if (handled) {
2899 cb->OnRedirectVerifyCallback(NS_ERROR_ABORT);
2900 } else {
2901 cb->OnRedirectVerifyCallback(NS_OK);
2905 } else
2906 #endif /* ANDROID */
2908 aCallback->OnRedirectVerifyCallback(NS_OK);
2910 return NS_OK;
2913 nsIURI* DocumentLoadListener::GetChannelCreationURI() const {
2914 nsCOMPtr<nsILoadInfo> channelLoadInfo = mChannel->LoadInfo();
2916 nsCOMPtr<nsIURI> uri;
2917 channelLoadInfo->GetChannelCreationOriginalURI(getter_AddRefs(uri));
2918 if (uri) {
2919 // See channelCreationOriginalURI for more info. We use this instead of the
2920 // originalURI of the channel to help us avoid the situation when we use
2921 // the URI of a redirect that has failed to happen.
2922 return uri;
2925 // Otherwise, get the original URI from the channel.
2926 mChannel->GetOriginalURI(getter_AddRefs(uri));
2927 return uri;
2930 // This method returns the cached result of running the Cross-Origin-Opener
2931 // policy compare algorithm by calling ComputeCrossOriginOpenerPolicyMismatch
2932 bool DocumentLoadListener::HasCrossOriginOpenerPolicyMismatch() const {
2933 // If we found a COOP mismatch on an earlier channel and then
2934 // redirected away from that, we should use that result.
2935 if (mHasCrossOriginOpenerPolicyMismatch) {
2936 return true;
2939 nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(mChannel);
2940 if (!httpChannel) {
2941 // Not an nsIHttpChannelInternal assume it's okay to switch.
2942 return false;
2945 bool isCOOPMismatch = false;
2946 Unused << NS_WARN_IF(NS_FAILED(
2947 httpChannel->HasCrossOriginOpenerPolicyMismatch(&isCOOPMismatch)));
2948 return isCOOPMismatch;
2951 auto DocumentLoadListener::AttachStreamFilter()
2952 -> RefPtr<ChildEndpointPromise> {
2953 LOG(("DocumentLoadListener AttachStreamFilter [this=%p]", this));
2955 StreamFilterRequest* request = mStreamFilterRequests.AppendElement();
2956 request->mPromise = new ChildEndpointPromise::Private(__func__);
2957 return request->mPromise;
2960 NS_IMETHODIMP DocumentLoadListener::OnProgress(nsIRequest* aRequest,
2961 int64_t aProgress,
2962 int64_t aProgressMax) {
2963 return NS_OK;
2966 NS_IMETHODIMP DocumentLoadListener::OnStatus(nsIRequest* aRequest,
2967 nsresult aStatus,
2968 const char16_t* aStatusArg) {
2969 nsCOMPtr<nsIChannel> channel = mChannel;
2971 RefPtr<BrowsingContextWebProgress> webProgress =
2972 GetLoadingBrowsingContext()->GetWebProgress();
2973 const nsString message(aStatusArg);
2975 if (webProgress) {
2976 NS_DispatchToMainThread(
2977 NS_NewRunnableFunction("DocumentLoadListener::OnStatus", [=]() {
2978 webProgress->OnStatusChange(webProgress, channel, aStatus,
2979 message.get());
2980 }));
2982 return NS_OK;
2985 NS_IMETHODIMP DocumentLoadListener::EarlyHint(const nsACString& aLinkHeader,
2986 const nsACString& aReferrerPolicy,
2987 const nsACString& aCSPHeader) {
2988 LOG(("DocumentLoadListener::EarlyHint.\n"));
2989 mEarlyHintsService.EarlyHint(aLinkHeader, GetChannelCreationURI(), mChannel,
2990 aReferrerPolicy, aCSPHeader, this);
2991 return NS_OK;
2994 } // namespace net
2995 } // namespace mozilla
2997 #undef LOG