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
.uriModified() = mUriModified
;
123 docArgs
.isEmbeddingBlockedError() = mIsEmbeddingBlockedError
;
125 ipcElementCreationArgs
= docArgs
;
129 case ExtContentPolicy::TYPE_OBJECT
: {
130 ObjectCreationArgs objectArgs
;
131 objectArgs
.embedderInnerWindowId() = InnerWindowIDForExtantDoc(docShell
);
132 objectArgs
.loadFlags() = mLoadFlags
;
133 objectArgs
.contentPolicyType() = mLoadInfo
->InternalContentPolicyType();
134 objectArgs
.isUrgentStart() = UserActivation::IsHandlingUserInput();
136 ipcElementCreationArgs
= objectArgs
;
141 MOZ_ASSERT_UNREACHABLE("unsupported content policy type");
142 return NS_ERROR_FAILURE
;
145 switch (mLoadInfo
->GetExternalContentPolicyType()) {
146 case ExtContentPolicy::TYPE_DOCUMENT
:
147 case ExtContentPolicy::TYPE_SUBDOCUMENT
:
148 MOZ_ALWAYS_SUCCEEDS(loadingContext
->SetCurrentLoadIdentifier(
149 Some(mLoadState
->GetLoadIdentifier())));
156 mLoadState
->AssertProcessCouldTriggerLoadIfSystem();
158 DocumentChannelCreationArgs
args(
159 mozilla::WrapNotNull(mLoadState
), TimeStamp::Now(), mChannelId
, mCacheKey
,
160 mTiming
, ipcClientInfo
, ipcElementCreationArgs
,
161 loadingContext
->GetParentInitiatedNavigationEpoch());
163 gNeckoChild
->SendPDocumentChannelConstructor(this, loadingContext
, args
);
167 mListener
= listener
;
172 IPCResult
DocumentChannelChild::RecvFailedAsyncOpen(
173 const nsresult
& aStatusCode
) {
174 if (aStatusCode
== NS_ERROR_RECURSIVE_DOCUMENT_LOAD
) {
175 // This exists so that we are able to fire an error event
176 // for when there are too many recursive iframe or object loads.
177 // This is an incomplete solution, because right now we don't have a unified
178 // way of firing error events due to errors in document channel.
179 // This should be fixed in bug 1629201.
180 MOZ_DIAGNOSTIC_ASSERT(mLoadingContext
);
181 if (RefPtr
<Element
> embedder
= mLoadingContext
->GetEmbedderElement()) {
182 if (RefPtr
<nsFrameLoaderOwner
> flo
= do_QueryObject(embedder
)) {
183 if (RefPtr
<nsFrameLoader
> fl
= flo
->GetFrameLoader()) {
184 fl
->FireErrorEvent();
189 ShutdownListeners(aStatusCode
);
193 IPCResult
DocumentChannelChild::RecvDisconnectChildListeners(
194 const nsresult
& aStatus
, const nsresult
& aLoadGroupStatus
,
195 bool aContinueNavigating
) {
196 // If this disconnect is not due to a process switch, perform the disconnect
198 if (!aContinueNavigating
) {
199 DisconnectChildListeners(aStatus
, aLoadGroupStatus
);
203 // Otherwise, the disconnect will occur later using some other mechanism,
204 // depending on what's happening to the loading DocShell. If this is a
205 // toplevel navigation, and this BrowsingContext enters the BFCache, we will
206 // cancel this channel when the PageHide event is firing, whereas if it does
207 // not enter BFCache (e.g. due to being an object, subframe or non-bfcached
208 // toplevel navigation), we will cancel this channel when the DocShell is
210 nsDocShell
* shell
= GetDocShell();
211 if (mLoadInfo
->GetExternalContentPolicyType() ==
212 ExtContentPolicy::TYPE_DOCUMENT
&&
214 MOZ_ASSERT(shell
->GetBrowsingContext()->IsTop());
215 if (mozilla::SessionHistoryInParent() &&
216 shell
->GetBrowsingContext()->IsInBFCache()) {
217 DisconnectChildListeners(aStatus
, aLoadGroupStatus
);
219 // Tell the DocShell which channel to cancel if it enters the BFCache.
220 shell
->SetChannelToDisconnectOnPageHide(mChannelId
);
227 IPCResult
DocumentChannelChild::RecvRedirectToRealChannel(
228 RedirectToRealChannelArgs
&& aArgs
,
229 nsTArray
<Endpoint
<extensions::PStreamFilterParent
>>&& aEndpoints
,
230 RedirectToRealChannelResolver
&& aResolve
) {
231 LOG(("DocumentChannelChild RecvRedirectToRealChannel [this=%p, uri=%s]", this,
232 aArgs
.uri()->GetSpecOrDefault().get()));
234 // The document that created the cspToInherit.
235 // This is used when deserializing LoadInfo from the parent
236 // process, since we can't serialize Documents directly.
237 // TODO: For a fission OOP iframe this will be unavailable,
238 // as will the loadingContext computed in LoadInfoArgsToLoadInfo.
239 // Figure out if we need these for cross-origin subdocs.
240 RefPtr
<dom::Document
> cspToInheritLoadingDocument
;
241 nsCOMPtr
<nsIContentSecurityPolicy
> policy
= mLoadState
->Csp();
244 static_cast<nsCSPContext
*>(policy
.get())->GetLoadingContext();
245 cspToInheritLoadingDocument
= do_QueryReferent(ctx
);
247 nsCOMPtr
<nsILoadInfo
> loadInfo
;
248 MOZ_ALWAYS_SUCCEEDS(LoadInfoArgsToLoadInfo(aArgs
.loadInfo(), NOT_REMOTE_TYPE
,
249 cspToInheritLoadingDocument
,
250 getter_AddRefs(loadInfo
)));
252 mRedirectResolver
= std::move(aResolve
);
254 nsCOMPtr
<nsIChannel
> newChannel
;
255 MOZ_ASSERT((aArgs
.loadStateInternalLoadFlags() &
256 nsDocShell::InternalLoad::INTERNAL_LOAD_FLAGS_IS_SRCDOC
) ||
257 aArgs
.srcdocData().IsVoid());
258 nsresult rv
= nsDocShell::CreateRealChannelForDocument(
259 getter_AddRefs(newChannel
), aArgs
.uri(), loadInfo
, nullptr,
260 aArgs
.newLoadFlags(), aArgs
.srcdocData(), aArgs
.baseUri());
262 newChannel
->SetLoadGroup(mLoadGroup
);
265 if (RefPtr
<HttpBaseChannel
> httpChannel
= do_QueryObject(newChannel
)) {
266 httpChannel
->SetEarlyHints(std::move(aArgs
.earlyHints()));
267 httpChannel
->SetEarlyHintLinkType(aArgs
.earlyHintLinkType());
270 // This is used to report any errors back to the parent by calling
271 // CrossProcessRedirectFinished.
272 auto scopeExit
= MakeScopeExit([&]() {
273 mRedirectResolver(rv
);
274 mRedirectResolver
= nullptr;
281 if (nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(newChannel
)) {
282 rv
= httpChannel
->SetChannelId(aArgs
.channelId());
288 rv
= newChannel
->SetOriginalURI(aArgs
.originalURI());
293 if (nsCOMPtr
<nsIHttpChannelInternal
> httpChannelInternal
=
294 do_QueryInterface(newChannel
)) {
295 rv
= httpChannelInternal
->SetRedirectMode(aArgs
.redirectMode());
301 newChannel
->SetNotificationCallbacks(mCallbacks
);
304 HttpBaseChannel::ReplacementChannelConfig
config(*aArgs
.init());
305 HttpBaseChannel::ConfigureReplacementChannel(
307 HttpBaseChannel::ReplacementReason::DocumentChannel
);
310 if (aArgs
.contentDisposition()) {
311 newChannel
->SetContentDisposition(*aArgs
.contentDisposition());
314 if (aArgs
.contentDispositionFilename()) {
315 newChannel
->SetContentDispositionFilename(
316 *aArgs
.contentDispositionFilename());
319 nsDocShell
* docShell
= GetDocShell();
320 if (docShell
&& aArgs
.loadingSessionHistoryInfo().isSome()) {
321 docShell
->SetLoadingSessionHistoryInfo(
322 aArgs
.loadingSessionHistoryInfo().ref());
325 // transfer any properties. This appears to be entirely a content-side
326 // interface and isn't copied across to the parent. Copying the values
327 // for this from this into the new actor will work, since the parent
328 // won't have the right details anyway.
329 // TODO: What about the process switch equivalent
330 // (ContentChild::RecvCrossProcessRedirect)? In that case there is no local
331 // existing actor in the destination process... We really need all information
332 // to go up to the parent, and then come down to the new child actor.
333 if (nsCOMPtr
<nsIWritablePropertyBag
> bag
= do_QueryInterface(newChannel
)) {
334 nsHashPropertyBag::CopyFrom(bag
, aArgs
.properties());
338 nsCOMPtr
<nsIChildChannel
> childChannel
= do_QueryInterface(newChannel
);
340 rv
= childChannel
->ConnectParent(
341 aArgs
.registrarId()); // creates parent channel
346 mRedirectChannel
= newChannel
;
347 mStreamFilterEndpoints
= std::move(aEndpoints
);
349 rv
= gHttpHandler
->AsyncOnChannelRedirect(this, newChannel
,
350 aArgs
.redirectFlags(),
351 GetMainThreadSerialEventTarget());
353 if (NS_SUCCEEDED(rv
)) {
357 // scopeExit will call CrossProcessRedirectFinished(rv) here
361 IPCResult
DocumentChannelChild::RecvUpgradeObjectLoad(
362 UpgradeObjectLoadResolver
&& aResolve
) {
363 // We're doing a load for an <object> or <embed> element if we got here.
364 MOZ_ASSERT(mLoadFlags
& nsIRequest::LOAD_HTML_OBJECT_DATA
,
365 "Should have LOAD_HTML_OBJECT_DATA set");
366 MOZ_ASSERT(!(mLoadFlags
& nsIChannel::LOAD_DOCUMENT_URI
),
367 "Shouldn't be a LOAD_DOCUMENT_URI load yet");
368 MOZ_ASSERT(mLoadInfo
->GetExternalContentPolicyType() ==
369 ExtContentPolicy::TYPE_OBJECT
,
370 "Should have the TYPE_OBJECT content policy type");
372 // If our load has already failed, or been cancelled, abort this attempt to
374 if (NS_FAILED(mStatus
)) {
379 nsCOMPtr
<nsIObjectLoadingContent
> loadingContent
;
380 NS_QueryNotificationCallbacks(this, loadingContent
);
381 if (!loadingContent
) {
382 return IPC_FAIL(this, "Channel is not for ObjectLoadingContent!");
385 // We're upgrading to a document channel now. Add the LOAD_DOCUMENT_URI flag
387 mLoadFlags
|= nsIChannel::LOAD_DOCUMENT_URI
;
389 RefPtr
<BrowsingContext
> browsingContext
;
390 nsresult rv
= loadingContent
->UpgradeLoadToDocument(
391 this, getter_AddRefs(browsingContext
));
392 if (NS_FAILED(rv
) || !browsingContext
) {
393 // Oops! Looks like something went wrong, so let's bail out.
394 mLoadFlags
&= ~nsIChannel::LOAD_DOCUMENT_URI
;
399 aResolve(browsingContext
);
404 DocumentChannelChild::OnRedirectVerifyCallback(nsresult aStatusCode
) {
406 ("DocumentChannelChild OnRedirectVerifyCallback [this=%p, "
407 "aRv=0x%08" PRIx32
" ]",
408 this, static_cast<uint32_t>(aStatusCode
)));
409 nsCOMPtr
<nsIChannel
> redirectChannel
= std::move(mRedirectChannel
);
410 RedirectToRealChannelResolver redirectResolver
= std::move(mRedirectResolver
);
412 // If we've already shut down, then just notify the parent that
414 if (NS_FAILED(mStatus
)) {
415 redirectChannel
->SetNotificationCallbacks(nullptr);
416 redirectResolver(aStatusCode
);
420 nsresult rv
= aStatusCode
;
421 if (NS_SUCCEEDED(rv
)) {
422 if (nsCOMPtr
<nsIChildChannel
> childChannel
=
423 do_QueryInterface(redirectChannel
)) {
424 rv
= childChannel
->CompleteRedirectSetup(mListener
);
426 rv
= redirectChannel
->AsyncOpen(mListener
);
429 redirectChannel
->SetNotificationCallbacks(nullptr);
432 for (auto& endpoint
: mStreamFilterEndpoints
) {
433 extensions::StreamFilterParent::Attach(redirectChannel
,
434 std::move(endpoint
));
437 redirectResolver(rv
);
440 ShutdownListeners(rv
);
445 mLoadGroup
->RemoveRequest(this, nullptr, NS_BINDING_REDIRECTED
);
447 mCallbacks
= nullptr;
450 // This calls NeckoChild::DeallocPDocumentChannel(), which deletes |this| if
451 // IPDL holds the last reference. Don't rely on |this| existing after here!
453 Send__delete__(this);
460 DocumentChannelChild::Cancel(nsresult aStatusCode
) {
461 return CancelWithReason(aStatusCode
, "DocumentChannelChild::Cancel"_ns
);
464 NS_IMETHODIMP
DocumentChannelChild::CancelWithReason(
465 nsresult aStatusCode
, const nsACString
& aReason
) {
472 SendCancel(aStatusCode
, aReason
);
475 ShutdownListeners(aStatusCode
);
481 } // namespace mozilla