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)
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
,
52 bool aIsEmbeddingBlockedError
)
53 : DocumentChannel(aLoadState
, aLoadInfo
, aLoadFlags
, aCacheKey
,
54 aUriModified
, aIsEmbeddingBlockedError
) {
55 mLoadingContext
= nullptr;
56 LOG(("DocumentChannelChild ctor [this=%p, uri=%s]", this,
57 aLoadState
->URI()->GetSpecOrDefault().get()));
60 DocumentChannelChild::~DocumentChannelChild() {
61 LOG(("DocumentChannelChild dtor [this=%p]", this));
65 DocumentChannelChild::AsyncOpen(nsIStreamListener
* aListener
) {
68 nsCOMPtr
<nsIStreamListener
> listener
= aListener
;
70 NS_ENSURE_TRUE(gNeckoChild
, NS_ERROR_FAILURE
);
71 NS_ENSURE_ARG_POINTER(listener
);
72 NS_ENSURE_TRUE(!mIsPending
, NS_ERROR_IN_PROGRESS
);
73 NS_ENSURE_TRUE(!mWasOpened
, NS_ERROR_ALREADY_OPENED
);
75 // Port checked in parent, but duplicate here so we can return with error
76 // immediately, as we've done since before e10s.
77 rv
= NS_CheckPortSafety(mURI
);
78 NS_ENSURE_SUCCESS(rv
, rv
);
80 bool isNotDownload
= mLoadState
->FileName().IsVoid();
82 // If not a download, add ourselves to the load group
83 if (isNotDownload
&& mLoadGroup
) {
84 // During this call, we can re-enter back into the DocumentChannelChild to
85 // call SetNavigationTiming.
86 mLoadGroup
->AddRequest(this, nullptr);
90 // We may have been canceled already, either by on-modify-request
91 // listeners or by load group observers; in that case, don't create IPDL
92 // connection. See nsHttpChannel::AsyncOpen().
96 gHttpHandler
->OnOpeningDocumentRequest(this);
98 RefPtr
<nsDocShell
> docShell
= GetDocShell();
100 return NS_ERROR_FAILURE
;
103 // `loadingContext` is the BC that is initiating the resource load.
104 // For normal subdocument loads, the BC is the one that the subdoc will load
105 // into. For <object>/<embed> it's the embedder doc's BC.
106 RefPtr
<BrowsingContext
> loadingContext
= docShell
->GetBrowsingContext();
107 if (!loadingContext
|| loadingContext
->IsDiscarded()) {
108 return NS_ERROR_FAILURE
;
110 mLoadingContext
= loadingContext
;
112 Maybe
<IPCClientInfo
> ipcClientInfo
;
113 if (mInitialClientInfo
.isSome()) {
114 ipcClientInfo
.emplace(mInitialClientInfo
.ref().ToIPC());
117 DocumentChannelElementCreationArgs ipcElementCreationArgs
;
118 switch (mLoadInfo
->GetExternalContentPolicyType()) {
119 case ExtContentPolicy::TYPE_DOCUMENT
:
120 case ExtContentPolicy::TYPE_SUBDOCUMENT
: {
121 DocumentCreationArgs docArgs
;
122 docArgs
.loadFlags() = mLoadFlags
;
123 docArgs
.uriModified() = mUriModified
;
124 docArgs
.isEmbeddingBlockedError() = mIsEmbeddingBlockedError
;
126 ipcElementCreationArgs
= docArgs
;
130 case ExtContentPolicy::TYPE_OBJECT
: {
131 ObjectCreationArgs objectArgs
;
132 objectArgs
.embedderInnerWindowId() = InnerWindowIDForExtantDoc(docShell
);
133 objectArgs
.loadFlags() = mLoadFlags
;
134 objectArgs
.contentPolicyType() = mLoadInfo
->InternalContentPolicyType();
135 objectArgs
.isUrgentStart() = UserActivation::IsHandlingUserInput();
137 ipcElementCreationArgs
= objectArgs
;
142 MOZ_ASSERT_UNREACHABLE("unsupported content policy type");
143 return NS_ERROR_FAILURE
;
146 switch (mLoadInfo
->GetExternalContentPolicyType()) {
147 case ExtContentPolicy::TYPE_DOCUMENT
:
148 case ExtContentPolicy::TYPE_SUBDOCUMENT
:
149 MOZ_ALWAYS_SUCCEEDS(loadingContext
->SetCurrentLoadIdentifier(
150 Some(mLoadState
->GetLoadIdentifier())));
157 mLoadState
->AssertProcessCouldTriggerLoadIfSystem();
159 DocumentChannelCreationArgs
args(
160 mozilla::WrapNotNull(mLoadState
), TimeStamp::Now(), mChannelId
, mCacheKey
,
161 mTiming
, ipcClientInfo
, ipcElementCreationArgs
,
162 loadingContext
->GetParentInitiatedNavigationEpoch());
164 gNeckoChild
->SendPDocumentChannelConstructor(this, loadingContext
, args
);
168 mListener
= listener
;
173 IPCResult
DocumentChannelChild::RecvFailedAsyncOpen(
174 const nsresult
& aStatusCode
) {
175 if (aStatusCode
== NS_ERROR_RECURSIVE_DOCUMENT_LOAD
) {
176 // This exists so that we are able to fire an error event
177 // for when there are too many recursive iframe or object loads.
178 // This is an incomplete solution, because right now we don't have a unified
179 // way of firing error events due to errors in document channel.
180 // This should be fixed in bug 1629201.
181 MOZ_DIAGNOSTIC_ASSERT(mLoadingContext
);
182 if (RefPtr
<Element
> embedder
= mLoadingContext
->GetEmbedderElement()) {
183 if (RefPtr
<nsFrameLoaderOwner
> flo
= do_QueryObject(embedder
)) {
184 if (RefPtr
<nsFrameLoader
> fl
= flo
->GetFrameLoader()) {
185 fl
->FireErrorEvent();
190 ShutdownListeners(aStatusCode
);
194 IPCResult
DocumentChannelChild::RecvDisconnectChildListeners(
195 const nsresult
& aStatus
, const nsresult
& aLoadGroupStatus
,
196 bool aContinueNavigating
) {
197 // If this disconnect is not due to a process switch, perform the disconnect
199 if (!aContinueNavigating
) {
200 DisconnectChildListeners(aStatus
, aLoadGroupStatus
);
204 // Otherwise, the disconnect will occur later using some other mechanism,
205 // depending on what's happening to the loading DocShell. If this is a
206 // toplevel navigation, and this BrowsingContext enters the BFCache, we will
207 // cancel this channel when the PageHide event is firing, whereas if it does
208 // not enter BFCache (e.g. due to being an object, subframe or non-bfcached
209 // toplevel navigation), we will cancel this channel when the DocShell is
211 nsDocShell
* shell
= GetDocShell();
212 if (mLoadInfo
->GetExternalContentPolicyType() ==
213 ExtContentPolicy::TYPE_DOCUMENT
&&
215 MOZ_ASSERT(shell
->GetBrowsingContext()->IsTop());
216 if (mozilla::SessionHistoryInParent() &&
217 shell
->GetBrowsingContext()->IsInBFCache()) {
218 DisconnectChildListeners(aStatus
, aLoadGroupStatus
);
220 // Tell the DocShell which channel to cancel if it enters the BFCache.
221 shell
->SetChannelToDisconnectOnPageHide(mChannelId
);
228 IPCResult
DocumentChannelChild::RecvRedirectToRealChannel(
229 RedirectToRealChannelArgs
&& aArgs
,
230 nsTArray
<Endpoint
<extensions::PStreamFilterParent
>>&& aEndpoints
,
231 RedirectToRealChannelResolver
&& aResolve
) {
232 LOG(("DocumentChannelChild RecvRedirectToRealChannel [this=%p, uri=%s]", this,
233 aArgs
.uri()->GetSpecOrDefault().get()));
235 // The document that created the cspToInherit.
236 // This is used when deserializing LoadInfo from the parent
237 // process, since we can't serialize Documents directly.
238 // TODO: For a fission OOP iframe this will be unavailable,
239 // as will the loadingContext computed in LoadInfoArgsToLoadInfo.
240 // Figure out if we need these for cross-origin subdocs.
241 RefPtr
<dom::Document
> cspToInheritLoadingDocument
;
242 nsCOMPtr
<nsIContentSecurityPolicy
> policy
= mLoadState
->Csp();
245 static_cast<nsCSPContext
*>(policy
.get())->GetLoadingContext();
246 cspToInheritLoadingDocument
= do_QueryReferent(ctx
);
248 nsCOMPtr
<nsILoadInfo
> loadInfo
;
249 MOZ_ALWAYS_SUCCEEDS(LoadInfoArgsToLoadInfo(aArgs
.loadInfo(), NOT_REMOTE_TYPE
,
250 cspToInheritLoadingDocument
,
251 getter_AddRefs(loadInfo
)));
253 mRedirectResolver
= std::move(aResolve
);
255 nsCOMPtr
<nsIChannel
> newChannel
;
256 MOZ_ASSERT((aArgs
.loadStateInternalLoadFlags() &
257 nsDocShell::InternalLoad::INTERNAL_LOAD_FLAGS_IS_SRCDOC
) ||
258 aArgs
.srcdocData().IsVoid());
259 nsresult rv
= nsDocShell::CreateRealChannelForDocument(
260 getter_AddRefs(newChannel
), aArgs
.uri(), loadInfo
, nullptr,
261 aArgs
.newLoadFlags(), aArgs
.srcdocData(), aArgs
.baseUri());
263 newChannel
->SetLoadGroup(mLoadGroup
);
266 if (RefPtr
<HttpBaseChannel
> httpChannel
= do_QueryObject(newChannel
)) {
267 httpChannel
->SetEarlyHints(std::move(aArgs
.earlyHints()));
268 httpChannel
->SetEarlyHintLinkType(aArgs
.earlyHintLinkType());
271 // This is used to report any errors back to the parent by calling
272 // CrossProcessRedirectFinished.
273 auto scopeExit
= MakeScopeExit([&]() {
274 mRedirectResolver(rv
);
275 mRedirectResolver
= nullptr;
282 if (nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(newChannel
)) {
283 rv
= httpChannel
->SetChannelId(aArgs
.channelId());
289 rv
= newChannel
->SetOriginalURI(aArgs
.originalURI());
294 if (nsCOMPtr
<nsIHttpChannelInternal
> httpChannelInternal
=
295 do_QueryInterface(newChannel
)) {
296 rv
= httpChannelInternal
->SetRedirectMode(aArgs
.redirectMode());
302 newChannel
->SetNotificationCallbacks(mCallbacks
);
305 HttpBaseChannel::ReplacementChannelConfig
config(*aArgs
.init());
306 HttpBaseChannel::ConfigureReplacementChannel(
308 HttpBaseChannel::ReplacementReason::DocumentChannel
);
311 if (aArgs
.contentDisposition()) {
312 newChannel
->SetContentDisposition(*aArgs
.contentDisposition());
315 if (aArgs
.contentDispositionFilename()) {
316 newChannel
->SetContentDispositionFilename(
317 *aArgs
.contentDispositionFilename());
320 nsDocShell
* docShell
= GetDocShell();
321 if (docShell
&& aArgs
.loadingSessionHistoryInfo().isSome()) {
322 docShell
->SetLoadingSessionHistoryInfo(
323 aArgs
.loadingSessionHistoryInfo().ref());
326 // transfer any properties. This appears to be entirely a content-side
327 // interface and isn't copied across to the parent. Copying the values
328 // for this from this into the new actor will work, since the parent
329 // won't have the right details anyway.
330 // TODO: What about the process switch equivalent
331 // (ContentChild::RecvCrossProcessRedirect)? In that case there is no local
332 // existing actor in the destination process... We really need all information
333 // to go up to the parent, and then come down to the new child actor.
334 if (nsCOMPtr
<nsIWritablePropertyBag
> bag
= do_QueryInterface(newChannel
)) {
335 nsHashPropertyBag::CopyFrom(bag
, aArgs
.properties());
339 nsCOMPtr
<nsIChildChannel
> childChannel
= do_QueryInterface(newChannel
);
341 rv
= childChannel
->ConnectParent(
342 aArgs
.registrarId()); // creates parent channel
347 mRedirectChannel
= newChannel
;
348 mStreamFilterEndpoints
= std::move(aEndpoints
);
350 rv
= gHttpHandler
->AsyncOnChannelRedirect(this, newChannel
,
351 aArgs
.redirectFlags(),
352 GetMainThreadSerialEventTarget());
354 if (NS_SUCCEEDED(rv
)) {
358 // scopeExit will call CrossProcessRedirectFinished(rv) here
362 IPCResult
DocumentChannelChild::RecvUpgradeObjectLoad(
363 UpgradeObjectLoadResolver
&& aResolve
) {
364 // We're doing a load for an <object> or <embed> element if we got here.
365 MOZ_ASSERT(mLoadFlags
& nsIRequest::LOAD_HTML_OBJECT_DATA
,
366 "Should have LOAD_HTML_OBJECT_DATA set");
367 MOZ_ASSERT(!(mLoadFlags
& nsIChannel::LOAD_DOCUMENT_URI
),
368 "Shouldn't be a LOAD_DOCUMENT_URI load yet");
369 MOZ_ASSERT(mLoadInfo
->GetExternalContentPolicyType() ==
370 ExtContentPolicy::TYPE_OBJECT
,
371 "Should have the TYPE_OBJECT content policy type");
373 // If our load has already failed, or been cancelled, abort this attempt to
375 if (NS_FAILED(mStatus
)) {
380 nsCOMPtr
<nsIObjectLoadingContent
> loadingContent
;
381 NS_QueryNotificationCallbacks(this, loadingContent
);
382 if (!loadingContent
) {
383 return IPC_FAIL(this, "Channel is not for ObjectLoadingContent!");
386 // We're upgrading to a document channel now. Add the LOAD_DOCUMENT_URI flag
388 mLoadFlags
|= nsIChannel::LOAD_DOCUMENT_URI
;
390 RefPtr
<BrowsingContext
> browsingContext
;
391 nsresult rv
= loadingContent
->UpgradeLoadToDocument(
392 this, getter_AddRefs(browsingContext
));
393 if (NS_FAILED(rv
) || !browsingContext
) {
394 // Oops! Looks like something went wrong, so let's bail out.
395 mLoadFlags
&= ~nsIChannel::LOAD_DOCUMENT_URI
;
400 aResolve(browsingContext
);
405 DocumentChannelChild::OnRedirectVerifyCallback(nsresult aStatusCode
) {
407 ("DocumentChannelChild OnRedirectVerifyCallback [this=%p, "
408 "aRv=0x%08" PRIx32
" ]",
409 this, static_cast<uint32_t>(aStatusCode
)));
410 nsCOMPtr
<nsIChannel
> redirectChannel
= std::move(mRedirectChannel
);
411 RedirectToRealChannelResolver redirectResolver
= std::move(mRedirectResolver
);
413 // If we've already shut down, then just notify the parent that
415 if (NS_FAILED(mStatus
)) {
416 redirectChannel
->SetNotificationCallbacks(nullptr);
417 redirectResolver(aStatusCode
);
421 nsresult rv
= aStatusCode
;
422 if (NS_SUCCEEDED(rv
)) {
423 if (nsCOMPtr
<nsIChildChannel
> childChannel
=
424 do_QueryInterface(redirectChannel
)) {
425 rv
= childChannel
->CompleteRedirectSetup(mListener
);
427 rv
= redirectChannel
->AsyncOpen(mListener
);
430 redirectChannel
->SetNotificationCallbacks(nullptr);
433 for (auto& endpoint
: mStreamFilterEndpoints
) {
434 extensions::StreamFilterParent::Attach(redirectChannel
,
435 std::move(endpoint
));
438 redirectResolver(rv
);
441 ShutdownListeners(rv
);
446 mLoadGroup
->RemoveRequest(this, nullptr, NS_BINDING_REDIRECTED
);
448 mCallbacks
= nullptr;
451 // This calls NeckoChild::DeallocPDocumentChannel(), which deletes |this| if
452 // IPDL holds the last reference. Don't rely on |this| existing after here!
454 Send__delete__(this);
461 DocumentChannelChild::Cancel(nsresult aStatusCode
) {
462 return CancelWithReason(aStatusCode
, "DocumentChannelChild::Cancel"_ns
);
465 NS_IMETHODIMP
DocumentChannelChild::CancelWithReason(
466 nsresult aStatusCode
, const nsACString
& aReason
) {
473 SendCancel(aStatusCode
, aReason
);
476 ShutdownListeners(aStatusCode
);
482 } // namespace mozilla