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