DocumentLoadListener.cpp (131697B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set sw=2 ts=8 et tw=80 : */ 3 4 /* This Source Code Form is subject to the terms of the Mozilla Public 5 * License, v. 2.0. If a copy of the MPL was not distributed with this 6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 7 8 #include "DocumentLoadListener.h" 9 10 #include "imgLoader.h" 11 #include "NeckoCommon.h" 12 #include "nsLoadGroup.h" 13 #include "mozilla/AntiTrackingUtils.h" 14 #include "mozilla/AppShutdown.h" 15 #include "mozilla/DebugOnly.h" 16 #include "mozilla/DynamicFpiNavigationHeuristic.h" 17 #include "mozilla/Components.h" 18 #include "mozilla/LoadInfo.h" 19 #include "mozilla/ipc/PBackgroundSharedTypes.h" 20 #include "mozilla/NullPrincipal.h" 21 #include "mozilla/RefPtr.h" 22 #include "mozilla/ResultVariant.h" 23 #include "mozilla/ScopeExit.h" 24 #include "mozilla/StaticPrefs_extensions.h" 25 #include "mozilla/StaticPrefs_fission.h" 26 #include "mozilla/StaticPrefs_security.h" 27 #include "mozilla/dom/BrowserParent.h" 28 #include "mozilla/dom/BrowsingContextGroup.h" 29 #include "mozilla/dom/CanonicalBrowsingContext.h" 30 #include "mozilla/dom/ChildProcessChannelListener.h" 31 #include "mozilla/dom/ClientChannelHelper.h" 32 #include "mozilla/dom/ContentParent.h" 33 #include "mozilla/dom/ContentProcessManager.h" 34 #include "mozilla/dom/ProcessIsolation.h" 35 #include "mozilla/dom/SessionHistoryEntry.h" 36 #include "mozilla/dom/WindowGlobalParent.h" 37 #include "mozilla/dom/ipc/IdType.h" 38 #include "mozilla/net/CookieJarSettings.h" 39 #include "mozilla/net/HttpChannelParent.h" 40 #include "mozilla/net/RedirectChannelRegistrar.h" 41 #include "nsContentSecurityUtils.h" 42 #include "nsContentSecurityManager.h" 43 #include "nsDocShell.h" 44 #include "nsDocShellLoadState.h" 45 #include "nsDocShellLoadTypes.h" 46 #include "nsDOMNavigationTiming.h" 47 #include "nsDSURIContentListener.h" 48 #include "nsObjectLoadingContent.h" 49 #include "nsOpenWindowInfo.h" 50 #include "nsExternalHelperAppService.h" 51 #include "nsHttpChannel.h" 52 #include "nsIBrowser.h" 53 #include "nsIClassifiedChannel.h" 54 #include "nsIHttpChannelInternal.h" 55 #include "nsINetworkInterceptController.h" 56 #include "nsIStreamConverterService.h" 57 #include "nsIViewSourceChannel.h" 58 #include "nsImportModule.h" 59 #include "nsIXULRuntime.h" 60 #include "nsMimeTypes.h" 61 #include "nsQueryObject.h" 62 #include "nsRedirectHistoryEntry.h" 63 #include "nsSandboxFlags.h" 64 #include "nsScriptSecurityManager.h" 65 #include "nsSHistory.h" 66 #include "nsStringStream.h" 67 #include "nsURILoader.h" 68 #include "nsWebNavigationInfo.h" 69 #include "mozilla/dom/BrowserParent.h" 70 #include "mozilla/dom/Element.h" 71 #include "mozilla/dom/nsHTTPSOnlyUtils.h" 72 #include "mozilla/dom/ReferrerInfo.h" 73 #include "mozilla/dom/RemoteWebProgressRequest.h" 74 #include "mozilla/net/UrlClassifierFeatureFactory.h" 75 #include "mozilla/ExtensionPolicyService.h" 76 #include "mozilla/intl/Localization.h" 77 #include "nsDocLoader.h" // for FormatStatusMessage 78 79 #ifdef ANDROID 80 # include "mozilla/widget/nsWindow.h" 81 #endif /* ANDROID */ 82 83 mozilla::LazyLogModule gDocumentChannelLog("DocumentChannel"); 84 #define LOG(fmt) MOZ_LOG(gDocumentChannelLog, mozilla::LogLevel::Verbose, fmt) 85 86 extern mozilla::LazyLogModule gSHIPBFCacheLog; 87 88 // Bug 136580: Limit to the number of nested content frames that can have the 89 // same URL. This is to stop content that is recursively loading 90 // itself. Note that "#foo" on the end of URL doesn't affect 91 // whether it's considered identical, but "?foo" or ";foo" are 92 // considered and compared. 93 // Limit this to 2, like chromium does. 94 static constexpr int kMaxSameURLContentFrames = 2; 95 96 using namespace mozilla::dom; 97 98 namespace mozilla { 99 namespace net { 100 101 static StaticRefPtr<mozilla::intl::Localization> sL10n; 102 103 static ContentParentId GetContentProcessId(ContentParent* aContentParent) { 104 return aContentParent ? aContentParent->ChildID() : ContentParentId{0}; 105 } 106 107 static void SetNeedToAddURIVisit(nsIChannel* aChannel, 108 bool aNeedToAddURIVisit) { 109 nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(aChannel)); 110 if (!props) { 111 return; 112 } 113 114 props->SetPropertyAsBool(u"docshell.needToAddURIVisit"_ns, 115 aNeedToAddURIVisit); 116 } 117 118 static auto SecurityFlagsForLoadInfo(nsDocShellLoadState* aLoadState) 119 -> nsSecurityFlags { 120 // TODO: Block copied from nsDocShell::DoURILoad, refactor out somewhere 121 nsSecurityFlags securityFlags = 122 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL; 123 124 if (aLoadState->LoadType() == LOAD_ERROR_PAGE) { 125 securityFlags |= nsILoadInfo::SEC_LOAD_ERROR_PAGE; 126 } 127 128 if (aLoadState->PrincipalToInherit()) { 129 nsIURI* uri = aLoadState->URI(); 130 bool isSrcdoc = aLoadState->HasInternalLoadFlags( 131 nsDocShell::INTERNAL_LOAD_FLAGS_IS_SRCDOC); 132 bool inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal( 133 aLoadState->PrincipalToInherit(), uri, 134 true, // aInheritForAboutBlank 135 isSrcdoc); 136 137 if (inheritAttrs && !uri->SchemeIs("data")) { 138 securityFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL; 139 } 140 } 141 142 return securityFlags; 143 } 144 145 // Construct a LoadInfo object to use when creating the internal channel for a 146 // Document/SubDocument load. 147 static auto CreateDocumentLoadInfo(CanonicalBrowsingContext* aBrowsingContext, 148 nsDocShellLoadState* aLoadState) 149 -> already_AddRefed<LoadInfo> { 150 uint32_t sandboxFlags = aBrowsingContext->GetSandboxFlags(); 151 RefPtr<LoadInfo> loadInfo; 152 153 auto securityFlags = SecurityFlagsForLoadInfo(aLoadState); 154 155 if (aBrowsingContext->GetParent()) { 156 loadInfo = LoadInfo::CreateForFrame( 157 aBrowsingContext, aLoadState->TriggeringPrincipal(), 158 aLoadState->GetEffectiveTriggeringRemoteType(), securityFlags, 159 sandboxFlags); 160 } else { 161 OriginAttributes attrs; 162 aBrowsingContext->GetOriginAttributes(attrs); 163 loadInfo = LoadInfo::CreateForDocument( 164 aBrowsingContext, aLoadState->URI(), aLoadState->TriggeringPrincipal(), 165 aLoadState->GetEffectiveTriggeringRemoteType(), attrs, securityFlags, 166 sandboxFlags); 167 } 168 169 if (aLoadState->IsExemptFromHTTPSFirstMode() && 170 nsHTTPSOnlyUtils::GetUpgradeMode(loadInfo) == 171 nsHTTPSOnlyUtils::HTTPS_FIRST_MODE) { 172 uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus(); 173 httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_EXEMPT; 174 loadInfo->SetHttpsOnlyStatus(httpsOnlyStatus); 175 } 176 177 loadInfo->SetSchemelessInput(aLoadState->GetSchemelessInput()); 178 loadInfo->SetHttpsUpgradeTelemetry(aLoadState->GetHttpsUpgradeTelemetry()); 179 180 loadInfo->SetTriggeringSandboxFlags(aLoadState->TriggeringSandboxFlags()); 181 loadInfo->SetTriggeringWindowId(aLoadState->TriggeringWindowId()); 182 loadInfo->SetTriggeringStorageAccess(aLoadState->TriggeringStorageAccess()); 183 ClassificationFlags classificationFlags = 184 aLoadState->TriggeringClassificationFlags(); 185 loadInfo->SetTriggeringFirstPartyClassificationFlags( 186 classificationFlags.firstPartyFlags); 187 loadInfo->SetTriggeringThirdPartyClassificationFlags( 188 classificationFlags.thirdPartyFlags); 189 loadInfo->SetHasValidUserGestureActivation( 190 aLoadState->HasValidUserGestureActivation()); 191 loadInfo->SetTextDirectiveUserActivation( 192 aLoadState->GetTextDirectiveUserActivation()); 193 loadInfo->SetIsMetaRefresh(aLoadState->IsMetaRefresh()); 194 195 return loadInfo.forget(); 196 } 197 198 // Construct a LoadInfo object to use when creating the internal channel for an 199 // Object/Embed load. 200 static auto CreateObjectLoadInfo(nsDocShellLoadState* aLoadState, 201 uint64_t aInnerWindowId, 202 nsContentPolicyType aContentPolicyType, 203 uint32_t aSandboxFlags) 204 -> already_AddRefed<LoadInfo> { 205 RefPtr<WindowGlobalParent> wgp = 206 WindowGlobalParent::GetByInnerWindowId(aInnerWindowId); 207 MOZ_RELEASE_ASSERT(wgp); 208 209 auto securityFlags = SecurityFlagsForLoadInfo(aLoadState); 210 211 RefPtr<LoadInfo> loadInfo = LoadInfo::CreateForNonDocument( 212 wgp, wgp->DocumentPrincipal(), aContentPolicyType, securityFlags, 213 aSandboxFlags); 214 215 loadInfo->SetHasValidUserGestureActivation( 216 aLoadState->HasValidUserGestureActivation()); 217 loadInfo->SetTextDirectiveUserActivation( 218 aLoadState->GetTextDirectiveUserActivation()); 219 loadInfo->SetTriggeringSandboxFlags(aLoadState->TriggeringSandboxFlags()); 220 loadInfo->SetTriggeringWindowId(aLoadState->TriggeringWindowId()); 221 loadInfo->SetTriggeringStorageAccess(aLoadState->TriggeringStorageAccess()); 222 net::ClassificationFlags classificationFlags = 223 aLoadState->TriggeringClassificationFlags(); 224 loadInfo->SetTriggeringFirstPartyClassificationFlags( 225 classificationFlags.firstPartyFlags); 226 loadInfo->SetTriggeringThirdPartyClassificationFlags( 227 classificationFlags.thirdPartyFlags); 228 loadInfo->SetIsMetaRefresh(aLoadState->IsMetaRefresh()); 229 230 return loadInfo.forget(); 231 } 232 233 /** 234 * An extension to nsDocumentOpenInfo that we run in the parent process, so 235 * that we can make the decision to retarget to content handlers or the external 236 * helper app, before we make process switching decisions. 237 * 238 * This modifies the behaviour of nsDocumentOpenInfo so that it can do 239 * retargeting, but doesn't do stream conversion (but confirms that we will be 240 * able to do so later). 241 * 242 * We still run nsDocumentOpenInfo in the content process, but disable 243 * retargeting, so that it can only apply stream conversion, and then send data 244 * to the docshell. 245 */ 246 class ParentProcessDocumentOpenInfo final : public nsDocumentOpenInfo, 247 public nsIMultiPartChannelListener { 248 public: 249 ParentProcessDocumentOpenInfo(ParentChannelListener* aListener, 250 uint32_t aFlags, 251 mozilla::dom::BrowsingContext* aBrowsingContext, 252 const nsACString& aTypeHint, 253 bool aIsDocumentLoad) 254 : nsDocumentOpenInfo(aFlags, false), 255 mBrowsingContext(aBrowsingContext), 256 mListener(aListener), 257 mTypeHint(aTypeHint), 258 mIsDocumentLoad(aIsDocumentLoad) { 259 LOG(("ParentProcessDocumentOpenInfo ctor [this=%p]", this)); 260 } 261 262 NS_DECL_ISUPPORTS_INHERITED 263 264 // The default content listener is always a docshell (potentially with an 265 // interstitial nsObjectLoadingContent), so this manually implements the same 266 // checks, and if it succeeds, uses the parent channel listener so that we 267 // forward onto DocumentLoadListener. 268 bool TryDefaultContentListener(nsIChannel* aChannel, 269 const nsCString& aContentType) { 270 uint32_t canHandle = nsWebNavigationInfo::IsTypeSupported(aContentType); 271 // NOTE: We do not support the default content listener for `FALLBACK` on 272 // object/embed loads, as there's no need to send content to the content 273 // process in the fallback case. By rejecting the channel will be cancelled 274 // with NS_ERROR_WONT_HANDLE_CONTENT, which will lead to a fallback in 275 // content without sending the response data down. 276 if (canHandle != nsIWebNavigationInfo::UNSUPPORTED && 277 (mIsDocumentLoad || canHandle != nsIWebNavigationInfo::FALLBACK)) { 278 m_targetStreamListener = mListener; 279 nsLoadFlags loadFlags = 0; 280 aChannel->GetLoadFlags(&loadFlags); 281 aChannel->SetLoadFlags(loadFlags | nsIChannel::LOAD_TARGETED); 282 return true; 283 } 284 return false; 285 } 286 287 bool TryDefaultContentListener(nsIChannel* aChannel) override { 288 return TryDefaultContentListener(aChannel, mContentType); 289 } 290 291 // Generally we only support stream converters that can tell 292 // use exactly what type they'll output. If we find one, then 293 // we just target to our default listener directly (without 294 // conversion), and the content process nsDocumentOpenInfo will 295 // run and do the actual conversion. 296 nsresult TryStreamConversion(nsIChannel* aChannel) override { 297 // The one exception is nsUnknownDecoder, which works in the parent 298 // (and we need to know what the content type is before we can 299 // decide if it will be handled in the parent), so we run that here. 300 if (mContentType.LowerCaseEqualsASCII(UNKNOWN_CONTENT_TYPE) || 301 mContentType.IsEmpty()) { 302 return nsDocumentOpenInfo::TryStreamConversion(aChannel); 303 } 304 305 nsresult rv; 306 nsCOMPtr<nsIStreamConverterService> streamConvService; 307 nsAutoCString str; 308 streamConvService = mozilla::components::StreamConverter::Service(&rv); 309 rv = streamConvService->ConvertedType(mContentType, aChannel, str); 310 NS_ENSURE_SUCCESS(rv, rv); 311 312 // We only support passing data to the default content listener 313 // (docshell), and we don't supported chaining converters. 314 if (TryDefaultContentListener(aChannel, str)) { 315 mContentType = str; 316 return NS_OK; 317 } 318 // This is the same result as nsStreamConverterService uses when it 319 // can't find a converter 320 return NS_ERROR_FAILURE; 321 } 322 323 nsresult TryExternalHelperApp(nsIExternalHelperAppService* aHelperAppService, 324 nsIChannel* aChannel) override { 325 RefPtr<nsIStreamListener> listener; 326 nsresult rv = aHelperAppService->CreateListener( 327 mContentType, aChannel, mBrowsingContext, false, nullptr, 328 getter_AddRefs(listener)); 329 if (NS_SUCCEEDED(rv)) { 330 m_targetStreamListener = listener; 331 } 332 return rv; 333 } 334 335 nsDocumentOpenInfo* Clone() override { 336 mCloned = true; 337 return new ParentProcessDocumentOpenInfo( 338 mListener, mFlags, mBrowsingContext, mTypeHint, mIsDocumentLoad); 339 } 340 341 nsresult OnDocumentStartRequest(nsIRequest* request) { 342 LOG(("ParentProcessDocumentOpenInfo OnDocumentStartRequest [this=%p]", 343 this)); 344 345 return nsDocumentOpenInfo::OnStartRequest(request); 346 } 347 348 nsresult OnObjectStartRequest(nsIRequest* request) { 349 LOG(("ParentProcessDocumentOpenInfo OnObjectStartRequest [this=%p]", this)); 350 351 // Respect the specified image MIME type if loading binary content type into 352 // an object/embed element. 353 if (nsCOMPtr<nsIChannel> channel = do_QueryInterface(request)) { 354 nsAutoCString channelType; 355 channel->GetContentType(channelType); 356 if (!mTypeHint.IsEmpty() && 357 imgLoader::SupportImageWithMimeType(mTypeHint) && 358 (channelType.EqualsASCII(APPLICATION_GUESS_FROM_EXT) || 359 channelType.EqualsASCII(APPLICATION_OCTET_STREAM) || 360 channelType.EqualsASCII(BINARY_OCTET_STREAM))) { 361 channel->SetContentType(mTypeHint); 362 } 363 } 364 365 // If the load is considered to have failed, we're going to display fallback 366 // content in the nsDocShellLoadingContent. Cancel the channel to reflect 367 // this. 368 nsresult status = NS_OK; 369 if (!nsObjectLoadingContent::IsSuccessfulRequest(request, &status)) { 370 LOG(("OnObjectStartRequest for unsuccessful request [this=%p, status=%s]", 371 this, GetStaticErrorName(status))); 372 return NS_ERROR_WONT_HANDLE_CONTENT; 373 } 374 375 // All successful object loads will be treated as document loads, so run 376 // through nsDocumentOpenInfo. This will check the MIME type to ensure it is 377 // supported, and attempt stream conversions where applicable. 378 // 379 // If the dom.navigation.object_embed.allow_retargeting pref is enabled, 380 // this may lead to the resource being downloaded. 381 return OnDocumentStartRequest(request); 382 } 383 384 NS_IMETHOD OnStartRequest(nsIRequest* request) override { 385 LOG(("ParentProcessDocumentOpenInfo OnStartRequest [this=%p]", this)); 386 387 nsresult rv = mIsDocumentLoad ? OnDocumentStartRequest(request) 388 : OnObjectStartRequest(request); 389 390 // If we didn't find a content handler, and we don't have a listener, then 391 // just forward to our default listener. This happens when the channel is in 392 // an error state, and we want to just forward that on to be handled in the 393 // content process, or when DONT_RETARGET is set. 394 if (!mUsedContentHandler && !m_targetStreamListener) { 395 m_targetStreamListener = mListener; 396 if (NS_FAILED(rv)) { 397 LOG(("nsDocumentOpenInfo OnStartRequest Failed [this=%p, rv=%s]", this, 398 GetStaticErrorName(rv))); 399 request->CancelWithReason( 400 rv, "nsDocumentOpenInfo::OnStartRequest failed"_ns); 401 } 402 return m_targetStreamListener->OnStartRequest(request); 403 } 404 if (m_targetStreamListener != mListener) { 405 LOG( 406 ("ParentProcessDocumentOpenInfo targeted to non-default listener " 407 "[this=%p]", 408 this)); 409 // If this is the only part, then we can immediately tell our listener 410 // that it won't be getting any content and disconnect it. For multipart 411 // channels we have to wait until we've handled all parts before we know. 412 // This does mean that the content process can still Cancel() a multipart 413 // response while the response is being handled externally, but this 414 // matches the single-process behaviour. 415 // If we got cloned, then we don't need to do this, as only the last link 416 // needs to do it. 417 // Multi-part channels are guaranteed to call OnAfterLastPart, which we 418 // forward to the listeners, so it will handle disconnection at that 419 // point. 420 nsCOMPtr<nsIMultiPartChannel> multiPartChannel = 421 do_QueryInterface(request); 422 if (!multiPartChannel && !mCloned) { 423 DisconnectChildListeners(NS_FAILED(rv) ? rv : NS_BINDING_RETARGETED, 424 rv); 425 } 426 } 427 428 return rv; 429 } 430 431 NS_IMETHOD OnAfterLastPart(nsresult aStatus) override { 432 mListener->OnAfterLastPart(aStatus); 433 return NS_OK; 434 } 435 436 private: 437 virtual ~ParentProcessDocumentOpenInfo() { 438 LOG(("ParentProcessDocumentOpenInfo dtor [this=%p]", this)); 439 } 440 441 void DisconnectChildListeners(nsresult aStatus, nsresult aLoadGroupStatus) { 442 // Tell the DocumentLoadListener to notify the content process that it's 443 // been entirely retargeted, and to stop waiting. 444 // Clear mListener's pointer to the DocumentLoadListener to break the 445 // reference cycle. 446 RefPtr<DocumentLoadListener> doc = do_GetInterface(ToSupports(mListener)); 447 MOZ_ASSERT(doc); 448 doc->DisconnectListeners(aStatus, aLoadGroupStatus); 449 mListener->SetListenerAfterRedirect(nullptr); 450 } 451 452 RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext; 453 RefPtr<ParentChannelListener> mListener; 454 nsCString mTypeHint; 455 const bool mIsDocumentLoad; 456 457 /** 458 * Set to true if we got cloned to create a chained listener. 459 */ 460 bool mCloned = false; 461 }; 462 463 NS_IMPL_ADDREF_INHERITED(ParentProcessDocumentOpenInfo, nsDocumentOpenInfo) 464 NS_IMPL_RELEASE_INHERITED(ParentProcessDocumentOpenInfo, nsDocumentOpenInfo) 465 466 NS_INTERFACE_MAP_BEGIN(ParentProcessDocumentOpenInfo) 467 NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannelListener) 468 NS_INTERFACE_MAP_END_INHERITING(nsDocumentOpenInfo) 469 470 NS_IMPL_ADDREF(DocumentLoadListener) 471 NS_IMPL_RELEASE(DocumentLoadListener) 472 473 NS_INTERFACE_MAP_BEGIN(DocumentLoadListener) 474 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInterfaceRequestor) 475 NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) 476 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) 477 NS_INTERFACE_MAP_ENTRY(nsIStreamListener) 478 NS_INTERFACE_MAP_ENTRY(nsIParentChannel) 479 NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectReadyCallback) 480 NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink) 481 NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannelListener) 482 NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink) 483 NS_INTERFACE_MAP_ENTRY(nsIEarlyHintObserver) 484 NS_INTERFACE_MAP_ENTRY_CONCRETE(DocumentLoadListener) 485 NS_INTERFACE_MAP_END 486 487 DocumentLoadListener::DocumentLoadListener( 488 CanonicalBrowsingContext* aLoadingBrowsingContext, bool aIsDocumentLoad) 489 : mIsDocumentLoad(aIsDocumentLoad) { 490 LOG(("DocumentLoadListener ctor [this=%p]", this)); 491 mParentChannelListener = 492 new ParentChannelListener(this, aLoadingBrowsingContext); 493 } 494 495 DocumentLoadListener::~DocumentLoadListener() { 496 LOG(("DocumentLoadListener dtor [this=%p]", this)); 497 } 498 499 void DocumentLoadListener::AddURIVisit(nsIChannel* aChannel, 500 uint32_t aLoadFlags) { 501 if (mLoadStateLoadType == LOAD_ERROR_PAGE || 502 mLoadStateLoadType == LOAD_BYPASS_HISTORY) { 503 return; 504 } 505 506 nsCOMPtr<nsIURI> uri; 507 NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); 508 509 nsCOMPtr<nsIURI> previousURI; 510 uint32_t previousFlags = 0; 511 if (mLoadStateLoadType & nsIDocShell::LOAD_CMD_RELOAD) { 512 previousURI = uri; 513 } else { 514 nsDocShell::ExtractLastVisit(aChannel, getter_AddRefs(previousURI), 515 &previousFlags); 516 } 517 518 // Get the HTTP response code, if available. 519 uint32_t responseStatus = 0; 520 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel); 521 if (httpChannel) { 522 (void)httpChannel->GetResponseStatus(&responseStatus); 523 } 524 525 RefPtr<CanonicalBrowsingContext> browsingContext = 526 GetDocumentBrowsingContext(); 527 nsCOMPtr<nsIWidget> widget = 528 browsingContext->GetParentProcessWidgetContaining(); 529 530 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); 531 532 // Check if the URI has a http scheme and if either HSTS is enabled for this 533 // host, or if we were upgraded by HTTPS-Only/First. If this is the case, we 534 // want to mark this URI specially, as it will be followed shortly by an 535 // almost identical https history entry. That way the global history 536 // implementation can handle the visit appropriately (e.g. by marking it as 537 // `hidden`, so only the https url appears in default history views). 538 bool wasUpgraded = 539 uri->SchemeIs("http") && 540 (loadInfo->GetHstsStatus() || 541 (loadInfo->GetHttpsOnlyStatus() & 542 (nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST | 543 nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_NOT_REGISTERED | 544 nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_REGISTERED))); 545 546 nsDocShell::InternalAddURIVisit(uri, previousURI, previousFlags, 547 responseStatus, browsingContext, widget, 548 mLoadStateLoadType, wasUpgraded); 549 } 550 551 CanonicalBrowsingContext* DocumentLoadListener::GetLoadingBrowsingContext() 552 const { 553 return mParentChannelListener ? mParentChannelListener->GetBrowsingContext() 554 : nullptr; 555 } 556 557 CanonicalBrowsingContext* DocumentLoadListener::GetDocumentBrowsingContext() 558 const { 559 return mIsDocumentLoad ? GetLoadingBrowsingContext() : nullptr; 560 } 561 562 CanonicalBrowsingContext* DocumentLoadListener::GetTopBrowsingContext() const { 563 auto* loadingContext = GetLoadingBrowsingContext(); 564 return loadingContext ? loadingContext->Top() : nullptr; 565 } 566 567 WindowGlobalParent* DocumentLoadListener::GetParentWindowContext() const { 568 return mParentWindowContext; 569 } 570 571 bool CheckRecursiveLoad(CanonicalBrowsingContext* aLoadingContext, 572 nsDocShellLoadState* aLoadState, bool aIsDocumentLoad) { 573 if (!aLoadState->ShouldCheckForRecursion()) { 574 return true; 575 } 576 577 // Bug 136580: Check for recursive frame loading excluding about:srcdoc URIs. 578 // srcdoc URIs require their contents to be specified inline, so it isn't 579 // possible for undesirable recursion to occur without the aid of a 580 // non-srcdoc URI, which this method will block normally. 581 // Besides, URI is not enough to guarantee uniqueness of srcdoc documents. 582 nsAutoCString buffer; 583 if (aLoadState->URI()->SchemeIs("about")) { 584 nsresult rv = aLoadState->URI()->GetPathQueryRef(buffer); 585 if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("srcdoc")) { 586 // Duplicates allowed up to depth limits 587 return true; 588 } 589 } 590 591 RefPtr<WindowGlobalParent> parent; 592 if (!aIsDocumentLoad) { // object load 593 parent = aLoadingContext->GetCurrentWindowGlobal(); 594 } else { 595 parent = aLoadingContext->GetParentWindowContext(); 596 } 597 598 int matchCount = 0; 599 CanonicalBrowsingContext* ancestorBC; 600 for (WindowGlobalParent* ancestorWGP = parent; ancestorWGP; 601 ancestorWGP = ancestorBC->GetParentWindowContext()) { 602 ancestorBC = ancestorWGP->BrowsingContext(); 603 MOZ_ASSERT(ancestorBC); 604 if (nsCOMPtr<nsIURI> parentURI = ancestorWGP->GetDocumentURI()) { 605 bool equal; 606 nsresult rv = aLoadState->URI()->EqualsExceptRef(parentURI, &equal); 607 NS_ENSURE_SUCCESS(rv, false); 608 609 if (equal) { 610 matchCount++; 611 if (matchCount >= kMaxSameURLContentFrames) { 612 NS_WARNING( 613 "Too many nested content frames/objects have the same url " 614 "(recursion?) " 615 "so giving up"); 616 return false; 617 } 618 } 619 } 620 } 621 return true; 622 } 623 624 // Check that the load state, potentially received from a child process, appears 625 // to be performing a load of the specified LoadingSessionHistoryInfo. 626 // Returns a Result<…> containing the SessionHistoryEntry found for the 627 // LoadingSessionHistoryInfo as success value if the validation succeeded, or a 628 // static (telemetry-safe) string naming what did not match as a failure value 629 // if the validation failed. 630 static Result<SessionHistoryEntry*, const char*> ValidateHistoryLoad( 631 CanonicalBrowsingContext* aLoadingContext, 632 nsDocShellLoadState* aLoadState) { 633 MOZ_ASSERT(SessionHistoryInParent()); 634 MOZ_ASSERT(aLoadState->LoadIsFromSessionHistory()); 635 636 if (!aLoadState->GetLoadingSessionHistoryInfo()) { 637 return Err("Missing LoadingSessionHistoryInfo"); 638 } 639 640 SessionHistoryEntry::LoadingEntry* loading = SessionHistoryEntry::GetByLoadId( 641 aLoadState->GetLoadingSessionHistoryInfo()->mLoadId); 642 if (!loading) { 643 return Err("Missing SessionHistoryEntry"); 644 } 645 646 SessionHistoryInfo* snapshot = loading->mInfoSnapshotForValidation.get(); 647 // History loads do not inherit principal. 648 if (aLoadState->HasInternalLoadFlags( 649 nsDocShell::INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL)) { 650 return Err("LOAD_FLAGS_INHERIT_PRINCIPAL"); 651 } 652 653 auto uriEq = [](nsIURI* a, nsIURI* b) -> bool { 654 bool eq = false; 655 return a == b || (a && b && NS_SUCCEEDED(a->Equals(b, &eq)) && eq); 656 }; 657 auto principalEq = [](nsIPrincipal* a, nsIPrincipal* b) -> bool { 658 return a == b || (a && b && a->Equals(b)); 659 }; 660 661 // XXX: Needing to do all of this validation manually is kinda gross. 662 if (!uriEq(snapshot->GetURI(), aLoadState->URI())) { 663 return Err("URI"); 664 } 665 if (!uriEq(snapshot->GetOriginalURI(), aLoadState->OriginalURI())) { 666 return Err("OriginalURI"); 667 } 668 if (!aLoadState->ResultPrincipalURIIsSome() || 669 !uriEq(snapshot->GetResultPrincipalURI(), 670 aLoadState->ResultPrincipalURI())) { 671 return Err("ResultPrincipalURI"); 672 } 673 if (!uriEq(snapshot->GetUnstrippedURI(), aLoadState->GetUnstrippedURI())) { 674 return Err("UnstrippedURI"); 675 } 676 if (!principalEq(snapshot->GetTriggeringPrincipal(), 677 aLoadState->TriggeringPrincipal())) { 678 return Err("TriggeringPrincipal"); 679 } 680 if (!principalEq(snapshot->GetPrincipalToInherit(), 681 aLoadState->PrincipalToInherit())) { 682 return Err("PrincipalToInherit"); 683 } 684 if (!principalEq(snapshot->GetPartitionedPrincipalToInherit(), 685 aLoadState->PartitionedPrincipalToInherit())) { 686 return Err("PartitionedPrincipalToInherit"); 687 } 688 689 // Everything matches! 690 return loading->mEntry; 691 } 692 693 auto DocumentLoadListener::Open(nsDocShellLoadState* aLoadState, 694 LoadInfo* aLoadInfo, nsLoadFlags aLoadFlags, 695 uint32_t aCacheKey, 696 const Maybe<uint64_t>& aChannelId, 697 const TimeStamp& aAsyncOpenTime, 698 nsDOMNavigationTiming* aTiming, 699 Maybe<ClientInfo>&& aInfo, bool aUrgentStart, 700 dom::ContentParent* aContentParent, 701 nsresult* aRv) -> RefPtr<OpenPromise> { 702 auto* loadingContext = GetLoadingBrowsingContext(); 703 704 // Snapshot the referrer policy to be used when running the "create internal 705 // ancestor origins list". 706 aLoadInfo->SetFrameReferrerPolicySnapshot( 707 loadingContext->GetEmbedderFrameReferrerPolicy()); 708 709 MOZ_DIAGNOSTIC_ASSERT_IF(loadingContext->GetParent(), 710 loadingContext->GetParentWindowContext()); 711 712 OriginAttributes attrs; 713 loadingContext->GetOriginAttributes(attrs); 714 715 aLoadInfo->SetContinerFeaturePolicy( 716 loadingContext->GetContainerFeaturePolicy()); 717 718 mLoadIdentifier = aLoadState->GetLoadIdentifier(); 719 // See description of mFileName in nsDocShellLoadState.h 720 mIsDownload = !aLoadState->FileName().IsVoid(); 721 mIsLoadingJSURI = aLoadState->URI()->SchemeIs("javascript"); 722 mHTTPSFirstDowngradeData = aLoadState->GetHttpsFirstDowngradeData().forget(); 723 724 // Check for infinite recursive object or iframe loads 725 if (!CheckRecursiveLoad(loadingContext, aLoadState, mIsDocumentLoad)) { 726 *aRv = NS_ERROR_RECURSIVE_DOCUMENT_LOAD; 727 mParentChannelListener = nullptr; 728 return nullptr; 729 } 730 731 auto* documentContext = GetDocumentBrowsingContext(); 732 733 // If we are using SHIP and this load is from session history, validate that 734 // the load matches our local copy of the loading history entry. 735 // 736 // NOTE: Keep this check in-sync with the check in 737 // `nsDocShellLoadState::GetEffectiveTriggeringRemoteType()`! 738 RefPtr<SessionHistoryEntry> existingEntry; 739 if (SessionHistoryInParent() && aLoadState->LoadIsFromSessionHistory() && 740 aLoadState->LoadType() != LOAD_ERROR_PAGE) { 741 Result<SessionHistoryEntry*, const char*> result = 742 ValidateHistoryLoad(loadingContext, aLoadState); 743 if (result.isErr()) { 744 const char* mismatch = result.unwrapErr(); 745 LOG( 746 ("DocumentLoadListener::Open with invalid loading history entry " 747 "[this=%p, mismatch=%s]", 748 this, mismatch)); 749 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 750 MOZ_CRASH_UNSAFE_PRINTF( 751 "DocumentLoadListener::Open for invalid history entry due to " 752 "mismatch of '%s'", 753 mismatch); 754 #endif 755 *aRv = NS_ERROR_DOM_SECURITY_ERR; 756 mParentChannelListener = nullptr; 757 return nullptr; 758 } 759 760 existingEntry = result.unwrap(); 761 if (!existingEntry->IsInSessionHistory() && 762 !documentContext->HasLoadingHistoryEntry(existingEntry)) { 763 SessionHistoryEntry::RemoveLoadId( 764 aLoadState->GetLoadingSessionHistoryInfo()->mLoadId); 765 LOG( 766 ("DocumentLoadListener::Open with disconnected history entry " 767 "[this=%p]", 768 this)); 769 770 *aRv = NS_BINDING_ABORTED; 771 mParentChannelListener = nullptr; 772 mChannel = nullptr; 773 return nullptr; 774 } 775 } 776 777 if (aLoadState->GetRemoteTypeOverride()) { 778 if (!mIsDocumentLoad || !NS_IsAboutBlank(aLoadState->URI()) || 779 !loadingContext->IsTopContent()) { 780 LOG( 781 ("DocumentLoadListener::Open with invalid remoteTypeOverride " 782 "[this=%p]", 783 this)); 784 *aRv = NS_ERROR_DOM_SECURITY_ERR; 785 mParentChannelListener = nullptr; 786 return nullptr; 787 } 788 789 mRemoteTypeOverride = aLoadState->GetRemoteTypeOverride(); 790 } 791 792 if (NS_WARN_IF(!loadingContext->IsOwnedByProcess( 793 GetContentProcessId(aContentParent)))) { 794 LOG( 795 ("DocumentLoadListener::Open called from non-current content process " 796 "[this=%p, current=%" PRIu64 ", caller=%" PRIu64 "]", 797 this, loadingContext->OwnerProcessId(), 798 uint64_t(GetContentProcessId(aContentParent)))); 799 *aRv = NS_BINDING_ABORTED; 800 mParentChannelListener = nullptr; 801 return nullptr; 802 } 803 804 if (mIsDocumentLoad && loadingContext->IsContent() && 805 NS_WARN_IF(loadingContext->IsReplaced())) { 806 LOG( 807 ("DocumentLoadListener::Open called from replaced BrowsingContext " 808 "[this=%p, browserid=%" PRIx64 ", bcid=%" PRIx64 "]", 809 this, loadingContext->BrowserId(), loadingContext->Id())); 810 *aRv = NS_BINDING_ABORTED; 811 mParentChannelListener = nullptr; 812 return nullptr; 813 } 814 815 if (!nsDocShell::CreateAndConfigureRealChannelForLoadState( 816 loadingContext, aLoadState, aLoadInfo, mParentChannelListener, 817 nullptr, attrs, aLoadFlags, aCacheKey, *aRv, 818 getter_AddRefs(mChannel))) { 819 LOG(("DocumentLoadListener::Open failed to create channel [this=%p]", 820 this)); 821 mParentChannelListener = nullptr; 822 return nullptr; 823 } 824 825 if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel)) { 826 // We need to set the partitioned principal to the load state so that we can 827 // propagate it to the loadingSessionHistoryInfo. To do this, we need to 828 // get the finalized cookieJarSettings for the channel because it is used by 829 // StoragePrincipalHelper to mint the partitioned principal. 830 // 831 // ClientChannelHelper below also needs us to have finalized the principal 832 // for the channel because it will request that StoragePrincipalHelper mint 833 // a principal that needs to match the same principal that a later call to 834 // StoragePrincipalHelper will mint when determining the right origin to 835 // look up the ServiceWorker. 836 // 837 // Because nsHttpChannel::AsyncOpen calls UpdateAntiTrackingInfoForChannel 838 // which potentially flips the third party bit/flag on the partition key on 839 // the cookie jar which impacts the principal that will be minted, it is 840 // essential that UpdateAntiTrackingInfoForChannel is called before 841 // AddClientChannelHelperInParent below. 842 // 843 // Because the call to UpdateAntiTrackingInfoForChannel is largely 844 // idempotent, we currently just make the call ourselves right now. The one 845 // caveat is that the RFPRandomKey may be spuriously regenerated for 846 // top-level documents. 847 AntiTrackingUtils::UpdateAntiTrackingInfoForChannel(httpChannel); 848 849 nsCOMPtr<nsIPrincipal> partitionedPrincipal; 850 851 (void)StoragePrincipalHelper::GetPrincipal( 852 httpChannel, StoragePrincipalHelper::ePartitionedPrincipal, 853 getter_AddRefs(partitionedPrincipal)); 854 855 aLoadState->SetPartitionedPrincipalToInherit(partitionedPrincipal); 856 } 857 858 if (documentContext && aLoadState->LoadType() != LOAD_ERROR_PAGE && 859 mozilla::SessionHistoryInParent()) { 860 // It's hard to know at this point whether session history will be enabled 861 // in the browsing context, so we always create an entry for a load here. 862 mLoadingSessionHistoryInfo = 863 documentContext->CreateLoadingSessionHistoryEntryForLoad( 864 aLoadState, existingEntry, mChannel); 865 MOZ_ASSERT(mLoadingSessionHistoryInfo); 866 } 867 868 nsCOMPtr<nsIURI> uriBeingLoaded; 869 (void)NS_WARN_IF(NS_FAILED(mChannel->GetURI(getter_AddRefs(uriBeingLoaded)))); 870 871 RefPtr<HttpBaseChannel> httpBaseChannel = do_QueryObject(mChannel, aRv); 872 if (uriBeingLoaded && httpBaseChannel) { 873 nsCOMPtr<nsIURI> topWindowURI; 874 if (mIsDocumentLoad && loadingContext->IsTop()) { 875 // If this is for the top level loading, the top window URI should be the 876 // URI which we are loading. 877 topWindowURI = uriBeingLoaded; 878 } else if (RefPtr<WindowGlobalParent> topWindow = AntiTrackingUtils:: 879 GetTopWindowExcludingExtensionAccessibleContentFrames( 880 loadingContext, uriBeingLoaded)) { 881 nsCOMPtr<nsIPrincipal> topWindowPrincipal = 882 topWindow->DocumentPrincipal(); 883 if (topWindowPrincipal && !topWindowPrincipal->GetIsNullPrincipal()) { 884 auto* basePrin = BasePrincipal::Cast(topWindowPrincipal); 885 basePrin->GetURI(getter_AddRefs(topWindowURI)); 886 } 887 } 888 httpBaseChannel->SetTopWindowURI(topWindowURI); 889 } 890 891 nsCOMPtr<nsIIdentChannel> identChannel = do_QueryInterface(mChannel); 892 if (identChannel && aChannelId) { 893 (void)identChannel->SetChannelId(*aChannelId); 894 } 895 mDocumentChannelId = aChannelId; 896 897 RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel); 898 if (httpChannelImpl) { 899 httpChannelImpl->SetWarningReporter(this); 900 901 if (mIsDocumentLoad && loadingContext->IsTop()) { 902 httpChannelImpl->SetEarlyHintObserver(this); 903 } 904 } 905 906 nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(mChannel); 907 if (timedChannel) { 908 timedChannel->SetAsyncOpen(aAsyncOpenTime); 909 } 910 911 if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel)) { 912 (void)httpChannel->SetRequestContextID( 913 loadingContext->GetRequestContextId()); 914 915 nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(httpChannel)); 916 if (cos && aUrgentStart) { 917 cos->AddClassFlags(nsIClassOfService::UrgentStart); 918 } 919 } 920 921 // Setup a ClientChannelHelper to watch for redirects, and copy 922 // across any serviceworker related data between channels as needed. 923 AddClientChannelHelperInParent(mChannel, std::move(aInfo)); 924 925 if (documentContext && !documentContext->StartDocumentLoad(this)) { 926 LOG(("DocumentLoadListener::Open failed StartDocumentLoad [this=%p]", 927 this)); 928 *aRv = NS_BINDING_ABORTED; 929 mParentChannelListener = nullptr; 930 mChannel = nullptr; 931 return nullptr; 932 } 933 934 // Recalculate the openFlags, matching the logic in use in Content process. 935 MOZ_ASSERT(!aLoadState->GetPendingRedirectedChannel()); 936 uint32_t openFlags = nsDocShell::ComputeURILoaderFlags( 937 loadingContext, aLoadState->LoadType(), mIsDocumentLoad); 938 939 RefPtr<ParentProcessDocumentOpenInfo> openInfo = 940 new ParentProcessDocumentOpenInfo(mParentChannelListener, openFlags, 941 loadingContext, aLoadState->TypeHint(), 942 mIsDocumentLoad); 943 openInfo->Prepare(); 944 945 #ifdef ANDROID 946 RefPtr<MozPromise<bool, bool, false>> promise; 947 if (documentContext && aLoadState->LoadType() != LOAD_ERROR_PAGE && 948 !(aLoadState->HasInternalLoadFlags( 949 nsDocShell::INTERNAL_LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE)) && 950 !(aLoadState->LoadType() & LOAD_HISTORY) && 951 !nsExternalHelperAppService::ExternalProtocolIsBlockedBySandbox( 952 documentContext, aLoadState->HasValidUserGestureActivation())) { 953 nsCOMPtr<nsIWidget> widget = 954 documentContext->GetParentProcessWidgetContaining(); 955 RefPtr<nsWindow> window = nsWindow::From(widget); 956 957 if (window) { 958 promise = window->OnLoadRequest( 959 aLoadState->URI(), nsIBrowserDOMWindow::OPEN_CURRENTWINDOW, 960 aLoadState->InternalLoadFlags(), aLoadState->TriggeringPrincipal(), 961 aLoadState->HasValidUserGestureActivation(), 962 documentContext->IsTopContent()); 963 } 964 } 965 966 if (promise) { 967 RefPtr<DocumentLoadListener> self = this; 968 promise->Then( 969 GetCurrentSerialEventTarget(), __func__, 970 [=](const MozPromise<bool, bool, false>::ResolveOrRejectValue& aValue) { 971 if (aValue.IsResolve()) { 972 bool handled = aValue.ResolveValue(); 973 if (handled) { 974 self->DisconnectListeners(NS_ERROR_ABORT, NS_ERROR_ABORT); 975 mParentChannelListener = nullptr; 976 } else { 977 nsresult rv = mChannel->AsyncOpen(openInfo); 978 if (NS_FAILED(rv)) { 979 self->DisconnectListeners(rv, rv); 980 mParentChannelListener = nullptr; 981 } 982 } 983 } 984 }); 985 } else 986 #endif /* ANDROID */ 987 { 988 *aRv = mChannel->AsyncOpen(openInfo); 989 if (NS_FAILED(*aRv)) { 990 LOG(("DocumentLoadListener::Open failed AsyncOpen [this=%p rv=%" PRIx32 991 "]", 992 this, static_cast<uint32_t>(*aRv))); 993 if (documentContext) { 994 documentContext->EndDocumentLoad(false); 995 } 996 mParentChannelListener = nullptr; 997 return nullptr; 998 } 999 } 1000 1001 // HTTPS-Only Mode fights potential timeouts caused by upgrades. Instantly 1002 // after opening the document channel we have to kick off countermeasures. 1003 nsHTTPSOnlyUtils::PotentiallyFireHttpRequestToShortenTimout(this); 1004 1005 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo(); 1006 loadInfo->SetChannelCreationOriginalURI(aLoadState->URI()); 1007 1008 mContentParent = aContentParent; 1009 mLoadStateExternalLoadFlags = aLoadState->LoadFlags(); 1010 mLoadStateInternalLoadFlags = aLoadState->InternalLoadFlags(); 1011 mLoadStateLoadType = aLoadState->LoadType(); 1012 mTiming = aTiming; 1013 mSrcdocData = aLoadState->SrcdocData(); 1014 mBaseURI = aLoadState->BaseURI(); 1015 mOriginalUriString = aLoadState->GetOriginalURIString(); 1016 if (documentContext) { 1017 mParentWindowContext = documentContext->GetParentWindowContext(); 1018 } else { 1019 mParentWindowContext = 1020 WindowGlobalParent::GetByInnerWindowId(aLoadInfo->GetInnerWindowID()); 1021 MOZ_RELEASE_ASSERT(mParentWindowContext->GetBrowsingContext() == 1022 GetLoadingBrowsingContext(), 1023 "mismatched parent window context?"); 1024 } 1025 1026 // For content-initiated loads, this flag is set in nsDocShell::LoadURI. 1027 // For parent-initiated loads, we have to set it here. 1028 // Below comment is copied from nsDocShell::LoadURI - 1029 // If we have a system triggering principal, we can assume that this load was 1030 // triggered by some UI in the browser chrome, such as the URL bar or 1031 // bookmark bar. This should count as a user interaction for the current sh 1032 // entry, so that the user may navigate back to the current entry, from the 1033 // entry that is going to be added as part of this load. 1034 if (!mSupportsRedirectToRealChannel && aLoadState->TriggeringPrincipal() && 1035 aLoadState->TriggeringPrincipal()->IsSystemPrincipal()) { 1036 WindowContext* topWc = loadingContext->GetTopWindowContext(); 1037 if (topWc && !topWc->IsDiscarded()) { 1038 MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(true)); 1039 } 1040 } 1041 1042 *aRv = NS_OK; 1043 mOpenPromise = new OpenPromise::Private(__func__); 1044 // We make the promise use direct task dispatch in order to reduce the number 1045 // of event loops iterations. 1046 mOpenPromise->UseDirectTaskDispatch(__func__); 1047 return mOpenPromise; 1048 } 1049 1050 auto DocumentLoadListener::OpenDocument( 1051 nsDocShellLoadState* aLoadState, nsLoadFlags aLoadFlags, uint32_t aCacheKey, 1052 const Maybe<uint64_t>& aChannelId, const TimeStamp& aAsyncOpenTime, 1053 nsDOMNavigationTiming* aTiming, Maybe<dom::ClientInfo>&& aInfo, 1054 bool aUriModified, Maybe<bool> aIsEmbeddingBlockedError, 1055 dom::ContentParent* aContentParent, nsresult* aRv) -> RefPtr<OpenPromise> { 1056 LOG(("DocumentLoadListener [%p] OpenDocument [uri=%s]", this, 1057 aLoadState->URI()->GetSpecOrDefault().get())); 1058 1059 MOZ_ASSERT(mIsDocumentLoad); 1060 1061 RefPtr<CanonicalBrowsingContext> browsingContext = 1062 GetDocumentBrowsingContext(); 1063 1064 // As a security check, check that aLoadFlags matches what we expect. We 1065 // expect them to be the same we compute on the parent, except for the load 1066 // group flags. 1067 { 1068 const nsLoadFlags parentLoadFlags = aLoadState->CalculateChannelLoadFlags( 1069 browsingContext, aUriModified, std::move(aIsEmbeddingBlockedError)); 1070 const nsLoadFlags differing = parentLoadFlags ^ aLoadFlags; 1071 if (differing & ~nsLoadGroup::kInheritedLoadFlags) { 1072 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 1073 MOZ_CRASH_UNSAFE_PRINTF( 1074 "DocumentLoadListener::OpenDocument: Unexpected load flags: " 1075 "%x vs. %x (differing %x vs. %x)", 1076 parentLoadFlags, aLoadFlags, differing & parentLoadFlags, 1077 differing & aLoadFlags); 1078 #endif 1079 *aRv = NS_ERROR_UNEXPECTED; 1080 return nullptr; 1081 } 1082 } 1083 1084 // If this is a top-level load, then rebuild the LoadInfo from scratch, 1085 // since the goal is to be able to initiate loads in the parent, where the 1086 // content process won't have provided us with an existing one. 1087 RefPtr<LoadInfo> loadInfo = 1088 CreateDocumentLoadInfo(browsingContext, aLoadState); 1089 1090 // Keep track of navigation for the Bounce Tracking Protection. 1091 if (browsingContext->IsTopContent()) { 1092 RefPtr<BounceTrackingState> bounceTrackingState = 1093 browsingContext->GetBounceTrackingState(); 1094 1095 // Not every browsing context has a BounceTrackingState. It's also null when 1096 // the feature is disabled. 1097 if (bounceTrackingState) { 1098 nsCOMPtr<nsIPrincipal> triggeringPrincipal; 1099 nsresult rv = 1100 loadInfo->GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal)); 1101 1102 if (!NS_WARN_IF(NS_FAILED(rv))) { 1103 DebugOnly<nsresult> rv = bounceTrackingState->OnStartNavigation( 1104 triggeringPrincipal, loadInfo->GetHasValidUserGestureActivation()); 1105 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1106 "BounceTrackingState::OnStartNavigation failed"); 1107 } 1108 } 1109 } 1110 1111 return Open(aLoadState, loadInfo, aLoadFlags, aCacheKey, aChannelId, 1112 aAsyncOpenTime, aTiming, std::move(aInfo), false, aContentParent, 1113 aRv); 1114 } 1115 1116 auto DocumentLoadListener::OpenObject( 1117 nsDocShellLoadState* aLoadState, uint32_t aCacheKey, 1118 const Maybe<uint64_t>& aChannelId, const TimeStamp& aAsyncOpenTime, 1119 nsDOMNavigationTiming* aTiming, Maybe<dom::ClientInfo>&& aInfo, 1120 uint64_t aInnerWindowId, nsLoadFlags aLoadFlags, 1121 nsContentPolicyType aContentPolicyType, bool aUrgentStart, 1122 dom::ContentParent* aContentParent, 1123 ObjectUpgradeHandler* aObjectUpgradeHandler, nsresult* aRv) 1124 -> RefPtr<OpenPromise> { 1125 LOG(("DocumentLoadListener [%p] OpenObject [uri=%s]", this, 1126 aLoadState->URI()->GetSpecOrDefault().get())); 1127 1128 MOZ_ASSERT(!mIsDocumentLoad); 1129 1130 auto sandboxFlags = aLoadState->TriggeringSandboxFlags(); 1131 1132 RefPtr<LoadInfo> loadInfo = CreateObjectLoadInfo( 1133 aLoadState, aInnerWindowId, aContentPolicyType, sandboxFlags); 1134 1135 mObjectUpgradeHandler = aObjectUpgradeHandler; 1136 1137 return Open(aLoadState, loadInfo, aLoadFlags, aCacheKey, aChannelId, 1138 aAsyncOpenTime, aTiming, std::move(aInfo), aUrgentStart, 1139 aContentParent, aRv); 1140 } 1141 1142 auto DocumentLoadListener::OpenInParent(nsDocShellLoadState* aLoadState, 1143 bool aSupportsRedirectToRealChannel) 1144 -> RefPtr<OpenPromise> { 1145 MOZ_ASSERT(mIsDocumentLoad); 1146 1147 // We currently only support passing nullptr for aLoadInfo for 1148 // top level browsing contexts. 1149 auto* browsingContext = GetDocumentBrowsingContext(); 1150 if (!browsingContext->IsTopContent() || 1151 !browsingContext->GetContentParent()) { 1152 LOG(("DocumentLoadListener::OpenInParent failed because of subdoc")); 1153 return nullptr; 1154 } 1155 1156 // Clone because this mutates the load flags in the load state, which 1157 // breaks nsDocShells expectations of being able to do it. 1158 RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(*aLoadState); 1159 loadState->CalculateLoadURIFlags(); 1160 1161 RefPtr<nsDOMNavigationTiming> timing = new nsDOMNavigationTiming(nullptr); 1162 timing->NotifyNavigationStart( 1163 browsingContext->IsActive() 1164 ? nsDOMNavigationTiming::DocShellState::eActive 1165 : nsDOMNavigationTiming::DocShellState::eInactive); 1166 1167 const mozilla::dom::LoadingSessionHistoryInfo* loadingInfo = 1168 loadState->GetLoadingSessionHistoryInfo(); 1169 1170 uint32_t cacheKey = 0; 1171 auto loadType = aLoadState->LoadType(); 1172 if (loadType == LOAD_HISTORY || loadType == LOAD_RELOAD_NORMAL || 1173 loadType == LOAD_RELOAD_CHARSET_CHANGE || 1174 loadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE || 1175 loadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE) { 1176 if (loadingInfo) { 1177 cacheKey = loadingInfo->mInfo.GetCacheKey(); 1178 } 1179 } 1180 1181 // Loads start in the content process might have exposed a channel id to 1182 // observers, so we need to preserve the value in the parent. That can't have 1183 // happened here, so Nothing() is fine. 1184 Maybe<uint64_t> channelId = Nothing(); 1185 1186 // Initial client info is only relevant for subdocument loads, which we're 1187 // not supporting yet. 1188 Maybe<dom::ClientInfo> initialClientInfo; 1189 1190 mSupportsRedirectToRealChannel = aSupportsRedirectToRealChannel; 1191 1192 // This is a top-level load, so rebuild the LoadInfo from scratch, 1193 // since in the parent the 1194 // content process won't have provided us with an existing one. 1195 RefPtr<LoadInfo> loadInfo = 1196 CreateDocumentLoadInfo(browsingContext, aLoadState); 1197 1198 nsLoadFlags loadFlags = loadState->CalculateChannelLoadFlags( 1199 browsingContext, loadingInfo && loadingInfo->mInfo.GetURIWasModified(), 1200 Nothing()); 1201 1202 nsresult rv; 1203 return Open(loadState, loadInfo, loadFlags, cacheKey, channelId, 1204 TimeStamp::Now(), timing, std::move(initialClientInfo), false, 1205 browsingContext->GetContentParent(), &rv); 1206 } 1207 1208 base::ProcessId DocumentLoadListener::OtherPid() const { 1209 return mContentParent ? mContentParent->OtherPid() : base::ProcessId{0}; 1210 } 1211 1212 void DocumentLoadListener::FireStateChange(uint32_t aStateFlags, 1213 nsresult aStatus) { 1214 nsCOMPtr<nsIChannel> request = GetChannel(); 1215 1216 RefPtr<BrowsingContextWebProgress> webProgress = 1217 GetLoadingBrowsingContext()->GetWebProgress(); 1218 1219 if (webProgress) { 1220 NS_DispatchToMainThread( 1221 NS_NewRunnableFunction("DocumentLoadListener::FireStateChange", [=]() { 1222 webProgress->OnStateChange(webProgress, request, aStateFlags, 1223 aStatus); 1224 })); 1225 } 1226 } 1227 1228 static void SetNavigating(CanonicalBrowsingContext* aBrowsingContext, 1229 bool aNavigating) { 1230 nsCOMPtr<nsIBrowser> browser; 1231 if (RefPtr<Element> currentElement = aBrowsingContext->GetEmbedderElement()) { 1232 browser = currentElement->AsBrowser(); 1233 } 1234 1235 if (!browser) { 1236 return; 1237 } 1238 1239 NS_DispatchToMainThread(NS_NewRunnableFunction( 1240 "DocumentLoadListener::SetNavigating", 1241 [browser, aNavigating]() { browser->SetIsNavigating(aNavigating); })); 1242 } 1243 1244 /* static */ bool DocumentLoadListener::LoadInParent( 1245 CanonicalBrowsingContext* aBrowsingContext, nsDocShellLoadState* aLoadState, 1246 bool aSetNavigating) { 1247 SetNavigating(aBrowsingContext, aSetNavigating); 1248 1249 RefPtr<DocumentLoadListener> load = 1250 new DocumentLoadListener(aBrowsingContext, true); 1251 RefPtr<DocumentLoadListener::OpenPromise> promise = load->OpenInParent( 1252 aLoadState, /* aSupportsRedirectToRealChannel */ false); 1253 if (!promise) { 1254 SetNavigating(aBrowsingContext, false); 1255 return false; 1256 } 1257 1258 // We passed false for aSupportsRedirectToRealChannel, so we should always 1259 // take the process switching path, and reject this promise. 1260 promise->Then( 1261 GetCurrentSerialEventTarget(), __func__, 1262 [load](DocumentLoadListener::OpenPromise::ResolveOrRejectValue&& aValue) { 1263 MOZ_ASSERT(aValue.IsReject()); 1264 DocumentLoadListener::OpenPromiseFailedType& rejectValue = 1265 aValue.RejectValue(); 1266 if (!rejectValue.mContinueNavigating) { 1267 // If we're not switching the load to a new process, then it is 1268 // finished (and failed), and we should fire a state change to notify 1269 // observers. Normally the docshell would fire this, and it would get 1270 // filtered out by BrowserParent if needed. 1271 load->FireStateChange(nsIWebProgressListener::STATE_STOP | 1272 nsIWebProgressListener::STATE_IS_WINDOW | 1273 nsIWebProgressListener::STATE_IS_NETWORK, 1274 rejectValue.mStatus); 1275 } 1276 }); 1277 1278 load->FireStateChange(nsIWebProgressListener::STATE_START | 1279 nsIWebProgressListener::STATE_IS_DOCUMENT | 1280 nsIWebProgressListener::STATE_IS_REQUEST | 1281 nsIWebProgressListener::STATE_IS_WINDOW | 1282 nsIWebProgressListener::STATE_IS_NETWORK, 1283 NS_OK); 1284 SetNavigating(aBrowsingContext, false); 1285 return true; 1286 } 1287 1288 /* static */ 1289 bool DocumentLoadListener::SpeculativeLoadInParent( 1290 dom::CanonicalBrowsingContext* aBrowsingContext, 1291 nsDocShellLoadState* aLoadState) { 1292 LOG(("DocumentLoadListener::OpenFromParent")); 1293 1294 RefPtr<DocumentLoadListener> listener = 1295 new DocumentLoadListener(aBrowsingContext, true); 1296 1297 auto promise = listener->OpenInParent(aLoadState, true); 1298 if (promise) { 1299 // Create an entry in the redirect channel registrar to 1300 // allocate an identifier for this load. 1301 nsCOMPtr<nsIRedirectChannelRegistrar> registrar = 1302 RedirectChannelRegistrar::GetOrCreate(); 1303 uint64_t loadIdentifier = aLoadState->GetLoadIdentifier(); 1304 DebugOnly<nsresult> rv = 1305 registrar->RegisterChannel(nullptr, loadIdentifier); 1306 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1307 // Register listener (as an nsIParentChannel) under our new identifier. 1308 rv = registrar->LinkChannels(loadIdentifier, listener, nullptr); 1309 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1310 } 1311 return !!promise; 1312 } 1313 1314 void DocumentLoadListener::CleanupParentLoadAttempt(uint64_t aLoadIdent) { 1315 nsCOMPtr<nsIRedirectChannelRegistrar> registrar = 1316 RedirectChannelRegistrar::GetOrCreate(); 1317 1318 nsCOMPtr<nsIParentChannel> parentChannel; 1319 registrar->GetParentChannel(aLoadIdent, getter_AddRefs(parentChannel)); 1320 RefPtr<DocumentLoadListener> loadListener = do_QueryObject(parentChannel); 1321 1322 if (loadListener) { 1323 // If the load listener is still registered, then we must have failed 1324 // to connect DocumentChannel into it. Better cancel it! 1325 loadListener->NotifyDocumentChannelFailed(); 1326 } 1327 1328 registrar->DeregisterChannels(aLoadIdent); 1329 } 1330 1331 auto DocumentLoadListener::ClaimParentLoad(DocumentLoadListener** aListener, 1332 uint64_t aLoadIdent, 1333 Maybe<uint64_t> aChannelId) 1334 -> RefPtr<OpenPromise> { 1335 nsCOMPtr<nsIRedirectChannelRegistrar> registrar = 1336 RedirectChannelRegistrar::GetOrCreate(); 1337 1338 nsCOMPtr<nsIParentChannel> parentChannel; 1339 registrar->GetParentChannel(aLoadIdent, getter_AddRefs(parentChannel)); 1340 RefPtr<DocumentLoadListener> loadListener = do_QueryObject(parentChannel); 1341 registrar->DeregisterChannels(aLoadIdent); 1342 1343 if (!loadListener) { 1344 // The parent went away unexpectedly. 1345 *aListener = nullptr; 1346 return nullptr; 1347 } 1348 1349 loadListener->mDocumentChannelId = aChannelId; 1350 1351 MOZ_DIAGNOSTIC_ASSERT(loadListener->mOpenPromise); 1352 loadListener.forget(aListener); 1353 1354 return (*aListener)->mOpenPromise; 1355 } 1356 1357 void DocumentLoadListener::NotifyDocumentChannelFailed() { 1358 LOG(("DocumentLoadListener NotifyDocumentChannelFailed [this=%p]", this)); 1359 // There's been no calls to ClaimParentLoad, and so no listeners have been 1360 // attached to mOpenPromise yet. As such we can run Then() on it. 1361 mOpenPromise->Then( 1362 GetMainThreadSerialEventTarget(), __func__, 1363 [](DocumentLoadListener::OpenPromiseSucceededType&& aResolveValue) { 1364 aResolveValue.mPromise->Resolve(NS_BINDING_ABORTED, __func__); 1365 }, 1366 []() {}); 1367 1368 Cancel(NS_BINDING_ABORTED, 1369 "DocumentLoadListener::NotifyDocumentChannelFailed"_ns); 1370 } 1371 1372 void DocumentLoadListener::Disconnect(bool aContinueNavigating) { 1373 LOG(("DocumentLoadListener Disconnect [this=%p, aContinueNavigating=%d]", 1374 this, aContinueNavigating)); 1375 // The nsHttpChannel may have a reference to this parent, release it 1376 // to avoid circular references. 1377 RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel); 1378 if (httpChannelImpl) { 1379 httpChannelImpl->SetWarningReporter(nullptr); 1380 httpChannelImpl->SetEarlyHintObserver(nullptr); 1381 } 1382 1383 // Don't cancel ongoing early hints when continuing to load the web page. 1384 // Early hints are loaded earlier in the code and shouldn't get cancelled 1385 // here. See also: Bug 1765652 1386 if (!aContinueNavigating) { 1387 mEarlyHintsService.Cancel("DocumentLoadListener::Disconnect"_ns); 1388 } 1389 1390 if (auto* ctx = GetDocumentBrowsingContext()) { 1391 ctx->EndDocumentLoad(aContinueNavigating); 1392 } 1393 } 1394 1395 void DocumentLoadListener::Cancel(const nsresult& aStatusCode, 1396 const nsACString& aReason) { 1397 LOG( 1398 ("DocumentLoadListener Cancel [this=%p, " 1399 "aStatusCode=%" PRIx32 " ]", 1400 this, static_cast<uint32_t>(aStatusCode))); 1401 if (mOpenPromiseResolved) { 1402 return; 1403 } 1404 if (mChannel) { 1405 mChannel->CancelWithReason(aStatusCode, aReason); 1406 } 1407 1408 DisconnectListeners(aStatusCode, aStatusCode); 1409 } 1410 1411 void DocumentLoadListener::DisconnectListeners(nsresult aStatus, 1412 nsresult aLoadGroupStatus, 1413 bool aContinueNavigating) { 1414 LOG( 1415 ("DocumentLoadListener DisconnectListener [this=%p, " 1416 "aStatus=%" PRIx32 ", aLoadGroupStatus=%" PRIx32 1417 ", aContinueNavigating=%d]", 1418 this, static_cast<uint32_t>(aStatus), 1419 static_cast<uint32_t>(aLoadGroupStatus), aContinueNavigating)); 1420 1421 RejectOpenPromise(aStatus, aLoadGroupStatus, aContinueNavigating, __func__); 1422 1423 Disconnect(aContinueNavigating); 1424 1425 // Clear any pending stream filter requests. If we're going to be sending a 1426 // response to the content process due to a navigation, our caller will have 1427 // already stashed the array to be passed to `TriggerRedirectToRealChannel`, 1428 // so it's safe for us to clear here. 1429 // TODO: If we retargeted the stream to a non-default handler (e.g. to trigger 1430 // a download), we currently never attach a stream filter. Should we attach a 1431 // stream filter in those situations as well? 1432 mStreamFilterRequests.Clear(); 1433 } 1434 1435 void DocumentLoadListener::RedirectToRealChannelFinished(nsresult aRv) { 1436 LOG( 1437 ("DocumentLoadListener RedirectToRealChannelFinished [this=%p, " 1438 "aRv=%" PRIx32 " ]", 1439 this, static_cast<uint32_t>(aRv))); 1440 if (NS_FAILED(aRv)) { 1441 FinishReplacementChannelSetup(aRv); 1442 return; 1443 } 1444 1445 // Wait for background channel ready on target channel 1446 nsCOMPtr<nsIRedirectChannelRegistrar> redirectReg = 1447 RedirectChannelRegistrar::GetOrCreate(); 1448 MOZ_ASSERT(redirectReg); 1449 1450 nsCOMPtr<nsIParentChannel> redirectParentChannel; 1451 redirectReg->GetParentChannel(mRedirectChannelId, 1452 getter_AddRefs(redirectParentChannel)); 1453 if (!redirectParentChannel) { 1454 FinishReplacementChannelSetup(NS_ERROR_FAILURE); 1455 return; 1456 } 1457 1458 nsCOMPtr<nsIParentRedirectingChannel> redirectingParent = 1459 do_QueryInterface(redirectParentChannel); 1460 if (!redirectingParent) { 1461 // Continue verification procedure if redirecting to non-Http protocol 1462 FinishReplacementChannelSetup(NS_OK); 1463 return; 1464 } 1465 1466 // Ask redirected channel if verification can proceed. 1467 // ReadyToVerify will be invoked when redirected channel is ready. 1468 redirectingParent->ContinueVerification(this); 1469 } 1470 1471 NS_IMETHODIMP 1472 DocumentLoadListener::ReadyToVerify(nsresult aResultCode) { 1473 FinishReplacementChannelSetup(aResultCode); 1474 return NS_OK; 1475 } 1476 1477 void DocumentLoadListener::FinishReplacementChannelSetup(nsresult aResult) { 1478 LOG( 1479 ("DocumentLoadListener FinishReplacementChannelSetup [this=%p, " 1480 "aResult=%x]", 1481 this, int(aResult))); 1482 1483 auto endDocumentLoad = MakeScopeExit([&]() { 1484 if (auto* ctx = GetDocumentBrowsingContext()) { 1485 ctx->EndDocumentLoad(false); 1486 } 1487 }); 1488 mStreamFilterRequests.Clear(); 1489 1490 nsCOMPtr<nsIRedirectChannelRegistrar> registrar = 1491 RedirectChannelRegistrar::GetOrCreate(); 1492 MOZ_ASSERT(registrar); 1493 1494 nsCOMPtr<nsIParentChannel> redirectChannel; 1495 nsresult rv = registrar->GetParentChannel(mRedirectChannelId, 1496 getter_AddRefs(redirectChannel)); 1497 if (NS_FAILED(rv) || !redirectChannel) { 1498 aResult = NS_ERROR_FAILURE; 1499 } 1500 1501 // Release all previously registered channels, they are no longer needed to 1502 // be kept in the registrar from this moment. 1503 registrar->DeregisterChannels(mRedirectChannelId); 1504 mRedirectChannelId = 0; 1505 if (NS_FAILED(aResult)) { 1506 if (redirectChannel) { 1507 redirectChannel->Delete(); 1508 } 1509 mChannel->Cancel(aResult); 1510 mChannel->Resume(); 1511 return; 1512 } 1513 1514 MOZ_ASSERT( 1515 !SameCOMIdentity(redirectChannel, static_cast<nsIParentChannel*>(this))); 1516 1517 redirectChannel->SetParentListener(mParentChannelListener); 1518 1519 ApplyPendingFunctions(redirectChannel); 1520 1521 if (!ResumeSuspendedChannel(redirectChannel)) { 1522 nsCOMPtr<nsILoadGroup> loadGroup; 1523 mChannel->GetLoadGroup(getter_AddRefs(loadGroup)); 1524 if (loadGroup) { 1525 // We added ourselves to the load group, but attempting 1526 // to resume has notified us that the channel is already 1527 // finished. Better remove ourselves from the loadgroup 1528 // again. The only time the channel will be in a loadgroup 1529 // is if we're connected to the parent process. 1530 nsresult status = NS_OK; 1531 mChannel->GetStatus(&status); 1532 loadGroup->RemoveRequest(mChannel, nullptr, status); 1533 } 1534 } 1535 } 1536 1537 void DocumentLoadListener::ApplyPendingFunctions( 1538 nsIParentChannel* aChannel) const { 1539 // We stored the values from all nsIParentChannel functions called since we 1540 // couldn't handle them. Copy them across to the real channel since it 1541 // should know what to do. 1542 1543 nsCOMPtr<nsIParentChannel> parentChannel = aChannel; 1544 for (const auto& variant : mIParentChannelFunctions) { 1545 variant.match( 1546 [parentChannel](const ClassifierMatchedInfoParams& aParams) { 1547 parentChannel->SetClassifierMatchedInfo( 1548 aParams.mList, aParams.mProvider, aParams.mFullHash); 1549 }, 1550 [parentChannel](const ClassifierMatchedTrackingInfoParams& aParams) { 1551 parentChannel->SetClassifierMatchedTrackingInfo(aParams.mLists, 1552 aParams.mFullHashes); 1553 }, 1554 [parentChannel](const ClassificationFlagsParams& aParams) { 1555 parentChannel->NotifyClassificationFlags(aParams.mClassificationFlags, 1556 aParams.mIsThirdParty); 1557 }); 1558 } 1559 1560 RefPtr<HttpChannelSecurityWarningReporter> reporter; 1561 if (RefPtr<HttpChannelParent> httpParent = do_QueryObject(aChannel)) { 1562 reporter = httpParent; 1563 } else if (RefPtr<nsHttpChannel> httpChannel = do_QueryObject(aChannel)) { 1564 reporter = httpChannel->GetWarningReporter(); 1565 } 1566 if (reporter) { 1567 for (const auto& variant : mSecurityWarningFunctions) { 1568 variant.match( 1569 [reporter](const ReportSecurityMessageParams& aParams) { 1570 (void)reporter->ReportSecurityMessage(aParams.mMessageTag, 1571 aParams.mMessageCategory); 1572 }, 1573 [reporter](const LogBlockedCORSRequestParams& aParams) { 1574 (void)reporter->LogBlockedCORSRequest( 1575 aParams.mMessage, aParams.mCategory, aParams.mIsWarning); 1576 }, 1577 [reporter](const LogMimeTypeMismatchParams& aParams) { 1578 (void)reporter->LogMimeTypeMismatch(aParams.mMessageName, 1579 aParams.mWarning, aParams.mURL, 1580 aParams.mContentType); 1581 }); 1582 } 1583 } 1584 } 1585 1586 bool DocumentLoadListener::ResumeSuspendedChannel( 1587 nsIStreamListener* aListener) { 1588 LOG(("DocumentLoadListener ResumeSuspendedChannel [this=%p]", this)); 1589 RefPtr<nsHttpChannel> httpChannel = do_QueryObject(mChannel); 1590 if (httpChannel) { 1591 httpChannel->SetApplyConversion(mOldApplyConversion); 1592 } 1593 1594 if (!mIsFinished) { 1595 mParentChannelListener->SetListenerAfterRedirect(aListener); 1596 } 1597 1598 // If we failed to suspend the channel, then we might have received 1599 // some messages while the redirected was being handled. 1600 // Manually send them on now. 1601 nsTArray<StreamListenerFunction> streamListenerFunctions = 1602 std::move(mStreamListenerFunctions); 1603 if (!aListener) { 1604 streamListenerFunctions.Clear(); 1605 } 1606 1607 ForwardStreamListenerFunctions(std::move(streamListenerFunctions), aListener); 1608 1609 // We don't expect to get new stream listener functions added 1610 // via re-entrancy. If this ever happens, we should understand 1611 // exactly why before allowing it. 1612 NS_ASSERTION(mStreamListenerFunctions.IsEmpty(), 1613 "Should not have added new stream listener function!"); 1614 1615 mChannel->Resume(); 1616 1617 // Our caller will invoke `EndDocumentLoad` for us. 1618 1619 return !mIsFinished; 1620 } 1621 1622 void DocumentLoadListener::CancelEarlyHintPreloads() { 1623 mEarlyHintsService.Cancel("DocumentLoadListener::CancelEarlyHintPreloads"_ns); 1624 } 1625 1626 void DocumentLoadListener::RegisterEarlyHintLinksAndGetConnectArgs( 1627 dom::ContentParentId aCpId, nsTArray<EarlyHintConnectArgs>& aOutLinks) { 1628 mEarlyHintsService.RegisterLinksAndGetConnectArgs(aCpId, aOutLinks); 1629 } 1630 1631 void DocumentLoadListener::SerializeRedirectData( 1632 RedirectToRealChannelArgs& aArgs, bool aIsCrossProcess, 1633 uint32_t aRedirectFlags, uint32_t aLoadFlags, 1634 nsTArray<EarlyHintConnectArgs>&& aEarlyHints, 1635 uint32_t aEarlyHintLinkType) const { 1636 aArgs.uri() = GetChannelCreationURI(); 1637 aArgs.loadIdentifier() = mLoadIdentifier; 1638 aArgs.earlyHints() = std::move(aEarlyHints); 1639 aArgs.earlyHintLinkType() = aEarlyHintLinkType; 1640 1641 // I previously used HttpBaseChannel::CloneLoadInfoForRedirect, but that 1642 // clears the principal to inherit, which fails tests (probably because this 1643 // 'redirect' is usually just an implementation detail). It's also http 1644 // only, and mChannel can be anything that we redirected to. 1645 nsCOMPtr<nsILoadInfo> channelLoadInfo = mChannel->LoadInfo(); 1646 nsCOMPtr<nsIPrincipal> principalToInherit; 1647 channelLoadInfo->GetPrincipalToInherit(getter_AddRefs(principalToInherit)); 1648 1649 const RefPtr<nsHttpChannel> baseChannel = do_QueryObject(mChannel); 1650 1651 nsCOMPtr<nsILoadContext> loadContext; 1652 NS_QueryNotificationCallbacks(mChannel, loadContext); 1653 nsCOMPtr<nsILoadInfo> redirectLoadInfo; 1654 1655 // Only use CloneLoadInfoForRedirect if we have a load context, 1656 // since it internally tries to pull OriginAttributes from the 1657 // the load context and asserts if they don't match the load info. 1658 // We can end up without a load context if the channel has been aborted 1659 // and the callbacks have been cleared. 1660 if (baseChannel && loadContext) { 1661 redirectLoadInfo = baseChannel->CloneLoadInfoForRedirect( 1662 aArgs.uri(), nsIChannelEventSink::REDIRECT_INTERNAL); 1663 redirectLoadInfo->SetResultPrincipalURI(aArgs.uri()); 1664 1665 // The clone process clears this, and then we fail tests.. 1666 // docshell/test/mochitest/test_forceinheritprincipal_overrule_owner.html 1667 if (principalToInherit) { 1668 redirectLoadInfo->SetPrincipalToInherit(principalToInherit); 1669 } 1670 } else { 1671 redirectLoadInfo = 1672 static_cast<mozilla::net::LoadInfo*>(channelLoadInfo.get())->Clone(); 1673 1674 redirectLoadInfo->AppendRedirectHistoryEntry(mChannel, true); 1675 } 1676 1677 const Maybe<ClientInfo>& reservedClientInfo = 1678 channelLoadInfo->GetReservedClientInfo(); 1679 if (reservedClientInfo) { 1680 redirectLoadInfo->SetReservedClientInfo(*reservedClientInfo); 1681 } 1682 1683 aArgs.registrarId() = mRedirectChannelId; 1684 1685 #ifdef DEBUG 1686 // We only set the granularFingerprintingProtection field when opening http 1687 // channels. So, we mark the field as set here if the channel is not a 1688 // nsHTTPChannel to pass the assertion check for getting this field in below 1689 // LoadInfoToLoadInfoArgs() call. 1690 if (!baseChannel) { 1691 static_cast<mozilla::net::LoadInfo*>(redirectLoadInfo.get()) 1692 ->MarkOverriddenFingerprintingSettingsAsSet(); 1693 } 1694 #endif 1695 1696 MOZ_ALWAYS_SUCCEEDS( 1697 ipc::LoadInfoToLoadInfoArgs(redirectLoadInfo, &aArgs.loadInfo())); 1698 1699 if (StaticPrefs::dom_location_ancestorOrigins_enabled()) { 1700 MOZ_ASSERT(XRE_IsParentProcess()); 1701 if (RefPtr bc = redirectLoadInfo->GetFrameBrowsingContext()) { 1702 nsCOMPtr<nsIPrincipal> resultPrincipal; 1703 // If this fails, we get an empty location.ancestorOrigins list 1704 if (NS_SUCCEEDED( 1705 nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal( 1706 mChannel, getter_AddRefs(resultPrincipal)))) { 1707 const auto referrerPolicy = 1708 static_cast<LoadInfo*>(channelLoadInfo.get()) 1709 ->GetFrameReferrerPolicySnapshot(); 1710 bc->Canonical()->CreateRedactedAncestorOriginsList(resultPrincipal, 1711 referrerPolicy); 1712 } 1713 1714 // convert principals to IPC data 1715 constexpr auto prepareInfo = 1716 [](nsIPrincipal* aPrincipal) -> Maybe<ipc::PrincipalInfo> { 1717 if (aPrincipal == nullptr) { 1718 return Nothing(); 1719 } 1720 ipc::PrincipalInfo data; 1721 return NS_SUCCEEDED(PrincipalToPrincipalInfo(aPrincipal, &data)) 1722 ? Some(std::move(data)) 1723 : Nothing(); 1724 }; 1725 1726 // The ancestorOrigins list the document should ultimately have, that we 1727 // send down with load args. 1728 auto& ancestorOrigins = aArgs.loadInfo().ancestorOrigins(); 1729 for (const auto& ancestorPrincipal : 1730 bc->Canonical()->GetPossiblyRedactedAncestorOriginsList()) { 1731 ancestorOrigins.AppendElement(prepareInfo(ancestorPrincipal)); 1732 } 1733 } 1734 } 1735 1736 mChannel->GetOriginalURI(getter_AddRefs(aArgs.originalURI())); 1737 1738 // mChannel can be a nsHttpChannel as well as InterceptedHttpChannel so we 1739 // can't use baseChannel here. 1740 if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel)) { 1741 MOZ_ALWAYS_SUCCEEDS(httpChannel->GetChannelId(&aArgs.channelId())); 1742 1743 // propagated the channel's referrerInfo back to child if the redirection 1744 // is caused by ServiceWorker interception. 1745 if (nsCOMPtr<nsIInterceptedChannel> interceptedChannel = 1746 do_QueryInterface(mChannel)) { 1747 nsCOMPtr<nsIReferrerInfo> referrerInfo; 1748 MOZ_ALWAYS_SUCCEEDS( 1749 httpChannel->GetReferrerInfo(getter_AddRefs(referrerInfo))); 1750 if (referrerInfo) { 1751 aArgs.referrerInfo() = referrerInfo; 1752 } 1753 } 1754 } 1755 1756 aArgs.redirectMode() = nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW; 1757 nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = 1758 do_QueryInterface(mChannel); 1759 if (httpChannelInternal) { 1760 MOZ_ALWAYS_SUCCEEDS( 1761 httpChannelInternal->GetRedirectMode(&aArgs.redirectMode())); 1762 } 1763 1764 if (baseChannel) { 1765 aArgs.init() = 1766 Some(baseChannel 1767 ->CloneReplacementChannelConfig( 1768 true, aRedirectFlags, 1769 HttpBaseChannel::ReplacementReason::DocumentChannel) 1770 .Serialize()); 1771 } 1772 1773 uint32_t contentDispositionTemp; 1774 nsresult rv = mChannel->GetContentDisposition(&contentDispositionTemp); 1775 if (NS_SUCCEEDED(rv)) { 1776 aArgs.contentDisposition() = Some(contentDispositionTemp); 1777 } 1778 1779 nsString contentDispositionFilenameTemp; 1780 rv = mChannel->GetContentDispositionFilename(contentDispositionFilenameTemp); 1781 if (NS_SUCCEEDED(rv)) { 1782 aArgs.contentDispositionFilename() = Some(contentDispositionFilenameTemp); 1783 } 1784 1785 SetNeedToAddURIVisit(mChannel, false); 1786 1787 aArgs.newLoadFlags() = aLoadFlags; 1788 aArgs.redirectFlags() = aRedirectFlags; 1789 aArgs.properties() = do_QueryObject(mChannel); 1790 aArgs.srcdocData() = mSrcdocData; 1791 aArgs.baseUri() = mBaseURI; 1792 aArgs.loadStateExternalLoadFlags() = mLoadStateExternalLoadFlags; 1793 aArgs.loadStateInternalLoadFlags() = mLoadStateInternalLoadFlags; 1794 aArgs.loadStateLoadType() = mLoadStateLoadType; 1795 aArgs.originalUriString() = mOriginalUriString; 1796 if (mLoadingSessionHistoryInfo) { 1797 aArgs.loadingSessionHistoryInfo().emplace(*mLoadingSessionHistoryInfo); 1798 } 1799 } 1800 1801 static bool IsFirstLoadInWindow(nsIChannel* aChannel) { 1802 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); 1803 return loadInfo->GetIsNewWindowTarget(); 1804 } 1805 1806 // Get where the document loaded by this nsIChannel should be rendered. This 1807 // will be `OPEN_CURRENTWINDOW` unless we're loading an attachment which would 1808 // normally open in an external program, but we're instead choosing to render 1809 // internally. 1810 static int32_t GetWhereToOpen(nsIChannel* aChannel, bool aIsDocumentLoad) { 1811 // Ignore content disposition for loads from an object or embed element. 1812 if (!aIsDocumentLoad) { 1813 return nsIBrowserDOMWindow::OPEN_CURRENTWINDOW; 1814 } 1815 1816 // Always continue in the same window if we're not loading an attachment. 1817 uint32_t disposition = nsIChannel::DISPOSITION_INLINE; 1818 if (NS_FAILED(aChannel->GetContentDisposition(&disposition)) || 1819 disposition != nsIChannel::DISPOSITION_ATTACHMENT) { 1820 return nsIBrowserDOMWindow::OPEN_CURRENTWINDOW; 1821 } 1822 1823 // If the channel is for a new window target, continue in the same window. 1824 if (IsFirstLoadInWindow(aChannel)) { 1825 return nsIBrowserDOMWindow::OPEN_CURRENTWINDOW; 1826 } 1827 1828 // Respect the user's preferences with browser.link.open_newwindow 1829 // FIXME: There should probably be a helper for this, as the logic is 1830 // duplicated in a few places. 1831 int32_t where = Preferences::GetInt("browser.link.open_newwindow", 1832 nsIBrowserDOMWindow::OPEN_NEWTAB); 1833 if (where == nsIBrowserDOMWindow::OPEN_CURRENTWINDOW || 1834 where == nsIBrowserDOMWindow::OPEN_NEWWINDOW || 1835 where == nsIBrowserDOMWindow::OPEN_NEWTAB) { 1836 return where; 1837 } 1838 // NOTE: nsIBrowserDOMWindow::OPEN_NEWTAB_BACKGROUND and 1839 // nsIBrowserDOMWindow::OPEN_NEWTAB_FOREGROUND are not allowed as pref 1840 // values. 1841 return nsIBrowserDOMWindow::OPEN_NEWTAB; 1842 } 1843 1844 static bool ContextCanProcessSwitch(CanonicalBrowsingContext* aBrowsingContext, 1845 WindowGlobalParent* aParentWindow, 1846 bool aSwitchToNewTab) { 1847 if (NS_WARN_IF(!aBrowsingContext)) { 1848 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, 1849 ("Process Switch Abort: no browsing context")); 1850 return false; 1851 } 1852 if (!aBrowsingContext->IsContent()) { 1853 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, 1854 ("Process Switch Abort: non-content browsing context")); 1855 return false; 1856 } 1857 1858 // If we're switching into a new tab, we can skip the remaining checks, as 1859 // we're not actually changing the process of aBrowsingContext, so whether or 1860 // not it is allowed to process switch isn't relevant. 1861 if (aSwitchToNewTab) { 1862 return true; 1863 } 1864 1865 if (aParentWindow) { 1866 // If remote subframes are disabled, subframes never process switch. 1867 if (!aBrowsingContext->UseRemoteSubframes()) { 1868 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, 1869 ("Process Switch Abort: remote subframes disabled")); 1870 return false; 1871 } 1872 1873 // Otherwise, subframes can always process-switch unless they are directly 1874 // embedded within a parent-process document. 1875 if (aParentWindow->IsInProcess()) { 1876 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, 1877 ("Process Switch Abort: Subframe with in-process parent")); 1878 return false; 1879 } 1880 return true; 1881 } 1882 1883 // Check if the "maychangeremoteness" attribute is present on the embedding 1884 // element. Assume the context can process switch if the embedder element is 1885 // unknown, as it's safer to fail to switch in that scenario. 1886 Element* browserElement = aBrowsingContext->Top()->GetEmbedderElement(); 1887 if (browserElement && 1888 !browserElement->HasAttribute(u"maychangeremoteness"_ns)) { 1889 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, 1890 ("Process Switch Abort: toplevel switch disabled by <browser>")); 1891 return false; 1892 } 1893 1894 // In some tests, we use `createWindowlessBrowser(false)` to create a 1895 // windowless content browser. As process switching relies on the `<browser>` 1896 // element to perform a remote abstraction, we cannot perform process 1897 // switching on the root element of this browser. 1898 if (!browserElement && aBrowsingContext->Windowless()) { 1899 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, 1900 ("Process Switch Abort: switch disabled by windowless browser")); 1901 return false; 1902 } 1903 1904 return true; 1905 } 1906 1907 static RefPtr<dom::BrowsingContextCallbackReceivedPromise> SwitchToNewTab( 1908 CanonicalBrowsingContext* aLoadingBrowsingContext, int32_t aWhere) { 1909 MOZ_ASSERT(aWhere == nsIBrowserDOMWindow::OPEN_NEWTAB || 1910 aWhere == nsIBrowserDOMWindow::OPEN_NEWTAB_BACKGROUND || 1911 aWhere == nsIBrowserDOMWindow::OPEN_NEWTAB_FOREGROUND || 1912 aWhere == nsIBrowserDOMWindow::OPEN_NEWWINDOW, 1913 "Unsupported open location"); 1914 1915 auto promise = 1916 MakeRefPtr<dom::BrowsingContextCallbackReceivedPromise::Private>( 1917 __func__); 1918 1919 // Get the nsIBrowserDOMWindow for the given BrowsingContext's tab. 1920 nsCOMPtr<nsIBrowserDOMWindow> browserDOMWindow = 1921 aLoadingBrowsingContext->GetBrowserDOMWindow(); 1922 if (NS_WARN_IF(!browserDOMWindow)) { 1923 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, 1924 ("Process Switch Abort: Unable to get nsIBrowserDOMWindow")); 1925 promise->Reject(NS_ERROR_FAILURE, __func__); 1926 return promise; 1927 } 1928 1929 // Open a new content tab by calling into frontend. We don't need to worry 1930 // about the triggering principal or CSP, as createContentWindow doesn't 1931 // actually start loading anything, but use a null principal anyway in case 1932 // something changes. 1933 nsCOMPtr<nsIPrincipal> triggeringPrincipal = 1934 NullPrincipal::Create(aLoadingBrowsingContext->OriginAttributesRef()); 1935 1936 RefPtr<nsOpenWindowInfo> openInfo = new nsOpenWindowInfo(); 1937 openInfo->mBrowsingContextReadyCallback = 1938 new nsBrowsingContextReadyCallback(promise); 1939 openInfo->mParent = aLoadingBrowsingContext; 1940 openInfo->mForceNoOpener = true; 1941 openInfo->mIsRemote = true; 1942 openInfo->mPrincipalToInheritForAboutBlank = triggeringPrincipal; 1943 1944 // Do the actual work to open a new tab or window async. 1945 nsresult rv = NS_DispatchToMainThread(NS_NewRunnableFunction( 1946 "DocumentLoadListener::SwitchToNewTab", 1947 [browserDOMWindow, openInfo, aWhere, triggeringPrincipal, promise] { 1948 RefPtr<BrowsingContext> bc; 1949 nsresult rv = browserDOMWindow->CreateContentWindow( 1950 /* uri */ nullptr, openInfo, aWhere, 1951 nsIBrowserDOMWindow::OPEN_NO_REFERRER, triggeringPrincipal, 1952 /* policyContainer */ nullptr, getter_AddRefs(bc)); 1953 if (NS_WARN_IF(NS_FAILED(rv))) { 1954 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, 1955 ("Process Switch Abort: CreateContentWindow threw")); 1956 promise->Reject(rv, __func__); 1957 } 1958 if (bc) { 1959 promise->Resolve(bc, __func__); 1960 } 1961 })); 1962 if (NS_WARN_IF(NS_FAILED(rv))) { 1963 promise->Reject(NS_ERROR_UNEXPECTED, __func__); 1964 } 1965 return promise; 1966 } 1967 1968 bool DocumentLoadListener::MaybeTriggerProcessSwitch( 1969 bool* aWillSwitchToRemote) { 1970 MOZ_ASSERT(XRE_IsParentProcess()); 1971 MOZ_DIAGNOSTIC_ASSERT(mChannel); 1972 MOZ_DIAGNOSTIC_ASSERT(mParentChannelListener); 1973 MOZ_DIAGNOSTIC_ASSERT(aWillSwitchToRemote); 1974 1975 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, 1976 ("DocumentLoadListener MaybeTriggerProcessSwitch [this=%p, uri=%s, " 1977 "browserid=%" PRIx64 "]", 1978 this, GetChannelCreationURI()->GetSpecOrDefault().get(), 1979 GetLoadingBrowsingContext()->Top()->BrowserId())); 1980 1981 // Check if we should handle this load in a different tab or window. 1982 int32_t where = GetWhereToOpen(mChannel, mIsDocumentLoad); 1983 bool switchToNewTab = where != nsIBrowserDOMWindow::OPEN_CURRENTWINDOW; 1984 1985 // Get the loading BrowsingContext. This may not be the context which will be 1986 // switching processes when switching to a new tab, and in the case of an 1987 // <object> or <embed> element, as we don't create the final context until 1988 // after process selection. 1989 // 1990 // - /!\ WARNING /!\ - 1991 // Don't use `browsingContext->IsTop()` in this method! It will behave 1992 // incorrectly for non-document loads such as `<object>` or `<embed>`. 1993 // Instead, check whether or not `parentWindow` is null. 1994 RefPtr<CanonicalBrowsingContext> browsingContext = 1995 GetLoadingBrowsingContext(); 1996 // If switching to a new tab, the final BC isn't a frame. 1997 RefPtr<WindowGlobalParent> parentWindow = 1998 switchToNewTab ? nullptr : GetParentWindowContext(); 1999 if (!ContextCanProcessSwitch(browsingContext, parentWindow, switchToNewTab)) { 2000 return false; 2001 } 2002 2003 if (!browsingContext->IsOwnedByProcess(GetContentProcessId(mContentParent))) { 2004 MOZ_LOG(gProcessIsolationLog, LogLevel::Error, 2005 ("Process Switch Abort: context no longer owned by creator")); 2006 Cancel(NS_BINDING_ABORTED, 2007 "Process Switch Abort: context no longer owned by creator"_ns); 2008 return false; 2009 } 2010 2011 if (browsingContext->IsReplaced()) { 2012 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, 2013 ("Process Switch Abort: replaced browsing context")); 2014 Cancel(NS_BINDING_ABORTED, 2015 "Process Switch Abort: replaced browsing context"_ns); 2016 return false; 2017 } 2018 2019 nsAutoCString currentRemoteType(NOT_REMOTE_TYPE); 2020 if (mContentParent) { 2021 currentRemoteType = mContentParent->GetRemoteType(); 2022 } 2023 2024 auto optionsResult = IsolationOptionsForNavigation( 2025 browsingContext->Top(), switchToNewTab ? nullptr : parentWindow.get(), 2026 GetChannelCreationURI(), mChannel, currentRemoteType, 2027 HasCrossOriginOpenerPolicyMismatch(), switchToNewTab, mLoadStateLoadType, 2028 mDocumentChannelId, mRemoteTypeOverride); 2029 if (optionsResult.isErr()) { 2030 MOZ_LOG(gProcessIsolationLog, LogLevel::Error, 2031 ("Process Switch Abort: CheckIsolationForNavigation Failed with %s", 2032 GetStaticErrorName(optionsResult.inspectErr()))); 2033 Cancel(optionsResult.unwrapErr(), 2034 "Process Switch Abort: CheckIsolationForNavigation Failed"_ns); 2035 return false; 2036 } 2037 2038 NavigationIsolationOptions options = optionsResult.unwrap(); 2039 2040 if (options.mTryUseBFCache) { 2041 MOZ_ASSERT(!parentWindow, "Can only BFCache toplevel windows"); 2042 MOZ_ASSERT(!switchToNewTab, "Can't BFCache for a tab switch"); 2043 bool sameOrigin = false; 2044 if (auto* wgp = browsingContext->GetCurrentWindowGlobal()) { 2045 nsCOMPtr<nsIPrincipal> resultPrincipal; 2046 MOZ_ALWAYS_SUCCEEDS( 2047 nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal( 2048 mChannel, getter_AddRefs(resultPrincipal))); 2049 sameOrigin = 2050 wgp->DocumentPrincipal()->EqualsConsideringDomain(resultPrincipal); 2051 } 2052 2053 // We only reset the window name for content. 2054 mLoadingSessionHistoryInfo->mForceMaybeResetName.emplace( 2055 StaticPrefs::privacy_window_name_update_enabled() && 2056 browsingContext->IsContent() && !sameOrigin); 2057 } 2058 2059 MOZ_LOG( 2060 gProcessIsolationLog, LogLevel::Verbose, 2061 ("CheckIsolationForNavigation -> current:(%s) remoteType:(%s) replace:%d " 2062 "group:%" PRIx64 " bfcache:%d shentry:%p newTab:%d", 2063 currentRemoteType.get(), options.mRemoteType.get(), 2064 options.mReplaceBrowsingContext, options.mSpecificGroupId, 2065 options.mTryUseBFCache, options.mActiveSessionHistoryEntry.get(), 2066 switchToNewTab)); 2067 2068 // Check if a process switch is needed. 2069 if (currentRemoteType == options.mRemoteType && 2070 !options.mReplaceBrowsingContext && !switchToNewTab) { 2071 MOZ_LOG(gProcessIsolationLog, LogLevel::Info, 2072 ("Process Switch Abort: type (%s) is compatible", 2073 options.mRemoteType.get())); 2074 return false; 2075 } 2076 2077 if (NS_WARN_IF(parentWindow && options.mRemoteType.IsEmpty())) { 2078 MOZ_LOG(gProcessIsolationLog, LogLevel::Error, 2079 ("Process Switch Abort: non-remote target process for subframe")); 2080 return false; 2081 } 2082 2083 *aWillSwitchToRemote = !options.mRemoteType.IsEmpty(); 2084 2085 // If we've decided to re-target this load into a new tab or window (see 2086 // `GetWhereToOpen`), do so before performing a process switch. This will 2087 // require creating the new <browser> to load in, which may be performed 2088 // async. 2089 if (switchToNewTab) { 2090 SwitchToNewTab(browsingContext, where) 2091 ->Then( 2092 GetMainThreadSerialEventTarget(), __func__, 2093 [self = RefPtr{this}, 2094 options](const RefPtr<BrowsingContext>& aBrowsingContext) 2095 MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA mutable { 2096 if (aBrowsingContext->IsDiscarded()) { 2097 MOZ_LOG(gProcessIsolationLog, LogLevel::Error, 2098 ("Process Switch: Got invalid new-tab " 2099 "BrowsingContext")); 2100 self->RedirectToRealChannelFinished(NS_ERROR_FAILURE); 2101 return; 2102 } 2103 2104 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, 2105 ("Process Switch: Redirected load to new tab")); 2106 self->TriggerProcessSwitch( 2107 MOZ_KnownLive(aBrowsingContext->Canonical()), options, 2108 /* aIsNewTab */ true); 2109 }, 2110 [self = RefPtr{this}](const CopyableErrorResult&) { 2111 MOZ_LOG(gProcessIsolationLog, LogLevel::Error, 2112 ("Process Switch: SwitchToNewTab failed")); 2113 self->RedirectToRealChannelFinished(NS_ERROR_FAILURE); 2114 }); 2115 return true; 2116 } 2117 2118 // If we're doing a document load, we can immediately perform a process 2119 // switch. 2120 if (mIsDocumentLoad) { 2121 TriggerProcessSwitch(browsingContext, options); 2122 return true; 2123 } 2124 2125 // We're not doing a document load, which means we must be performing an 2126 // object load. We need a BrowsingContext to perform the switch in, so will 2127 // trigger an upgrade. 2128 if (!mObjectUpgradeHandler) { 2129 MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, 2130 ("Process Switch Abort: no object upgrade handler")); 2131 return false; 2132 } 2133 2134 mObjectUpgradeHandler->UpgradeObjectLoad()->Then( 2135 GetMainThreadSerialEventTarget(), __func__, 2136 [self = RefPtr{this}, options, 2137 parentWindow](const RefPtr<CanonicalBrowsingContext>& aBrowsingContext) 2138 MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA mutable { 2139 if (aBrowsingContext->IsDiscarded() || 2140 parentWindow != aBrowsingContext->GetParentWindowContext()) { 2141 MOZ_LOG( 2142 gProcessIsolationLog, LogLevel::Error, 2143 ("Process Switch: Got invalid BrowsingContext from object " 2144 "upgrade!")); 2145 self->RedirectToRealChannelFinished(NS_ERROR_FAILURE); 2146 return; 2147 } 2148 2149 // At this point the element has stored the container feature policy 2150 // in the new browsing context, but we need to make sure that we 2151 // copy it over to the load info. 2152 nsCOMPtr<nsILoadInfo> loadInfo = self->mChannel->LoadInfo(); 2153 if (aBrowsingContext->GetContainerFeaturePolicy()) { 2154 loadInfo->SetContainerFeaturePolicyInfo( 2155 *aBrowsingContext->GetContainerFeaturePolicy()); 2156 } 2157 2158 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, 2159 ("Process Switch: Upgraded Object to Document Load")); 2160 self->TriggerProcessSwitch(aBrowsingContext, options); 2161 }, 2162 [self = RefPtr{this}](nsresult aStatusCode) { 2163 MOZ_ASSERT(NS_FAILED(aStatusCode), "Status should be error"); 2164 self->RedirectToRealChannelFinished(aStatusCode); 2165 }); 2166 return true; 2167 } 2168 2169 void DocumentLoadListener::TriggerProcessSwitch( 2170 CanonicalBrowsingContext* aContext, 2171 const NavigationIsolationOptions& aOptions, bool aIsNewTab) { 2172 MOZ_DIAGNOSTIC_ASSERT(aIsNewTab || aContext->IsOwnedByProcess( 2173 GetContentProcessId(mContentParent)), 2174 "not owned by creator process anymore?"); 2175 if (MOZ_LOG_TEST(gProcessIsolationLog, LogLevel::Info)) { 2176 nsCString currentRemoteType = "INVALID"_ns; 2177 aContext->GetCurrentRemoteType(currentRemoteType, IgnoreErrors()); 2178 2179 MOZ_LOG(gProcessIsolationLog, LogLevel::Info, 2180 ("Process Switch: Changing Remoteness from '%s' to '%s'", 2181 currentRemoteType.get(), aOptions.mRemoteType.get())); 2182 } 2183 2184 // Stash our stream filter requests to pass to TriggerRedirectToRealChannel, 2185 // as the call to `DisconnectListeners` will clear our list. 2186 nsTArray<StreamFilterRequest> streamFilterRequests = 2187 std::move(mStreamFilterRequests); 2188 2189 // We're now committing to a process switch, so we can disconnect from 2190 // the listeners in the old process. 2191 // As the navigation is continuing, we don't actually want to cancel the 2192 // request in the old process unless we're redirecting the load into a new 2193 // tab. 2194 DisconnectListeners(NS_BINDING_ABORTED, NS_BINDING_ABORTED, !aIsNewTab); 2195 2196 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, 2197 ("Process Switch: Calling ChangeRemoteness")); 2198 aContext->ChangeRemoteness(aOptions, mLoadIdentifier) 2199 ->Then( 2200 GetMainThreadSerialEventTarget(), __func__, 2201 [self = RefPtr{this}, requests = std::move(streamFilterRequests)]( 2202 const std::pair<RefPtr<BrowserParent>, 2203 RefPtr<CanonicalBrowsingContext>>& 2204 aResult) mutable { 2205 MOZ_ASSERT(self->mChannel, 2206 "Something went wrong, channel got cancelled"); 2207 const auto& [browserParent, browsingContext] = aResult; 2208 self->TriggerRedirectToRealChannel( 2209 browsingContext, 2210 Some(browserParent ? browserParent->Manager() : nullptr), 2211 std::move(requests)); 2212 }, 2213 [self = RefPtr{this}](nsresult aStatusCode) { 2214 MOZ_ASSERT(NS_FAILED(aStatusCode), "Status should be error"); 2215 self->RedirectToRealChannelFinished(aStatusCode); 2216 }); 2217 } 2218 2219 RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise> 2220 DocumentLoadListener::RedirectToParentProcess(uint32_t aRedirectFlags, 2221 uint32_t aLoadFlags) { 2222 // This is largely the same as ContentChild::RecvCrossProcessRedirect, 2223 // except without needing to deserialize or create an nsIChildChannel. 2224 2225 RefPtr<nsDocShellLoadState> loadState; 2226 nsDocShellLoadState::CreateFromPendingChannel( 2227 mChannel, mLoadIdentifier, mRedirectChannelId, getter_AddRefs(loadState)); 2228 2229 loadState->SetLoadFlags(mLoadStateExternalLoadFlags); 2230 loadState->SetInternalLoadFlags(mLoadStateInternalLoadFlags); 2231 loadState->SetLoadType(mLoadStateLoadType); 2232 if (mLoadingSessionHistoryInfo) { 2233 loadState->SetLoadingSessionHistoryInfo(*mLoadingSessionHistoryInfo); 2234 } 2235 2236 // This is poorly named now. 2237 RefPtr<ChildProcessChannelListener> processListener = 2238 ChildProcessChannelListener::GetSingleton(); 2239 2240 auto promise = 2241 MakeRefPtr<PDocumentChannelParent::RedirectToRealChannelPromise::Private>( 2242 __func__); 2243 promise->UseDirectTaskDispatch(__func__); 2244 auto resolve = [promise](nsresult aResult) { 2245 promise->Resolve(aResult, __func__); 2246 }; 2247 2248 nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>> endpoints; 2249 processListener->OnChannelReady(loadState, mLoadIdentifier, 2250 std::move(endpoints), mTiming, 2251 std::move(resolve)); 2252 2253 return promise; 2254 } 2255 2256 RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise> 2257 DocumentLoadListener::RedirectToRealChannel( 2258 uint32_t aRedirectFlags, uint32_t aLoadFlags, 2259 const Maybe<ContentParent*>& aDestinationProcess, 2260 nsTArray<ParentEndpoint>&& aStreamFilterEndpoints) { 2261 LOG( 2262 ("DocumentLoadListener RedirectToRealChannel [this=%p] " 2263 "aRedirectFlags=%" PRIx32 ", aLoadFlags=%" PRIx32, 2264 this, aRedirectFlags, aLoadFlags)); 2265 2266 if (mIsDocumentLoad) { 2267 // TODO(djg): Add the last URI visit to history if success. Is there a 2268 // better place to handle this? Need access to the updated aLoadFlags. 2269 nsresult status = NS_OK; 2270 mChannel->GetStatus(&status); 2271 bool updateGHistory = 2272 nsDocShell::ShouldUpdateGlobalHistory(mLoadStateLoadType); 2273 if (NS_SUCCEEDED(status) && updateGHistory && 2274 !net::ChannelIsPost(mChannel)) { 2275 AddURIVisit(mChannel, aLoadFlags); 2276 } 2277 } 2278 2279 // Register the new channel and obtain id for it 2280 nsCOMPtr<nsIRedirectChannelRegistrar> registrar = 2281 RedirectChannelRegistrar::GetOrCreate(); 2282 MOZ_ASSERT(registrar); 2283 nsCOMPtr<nsIChannel> chan = mChannel; 2284 if (nsCOMPtr<nsIViewSourceChannel> vsc = do_QueryInterface(chan)) { 2285 chan = vsc->GetInnerChannel(); 2286 } 2287 mRedirectChannelId = nsContentUtils::GenerateLoadIdentifier(); 2288 MOZ_ALWAYS_SUCCEEDS(registrar->RegisterChannel(chan, mRedirectChannelId)); 2289 2290 if (aDestinationProcess) { 2291 RefPtr<ContentParent> cp = *aDestinationProcess; 2292 if (!cp) { 2293 MOZ_ASSERT(aStreamFilterEndpoints.IsEmpty()); 2294 return RedirectToParentProcess(aRedirectFlags, aLoadFlags); 2295 } 2296 2297 if (!cp->CanSend()) { 2298 return PDocumentChannelParent::RedirectToRealChannelPromise:: 2299 CreateAndReject(ipc::ResponseRejectReason::SendError, __func__); 2300 } 2301 2302 nsTArray<EarlyHintConnectArgs> ehArgs; 2303 mEarlyHintsService.RegisterLinksAndGetConnectArgs(cp->ChildID(), ehArgs); 2304 2305 RedirectToRealChannelArgs args; 2306 SerializeRedirectData(args, /* aIsCrossProcess */ true, aRedirectFlags, 2307 aLoadFlags, std::move(ehArgs), 2308 mEarlyHintsService.LinkType()); 2309 if (mTiming) { 2310 mTiming->Anonymize(args.uri()); 2311 args.timing() = std::move(mTiming); 2312 } 2313 2314 cp->TransmitBlobDataIfBlobURL(args.uri()); 2315 2316 if (CanonicalBrowsingContext* bc = GetDocumentBrowsingContext()) { 2317 if (bc->IsTop() && bc->IsActive()) { 2318 nsContentUtils::RequestGeckoTaskBurst(); 2319 } 2320 } 2321 2322 return cp->SendCrossProcessRedirect(args, 2323 std::move(aStreamFilterEndpoints)); 2324 } 2325 2326 if (mOpenPromiseResolved) { 2327 LOG( 2328 ("DocumentLoadListener RedirectToRealChannel [this=%p] " 2329 "promise already resolved. Aborting.", 2330 this)); 2331 // The promise has already been resolved or aborted, so we have no way to 2332 // return a promise again to the listener which would cancel the operation. 2333 // Reject the promise immediately. 2334 return PDocumentChannelParent::RedirectToRealChannelPromise:: 2335 CreateAndResolve(NS_BINDING_ABORTED, __func__); 2336 } 2337 2338 // This promise will be passed on the promise listener which will 2339 // resolve this promise for us. 2340 auto promise = 2341 MakeRefPtr<PDocumentChannelParent::RedirectToRealChannelPromise::Private>( 2342 __func__); 2343 2344 mOpenPromise->Resolve( 2345 OpenPromiseSucceededType({std::move(aStreamFilterEndpoints), 2346 aRedirectFlags, aLoadFlags, 2347 mEarlyHintsService.LinkType(), promise}), 2348 __func__); 2349 2350 // There is no way we could come back here if the promise had been resolved 2351 // previously. But for clarity and to avoid all doubt, we set this boolean to 2352 // true. 2353 mOpenPromiseResolved = true; 2354 2355 return promise; 2356 } 2357 2358 void DocumentLoadListener::TriggerRedirectToRealChannel( 2359 CanonicalBrowsingContext* aDestinationBrowsingContext, 2360 const Maybe<ContentParent*>& aDestinationProcess, 2361 nsTArray<StreamFilterRequest> aStreamFilterRequests) { 2362 LOG( 2363 ("DocumentLoadListener::TriggerRedirectToRealChannel [this=%p] " 2364 "aDestinationBrowsingContext=%" PRIx64 " aDestinationProcess=%" PRId64, 2365 this, aDestinationBrowsingContext->Id(), 2366 aDestinationProcess.valueOr(nullptr) 2367 ? int64_t((*aDestinationProcess)->ChildID()) 2368 : int64_t(-1))); 2369 MOZ_ASSERT(aDestinationBrowsingContext); 2370 2371 // This initiates replacing the current DocumentChannel with a 2372 // protocol specific 'real' channel, maybe in a different process than 2373 // the current DocumentChannelChild, if aDestinationProces is set. 2374 // It registers the current mChannel with the registrar to get an ID 2375 // so that the remote end can setup a new IPDL channel and lookup 2376 // the same underlying channel. 2377 // We expect this process to finish with FinishReplacementChannelSetup 2378 // (for both in-process and process switch cases), where we cleanup 2379 // the registrar and copy across any needed state to the replacing 2380 // IPDL parent object. 2381 2382 // If we've already called `DisconnectListeners`, and have no destination 2383 // process we're switching to, abort early to skip the following checks. 2384 if (mOpenPromiseResolved && !aDestinationProcess) { 2385 LOG( 2386 ("DocumentLoadListener::TriggerRedirectToRealChannel [this=%p] " 2387 "Listeners already disconnected for non-switching redirect. Aborting.", 2388 this)); 2389 RedirectToRealChannelFinished(NS_BINDING_ABORTED); 2390 return; 2391 } 2392 2393 RefPtr<ContentParent> contentParent = 2394 aDestinationProcess.valueOr(mContentParent); 2395 2396 nsTArray<ParentEndpoint> parentEndpoints(aStreamFilterRequests.Length()); 2397 if (!aStreamFilterRequests.IsEmpty()) { 2398 base::ProcessId pid = 2399 contentParent ? contentParent->OtherPid() : base::ProcessId{0}; 2400 2401 for (StreamFilterRequest& request : aStreamFilterRequests) { 2402 if (!pid) { 2403 request.mPromise->Reject(false, __func__); 2404 request.mPromise = nullptr; 2405 continue; 2406 } 2407 ParentEndpoint parent; 2408 nsresult rv = extensions::PStreamFilter::CreateEndpoints( 2409 &parent, &request.mChildEndpoint); 2410 2411 if (NS_FAILED(rv)) { 2412 request.mPromise->Reject(false, __func__); 2413 request.mPromise = nullptr; 2414 } else { 2415 parentEndpoints.AppendElement(std::move(parent)); 2416 } 2417 } 2418 } 2419 2420 // Check if the load is for a "silent" error (i.e. no document or error page 2421 // will load). This is the case for all failed object/embed loads, and some 2422 // failed document loads. 2423 // We never process switch for these silent loads (as that will destroy the 2424 // existing document, which is being navigated away from) 2425 nsresult status = NS_OK; 2426 mChannel->GetStatus(&status); 2427 bool silentErrorLoad = !DocShellWillDisplayContent(status); 2428 2429 // Get the unsandboxed result principal for our channel. This is required both 2430 // to validate that the response which will be loaded is being sent to the 2431 // appropriate process, as well as to apply origin keying. 2432 nsCOMPtr<nsIPrincipal> unsandboxedPrincipal; 2433 nsresult rv = nsScriptSecurityManager::GetScriptSecurityManager() 2434 ->GetChannelResultPrincipalIfNotSandboxed( 2435 mChannel, getter_AddRefs(unsandboxedPrincipal)); 2436 if (NS_FAILED(rv)) { 2437 LOG( 2438 ("DocumentLoadListener::TriggerRedirectToRealChannel [this=%p] " 2439 "GetChannelResultPrincipalIfNotSandboxed failed", 2440 this)); 2441 RedirectToRealChannelFinished(NS_ERROR_FAILURE); 2442 return; 2443 } 2444 2445 // Validate that the target process, if specified, would be allowed to load 2446 // this principal, and fail the navigation if it would not. 2447 // Don't enforce this requirement for silent error loads, as those never 2448 // process switch, and should not result in a document being loaded in the 2449 // content process. 2450 // System principals are allowed for now, as they are used in some edge-cases. 2451 if (!silentErrorLoad && contentParent && 2452 !contentParent->ValidatePrincipal( 2453 unsandboxedPrincipal, {ValidatePrincipalOptions::AllowSystem})) { 2454 ContentParent::LogAndAssertFailedPrincipalValidationInfo( 2455 unsandboxedPrincipal, "TriggerRedirectToRealChannel"); 2456 RedirectToRealChannelFinished(NS_ERROR_FAILURE); 2457 return; 2458 } 2459 2460 // Ensure that the BrowsingContextGroup which will finish this load has the 2461 // UseOriginAgentCluster flag set to a value. We'll try to base it on 2462 // `mChannel` if it has the appropriate header. 2463 // 2464 // This needs to be set such that we can perform correct DocGroup keying in 2465 // the content process. 2466 // 2467 // In effect, this is performing the side-effect component of "obtain a 2468 // similar-origin window agent", leading to the historical agent cluster key 2469 // map being populated in the BrowsingContextGroup. 2470 // 2471 // https://html.spec.whatwg.org/#obtain-similar-origin-window-agent 2472 if (aDestinationBrowsingContext->Group() 2473 ->UsesOriginAgentCluster(unsandboxedPrincipal) 2474 .isNothing()) { 2475 // UseOriginAgentCluster requires a secure context, so never origin key 2476 // unless we're a potentially-trustworthy origin. 2477 // 2478 // We don't handle this within BrowsingContextGroup, as the set of URIs 2479 // which are considered "potentially trustworthy" can change at runtime, so 2480 // we want to cache the decision at the time we make it. 2481 bool isSecureContext = 2482 unsandboxedPrincipal->GetIsOriginPotentiallyTrustworthy(); 2483 bool hasOriginAgentCluster = 2484 StaticPrefs::dom_origin_agent_cluster_default() && isSecureContext; 2485 if (nsCOMPtr<nsIHttpChannelInternal> httpChannel = 2486 do_QueryInterface(mChannel); 2487 httpChannel && isSecureContext && 2488 StaticPrefs::dom_origin_agent_cluster_enabled()) { 2489 bool headerValue = false; 2490 if (NS_SUCCEEDED( 2491 httpChannel->GetOriginAgentClusterHeader(&headerValue))) { 2492 hasOriginAgentCluster = headerValue; 2493 } 2494 } 2495 aDestinationBrowsingContext->Group()->SetUseOriginAgentClusterFromNetwork( 2496 unsandboxedPrincipal, hasOriginAgentCluster); 2497 } 2498 2499 // If we didn't have any redirects, then we pass the REDIRECT_INTERNAL flag 2500 // for this channel switch so that it isn't recorded in session history etc. 2501 // If there were redirect(s), then we want this switch to be recorded as a 2502 // real one, since we have a new URI. 2503 uint32_t redirectFlags = 0; 2504 if (!mHaveVisibleRedirect) { 2505 redirectFlags = nsIChannelEventSink::REDIRECT_INTERNAL; 2506 } 2507 2508 uint32_t newLoadFlags = nsIRequest::LOAD_NORMAL; 2509 MOZ_ALWAYS_SUCCEEDS(mChannel->GetLoadFlags(&newLoadFlags)); 2510 // We're pulling our flags from the inner channel, which may not have this 2511 // flag set on it. This is the case when loading a 'view-source' channel. 2512 if (mIsDocumentLoad || aDestinationProcess) { 2513 newLoadFlags |= nsIChannel::LOAD_DOCUMENT_URI; 2514 } 2515 if (!aDestinationProcess) { 2516 newLoadFlags |= nsIChannel::LOAD_REPLACE; 2517 } 2518 2519 // INHIBIT_PERSISTENT_CACHING is clearing during http redirects (from 2520 // both parent and content process channel instances), but only ever 2521 // re-added to the parent-side nsHttpChannel. 2522 // To match that behaviour, we want to explicitly avoid copying this flag 2523 // back to our newly created content side channel, otherwise it can 2524 // affect sub-resources loads in the same load group. 2525 nsCOMPtr<nsIURI> uri; 2526 mChannel->GetURI(getter_AddRefs(uri)); 2527 if (uri && uri->SchemeIs("https")) { 2528 newLoadFlags &= ~nsIRequest::INHIBIT_PERSISTENT_CACHING; 2529 } 2530 2531 RefPtr<DocumentLoadListener> self = this; 2532 RedirectToRealChannel(redirectFlags, newLoadFlags, aDestinationProcess, 2533 std::move(parentEndpoints)) 2534 ->Then( 2535 GetCurrentSerialEventTarget(), __func__, 2536 [self, requests = std::move(aStreamFilterRequests)]( 2537 const nsresult& aResponse) mutable { 2538 for (StreamFilterRequest& request : requests) { 2539 if (request.mPromise) { 2540 request.mPromise->Resolve(std::move(request.mChildEndpoint), 2541 __func__); 2542 request.mPromise = nullptr; 2543 } 2544 } 2545 self->RedirectToRealChannelFinished(aResponse); 2546 }, 2547 [self](const mozilla::ipc::ResponseRejectReason) { 2548 self->RedirectToRealChannelFinished(NS_ERROR_FAILURE); 2549 }); 2550 } 2551 2552 void DocumentLoadListener::MaybeReportBlockedByURLClassifier(nsresult aStatus) { 2553 auto* browsingContext = GetDocumentBrowsingContext(); 2554 if (!browsingContext || browsingContext->IsTop() || 2555 !StaticPrefs::privacy_trackingprotection_testing_report_blocked_node()) { 2556 return; 2557 } 2558 2559 if (!UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aStatus)) { 2560 return; 2561 } 2562 2563 RefPtr<WindowGlobalParent> parent = browsingContext->GetParentWindowContext(); 2564 if (parent) { 2565 (void)parent->SendAddBlockedFrameNodeByClassifier(browsingContext); 2566 } 2567 } 2568 2569 bool DocumentLoadListener::DocShellWillDisplayContent(nsresult aStatus) { 2570 if (NS_SUCCEEDED(aStatus)) { 2571 return true; 2572 } 2573 2574 // Always return errored loads to the <object> or <embed> element's process, 2575 // as load errors will not be rendered as documents. 2576 if (!mIsDocumentLoad) { 2577 return false; 2578 } 2579 2580 // nsDocShell attempts urifixup on some failure types, 2581 // but also of those also display an error page if we don't 2582 // succeed with fixup, so we don't need to check for it 2583 // here. 2584 2585 auto* loadingContext = GetLoadingBrowsingContext(); 2586 2587 nsresult rv = nsDocShell::FilterStatusForErrorPage( 2588 aStatus, mChannel, mLoadStateLoadType, loadingContext->IsTop(), 2589 loadingContext->GetUseErrorPages(), nullptr); 2590 2591 // If filtering returned a failure code, then an error page will 2592 // be display for that code, so return true; 2593 return NS_FAILED(rv); 2594 } 2595 2596 bool DocumentLoadListener::MaybeHandleLoadErrorWithURIFixup(nsresult aStatus) { 2597 RefPtr<CanonicalBrowsingContext> bc = GetDocumentBrowsingContext(); 2598 if (!bc) { 2599 return false; 2600 } 2601 2602 nsCOMPtr<nsIInputStream> newPostData; 2603 nsILoadInfo::SchemelessInputType schemelessInput = 2604 nsILoadInfo::SchemelessInputTypeUnset; 2605 nsCOMPtr<nsIURI> newURI = nsDocShell::AttemptURIFixup( 2606 mChannel, aStatus, mOriginalUriString, mLoadStateLoadType, bc->IsTop(), 2607 mLoadStateInternalLoadFlags & 2608 nsDocShell::INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP, 2609 bc->UsePrivateBrowsing(), true, getter_AddRefs(newPostData), 2610 &schemelessInput); 2611 2612 // Since aStatus will be NS_OK for 4xx and 5xx error codes we 2613 // have to check each request which was upgraded by https-first. 2614 // If an error (including 4xx and 5xx) occured, then let's check if 2615 // we can downgrade the scheme to HTTP again. 2616 bool isHTTPSFirstFixup = false; 2617 if (!newURI) { 2618 newURI = 2619 nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(this, aStatus); 2620 isHTTPSFirstFixup = true; 2621 } 2622 2623 if (!newURI) { 2624 return false; 2625 } 2626 2627 // If we got a new URI, then we should initiate a load with that. 2628 // Notify the listeners that this load is complete (with a code that 2629 // won't trigger an error page), and then start the new one. 2630 DisconnectListeners(NS_BINDING_ABORTED, NS_BINDING_ABORTED); 2631 2632 RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(newURI); 2633 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo(); 2634 2635 nsCOMPtr<nsIPolicyContainer> policyContainerToInherit = 2636 loadInfo->GetPolicyContainerToInherit(); 2637 loadState->SetPolicyContainer(policyContainerToInherit); 2638 2639 nsCOMPtr<nsIPrincipal> triggeringPrincipal = loadInfo->TriggeringPrincipal(); 2640 loadState->SetTriggeringPrincipal(triggeringPrincipal); 2641 2642 loadState->SetPostDataStream(newPostData); 2643 2644 // Record whether the protocol was added through a fixup. 2645 loadState->SetSchemelessInput(schemelessInput); 2646 2647 if (isHTTPSFirstFixup) { 2648 nsHTTPSOnlyUtils::UpdateLoadStateAfterHTTPSFirstDowngrade(this, loadState); 2649 } 2650 2651 // Ensure to set referrer information in the fallback channel equally to the 2652 // not-upgraded original referrer info. 2653 // 2654 // A simply copy of the referrer info from the upgraded one leads to problems. 2655 // For example: 2656 // 1. https://some-site.com redirects to http://other-site.com with referrer 2657 // policy 2658 // "no-referrer-when-downgrade". 2659 // 2. https-first upgrades the redirection, so redirects to 2660 // https://other-site.com, 2661 // according to referrer policy the referrer will be send (https-> https) 2662 // 3. Assume other-site.com is not supporting https, https-first performs 2663 // fall- 2664 // back. 2665 // If the referrer info from the upgraded channel gets copied into the 2666 // http fallback channel, the referrer info would contain the referrer 2667 // (https://some-site.com). That would violate the policy 2668 // "no-referrer-when-downgrade". A recreation of the original referrer info 2669 // would ensure us that the referrer is set according to the referrer policy. 2670 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel); 2671 if (httpChannel) { 2672 nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo(); 2673 if (referrerInfo) { 2674 ReferrerPolicy referrerPolicy = referrerInfo->ReferrerPolicy(); 2675 nsCOMPtr<nsIURI> originalReferrer = referrerInfo->GetOriginalReferrer(); 2676 if (originalReferrer) { 2677 // Create new ReferrerInfo with the original referrer and the referrer 2678 // policy. 2679 nsCOMPtr<nsIReferrerInfo> newReferrerInfo = 2680 new ReferrerInfo(originalReferrer, referrerPolicy); 2681 loadState->SetReferrerInfo(newReferrerInfo); 2682 } 2683 } 2684 } 2685 2686 bc->LoadURI(loadState, false); 2687 return true; 2688 } 2689 2690 NS_IMETHODIMP 2691 DocumentLoadListener::OnStartRequest(nsIRequest* aRequest) { 2692 return DoOnStartRequest(aRequest); 2693 } 2694 2695 nsresult DocumentLoadListener::DoOnStartRequest(nsIRequest* aRequest) { 2696 LOG(("DocumentLoadListener OnStartRequest [this=%p]", this)); 2697 2698 nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest); 2699 if (multiPartChannel) { 2700 multiPartChannel->GetBaseChannel(getter_AddRefs(mChannel)); 2701 } else { 2702 mChannel = do_QueryInterface(aRequest); 2703 } 2704 MOZ_DIAGNOSTIC_ASSERT(mChannel); 2705 2706 if (mHaveVisibleRedirect && GetDocumentBrowsingContext() && 2707 mLoadingSessionHistoryInfo) { 2708 mLoadingSessionHistoryInfo = 2709 GetDocumentBrowsingContext()->ReplaceLoadingSessionHistoryEntryForLoad( 2710 mLoadingSessionHistoryInfo.get(), mChannel); 2711 } 2712 2713 RefPtr<nsHttpChannel> httpChannel = do_QueryObject(mChannel); 2714 2715 // Enforce CSP frame-ancestors and x-frame-options checks which 2716 // might cancel the channel. 2717 nsContentSecurityUtils::PerformCSPFrameAncestorAndXFOCheck(mChannel); 2718 2719 // HTTPS-Only Mode tries to upgrade connections to https. Once loading 2720 // is in progress we set that flag so that timeout counter measures 2721 // do not kick in. 2722 if (httpChannel) { 2723 nsCOMPtr<nsILoadInfo> loadInfo = httpChannel->LoadInfo(); 2724 if (nsHTTPSOnlyUtils::GetUpgradeMode(loadInfo) == 2725 nsHTTPSOnlyUtils::HTTPS_ONLY_MODE) { 2726 uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus(); 2727 httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_TOP_LEVEL_LOAD_IN_PROGRESS; 2728 loadInfo->SetHttpsOnlyStatus(httpsOnlyStatus); 2729 } 2730 2731 if (mLoadingSessionHistoryInfo && 2732 nsDocShell::ShouldDiscardLayoutState(httpChannel)) { 2733 mLoadingSessionHistoryInfo->mInfo.SetSaveLayoutStateFlag(false); 2734 } 2735 } 2736 2737 auto* loadingContext = GetLoadingBrowsingContext(); 2738 if (!loadingContext || loadingContext->IsDiscarded()) { 2739 Cancel(NS_ERROR_UNEXPECTED, "No valid LoadingBrowsingContext."_ns); 2740 return NS_ERROR_UNEXPECTED; 2741 } 2742 2743 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { 2744 Cancel(NS_ERROR_ILLEGAL_DURING_SHUTDOWN, 2745 "Aborting OnStartRequest after shutdown started."_ns); 2746 return NS_OK; 2747 } 2748 2749 // Block top-level data URI navigations if triggered by the web. Logging is 2750 // performed in AllowTopLevelNavigationToDataURI. 2751 if (!nsContentSecurityManager::AllowTopLevelNavigationToDataURI(mChannel)) { 2752 mChannel->Cancel(NS_ERROR_DOM_BAD_URI); 2753 if (loadingContext) { 2754 RefPtr<MaybeCloseWindowHelper> maybeCloseWindowHelper = 2755 new MaybeCloseWindowHelper(loadingContext); 2756 // If a new window was opened specifically for this request, close it 2757 // after blocking the navigation. 2758 maybeCloseWindowHelper->SetShouldCloseWindow( 2759 IsFirstLoadInWindow(mChannel)); 2760 (void)maybeCloseWindowHelper->MaybeCloseWindow(); 2761 } 2762 DisconnectListeners(NS_ERROR_DOM_BAD_URI, NS_ERROR_DOM_BAD_URI); 2763 return NS_OK; 2764 } 2765 2766 // Generally we want to switch to a real channel even if the request failed, 2767 // since the listener might want to access protocol-specific data (like http 2768 // response headers) in its error handling. 2769 // An exception to this is when nsExtProtocolChannel handled the request and 2770 // returned NS_ERROR_NO_CONTENT, since creating a real one in the content 2771 // process will attempt to handle the URI a second time. 2772 nsresult status = NS_OK; 2773 aRequest->GetStatus(&status); 2774 if (status == NS_ERROR_NO_CONTENT) { 2775 DisconnectListeners(status, status); 2776 return NS_OK; 2777 } 2778 2779 // PerformCSPFrameAncestorAndXFOCheck may cancel a moz-extension request that 2780 // needs to be handled here. Without this, the resource would be loaded and 2781 // not blocked when the real channel is created in the content process. 2782 if (status == NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION && !httpChannel) { 2783 DisconnectListeners(status, status); 2784 return NS_OK; 2785 } 2786 2787 // If this was a failed load and we want to try fixing the uri, then 2788 // this will initiate a new load (and disconnect this one), and we don't 2789 // need to do anything else. 2790 if (MaybeHandleLoadErrorWithURIFixup(status)) { 2791 return NS_OK; 2792 } 2793 2794 // If this is a successful load with a successful status code, we can possibly 2795 // submit HTTPS-First telemetry. 2796 if (NS_SUCCEEDED(status) && httpChannel) { 2797 uint32_t responseStatus = 0; 2798 if (NS_SUCCEEDED(httpChannel->GetResponseStatus(&responseStatus)) && 2799 responseStatus < 400) { 2800 nsHTTPSOnlyUtils::SubmitHTTPSFirstTelemetry( 2801 mChannel->LoadInfo(), mHTTPSFirstDowngradeData.forget()); 2802 } 2803 } 2804 2805 mStreamListenerFunctions.AppendElement(StreamListenerFunction{ 2806 VariantIndex<0>{}, OnStartRequestParams{aRequest}}); 2807 2808 if (mOpenPromiseResolved || mInitiatedRedirectToRealChannel) { 2809 // I we have already resolved the promise, there's no point to continue 2810 // attempting a process switch or redirecting to the real channel. 2811 // We can also have multiple calls to OnStartRequest when dealing with 2812 // multi-part content, but only want to redirect once. 2813 return NS_OK; 2814 } 2815 2816 // Keep track of server responses resulting in a document for the Bounce 2817 // Tracking Protection. 2818 if (mIsDocumentLoad && GetParentWindowContext() == nullptr && 2819 loadingContext->IsTopContent()) { 2820 RefPtr<BounceTrackingState> bounceTrackingState = 2821 loadingContext->GetBounceTrackingState(); 2822 2823 // Not every browsing context has a BounceTrackingState. It's also null when 2824 // the feature is disabled. 2825 if (bounceTrackingState) { 2826 // Don't warn when OnDocumentStartRequest fails until bug 1894936 is 2827 // fixed, because it fails frequently because of that. 2828 (void)bounceTrackingState->OnDocumentStartRequest(mChannel); 2829 2830 DynamicFpiNavigationHeuristic::MaybeGrantStorageAccess(loadingContext, 2831 mChannel); 2832 } 2833 } 2834 2835 mChannel->Suspend(); 2836 2837 mInitiatedRedirectToRealChannel = true; 2838 2839 MaybeReportBlockedByURLClassifier(status); 2840 2841 // If the channel has failed, and the docshell isn't going to display an 2842 // error page for that failure, then don't allow process switching, since 2843 // we just want to keep our existing document. 2844 bool silentErrorLoad = !DocShellWillDisplayContent(status); 2845 if (silentErrorLoad) { 2846 MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, 2847 ("Skipping process switch, as DocShell will not display content " 2848 "(status: %s) %s", 2849 GetStaticErrorName(status), 2850 GetChannelCreationURI()->GetSpecOrDefault().get())); 2851 2852 // If this load would not lead to the content docShell displaying any 2853 // content, cancel it here to ensure that we don't spuriously succeed when 2854 // finishing the load in the content process. We don't do this for HTTP 2855 // channels, which may have extra information (e.g. navigation timing) which 2856 // would be relevant to the content process. 2857 if (!httpChannel) { 2858 DisconnectListeners(status, status); 2859 return NS_OK; 2860 } 2861 } 2862 2863 // Determine if a new process needs to be spawned. If it does, this will 2864 // trigger a cross process switch, and we should hold off on redirecting to 2865 // the real channel. 2866 bool willBeRemote = false; 2867 if (silentErrorLoad || !MaybeTriggerProcessSwitch(&willBeRemote)) { 2868 // We're not going to be doing a process switch, so redirect to the real 2869 // channel within our current process. 2870 nsTArray<StreamFilterRequest> streamFilterRequests = 2871 std::move(mStreamFilterRequests); 2872 if (!mSupportsRedirectToRealChannel) { 2873 RefPtr<BrowserParent> browserParent = loadingContext->GetBrowserParent(); 2874 if (browserParent->Manager() != mContentParent) { 2875 LOG( 2876 ("DocumentLoadListener::RedirectToRealChannel failed because " 2877 "browsingContext no longer owned by creator")); 2878 Cancel(NS_BINDING_ABORTED, 2879 "DocumentLoadListener::RedirectToRealChannel failed because " 2880 "browsingContext no longer owned by creator"_ns); 2881 return NS_OK; 2882 } 2883 MOZ_DIAGNOSTIC_ASSERT( 2884 browserParent->GetBrowsingContext() == loadingContext, 2885 "make sure the load is going to the right place"); 2886 2887 // If the existing process is right for this load, but the bridge doesn't 2888 // support redirects, then we need to do it manually, by faking a process 2889 // switch. 2890 DisconnectListeners(NS_BINDING_ABORTED, NS_BINDING_ABORTED, 2891 /* aContinueNavigating */ true); 2892 2893 // Notify the docshell that it should load using the newly connected 2894 // channel 2895 browserParent->ResumeLoad(mLoadIdentifier); 2896 2897 // Use the current process ID to run the 'process switch' path and connect 2898 // the channel into the current process. 2899 TriggerRedirectToRealChannel(loadingContext, Some(mContentParent), 2900 std::move(streamFilterRequests)); 2901 } else { 2902 TriggerRedirectToRealChannel(loadingContext, Nothing(), 2903 std::move(streamFilterRequests)); 2904 } 2905 2906 // If we're not switching, then check if we're currently remote. 2907 if (mContentParent) { 2908 willBeRemote = true; 2909 } 2910 } 2911 2912 if (httpChannel) { 2913 mEarlyHintsService.Reset(); 2914 } else { 2915 mEarlyHintsService.Cancel( 2916 "DocumentLoadListener::OnStartRequest: no httpChannel"_ns); 2917 } 2918 2919 // If we're going to be delivering this channel to a remote content 2920 // process, then we want to install any required content conversions 2921 // in the content process. 2922 // The caller of this OnStartRequest will install a conversion 2923 // helper after we return if we haven't disabled conversion. Normally 2924 // HttpChannelParent::OnStartRequest would disable conversion, but we're 2925 // defering calling that until later. Manually disable it now to prevent the 2926 // converter from being installed (since we want the child to do it), and 2927 // also save the value so that when we do call 2928 // HttpChannelParent::OnStartRequest, we can have the value as it originally 2929 // was. 2930 if (httpChannel) { 2931 (void)httpChannel->GetApplyConversion(&mOldApplyConversion); 2932 if (willBeRemote) { 2933 httpChannel->SetApplyConversion(false); 2934 } 2935 } 2936 2937 return NS_OK; 2938 } 2939 2940 NS_IMETHODIMP 2941 DocumentLoadListener::OnStopRequest(nsIRequest* aRequest, 2942 nsresult aStatusCode) { 2943 LOG(("DocumentLoadListener OnStopRequest [this=%p]", this)); 2944 mStreamListenerFunctions.AppendElement(StreamListenerFunction{ 2945 VariantIndex<2>{}, OnStopRequestParams{aRequest, aStatusCode}}); 2946 2947 // If we're not a multi-part channel, then we're finished and we don't 2948 // expect any further events. If we are, then this might be called again, 2949 // so wait for OnAfterLastPart instead. 2950 nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest); 2951 if (!multiPartChannel) { 2952 mIsFinished = true; 2953 } 2954 2955 mStreamFilterRequests.Clear(); 2956 2957 return NS_OK; 2958 } 2959 2960 NS_IMETHODIMP 2961 DocumentLoadListener::OnDataAvailable(nsIRequest* aRequest, 2962 nsIInputStream* aInputStream, 2963 uint64_t aOffset, uint32_t aCount) { 2964 LOG(("DocumentLoadListener OnDataAvailable [this=%p]", this)); 2965 // This isn't supposed to happen, since we suspended the channel, but 2966 // sometimes Suspend just doesn't work. This can happen when we're routing 2967 // through nsUnknownDecoder to sniff the content type, and it doesn't handle 2968 // being suspended. Let's just store the data and manually forward it to our 2969 // redirected channel when it's ready. 2970 nsCString data; 2971 nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount); 2972 NS_ENSURE_SUCCESS(rv, rv); 2973 2974 mStreamListenerFunctions.AppendElement(StreamListenerFunction{ 2975 VariantIndex<1>{}, 2976 OnDataAvailableParams{aRequest, std::move(data), aOffset, aCount}}); 2977 2978 return NS_OK; 2979 } 2980 2981 //----------------------------------------------------------------------------- 2982 // DoucmentLoadListener::nsIMultiPartChannelListener 2983 //----------------------------------------------------------------------------- 2984 2985 NS_IMETHODIMP 2986 DocumentLoadListener::OnAfterLastPart(nsresult aStatus) { 2987 LOG(("DocumentLoadListener OnAfterLastPart [this=%p]", this)); 2988 if (!mInitiatedRedirectToRealChannel) { 2989 // if we get here, and we haven't initiated a redirect to a real 2990 // channel, then it means we never got OnStartRequest (maybe a problem?) 2991 // and we retargeted everything. 2992 LOG(("DocumentLoadListener Disconnecting child")); 2993 DisconnectListeners(NS_BINDING_RETARGETED, NS_OK); 2994 return NS_OK; 2995 } 2996 mStreamListenerFunctions.AppendElement(StreamListenerFunction{ 2997 VariantIndex<3>{}, OnAfterLastPartParams{aStatus}}); 2998 mIsFinished = true; 2999 return NS_OK; 3000 } 3001 3002 NS_IMETHODIMP 3003 DocumentLoadListener::GetInterface(const nsIID& aIID, void** result) { 3004 RefPtr<CanonicalBrowsingContext> browsingContext = 3005 GetLoadingBrowsingContext(); 3006 if (aIID.Equals(NS_GET_IID(nsILoadContext)) && browsingContext) { 3007 browsingContext.forget(result); 3008 return NS_OK; 3009 } 3010 3011 return QueryInterface(aIID, result); 3012 } 3013 3014 //////////////////////////////////////////////////////////////////////////////// 3015 // nsIParentChannel 3016 //////////////////////////////////////////////////////////////////////////////// 3017 3018 NS_IMETHODIMP 3019 DocumentLoadListener::SetParentListener( 3020 mozilla::net::ParentChannelListener* listener) { 3021 // We don't need this (do we?) 3022 return NS_OK; 3023 } 3024 3025 NS_IMETHODIMP 3026 DocumentLoadListener::SetClassifierMatchedInfo(const nsACString& aList, 3027 const nsACString& aProvider, 3028 const nsACString& aFullHash) { 3029 ClassifierMatchedInfoParams params; 3030 params.mList = aList; 3031 params.mProvider = aProvider; 3032 params.mFullHash = aFullHash; 3033 3034 mIParentChannelFunctions.AppendElement( 3035 IParentChannelFunction{VariantIndex<0>{}, std::move(params)}); 3036 return NS_OK; 3037 } 3038 3039 NS_IMETHODIMP 3040 DocumentLoadListener::SetClassifierMatchedTrackingInfo( 3041 const nsACString& aLists, const nsACString& aFullHash) { 3042 ClassifierMatchedTrackingInfoParams params; 3043 params.mLists = aLists; 3044 params.mFullHashes = aFullHash; 3045 3046 mIParentChannelFunctions.AppendElement( 3047 IParentChannelFunction{VariantIndex<1>{}, std::move(params)}); 3048 return NS_OK; 3049 } 3050 3051 NS_IMETHODIMP 3052 DocumentLoadListener::NotifyClassificationFlags(uint32_t aClassificationFlags, 3053 bool aIsThirdParty) { 3054 mIParentChannelFunctions.AppendElement(IParentChannelFunction{ 3055 VariantIndex<2>{}, 3056 ClassificationFlagsParams{aClassificationFlags, aIsThirdParty}}); 3057 return NS_OK; 3058 } 3059 3060 NS_IMETHODIMP 3061 DocumentLoadListener::Delete() { 3062 MOZ_ASSERT_UNREACHABLE("This method is unused"); 3063 return NS_OK; 3064 } 3065 3066 NS_IMETHODIMP 3067 DocumentLoadListener::GetRemoteType(nsACString& aRemoteType) { 3068 // FIXME: The remote type here should be pulled from the remote process used 3069 // to create this DLL, not from the current `browsingContext`. 3070 RefPtr<CanonicalBrowsingContext> browsingContext = 3071 GetDocumentBrowsingContext(); 3072 if (!browsingContext) { 3073 return NS_ERROR_UNEXPECTED; 3074 } 3075 3076 ErrorResult error; 3077 browsingContext->GetCurrentRemoteType(aRemoteType, error); 3078 if (error.Failed()) { 3079 aRemoteType = NOT_REMOTE_TYPE; 3080 } 3081 return NS_OK; 3082 } 3083 3084 //////////////////////////////////////////////////////////////////////////////// 3085 // nsIChannelEventSink 3086 //////////////////////////////////////////////////////////////////////////////// 3087 3088 NS_IMETHODIMP 3089 DocumentLoadListener::AsyncOnChannelRedirect( 3090 nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags, 3091 nsIAsyncVerifyRedirectCallback* aCallback) { 3092 LOG(("DocumentLoadListener::AsyncOnChannelRedirect [this=%p flags=%" PRIu32 3093 "]", 3094 this, aFlags)); 3095 // We generally don't want to notify the content process about redirects, 3096 // so just update our channel and tell the callback that we're good to go. 3097 mChannel = aNewChannel; 3098 3099 // We need the original URI of the current channel to use to open the real 3100 // channel in the content process. Unfortunately we overwrite the original 3101 // uri of the new channel with the original pre-redirect URI, so grab 3102 // a copy of it now and save it on the loadInfo corresponding to the 3103 // new channel. 3104 nsCOMPtr<nsILoadInfo> loadInfoFromChannel = mChannel->LoadInfo(); 3105 MOZ_ASSERT(loadInfoFromChannel); 3106 nsCOMPtr<nsIURI> uri; 3107 mChannel->GetOriginalURI(getter_AddRefs(uri)); 3108 loadInfoFromChannel->SetChannelCreationOriginalURI(uri); 3109 3110 // Since we're redirecting away from aOldChannel, we should check if it 3111 // had a COOP mismatch, since we want the final result for this to 3112 // include the state of all channels we redirected through. 3113 nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(aOldChannel); 3114 if (httpChannel) { 3115 bool isCOOPMismatch = false; 3116 (void)NS_WARN_IF(NS_FAILED( 3117 httpChannel->HasCrossOriginOpenerPolicyMismatch(&isCOOPMismatch))); 3118 mHasCrossOriginOpenerPolicyMismatch |= isCOOPMismatch; 3119 } 3120 3121 // If HTTPS-Only mode is enabled, we need to check whether the exception-flag 3122 // needs to be removed or set, by asking the PermissionManager. 3123 nsHTTPSOnlyUtils::TestSitePermissionAndPotentiallyAddExemption(mChannel); 3124 3125 // We don't need to confirm internal redirects or record any 3126 // history for them, so just immediately verify and return. 3127 if (aFlags & nsIChannelEventSink::REDIRECT_INTERNAL) { 3128 LOG( 3129 ("DocumentLoadListener AsyncOnChannelRedirect [this=%p] " 3130 "flags=REDIRECT_INTERNAL", 3131 this)); 3132 aCallback->OnRedirectVerifyCallback(NS_OK); 3133 return NS_OK; 3134 } 3135 3136 // Cancel cross origin redirects as described by whatwg: 3137 // > Note: [The early hint reponse] is discarded if it is succeeded by a 3138 // > cross-origin redirect. 3139 // https://html.spec.whatwg.org/multipage/semantics.html#early-hints 3140 nsCOMPtr<nsIURI> oldURI; 3141 aOldChannel->GetURI(getter_AddRefs(oldURI)); 3142 nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); 3143 nsresult rv = ssm->CheckSameOriginURI(oldURI, uri, false, false); 3144 if (NS_FAILED(rv)) { 3145 mEarlyHintsService.Cancel( 3146 "DocumentLoadListener::AsyncOnChannelRedirect: cors redirect"_ns); 3147 } 3148 3149 if (GetDocumentBrowsingContext()) { 3150 if (!net::ChannelIsPost(aOldChannel)) { 3151 AddURIVisit(aOldChannel, 0); 3152 nsDocShell::SaveLastVisit(aNewChannel, oldURI, aFlags); 3153 } 3154 } 3155 mHaveVisibleRedirect |= true; 3156 3157 LOG( 3158 ("DocumentLoadListener AsyncOnChannelRedirect [this=%p] " 3159 "mHaveVisibleRedirect=%c", 3160 this, mHaveVisibleRedirect ? 'T' : 'F')); 3161 3162 // Clear out our nsIParentChannel functions, since a normal parent 3163 // channel would actually redirect and not have those values on the new one. 3164 // We expect the URI classifier to run on the redirected channel with 3165 // the new URI and set these again. 3166 mIParentChannelFunctions.Clear(); 3167 3168 // If we had a remote type override, ensure it's been cleared after a 3169 // redirect, as it can't apply anymore. 3170 mRemoteTypeOverride.reset(); 3171 3172 #ifdef ANDROID 3173 nsCOMPtr<nsIURI> uriBeingLoaded = 3174 AntiTrackingUtils::MaybeGetDocumentURIBeingLoaded(mChannel); 3175 3176 RefPtr<MozPromise<bool, bool, false>> promise; 3177 RefPtr<CanonicalBrowsingContext> bc = 3178 mParentChannelListener->GetBrowsingContext(); 3179 nsCOMPtr<nsIWidget> widget = 3180 bc ? bc->GetParentProcessWidgetContaining() : nullptr; 3181 RefPtr<nsWindow> window = nsWindow::From(widget); 3182 3183 if (window && !nsExternalHelperAppService::ExternalProtocolIsBlockedBySandbox( 3184 bc, false)) { 3185 promise = window->OnLoadRequest(uriBeingLoaded, 3186 nsIBrowserDOMWindow::OPEN_CURRENTWINDOW, 3187 nsIWebNavigation::LOAD_FLAGS_IS_REDIRECT, 3188 nullptr, false, bc->IsTopContent()); 3189 } 3190 3191 if (promise) { 3192 RefPtr<nsIAsyncVerifyRedirectCallback> cb = aCallback; 3193 promise->Then( 3194 GetCurrentSerialEventTarget(), __func__, 3195 [=](const MozPromise<bool, bool, false>::ResolveOrRejectValue& aValue) { 3196 if (aValue.IsResolve()) { 3197 bool handled = aValue.ResolveValue(); 3198 if (handled) { 3199 cb->OnRedirectVerifyCallback(NS_ERROR_ABORT); 3200 } else { 3201 cb->OnRedirectVerifyCallback(NS_OK); 3202 } 3203 } 3204 }); 3205 } else 3206 #endif /* ANDROID */ 3207 { 3208 aCallback->OnRedirectVerifyCallback(NS_OK); 3209 } 3210 return NS_OK; 3211 } 3212 3213 nsIURI* DocumentLoadListener::GetChannelCreationURI() const { 3214 nsCOMPtr<nsILoadInfo> channelLoadInfo = mChannel->LoadInfo(); 3215 3216 nsCOMPtr<nsIURI> uri; 3217 channelLoadInfo->GetChannelCreationOriginalURI(getter_AddRefs(uri)); 3218 if (uri) { 3219 // See channelCreationOriginalURI for more info. We use this instead of the 3220 // originalURI of the channel to help us avoid the situation when we use 3221 // the URI of a redirect that has failed to happen. 3222 return uri; 3223 } 3224 3225 // Otherwise, get the original URI from the channel. 3226 mChannel->GetOriginalURI(getter_AddRefs(uri)); 3227 return uri; 3228 } 3229 3230 // This method returns the cached result of running the Cross-Origin-Opener 3231 // policy compare algorithm by calling ComputeCrossOriginOpenerPolicyMismatch 3232 bool DocumentLoadListener::HasCrossOriginOpenerPolicyMismatch() const { 3233 // If we found a COOP mismatch on an earlier channel and then 3234 // redirected away from that, we should use that result. 3235 if (mHasCrossOriginOpenerPolicyMismatch) { 3236 return true; 3237 } 3238 3239 nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(mChannel); 3240 if (!httpChannel) { 3241 // Not an nsIHttpChannelInternal assume it's okay to switch. 3242 return false; 3243 } 3244 3245 bool isCOOPMismatch = false; 3246 (void)NS_WARN_IF(NS_FAILED( 3247 httpChannel->HasCrossOriginOpenerPolicyMismatch(&isCOOPMismatch))); 3248 return isCOOPMismatch; 3249 } 3250 3251 auto DocumentLoadListener::AttachStreamFilter() 3252 -> RefPtr<ChildEndpointPromise> { 3253 LOG(("DocumentLoadListener AttachStreamFilter [this=%p]", this)); 3254 3255 StreamFilterRequest* request = mStreamFilterRequests.AppendElement(); 3256 request->mPromise = new ChildEndpointPromise::Private(__func__); 3257 return request->mPromise; 3258 } 3259 3260 NS_IMETHODIMP DocumentLoadListener::OnProgress(nsIRequest* aRequest, 3261 int64_t aProgress, 3262 int64_t aProgressMax) { 3263 return NS_OK; 3264 } 3265 3266 NS_IMETHODIMP DocumentLoadListener::OnStatus(nsIRequest* aRequest, 3267 nsresult aStatus, 3268 const char16_t* aStatusArg) { 3269 nsCOMPtr<nsIChannel> channel = mChannel; 3270 3271 RefPtr<BrowsingContextWebProgress> webProgress = 3272 GetLoadingBrowsingContext()->GetWebProgress(); 3273 3274 nsAutoString host; 3275 host.Append(aStatusArg); 3276 3277 nsAutoString message; 3278 nsresult rv = nsDocLoader::FormatStatusMessage(aStatus, host, message, sL10n); 3279 if (NS_WARN_IF(NS_FAILED(rv))) { 3280 return rv; 3281 } 3282 3283 if (webProgress) { 3284 NS_DispatchToMainThread( 3285 NS_NewRunnableFunction("DocumentLoadListener::OnStatus", [=]() { 3286 webProgress->OnStatusChange(webProgress, channel, aStatus, 3287 message.get()); 3288 })); 3289 } 3290 return NS_OK; 3291 } 3292 3293 NS_IMETHODIMP DocumentLoadListener::EarlyHint(const nsACString& aLinkHeader, 3294 const nsACString& aReferrerPolicy, 3295 const nsACString& aCSPHeader) { 3296 LOG(("DocumentLoadListener::EarlyHint.\n")); 3297 mEarlyHintsService.EarlyHint(aLinkHeader, GetChannelCreationURI(), mChannel, 3298 aReferrerPolicy, aCSPHeader, 3299 GetLoadingBrowsingContext()); 3300 return NS_OK; 3301 } 3302 3303 } // namespace net 3304 } // namespace mozilla 3305 3306 #undef LOG