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
,
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));
64 DocumentChannelChild::AsyncOpen(nsIStreamListener
* aListener
) {
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);
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().
95 gHttpHandler
->OnOpeningDocumentRequest(this);
97 RefPtr
<nsDocShell
> docShell
= GetDocShell();
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
;
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
;
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())));
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
);
166 mListener
= listener
;
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
);
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
197 if (!aContinueNavigating
) {
198 DisconnectChildListeners(aStatus
, aLoadGroupStatus
);
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
209 nsDocShell
* shell
= GetDocShell();
210 if (mLoadInfo
->GetExternalContentPolicyType() ==
211 ExtContentPolicy::TYPE_DOCUMENT
&&
213 MOZ_ASSERT(shell
->GetBrowsingContext()->IsTop());
214 if (mozilla::SessionHistoryInParent() &&
215 shell
->GetBrowsingContext()->IsInBFCache()) {
216 DisconnectChildListeners(aStatus
, aLoadGroupStatus
);
218 // Tell the DocShell which channel to cancel if it enters the BFCache.
219 shell
->SetChannelToDisconnectOnPageHide(mChannelId
);
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();
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());
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;
280 if (nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(newChannel
)) {
281 rv
= httpChannel
->SetChannelId(aArgs
.channelId());
287 rv
= newChannel
->SetOriginalURI(aArgs
.originalURI());
292 if (nsCOMPtr
<nsIHttpChannelInternal
> httpChannelInternal
=
293 do_QueryInterface(newChannel
)) {
294 rv
= httpChannelInternal
->SetRedirectMode(aArgs
.redirectMode());
300 newChannel
->SetNotificationCallbacks(mCallbacks
);
303 HttpBaseChannel::ReplacementChannelConfig
config(*aArgs
.init());
304 HttpBaseChannel::ConfigureReplacementChannel(
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());
337 nsCOMPtr
<nsIChildChannel
> childChannel
= do_QueryInterface(newChannel
);
339 rv
= childChannel
->ConnectParent(
340 aArgs
.registrarId()); // creates parent channel
345 mRedirectChannel
= newChannel
;
346 mStreamFilterEndpoints
= std::move(aEndpoints
);
348 rv
= gHttpHandler
->AsyncOnChannelRedirect(this, newChannel
,
349 aArgs
.redirectFlags(),
350 GetMainThreadSerialEventTarget());
352 if (NS_SUCCEEDED(rv
)) {
356 // scopeExit will call CrossProcessRedirectFinished(rv) here
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
373 if (NS_FAILED(mStatus
)) {
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
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
;
398 aResolve(browsingContext
);
403 DocumentChannelChild::OnRedirectVerifyCallback(nsresult aStatusCode
) {
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
413 if (NS_FAILED(mStatus
)) {
414 redirectChannel
->SetNotificationCallbacks(nullptr);
415 redirectResolver(aStatusCode
);
419 nsresult rv
= aStatusCode
;
420 if (NS_SUCCEEDED(rv
)) {
421 if (nsCOMPtr
<nsIChildChannel
> childChannel
=
422 do_QueryInterface(redirectChannel
)) {
423 rv
= childChannel
->CompleteRedirectSetup(mListener
);
425 rv
= redirectChannel
->AsyncOpen(mListener
);
428 redirectChannel
->SetNotificationCallbacks(nullptr);
431 for (auto& endpoint
: mStreamFilterEndpoints
) {
432 extensions::StreamFilterParent::Attach(redirectChannel
,
433 std::move(endpoint
));
436 redirectResolver(rv
);
439 ShutdownListeners(rv
);
444 mLoadGroup
->RemoveRequest(this, nullptr, NS_BINDING_REDIRECTED
);
446 mCallbacks
= nullptr;
449 // This calls NeckoChild::DeallocPDocumentChannel(), which deletes |this| if
450 // IPDL holds the last reference. Don't rely on |this| existing after here!
452 Send__delete__(this);
459 DocumentChannelChild::Cancel(nsresult aStatusCode
) {
460 return CancelWithReason(aStatusCode
, "DocumentChannelChild::Cancel"_ns
);
463 NS_IMETHODIMP
DocumentChannelChild::CancelWithReason(
464 nsresult aStatusCode
, const nsACString
& aReason
) {
471 SendCancel(aStatusCode
, aReason
);
474 ShutdownListeners(aStatusCode
);
480 } // namespace mozilla