DocumentChannelChild.cpp (16791B)
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 "DocumentChannelChild.h" 9 10 #include "mozilla/dom/Document.h" 11 #include "mozilla/dom/PolicyContainer.h" 12 #include "mozilla/dom/RemoteType.h" 13 #include "mozilla/extensions/StreamFilterParent.h" 14 #include "mozilla/ipc/Endpoint.h" 15 #include "mozilla/net/HttpBaseChannel.h" 16 #include "mozilla/net/NeckoChild.h" 17 #include "mozilla/ScopeExit.h" 18 #include "mozilla/StaticPrefs_fission.h" 19 #include "nsHashPropertyBag.h" 20 #include "nsIHttpChannelInternal.h" 21 #include "nsIObjectLoadingContent.h" 22 #include "nsIXULRuntime.h" 23 #include "nsIWritablePropertyBag.h" 24 #include "nsFrameLoader.h" 25 #include "nsFrameLoaderOwner.h" 26 #include "nsQueryObject.h" 27 #include "nsDocShellLoadState.h" 28 29 using namespace mozilla::dom; 30 using namespace mozilla::ipc; 31 32 extern mozilla::LazyLogModule gDocumentChannelLog; 33 #define LOG(fmt) MOZ_LOG(gDocumentChannelLog, mozilla::LogLevel::Verbose, fmt) 34 35 namespace mozilla { 36 namespace net { 37 38 //----------------------------------------------------------------------------- 39 // DocumentChannelChild::nsISupports 40 41 NS_INTERFACE_MAP_BEGIN(DocumentChannelChild) 42 NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback) 43 NS_INTERFACE_MAP_END_INHERITING(DocumentChannel) 44 45 NS_IMPL_ADDREF_INHERITED(DocumentChannelChild, DocumentChannel) 46 NS_IMPL_RELEASE_INHERITED(DocumentChannelChild, DocumentChannel) 47 48 DocumentChannelChild::DocumentChannelChild(nsDocShellLoadState* aLoadState, 49 net::LoadInfo* aLoadInfo, 50 nsLoadFlags aLoadFlags, 51 uint32_t aCacheKey, 52 bool aUriModified, 53 bool aIsEmbeddingBlockedError) 54 : DocumentChannel(aLoadState, aLoadInfo, aLoadFlags, aCacheKey, 55 aUriModified, aIsEmbeddingBlockedError) { 56 mLoadingContext = nullptr; 57 LOG(("DocumentChannelChild ctor [this=%p, uri=%s]", this, 58 aLoadState->URI()->GetSpecOrDefault().get())); 59 } 60 61 DocumentChannelChild::~DocumentChannelChild() { 62 LOG(("DocumentChannelChild dtor [this=%p]", this)); 63 } 64 65 NS_IMETHODIMP 66 DocumentChannelChild::AsyncOpen(nsIStreamListener* aListener) { 67 nsresult rv = NS_OK; 68 69 nsCOMPtr<nsIStreamListener> listener = aListener; 70 71 NS_ENSURE_TRUE(gNeckoChild, NS_ERROR_FAILURE); 72 NS_ENSURE_ARG_POINTER(listener); 73 NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS); 74 NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED); 75 76 // Port checked in parent, but duplicate here so we can return with error 77 // immediately, as we've done since before e10s. 78 rv = NS_CheckPortSafety(mURI); 79 NS_ENSURE_SUCCESS(rv, rv); 80 81 bool isNotDownload = mLoadState->FileName().IsVoid(); 82 83 // If not a download, add ourselves to the load group 84 if (isNotDownload && mLoadGroup) { 85 // During this call, we can re-enter back into the DocumentChannelChild to 86 // call SetNavigationTiming. 87 mLoadGroup->AddRequest(this, nullptr); 88 } 89 90 if (mCanceled) { 91 // We may have been canceled already, either by on-modify-request 92 // listeners or by load group observers; in that case, don't create IPDL 93 // connection. See nsHttpChannel::AsyncOpen(). 94 return mStatus; 95 } 96 97 gHttpHandler->OnOpeningDocumentRequest(this); 98 99 RefPtr<nsDocShell> docShell = GetDocShell(); 100 if (!docShell) { 101 return NS_ERROR_FAILURE; 102 } 103 104 // `loadingContext` is the BC that is initiating the resource load. 105 // For normal subdocument loads, the BC is the one that the subdoc will load 106 // into. For <object>/<embed> it's the embedder doc's BC. 107 RefPtr<BrowsingContext> loadingContext = docShell->GetBrowsingContext(); 108 if (!loadingContext || loadingContext->IsDiscarded()) { 109 return NS_ERROR_FAILURE; 110 } 111 mLoadingContext = loadingContext; 112 113 Maybe<IPCClientInfo> ipcClientInfo; 114 if (mInitialClientInfo.isSome()) { 115 ipcClientInfo.emplace(mInitialClientInfo.ref().ToIPC()); 116 } 117 118 DocumentChannelElementCreationArgs ipcElementCreationArgs; 119 switch (mLoadInfo->GetExternalContentPolicyType()) { 120 case ExtContentPolicy::TYPE_DOCUMENT: 121 case ExtContentPolicy::TYPE_SUBDOCUMENT: { 122 DocumentCreationArgs docArgs; 123 docArgs.loadFlags() = mLoadFlags; 124 docArgs.uriModified() = mUriModified; 125 docArgs.isEmbeddingBlockedError() = mIsEmbeddingBlockedError; 126 127 ipcElementCreationArgs = docArgs; 128 break; 129 } 130 131 case ExtContentPolicy::TYPE_OBJECT: { 132 ObjectCreationArgs objectArgs; 133 objectArgs.embedderInnerWindowId() = InnerWindowIDForExtantDoc(docShell); 134 objectArgs.loadFlags() = mLoadFlags; 135 objectArgs.contentPolicyType() = mLoadInfo->InternalContentPolicyType(); 136 objectArgs.isUrgentStart() = UserActivation::IsHandlingUserInput(); 137 138 ipcElementCreationArgs = objectArgs; 139 break; 140 } 141 142 default: 143 MOZ_ASSERT_UNREACHABLE("unsupported content policy type"); 144 return NS_ERROR_FAILURE; 145 } 146 147 switch (mLoadInfo->GetExternalContentPolicyType()) { 148 case ExtContentPolicy::TYPE_DOCUMENT: 149 case ExtContentPolicy::TYPE_SUBDOCUMENT: 150 MOZ_ALWAYS_SUCCEEDS(loadingContext->SetCurrentLoadIdentifier( 151 Some(mLoadState->GetLoadIdentifier()))); 152 break; 153 154 default: 155 break; 156 } 157 158 mLoadState->AssertProcessCouldTriggerLoadIfSystem(); 159 160 DocumentChannelCreationArgs args( 161 mozilla::WrapNotNull(mLoadState), TimeStamp::Now(), mChannelId, mCacheKey, 162 mTiming, ipcClientInfo, ipcElementCreationArgs, 163 loadingContext->GetParentInitiatedNavigationEpoch()); 164 165 gNeckoChild->SendPDocumentChannelConstructor(this, loadingContext, args); 166 167 mIsPending = true; 168 mWasOpened = true; 169 mListener = listener; 170 171 return NS_OK; 172 } 173 174 IPCResult DocumentChannelChild::RecvFailedAsyncOpen( 175 const nsresult& aStatusCode) { 176 if (aStatusCode == NS_ERROR_RECURSIVE_DOCUMENT_LOAD) { 177 // This exists so that we are able to fire an error event 178 // for when there are too many recursive iframe or object loads. 179 // This is an incomplete solution, because right now we don't have a unified 180 // way of firing error events due to errors in document channel. 181 // This should be fixed in bug 1629201. 182 MOZ_DIAGNOSTIC_ASSERT(mLoadingContext); 183 if (RefPtr<Element> embedder = mLoadingContext->GetEmbedderElement()) { 184 if (RefPtr<nsFrameLoaderOwner> flo = do_QueryObject(embedder)) { 185 if (RefPtr<nsFrameLoader> fl = flo->GetFrameLoader()) { 186 fl->FireErrorEvent(); 187 } 188 } 189 } 190 } 191 ShutdownListeners(aStatusCode); 192 return IPC_OK(); 193 } 194 195 IPCResult DocumentChannelChild::RecvDisconnectChildListeners( 196 const nsresult& aStatus, const nsresult& aLoadGroupStatus, 197 bool aContinueNavigating) { 198 // If this disconnect is not due to a process switch, perform the disconnect 199 // immediately. 200 if (!aContinueNavigating) { 201 DisconnectChildListeners(aStatus, aLoadGroupStatus); 202 return IPC_OK(); 203 } 204 205 // Otherwise, the disconnect will occur later using some other mechanism, 206 // depending on what's happening to the loading DocShell. If this is a 207 // toplevel navigation, and this BrowsingContext enters the BFCache, we will 208 // cancel this channel when the PageHide event is firing, whereas if it does 209 // not enter BFCache (e.g. due to being an object, subframe or non-bfcached 210 // toplevel navigation), we will cancel this channel when the DocShell is 211 // destroyed. 212 nsDocShell* shell = GetDocShell(); 213 if (mLoadInfo->GetExternalContentPolicyType() == 214 ExtContentPolicy::TYPE_DOCUMENT && 215 shell) { 216 MOZ_ASSERT(shell->GetBrowsingContext()->IsTop()); 217 if (mozilla::SessionHistoryInParent() && 218 shell->GetBrowsingContext()->IsInBFCache()) { 219 DisconnectChildListeners(aStatus, aLoadGroupStatus); 220 } else { 221 // Tell the DocShell which channel to cancel if it enters the BFCache. 222 shell->SetChannelToDisconnectOnPageHide(mChannelId); 223 } 224 } 225 226 return IPC_OK(); 227 } 228 229 IPCResult DocumentChannelChild::RecvRedirectToRealChannel( 230 RedirectToRealChannelArgs&& aArgs, 231 nsTArray<Endpoint<extensions::PStreamFilterParent>>&& aEndpoints, 232 RedirectToRealChannelResolver&& aResolve) { 233 LOG(("DocumentChannelChild RecvRedirectToRealChannel [this=%p, uri=%s]", this, 234 aArgs.uri()->GetSpecOrDefault().get())); 235 236 // The document that created the cspToInherit. 237 // This is used when deserializing LoadInfo from the parent 238 // process, since we can't serialize Documents directly. 239 // TODO: For a fission OOP iframe this will be unavailable, 240 // as will the loadingContext computed in LoadInfoArgsToLoadInfo. 241 // Figure out if we need these for cross-origin subdocs. 242 RefPtr<dom::Document> cspToInheritLoadingDocument; 243 nsCOMPtr<nsIContentSecurityPolicy> csp = 244 PolicyContainer::GetCSP(mLoadState->PolicyContainer()); 245 if (csp) { 246 nsWeakPtr ctx = nsCSPContext::Cast(csp.get())->GetLoadingContext(); 247 cspToInheritLoadingDocument = do_QueryReferent(ctx); 248 } 249 nsCOMPtr<nsILoadInfo> loadInfo; 250 MOZ_ALWAYS_SUCCEEDS(LoadInfoArgsToLoadInfo(aArgs.loadInfo(), NOT_REMOTE_TYPE, 251 cspToInheritLoadingDocument, 252 getter_AddRefs(loadInfo))); 253 254 mRedirectResolver = std::move(aResolve); 255 256 nsCOMPtr<nsIChannel> newChannel; 257 MOZ_ASSERT((aArgs.loadStateInternalLoadFlags() & 258 nsDocShell::InternalLoad::INTERNAL_LOAD_FLAGS_IS_SRCDOC) || 259 aArgs.srcdocData().IsVoid()); 260 nsresult rv = nsDocShell::CreateRealChannelForDocument( 261 getter_AddRefs(newChannel), aArgs.uri(), loadInfo, nullptr, 262 aArgs.newLoadFlags(), aArgs.srcdocData(), aArgs.baseUri()); 263 if (newChannel) { 264 newChannel->SetLoadGroup(mLoadGroup); 265 } 266 267 if (RefPtr<HttpBaseChannel> httpChannel = do_QueryObject(newChannel)) { 268 httpChannel->SetEarlyHints(std::move(aArgs.earlyHints())); 269 httpChannel->SetEarlyHintLinkType(aArgs.earlyHintLinkType()); 270 } 271 272 // This is used to report any errors back to the parent by calling 273 // CrossProcessRedirectFinished. 274 auto scopeExit = MakeScopeExit([&]() { 275 mRedirectResolver(rv); 276 mRedirectResolver = nullptr; 277 }); 278 279 if (NS_FAILED(rv)) { 280 return IPC_OK(); 281 } 282 283 if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel)) { 284 rv = httpChannel->SetChannelId(aArgs.channelId()); 285 if (aArgs.referrerInfo()) { 286 rv = httpChannel->SetReferrerInfo(aArgs.referrerInfo()); 287 } 288 } 289 if (NS_FAILED(rv)) { 290 return IPC_OK(); 291 } 292 293 rv = newChannel->SetOriginalURI(aArgs.originalURI()); 294 if (NS_FAILED(rv)) { 295 return IPC_OK(); 296 } 297 298 if (nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = 299 do_QueryInterface(newChannel)) { 300 rv = httpChannelInternal->SetRedirectMode(aArgs.redirectMode()); 301 } 302 if (NS_FAILED(rv)) { 303 return IPC_OK(); 304 } 305 306 newChannel->SetNotificationCallbacks(mCallbacks); 307 308 if (aArgs.init()) { 309 HttpBaseChannel::ReplacementChannelConfig config(*aArgs.init()); 310 HttpBaseChannel::ConfigureReplacementChannel( 311 newChannel, config, 312 HttpBaseChannel::ReplacementReason::DocumentChannel); 313 } 314 315 if (aArgs.contentDisposition()) { 316 newChannel->SetContentDisposition(*aArgs.contentDisposition()); 317 } 318 319 if (aArgs.contentDispositionFilename()) { 320 newChannel->SetContentDispositionFilename( 321 *aArgs.contentDispositionFilename()); 322 } 323 324 nsDocShell* docShell = GetDocShell(); 325 if (docShell && aArgs.loadingSessionHistoryInfo().isSome()) { 326 docShell->SetLoadingSessionHistoryInfo( 327 aArgs.loadingSessionHistoryInfo().ref()); 328 } 329 330 // transfer any properties. This appears to be entirely a content-side 331 // interface and isn't copied across to the parent. Copying the values 332 // for this from this into the new actor will work, since the parent 333 // won't have the right details anyway. 334 // TODO: What about the process switch equivalent 335 // (ContentChild::RecvCrossProcessRedirect)? In that case there is no local 336 // existing actor in the destination process... We really need all information 337 // to go up to the parent, and then come down to the new child actor. 338 if (nsCOMPtr<nsIWritablePropertyBag> bag = do_QueryInterface(newChannel)) { 339 nsHashPropertyBag::CopyFrom(bag, aArgs.properties()); 340 } 341 342 // connect parent. 343 nsCOMPtr<nsIChildChannel> childChannel = do_QueryInterface(newChannel); 344 if (childChannel) { 345 rv = childChannel->ConnectParent( 346 aArgs.registrarId()); // creates parent channel 347 if (NS_FAILED(rv)) { 348 return IPC_OK(); 349 } 350 } 351 mRedirectChannel = newChannel; 352 mStreamFilterEndpoints = std::move(aEndpoints); 353 354 rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, 355 aArgs.redirectFlags(), 356 GetMainThreadSerialEventTarget()); 357 358 if (NS_SUCCEEDED(rv)) { 359 scopeExit.release(); 360 } 361 362 // scopeExit will call CrossProcessRedirectFinished(rv) here 363 return IPC_OK(); 364 } 365 366 IPCResult DocumentChannelChild::RecvUpgradeObjectLoad( 367 UpgradeObjectLoadResolver&& aResolve) { 368 // We're doing a load for an <object> or <embed> element if we got here. 369 MOZ_ASSERT(mLoadFlags & nsIRequest::LOAD_HTML_OBJECT_DATA, 370 "Should have LOAD_HTML_OBJECT_DATA set"); 371 MOZ_ASSERT(!(mLoadFlags & nsIChannel::LOAD_DOCUMENT_URI), 372 "Shouldn't be a LOAD_DOCUMENT_URI load yet"); 373 MOZ_ASSERT(mLoadInfo->GetExternalContentPolicyType() == 374 ExtContentPolicy::TYPE_OBJECT, 375 "Should have the TYPE_OBJECT content policy type"); 376 377 // If our load has already failed, or been cancelled, abort this attempt to 378 // upgade the load. 379 if (NS_FAILED(mStatus)) { 380 aResolve(nullptr); 381 return IPC_OK(); 382 } 383 384 nsCOMPtr<nsIObjectLoadingContent> loadingContent; 385 NS_QueryNotificationCallbacks(this, loadingContent); 386 if (!loadingContent) { 387 return IPC_FAIL(this, "Channel is not for ObjectLoadingContent!"); 388 } 389 390 // We're upgrading to a document channel now. Add the LOAD_DOCUMENT_URI flag 391 // after-the-fact. 392 mLoadFlags |= nsIChannel::LOAD_DOCUMENT_URI; 393 394 RefPtr<BrowsingContext> browsingContext; 395 nsresult rv = loadingContent->UpgradeLoadToDocument( 396 this, getter_AddRefs(browsingContext)); 397 if (NS_FAILED(rv) || !browsingContext) { 398 // Oops! Looks like something went wrong, so let's bail out. 399 mLoadFlags &= ~nsIChannel::LOAD_DOCUMENT_URI; 400 aResolve(nullptr); 401 return IPC_OK(); 402 } 403 404 aResolve(browsingContext); 405 return IPC_OK(); 406 } 407 408 NS_IMETHODIMP 409 DocumentChannelChild::OnRedirectVerifyCallback(nsresult aStatusCode) { 410 LOG( 411 ("DocumentChannelChild OnRedirectVerifyCallback [this=%p, " 412 "aRv=0x%08" PRIx32 " ]", 413 this, static_cast<uint32_t>(aStatusCode))); 414 nsCOMPtr<nsIChannel> redirectChannel = std::move(mRedirectChannel); 415 RedirectToRealChannelResolver redirectResolver = std::move(mRedirectResolver); 416 417 // If we've already shut down, then just notify the parent that 418 // we're done. 419 if (NS_FAILED(mStatus)) { 420 redirectChannel->SetNotificationCallbacks(nullptr); 421 redirectResolver(aStatusCode); 422 return NS_OK; 423 } 424 425 nsresult rv = aStatusCode; 426 if (NS_SUCCEEDED(rv)) { 427 if (nsCOMPtr<nsIChildChannel> childChannel = 428 do_QueryInterface(redirectChannel)) { 429 rv = childChannel->CompleteRedirectSetup(mListener); 430 } else { 431 rv = redirectChannel->AsyncOpen(mListener); 432 } 433 } else { 434 redirectChannel->SetNotificationCallbacks(nullptr); 435 } 436 437 for (auto& endpoint : mStreamFilterEndpoints) { 438 extensions::StreamFilterParent::Attach(redirectChannel, 439 std::move(endpoint)); 440 } 441 442 redirectResolver(rv); 443 444 if (NS_FAILED(rv)) { 445 ShutdownListeners(rv); 446 return NS_OK; 447 } 448 449 if (mLoadGroup) { 450 mLoadGroup->RemoveRequest(this, nullptr, NS_BINDING_REDIRECTED); 451 } 452 mCallbacks = nullptr; 453 mListener = nullptr; 454 455 // This calls NeckoChild::DeallocPDocumentChannel(), which deletes |this| if 456 // IPDL holds the last reference. Don't rely on |this| existing after here! 457 if (CanSend()) { 458 Send__delete__(this); 459 } 460 461 return NS_OK; 462 } 463 464 NS_IMETHODIMP 465 DocumentChannelChild::Cancel(nsresult aStatusCode) { 466 return CancelWithReason(aStatusCode, "DocumentChannelChild::Cancel"_ns); 467 } 468 469 NS_IMETHODIMP DocumentChannelChild::CancelWithReason( 470 nsresult aStatusCode, const nsACString& aReason) { 471 if (mCanceled) { 472 return NS_OK; 473 } 474 475 mCanceled = true; 476 if (CanSend()) { 477 SendCancel(aStatusCode, aReason); 478 } 479 480 ShutdownListeners(aStatusCode); 481 482 return NS_OK; 483 } 484 485 } // namespace net 486 } // namespace mozilla 487 488 #undef LOG