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 "DocumentLoadListener.h"
9
10 #include "mozilla/AntiTrackingUtils.h"
11 #include "mozilla/LoadInfo.h"
12 #include "mozilla/MozPromiseInlines.h" // For MozPromise::FromDomPromise
13 #include "mozilla/StaticPrefs_extensions.h"
14 #include "mozilla/StaticPrefs_fission.h"
15 #include "mozilla/StaticPrefs_security.h"
16 #include "mozilla/dom/CanonicalBrowsingContext.h"
17 #include "mozilla/dom/ClientChannelHelper.h"
18 #include "mozilla/dom/ContentParent.h"
19 #include "mozilla/dom/ContentProcessManager.h"
20 #include "mozilla/dom/SessionHistoryEntry.h"
21 #include "mozilla/dom/WindowGlobalParent.h"
22 #include "mozilla/dom/ipc/IdType.h"
23 #include "mozilla/net/CookieJarSettings.h"
24 #include "mozilla/net/HttpChannelParent.h"
25 #include "mozilla/net/RedirectChannelRegistrar.h"
26 #include "mozilla/net/UrlClassifierCommon.h"
27 #include "nsContentSecurityUtils.h"
28 #include "nsDocShell.h"
29 #include "nsDocShellLoadState.h"
30 #include "nsDocShellLoadTypes.h"
31 #include "nsExternalHelperAppService.h"
32 #include "nsHttpChannel.h"
33 #include "nsIHttpChannelInternal.h"
34 #include "nsIBrowser.h"
35 #include "nsIE10SUtils.h"
36 #include "nsIStreamConverterService.h"
37 #include "nsIViewSourceChannel.h"
38 #include "nsImportModule.h"
39 #include "nsMimeTypes.h"
40 #include "nsRedirectHistoryEntry.h"
41 #include "nsSandboxFlags.h"
42 #include "nsURILoader.h"
43 #include "nsWebNavigationInfo.h"
44
45 #ifdef ANDROID
46 # include "mozilla/widget/nsWindow.h"
47 #endif /* ANDROID */
48
49 mozilla::LazyLogModule gDocumentChannelLog("DocumentChannel");
50 #define LOG(fmt) MOZ_LOG(gDocumentChannelLog, mozilla::LogLevel::Verbose, fmt)
51
52 using namespace mozilla::dom;
53
54 namespace mozilla {
55 namespace net {
56
SetNeedToAddURIVisit(nsIChannel * aChannel,bool aNeedToAddURIVisit)57 static void SetNeedToAddURIVisit(nsIChannel* aChannel,
58 bool aNeedToAddURIVisit) {
59 nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(aChannel));
60 if (!props) {
61 return;
62 }
63
64 props->SetPropertyAsBool(NS_LITERAL_STRING("docshell.needToAddURIVisit"),
65 aNeedToAddURIVisit);
66 }
67
68 /**
69 * An extension to nsDocumentOpenInfo that we run in the parent process, so
70 * that we can make the decision to retarget to content handlers or the external
71 * helper app, before we make process switching decisions.
72 *
73 * This modifies the behaviour of nsDocumentOpenInfo so that it can do
74 * retargeting, but doesn't do stream conversion (but confirms that we will be
75 * able to do so later).
76 *
77 * We still run nsDocumentOpenInfo in the content process, but disable
78 * retargeting, so that it can only apply stream conversion, and then send data
79 * to the docshell.
80 */
81 class ParentProcessDocumentOpenInfo final : public nsDocumentOpenInfo,
82 public nsIMultiPartChannelListener {
83 public:
ParentProcessDocumentOpenInfo(ParentChannelListener * aListener,uint32_t aFlags,mozilla::dom::BrowsingContext * aBrowsingContext)84 ParentProcessDocumentOpenInfo(ParentChannelListener* aListener,
85 uint32_t aFlags,
86 mozilla::dom::BrowsingContext* aBrowsingContext)
87 : nsDocumentOpenInfo(aFlags, false),
88 mBrowsingContext(aBrowsingContext),
89 mListener(aListener) {
90 LOG(("ParentProcessDocumentOpenInfo ctor [this=%p]", this));
91 }
92
93 NS_DECL_ISUPPORTS_INHERITED
94
95 // The default content listener is always a docshell, so this manually
96 // implements the same checks, and if it succeeds, uses the parent
97 // channel listener so that we forward onto DocumentLoadListener.
TryDefaultContentListener(nsIChannel * aChannel,const nsCString & aContentType)98 bool TryDefaultContentListener(nsIChannel* aChannel,
99 const nsCString& aContentType) {
100 uint32_t canHandle = nsWebNavigationInfo::IsTypeSupported(
101 aContentType, mBrowsingContext->GetAllowPlugins());
102 if (canHandle != nsIWebNavigationInfo::UNSUPPORTED) {
103 m_targetStreamListener = mListener;
104 nsLoadFlags loadFlags = 0;
105 aChannel->GetLoadFlags(&loadFlags);
106 aChannel->SetLoadFlags(loadFlags | nsIChannel::LOAD_TARGETED);
107 return true;
108 }
109 return false;
110 }
111
TryDefaultContentListener(nsIChannel * aChannel)112 bool TryDefaultContentListener(nsIChannel* aChannel) override {
113 return TryDefaultContentListener(aChannel, mContentType);
114 }
115
116 // Generally we only support stream converters that can tell
117 // use exactly what type they'll output. If we find one, then
118 // we just target to our default listener directly (without
119 // conversion), and the content process nsDocumentOpenInfo will
120 // run and do the actual conversion.
TryStreamConversion(nsIChannel * aChannel)121 nsresult TryStreamConversion(nsIChannel* aChannel) override {
122 // The one exception is nsUnknownDecoder, which works in the parent
123 // (and we need to know what the content type is before we can
124 // decide if it will be handled in the parent), so we run that here.
125 if (mContentType.LowerCaseEqualsASCII(UNKNOWN_CONTENT_TYPE)) {
126 return nsDocumentOpenInfo::TryStreamConversion(aChannel);
127 }
128
129 nsresult rv;
130 nsCOMPtr<nsIStreamConverterService> streamConvService =
131 do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
132 nsAutoCString str;
133 rv = streamConvService->ConvertedType(mContentType, aChannel, str);
134 NS_ENSURE_SUCCESS(rv, rv);
135
136 // We only support passing data to the default content listener
137 // (docshell), and we don't supported chaining converters.
138 if (TryDefaultContentListener(aChannel, str)) {
139 mContentType = str;
140 return NS_OK;
141 }
142 // This is the same result as nsStreamConverterService uses when it
143 // can't find a converter
144 return NS_ERROR_FAILURE;
145 }
146
TryExternalHelperApp(nsIExternalHelperAppService * aHelperAppService,nsIChannel * aChannel)147 nsresult TryExternalHelperApp(nsIExternalHelperAppService* aHelperAppService,
148 nsIChannel* aChannel) override {
149 RefPtr<nsExternalAppHandler> handler;
150 nsresult rv = aHelperAppService->CreateListener(
151 mContentType, aChannel, mBrowsingContext, false, nullptr,
152 getter_AddRefs(handler));
153 if (NS_SUCCEEDED(rv)) {
154 m_targetStreamListener = handler;
155 }
156 return rv;
157 }
158
Clone()159 nsDocumentOpenInfo* Clone() override {
160 mCloned = true;
161 return new ParentProcessDocumentOpenInfo(mListener, mFlags,
162 mBrowsingContext);
163 }
164
OnStartRequest(nsIRequest * request)165 NS_IMETHOD OnStartRequest(nsIRequest* request) override {
166 LOG(("ParentProcessDocumentOpenInfo OnStartRequest [this=%p]", this));
167
168 nsresult rv = nsDocumentOpenInfo::OnStartRequest(request);
169
170 // If we didn't find a content handler,
171 // and we don't have a listener, then just forward to our
172 // default listener. This happens when the channel is in
173 // an error state, and we want to just forward that on to be
174 // handled in the content process.
175 if (!mUsedContentHandler && !m_targetStreamListener) {
176 m_targetStreamListener = mListener;
177 return m_targetStreamListener->OnStartRequest(request);
178 }
179 if (m_targetStreamListener != mListener) {
180 LOG(
181 ("ParentProcessDocumentOpenInfo targeted to non-default listener "
182 "[this=%p]",
183 this));
184 // If this is the only part, then we can immediately tell our listener
185 // that it won't be getting any content and disconnect it. For multipart
186 // channels we have to wait until we've handled all parts before we know.
187 // This does mean that the content process can still Cancel() a multipart
188 // response while the response is being handled externally, but this
189 // matches the single-process behaviour.
190 // If we got cloned, then we don't need to do this, as only the last link
191 // needs to do it.
192 // Multi-part channels are guaranteed to call OnAfterLastPart, which we
193 // forward to the listeners, so it will handle disconnection at that
194 // point.
195 nsCOMPtr<nsIMultiPartChannel> multiPartChannel =
196 do_QueryInterface(request);
197 if (!multiPartChannel && !mCloned) {
198 DisconnectChildListeners();
199 }
200 }
201 return rv;
202 }
203
OnAfterLastPart(nsresult aStatus)204 NS_IMETHOD OnAfterLastPart(nsresult aStatus) override {
205 mListener->OnAfterLastPart(aStatus);
206 return NS_OK;
207 }
208
209 private:
~ParentProcessDocumentOpenInfo()210 virtual ~ParentProcessDocumentOpenInfo() {
211 LOG(("ParentProcessDocumentOpenInfo dtor [this=%p]", this));
212 }
213
DisconnectChildListeners()214 void DisconnectChildListeners() {
215 // Tell the DocumentLoadListener to notify the content process that it's
216 // been entirely retargeted, and to stop waiting.
217 // Clear mListener's pointer to the DocumentLoadListener to break the
218 // reference cycle.
219 RefPtr<DocumentLoadListener> doc = do_GetInterface(ToSupports(mListener));
220 MOZ_ASSERT(doc);
221 doc->DisconnectChildListeners(NS_BINDING_RETARGETED, NS_OK);
222 mListener->SetListenerAfterRedirect(nullptr);
223 }
224
225 RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext;
226 RefPtr<ParentChannelListener> mListener;
227
228 /**
229 * Set to true if we got cloned to create a chained listener.
230 */
231 bool mCloned = false;
232 };
233
NS_IMPL_ADDREF_INHERITED(ParentProcessDocumentOpenInfo,nsDocumentOpenInfo)234 NS_IMPL_ADDREF_INHERITED(ParentProcessDocumentOpenInfo, nsDocumentOpenInfo)
235 NS_IMPL_RELEASE_INHERITED(ParentProcessDocumentOpenInfo, nsDocumentOpenInfo)
236
237 NS_INTERFACE_MAP_BEGIN(ParentProcessDocumentOpenInfo)
238 NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannelListener)
239 NS_INTERFACE_MAP_END_INHERITING(nsDocumentOpenInfo)
240
241 NS_IMPL_ADDREF(DocumentLoadListener)
242 NS_IMPL_RELEASE(DocumentLoadListener)
243
244 NS_INTERFACE_MAP_BEGIN(DocumentLoadListener)
245 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInterfaceRequestor)
246 NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
247 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
248 NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
249 NS_INTERFACE_MAP_ENTRY(nsIParentChannel)
250 NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectReadyCallback)
251 NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
252 NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannelListener)
253 NS_INTERFACE_MAP_ENTRY_CONCRETE(DocumentLoadListener)
254 NS_INTERFACE_MAP_END
255
256 DocumentLoadListener::DocumentLoadListener(
257 CanonicalBrowsingContext* aBrowsingContext, ADocumentChannelBridge* aBridge)
258 : mDocumentChannelBridge(aBridge) {
259 MOZ_ASSERT(aBridge);
260 LOG(("DocumentLoadListener ctor [this=%p]", this));
261 mParentChannelListener = new ParentChannelListener(
262 this, aBrowsingContext, aBrowsingContext->UsePrivateBrowsing());
263 }
264
DocumentLoadListener(CanonicalBrowsingContext * aBrowsingContext,base::ProcessId aPendingBridgeProcess)265 DocumentLoadListener::DocumentLoadListener(
266 CanonicalBrowsingContext* aBrowsingContext,
267 base::ProcessId aPendingBridgeProcess) {
268 LOG(("DocumentLoadListener ctor [this=%p]", this));
269 mParentChannelListener = new ParentChannelListener(
270 this, aBrowsingContext, aBrowsingContext->UsePrivateBrowsing());
271 mPendingDocumentChannelBridgeProcess = Some(aPendingBridgeProcess);
272 }
273
~DocumentLoadListener()274 DocumentLoadListener::~DocumentLoadListener() {
275 LOG(("DocumentLoadListener dtor [this=%p]", this));
276 }
277
AddURIVisit(nsIChannel * aChannel,uint32_t aLoadFlags)278 void DocumentLoadListener::AddURIVisit(nsIChannel* aChannel,
279 uint32_t aLoadFlags) {
280 if (mLoadStateLoadType == LOAD_ERROR_PAGE ||
281 mLoadStateLoadType == LOAD_BYPASS_HISTORY) {
282 return;
283 }
284
285 nsCOMPtr<nsIURI> uri;
286 NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
287
288 nsCOMPtr<nsIURI> previousURI;
289 uint32_t previousFlags = 0;
290 if (mLoadStateLoadType & nsIDocShell::LOAD_CMD_RELOAD) {
291 previousURI = uri;
292 } else {
293 nsDocShell::ExtractLastVisit(aChannel, getter_AddRefs(previousURI),
294 &previousFlags);
295 }
296
297 // Get the HTTP response code, if available.
298 uint32_t responseStatus = 0;
299 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
300 if (httpChannel) {
301 Unused << httpChannel->GetResponseStatus(&responseStatus);
302 }
303
304 RefPtr<CanonicalBrowsingContext> browsingContext =
305 mParentChannelListener->GetBrowsingContext();
306 nsCOMPtr<nsIWidget> widget =
307 browsingContext->GetParentProcessWidgetContaining();
308
309 nsDocShell::InternalAddURIVisit(uri, previousURI, previousFlags,
310 responseStatus, browsingContext, widget,
311 mLoadStateLoadType);
312 }
313
CreateLoadInfo(CanonicalBrowsingContext * aBrowsingContext,nsDocShellLoadState * aLoadState,uint64_t aOuterWindowId)314 already_AddRefed<LoadInfo> DocumentLoadListener::CreateLoadInfo(
315 CanonicalBrowsingContext* aBrowsingContext, nsDocShellLoadState* aLoadState,
316 uint64_t aOuterWindowId) {
317 // TODO: Block copied from nsDocShell::DoURILoad, refactor out somewhere
318 bool inheritPrincipal = false;
319
320 if (aLoadState->PrincipalToInherit()) {
321 bool isSrcdoc =
322 aLoadState->HasLoadFlags(nsDocShell::INTERNAL_LOAD_FLAGS_IS_SRCDOC);
323 bool inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal(
324 aLoadState->PrincipalToInherit(), aLoadState->URI(),
325 true, // aInheritForAboutBlank
326 isSrcdoc);
327
328 bool isURIUniqueOrigin =
329 StaticPrefs::security_data_uri_unique_opaque_origin() &&
330 SchemeIsData(aLoadState->URI());
331 inheritPrincipal = inheritAttrs && !isURIUniqueOrigin;
332 }
333
334 nsSecurityFlags securityFlags =
335 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL;
336 uint32_t sandboxFlags = aBrowsingContext->GetSandboxFlags();
337
338 if (aLoadState->LoadType() == LOAD_ERROR_PAGE) {
339 securityFlags |= nsILoadInfo::SEC_LOAD_ERROR_PAGE;
340 }
341
342 if (inheritPrincipal) {
343 securityFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
344 }
345
346 RefPtr<LoadInfo> loadInfo;
347 if (aBrowsingContext->GetParent()) {
348 // Build LoadInfo for TYPE_SUBDOCUMENT
349 loadInfo = new LoadInfo(aBrowsingContext, aLoadState->TriggeringPrincipal(),
350 aOuterWindowId, securityFlags, sandboxFlags);
351 } else {
352 // Build LoadInfo for TYPE_DOCUMENT
353 OriginAttributes attrs;
354 aBrowsingContext->GetOriginAttributes(attrs);
355 loadInfo = new LoadInfo(aBrowsingContext, aLoadState->TriggeringPrincipal(),
356 attrs, aOuterWindowId, securityFlags, sandboxFlags);
357 }
358
359 loadInfo->SetHasValidUserGestureActivation(
360 aLoadState->HasValidUserGestureActivation());
361
362 return loadInfo.forget();
363 }
364
GetBrowsingContext()365 CanonicalBrowsingContext* DocumentLoadListener::GetBrowsingContext() {
366 if (!mParentChannelListener) {
367 return nullptr;
368 }
369 return mParentChannelListener->GetBrowsingContext();
370 }
371
Open(nsDocShellLoadState * aLoadState,uint32_t aCacheKey,const Maybe<uint64_t> & aChannelId,const TimeStamp & aAsyncOpenTime,nsDOMNavigationTiming * aTiming,Maybe<ClientInfo> && aInfo,uint64_t aOuterWindowId,bool aHasGesture,Maybe<bool> aUriModified,Maybe<bool> aIsXFOError,nsresult * aRv)372 bool DocumentLoadListener::Open(
373 nsDocShellLoadState* aLoadState, uint32_t aCacheKey,
374 const Maybe<uint64_t>& aChannelId, const TimeStamp& aAsyncOpenTime,
375 nsDOMNavigationTiming* aTiming, Maybe<ClientInfo>&& aInfo,
376 uint64_t aOuterWindowId, bool aHasGesture, Maybe<bool> aUriModified,
377 Maybe<bool> aIsXFOError, nsresult* aRv) {
378 LOG(("DocumentLoadListener Open [this=%p, uri=%s]", this,
379 aLoadState->URI()->GetSpecOrDefault().get()));
380 RefPtr<CanonicalBrowsingContext> browsingContext =
381 mParentChannelListener->GetBrowsingContext();
382
383 OriginAttributes attrs;
384 browsingContext->GetOriginAttributes(attrs);
385
386 MOZ_DIAGNOSTIC_ASSERT_IF(browsingContext->GetParent(),
387 browsingContext->GetParentWindowContext());
388
389 // If this is a top-level load, then rebuild the LoadInfo from scratch,
390 // since the goal is to be able to initiate loads in the parent, where the
391 // content process won't have provided us with an existing one.
392 RefPtr<LoadInfo> loadInfo =
393 CreateLoadInfo(browsingContext, aLoadState, aOuterWindowId);
394
395 nsLoadFlags loadFlags = aLoadState->CalculateChannelLoadFlags(
396 browsingContext, std::move(aUriModified), std::move(aIsXFOError));
397
398 if (!nsDocShell::CreateAndConfigureRealChannelForLoadState(
399 browsingContext, aLoadState, loadInfo, mParentChannelListener,
400 nullptr, attrs, loadFlags, aCacheKey, *aRv,
401 getter_AddRefs(mChannel))) {
402 mParentChannelListener = nullptr;
403 return false;
404 }
405
406 nsCOMPtr<nsIURI> uriBeingLoaded =
407 AntiTrackingUtils::MaybeGetDocumentURIBeingLoaded(mChannel);
408
409 RefPtr<HttpBaseChannel> httpBaseChannel = do_QueryObject(mChannel, aRv);
410 if (httpBaseChannel) {
411 nsCOMPtr<nsIURI> topWindowURI;
412 if (browsingContext->IsTop()) {
413 // If this is for the top level loading, the top window URI should be the
414 // URI which we are loading.
415 topWindowURI = uriBeingLoaded;
416 } else if (RefPtr<WindowGlobalParent> topWindow = AntiTrackingUtils::
417 GetTopWindowExcludingExtensionAccessibleContentFrames(
418 browsingContext, uriBeingLoaded)) {
419 nsCOMPtr<nsIPrincipal> topWindowPrincipal =
420 topWindow->DocumentPrincipal();
421 if (topWindowPrincipal && !topWindowPrincipal->GetIsNullPrincipal()) {
422 auto* basePrin = BasePrincipal::Cast(topWindowPrincipal);
423 basePrin->GetURI(getter_AddRefs(topWindowURI));
424 }
425 }
426 httpBaseChannel->SetTopWindowURI(topWindowURI);
427 }
428
429 nsCOMPtr<nsIIdentChannel> identChannel = do_QueryInterface(mChannel);
430 if (identChannel && aChannelId) {
431 Unused << identChannel->SetChannelId(*aChannelId);
432 }
433
434 RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
435 if (httpChannelImpl) {
436 httpChannelImpl->SetWarningReporter(this);
437 }
438
439 nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(mChannel);
440 if (timedChannel) {
441 timedChannel->SetAsyncOpen(aAsyncOpenTime);
442 }
443
444 if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel)) {
445 Unused << httpChannel->SetRequestContextID(
446 browsingContext->GetRequestContextId());
447 }
448
449 // nsViewSourceChannel normally replaces the nsIRequest passed to
450 // OnStart/StopRequest with itself. We don't need this, and instead
451 // we want the original request so that we get different ones for
452 // each part of a multipart channel.
453 nsCOMPtr<nsIViewSourceChannel> viewSourceChannel;
454 if (OtherPid() && (viewSourceChannel = do_QueryInterface(mChannel))) {
455 viewSourceChannel->SetReplaceRequest(false);
456 }
457
458 // Setup a ClientChannelHelper to watch for redirects, and copy
459 // across any serviceworker related data between channels as needed.
460 AddClientChannelHelperInParent(mChannel, std::move(aInfo));
461
462 // Recalculate the openFlags, matching the logic in use in Content process.
463 // NOTE: The only case not handled here to mirror Content process is
464 // redirecting to re-use the channel.
465 MOZ_ASSERT(!aLoadState->GetPendingRedirectedChannel());
466 uint32_t openFlags = nsDocShell::ComputeURILoaderFlags(
467 browsingContext, aLoadState->LoadType());
468
469 RefPtr<ParentProcessDocumentOpenInfo> openInfo =
470 new ParentProcessDocumentOpenInfo(mParentChannelListener, openFlags,
471 browsingContext);
472 openInfo->Prepare();
473
474 #ifdef ANDROID
475 RefPtr<MozPromise<bool, bool, false>> promise;
476 if (aLoadState->LoadType() != LOAD_ERROR_PAGE &&
477 !(aLoadState->HasLoadFlags(
478 nsDocShell::INTERNAL_LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE)) &&
479 !(aLoadState->LoadType() & LOAD_HISTORY)) {
480 nsCOMPtr<nsIWidget> widget =
481 browsingContext->GetParentProcessWidgetContaining();
482 RefPtr<nsWindow> window = nsWindow::From(widget);
483
484 if (window) {
485 promise = window->OnLoadRequest(
486 aLoadState->URI(), nsIBrowserDOMWindow::OPEN_CURRENTWINDOW,
487 aLoadState->LoadFlags(), aLoadState->TriggeringPrincipal(),
488 aHasGesture, browsingContext->IsTopContent());
489 }
490 }
491
492 if (promise) {
493 RefPtr<DocumentLoadListener> self = this;
494 promise->Then(
495 GetCurrentThreadSerialEventTarget(), __func__,
496 [=](const MozPromise<bool, bool, false>::ResolveOrRejectValue& aValue) {
497 if (aValue.IsResolve()) {
498 bool handled = aValue.ResolveValue();
499 if (handled) {
500 self->DisconnectChildListeners(NS_ERROR_ABORT, NS_ERROR_ABORT);
501 mParentChannelListener = nullptr;
502 } else {
503 nsresult rv = mChannel->AsyncOpen(openInfo);
504 if (NS_FAILED(rv)) {
505 self->DisconnectChildListeners(NS_ERROR_ABORT, NS_ERROR_ABORT);
506 mParentChannelListener = nullptr;
507 }
508 }
509 }
510 });
511 } else
512 #endif /* ANDROID */
513 {
514 *aRv = mChannel->AsyncOpen(openInfo);
515 if (NS_FAILED(*aRv)) {
516 mParentChannelListener = nullptr;
517 return false;
518 }
519 }
520
521 mChannelCreationURI = aLoadState->URI();
522 mLoadStateLoadFlags = aLoadState->LoadFlags();
523 mLoadStateLoadType = aLoadState->LoadType();
524 mTiming = aTiming;
525 mSrcdocData = aLoadState->SrcdocData();
526 mBaseURI = aLoadState->BaseURI();
527 if (StaticPrefs::fission_sessionHistoryInParent() &&
528 browsingContext->GetSessionHistory()) {
529 mSessionHistoryInfo =
530 browsingContext->CreateSessionHistoryEntryForLoad(aLoadState, mChannel);
531 }
532
533 if (auto* ctx = GetBrowsingContext()) {
534 ctx->StartDocumentLoad(this);
535 }
536 return true;
537 }
538
539 /* static */
OpenFromParent(dom::CanonicalBrowsingContext * aBrowsingContext,nsDocShellLoadState * aLoadState,uint64_t aOuterWindowId,uint32_t * aOutIdent)540 bool DocumentLoadListener::OpenFromParent(
541 dom::CanonicalBrowsingContext* aBrowsingContext,
542 nsDocShellLoadState* aLoadState, uint64_t aOuterWindowId,
543 uint32_t* aOutIdent) {
544 LOG(("DocumentLoadListener::OpenFromParent"));
545
546 // We currently only support passing nullptr for aLoadInfo for
547 // top level browsing contexts.
548 if (!aBrowsingContext->IsTopContent() ||
549 !aBrowsingContext->GetContentParent()) {
550 LOG(("DocumentLoadListener::OpenFromParent failed because of subdoc"));
551 return false;
552 }
553
554 if (nsCOMPtr<nsIContentSecurityPolicy> csp = aLoadState->Csp()) {
555 // Check CSP navigate-to
556 bool allowsNavigateTo = false;
557 nsresult rv = csp->GetAllowsNavigateTo(aLoadState->URI(),
558 aLoadState->IsFormSubmission(),
559 false, /* aWasRedirected */
560 false, /* aEnforceWhitelist */
561 &allowsNavigateTo);
562 if (NS_FAILED(rv) || !allowsNavigateTo) {
563 return false;
564 }
565 }
566
567 // Any sort of reload/history load would need the cacheKey, and session
568 // history data for load flags. We don't have those available in the parent
569 // yet, so don't support these load types.
570 auto loadType = aLoadState->LoadType();
571 if (loadType == LOAD_HISTORY || loadType == LOAD_RELOAD_NORMAL ||
572 loadType == LOAD_RELOAD_CHARSET_CHANGE ||
573 loadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE ||
574 loadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE) {
575 LOG(
576 ("DocumentLoadListener::OpenFromParent failed because of history "
577 "load"));
578 return false;
579 }
580
581 // Clone because this mutates the load flags in the load state, which
582 // breaks nsDocShells expectations of being able to do it.
583 RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(*aLoadState);
584 loadState->CalculateLoadURIFlags();
585
586 RefPtr<nsDOMNavigationTiming> timing = new nsDOMNavigationTiming(nullptr);
587 timing->NotifyNavigationStart(
588 aBrowsingContext->GetIsActive()
589 ? nsDOMNavigationTiming::DocShellState::eActive
590 : nsDOMNavigationTiming::DocShellState::eInactive);
591
592 // We're not a history load or a reload, so we don't need this.
593 uint32_t cacheKey = 0;
594
595 // Loads start in the content process might have exposed a channel id to
596 // observers, so we need to preserve the value in the parent. That can't have
597 // happened here, so Nothing() is fine.
598 Maybe<uint64_t> channelId = Nothing();
599
600 // Initial client info is only relevant for subdocument loads, which we're
601 // not supporting yet.
602 Maybe<dom::ClientInfo> initialClientInfo;
603
604 RefPtr<DocumentLoadListener> listener = new DocumentLoadListener(
605 aBrowsingContext, aBrowsingContext->GetContentParent()->OtherPid());
606
607 nsresult rv;
608 bool result =
609 listener->Open(loadState, cacheKey, channelId, TimeStamp::Now(), timing,
610 std::move(initialClientInfo), aOuterWindowId, false,
611 Nothing(), Nothing(), &rv);
612 if (result) {
613 // Create an entry in the redirect channel registrar to
614 // allocate an identifier for this load.
615 nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
616 RedirectChannelRegistrar::GetOrCreate();
617 rv = registrar->RegisterChannel(nullptr, aOutIdent);
618 MOZ_ASSERT(NS_SUCCEEDED(rv));
619 // Register listener (as an nsIParentChannel) under our new identifier.
620 rv = registrar->LinkChannels(*aOutIdent, listener, nullptr);
621 MOZ_ASSERT(NS_SUCCEEDED(rv));
622 }
623 return result;
624 }
625
CleanupParentLoadAttempt(uint32_t aLoadIdent)626 void DocumentLoadListener::CleanupParentLoadAttempt(uint32_t aLoadIdent) {
627 nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
628 RedirectChannelRegistrar::GetOrCreate();
629
630 nsCOMPtr<nsIParentChannel> parentChannel;
631 registrar->GetParentChannel(aLoadIdent, getter_AddRefs(parentChannel));
632 RefPtr<DocumentLoadListener> loadListener = do_QueryObject(parentChannel);
633
634 if (loadListener) {
635 // If the load listener is still registered, then we must have failed
636 // to connect DocumentChannel into it. Better cancel it!
637 loadListener->NotifyBridgeFailed();
638 }
639
640 registrar->DeregisterChannels(aLoadIdent);
641 }
642
ClaimParentLoad(uint32_t aLoadIdent,ADocumentChannelBridge * aBridge)643 already_AddRefed<DocumentLoadListener> DocumentLoadListener::ClaimParentLoad(
644 uint32_t aLoadIdent, ADocumentChannelBridge* aBridge) {
645 nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
646 RedirectChannelRegistrar::GetOrCreate();
647
648 nsCOMPtr<nsIParentChannel> parentChannel;
649 registrar->GetParentChannel(aLoadIdent, getter_AddRefs(parentChannel));
650 RefPtr<DocumentLoadListener> loadListener = do_QueryObject(parentChannel);
651 registrar->DeregisterChannels(aLoadIdent);
652
653 MOZ_ASSERT(loadListener);
654 if (loadListener) {
655 loadListener->NotifyBridgeConnected(aBridge);
656 }
657 return loadListener.forget();
658 }
659
NotifyBridgeConnected(ADocumentChannelBridge * aBridge)660 void DocumentLoadListener::NotifyBridgeConnected(
661 ADocumentChannelBridge* aBridge) {
662 LOG(("DocumentLoadListener NotifyBridgeConnected [this=%p]", this));
663 MOZ_ASSERT(!mDocumentChannelBridge);
664 MOZ_ASSERT(mPendingDocumentChannelBridgeProcess);
665 MOZ_ASSERT(aBridge->OtherPid() == *mPendingDocumentChannelBridgeProcess);
666
667 mDocumentChannelBridge = aBridge;
668 mPendingDocumentChannelBridgeProcess.reset();
669 mBridgePromise.ResolveIfExists(aBridge, __func__);
670 }
671
NotifyBridgeFailed()672 void DocumentLoadListener::NotifyBridgeFailed() {
673 LOG(("DocumentLoadListener NotifyBridgeFailed [this=%p]", this));
674 MOZ_ASSERT(!mDocumentChannelBridge);
675 MOZ_ASSERT(mPendingDocumentChannelBridgeProcess);
676 mPendingDocumentChannelBridgeProcess.reset();
677
678 Cancel(NS_BINDING_ABORTED);
679
680 mBridgePromise.RejectIfExists(false, __func__);
681 }
682
DocumentChannelBridgeDisconnected()683 void DocumentLoadListener::DocumentChannelBridgeDisconnected() {
684 LOG(("DocumentLoadListener DocumentChannelBridgeDisconnected [this=%p]",
685 this));
686 // The nsHttpChannel may have a reference to this parent, release it
687 // to avoid circular references.
688 RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
689 if (httpChannelImpl) {
690 httpChannelImpl->SetWarningReporter(nullptr);
691 }
692 mDocumentChannelBridge = nullptr;
693
694 if (auto* ctx = GetBrowsingContext()) {
695 ctx->EndDocumentLoad(this);
696 }
697 }
698
Cancel(const nsresult & aStatusCode)699 void DocumentLoadListener::Cancel(const nsresult& aStatusCode) {
700 LOG(
701 ("DocumentLoadListener Cancel [this=%p, "
702 "aStatusCode=%" PRIx32 " ]",
703 this, static_cast<uint32_t>(aStatusCode)));
704 mCancelled = true;
705
706 if (mDoingProcessSwitch) {
707 // If we've already initiated process-switching
708 // then we can no longer be cancelled and we'll
709 // disconnect the old listeners when done.
710 return;
711 }
712
713 if (mChannel) {
714 mChannel->Cancel(aStatusCode);
715 }
716
717 DisconnectChildListeners(aStatusCode, aStatusCode);
718 }
719
DisconnectChildListeners(nsresult aStatus,nsresult aLoadGroupStatus)720 void DocumentLoadListener::DisconnectChildListeners(nsresult aStatus,
721 nsresult aLoadGroupStatus) {
722 LOG(
723 ("DocumentLoadListener DisconnectChildListener [this=%p, "
724 "aStatus=%" PRIx32 " aLoadGroupStatus=%" PRIx32 " ]",
725 this, static_cast<uint32_t>(aStatus),
726 static_cast<uint32_t>(aLoadGroupStatus)));
727 RefPtr<DocumentLoadListener> keepAlive(this);
728 if (mDocumentChannelBridge) {
729 // This will drop the bridge's reference to us, so we use keepAlive to
730 // make sure we don't get deleted until we exit the function.
731 mDocumentChannelBridge->DisconnectChildListeners(aStatus, aLoadGroupStatus);
732 } else if (mPendingDocumentChannelBridgeProcess) {
733 EnsureBridge()->Then(
734 GetCurrentThreadSerialEventTarget(), __func__,
735 [keepAlive, aStatus,
736 aLoadGroupStatus](ADocumentChannelBridge* aBridge) {
737 aBridge->DisconnectChildListeners(aStatus, aLoadGroupStatus);
738 keepAlive->mDocumentChannelBridge = nullptr;
739 },
740 [](bool aDummy) {});
741 }
742 DocumentChannelBridgeDisconnected();
743
744 // If we're not going to send anything else to the content process, and
745 // we haven't yet consumed a stream filter promise, then we're never going
746 // to.
747 // TODO: This might be because we retargeted the stream to the download
748 // handler or similar. Do we need to attach a stream filter to that?
749 mStreamFilterRequests.Clear();
750 }
751
RedirectToRealChannelFinished(nsresult aRv)752 void DocumentLoadListener::RedirectToRealChannelFinished(nsresult aRv) {
753 LOG(
754 ("DocumentLoadListener RedirectToRealChannelFinished [this=%p, "
755 "aRv=%" PRIx32 " ]",
756 this, static_cast<uint32_t>(aRv)));
757 if (NS_FAILED(aRv) || !mRedirectChannelId) {
758 FinishReplacementChannelSetup(aRv);
759 return;
760 }
761
762 // Wait for background channel ready on target channel
763 nsCOMPtr<nsIRedirectChannelRegistrar> redirectReg =
764 RedirectChannelRegistrar::GetOrCreate();
765 MOZ_ASSERT(redirectReg);
766
767 nsCOMPtr<nsIParentChannel> redirectParentChannel;
768 redirectReg->GetParentChannel(mRedirectChannelId,
769 getter_AddRefs(redirectParentChannel));
770 if (!redirectParentChannel) {
771 FinishReplacementChannelSetup(NS_ERROR_FAILURE);
772 return;
773 }
774
775 nsCOMPtr<nsIParentRedirectingChannel> redirectingParent =
776 do_QueryInterface(redirectParentChannel);
777 if (!redirectingParent) {
778 // Continue verification procedure if redirecting to non-Http protocol
779 FinishReplacementChannelSetup(NS_OK);
780 return;
781 }
782
783 // Ask redirected channel if verification can proceed.
784 // ReadyToVerify will be invoked when redirected channel is ready.
785 redirectingParent->ContinueVerification(this);
786 }
787
788 NS_IMETHODIMP
ReadyToVerify(nsresult aResultCode)789 DocumentLoadListener::ReadyToVerify(nsresult aResultCode) {
790 FinishReplacementChannelSetup(aResultCode);
791 return NS_OK;
792 }
793
FinishReplacementChannelSetup(nsresult aResult)794 void DocumentLoadListener::FinishReplacementChannelSetup(nsresult aResult) {
795 LOG(
796 ("DocumentLoadListener FinishReplacementChannelSetup [this=%p, "
797 "aResult=%x]",
798 this, int(aResult)));
799
800 if (mDoingProcessSwitch) {
801 DisconnectChildListeners(NS_BINDING_ABORTED, NS_BINDING_ABORTED);
802 }
803
804 if (!mRedirectChannelId) {
805 if (NS_FAILED(aResult)) {
806 mChannel->Cancel(aResult);
807 mChannel->Resume();
808 DisconnectChildListeners(aResult, aResult);
809 return;
810 }
811 ApplyPendingFunctions(mChannel);
812 // ResumeSuspendedChannel will be called later as RedirectToRealChannel
813 // continues, so we can return early.
814 return;
815 }
816
817 nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
818 RedirectChannelRegistrar::GetOrCreate();
819 MOZ_ASSERT(registrar);
820
821 nsCOMPtr<nsIParentChannel> redirectChannel;
822 nsresult rv = registrar->GetParentChannel(mRedirectChannelId,
823 getter_AddRefs(redirectChannel));
824 if (NS_FAILED(rv) || !redirectChannel) {
825 // Redirect might get canceled before we got AsyncOnChannelRedirect
826 nsCOMPtr<nsIChannel> newChannel;
827 rv = registrar->GetRegisteredChannel(mRedirectChannelId,
828 getter_AddRefs(newChannel));
829 MOZ_ASSERT(newChannel, "Already registered channel not found");
830
831 if (NS_SUCCEEDED(rv)) {
832 newChannel->Cancel(NS_ERROR_FAILURE);
833 }
834 if (!redirectChannel) {
835 aResult = NS_ERROR_FAILURE;
836 }
837 }
838
839 // Release all previously registered channels, they are no longer needed to
840 // be kept in the registrar from this moment.
841 registrar->DeregisterChannels(mRedirectChannelId);
842 mRedirectChannelId = 0;
843 if (NS_FAILED(aResult)) {
844 if (redirectChannel) {
845 redirectChannel->Delete();
846 }
847 mChannel->Cancel(aResult);
848 mChannel->Resume();
849 if (auto* ctx = GetBrowsingContext()) {
850 ctx->EndDocumentLoad(this);
851 }
852 return;
853 }
854
855 MOZ_ASSERT(
856 !SameCOMIdentity(redirectChannel, static_cast<nsIParentChannel*>(this)));
857
858 Delete();
859 redirectChannel->SetParentListener(mParentChannelListener);
860
861 ApplyPendingFunctions(redirectChannel);
862
863 ResumeSuspendedChannel(redirectChannel);
864 }
865
ApplyPendingFunctions(nsISupports * aChannel) const866 void DocumentLoadListener::ApplyPendingFunctions(nsISupports* aChannel) const {
867 // We stored the values from all nsIParentChannel functions called since we
868 // couldn't handle them. Copy them across to the real channel since it
869 // should know what to do.
870
871 nsCOMPtr<nsIParentChannel> parentChannel = do_QueryInterface(aChannel);
872 if (parentChannel) {
873 for (auto& variant : mIParentChannelFunctions) {
874 variant.match(
875 [parentChannel](const nsIHttpChannel::FlashPluginState& aState) {
876 parentChannel->NotifyFlashPluginStateChanged(aState);
877 },
878 [parentChannel](const ClassifierMatchedInfoParams& aParams) {
879 parentChannel->SetClassifierMatchedInfo(
880 aParams.mList, aParams.mProvider, aParams.mFullHash);
881 },
882 [parentChannel](const ClassifierMatchedTrackingInfoParams& aParams) {
883 parentChannel->SetClassifierMatchedTrackingInfo(
884 aParams.mLists, aParams.mFullHashes);
885 },
886 [parentChannel](const ClassificationFlagsParams& aParams) {
887 parentChannel->NotifyClassificationFlags(
888 aParams.mClassificationFlags, aParams.mIsThirdParty);
889 });
890 }
891 } else {
892 for (auto& variant : mIParentChannelFunctions) {
893 variant.match(
894 [&](const nsIHttpChannel::FlashPluginState& aState) {
895 // For now, only HttpChannel use this attribute.
896 RefPtr<HttpBaseChannel> httpChannel = do_QueryObject(aChannel);
897 if (httpChannel) {
898 httpChannel->SetFlashPluginState(aState);
899 }
900 },
901 [&](const ClassifierMatchedInfoParams& aParams) {
902 nsCOMPtr<nsIClassifiedChannel> classifiedChannel =
903 do_QueryInterface(aChannel);
904 if (classifiedChannel) {
905 classifiedChannel->SetMatchedInfo(
906 aParams.mList, aParams.mProvider, aParams.mFullHash);
907 }
908 },
909 [&](const ClassifierMatchedTrackingInfoParams& aParams) {
910 nsCOMPtr<nsIClassifiedChannel> classifiedChannel =
911 do_QueryInterface(aChannel);
912 if (classifiedChannel) {
913 nsTArray<nsCString> lists, fullhashes;
914 for (const nsACString& token : aParams.mLists.Split(',')) {
915 lists.AppendElement(token);
916 }
917 for (const nsACString& token : aParams.mFullHashes.Split(',')) {
918 fullhashes.AppendElement(token);
919 }
920 classifiedChannel->SetMatchedTrackingInfo(lists, fullhashes);
921 }
922 },
923 [&](const ClassificationFlagsParams& aParams) {
924 UrlClassifierCommon::SetClassificationFlagsHelper(
925 static_cast<nsIChannel*>(aChannel),
926 aParams.mClassificationFlags, aParams.mIsThirdParty);
927 });
928 }
929 }
930
931 RefPtr<HttpChannelSecurityWarningReporter> reporter;
932 if (RefPtr<HttpChannelParent> httpParent = do_QueryObject(aChannel)) {
933 reporter = httpParent;
934 } else if (RefPtr<nsHttpChannel> httpChannel = do_QueryObject(aChannel)) {
935 reporter = httpChannel->GetWarningReporter();
936 }
937 if (reporter) {
938 for (auto& variant : mSecurityWarningFunctions) {
939 variant.match(
940 [reporter](const ReportSecurityMessageParams& aParams) {
941 Unused << reporter->ReportSecurityMessage(aParams.mMessageTag,
942 aParams.mMessageCategory);
943 },
944 [reporter](const LogBlockedCORSRequestParams& aParams) {
945 Unused << reporter->LogBlockedCORSRequest(aParams.mMessage,
946 aParams.mCategory);
947 },
948 [reporter](const LogMimeTypeMismatchParams& aParams) {
949 Unused << reporter->LogMimeTypeMismatch(
950 aParams.mMessageName, aParams.mWarning, aParams.mURL,
951 aParams.mContentType);
952 });
953 }
954 }
955 }
956
ResumeSuspendedChannel(nsIStreamListener * aListener)957 bool DocumentLoadListener::ResumeSuspendedChannel(
958 nsIStreamListener* aListener) {
959 LOG(("DocumentLoadListener ResumeSuspendedChannel [this=%p]", this));
960 RefPtr<nsHttpChannel> httpChannel = do_QueryObject(mChannel);
961 if (httpChannel) {
962 httpChannel->SetApplyConversion(mOldApplyConversion);
963 }
964
965 if (!mIsFinished) {
966 mParentChannelListener->SetListenerAfterRedirect(aListener);
967 }
968
969 // If we failed to suspend the channel, then we might have received
970 // some messages while the redirected was being handled.
971 // Manually send them on now.
972 nsTArray<StreamListenerFunction> streamListenerFunctions =
973 std::move(mStreamListenerFunctions);
974 if (!aListener) {
975 streamListenerFunctions.Clear();
976 }
977 nsresult rv = NS_OK;
978 for (auto& variant : streamListenerFunctions) {
979 variant.match(
980 [&](const OnStartRequestParams& aParams) {
981 rv = aListener->OnStartRequest(aParams.request);
982 if (NS_FAILED(rv)) {
983 aParams.request->Cancel(rv);
984 }
985 },
986 [&](const OnDataAvailableParams& aParams) {
987 // Don't deliver OnDataAvailable if we've
988 // already failed.
989 if (NS_FAILED(rv)) {
990 return;
991 }
992 nsCOMPtr<nsIInputStream> stringStream;
993 rv = NS_NewByteInputStream(
994 getter_AddRefs(stringStream),
995 Span<const char>(aParams.data.get(), aParams.count),
996 NS_ASSIGNMENT_DEPEND);
997 if (NS_SUCCEEDED(rv)) {
998 rv = aListener->OnDataAvailable(aParams.request, stringStream,
999 aParams.offset, aParams.count);
1000 }
1001 if (NS_FAILED(rv)) {
1002 aParams.request->Cancel(rv);
1003 }
1004 },
1005 [&](const OnStopRequestParams& aParams) {
1006 if (NS_SUCCEEDED(rv)) {
1007 aListener->OnStopRequest(aParams.request, aParams.status);
1008 } else {
1009 aListener->OnStopRequest(aParams.request, rv);
1010 }
1011 rv = NS_OK;
1012 },
1013 [&](const OnAfterLastPartParams& aParams) {
1014 nsCOMPtr<nsIMultiPartChannelListener> multiListener =
1015 do_QueryInterface(aListener);
1016 if (multiListener) {
1017 if (NS_SUCCEEDED(rv)) {
1018 multiListener->OnAfterLastPart(aParams.status);
1019 } else {
1020 multiListener->OnAfterLastPart(rv);
1021 }
1022 }
1023 });
1024 }
1025 // We don't expect to get new stream listener functions added
1026 // via re-entrancy. If this ever happens, we should understand
1027 // exactly why before allowing it.
1028 NS_ASSERTION(mStreamListenerFunctions.IsEmpty(),
1029 "Should not have added new stream listener function!");
1030
1031 mChannel->Resume();
1032
1033 if (auto* ctx = GetBrowsingContext()) {
1034 ctx->EndDocumentLoad(this);
1035 }
1036
1037 return !mIsFinished;
1038 }
1039
SerializeRedirectData(RedirectToRealChannelArgs & aArgs,bool aIsCrossProcess,uint32_t aRedirectFlags,uint32_t aLoadFlags,ContentParent * aParent) const1040 void DocumentLoadListener::SerializeRedirectData(
1041 RedirectToRealChannelArgs& aArgs, bool aIsCrossProcess,
1042 uint32_t aRedirectFlags, uint32_t aLoadFlags,
1043 ContentParent* aParent) const {
1044 // Use the original URI of the current channel, as this is what
1045 // we'll use to construct the channel in the content process.
1046 aArgs.uri() = mChannelCreationURI;
1047 if (!aArgs.uri()) {
1048 mChannel->GetOriginalURI(getter_AddRefs(aArgs.uri()));
1049 }
1050
1051 // I previously used HttpBaseChannel::CloneLoadInfoForRedirect, but that
1052 // clears the principal to inherit, which fails tests (probably because this
1053 // 'redirect' is usually just an implementation detail). It's also http
1054 // only, and mChannel can be anything that we redirected to.
1055 nsCOMPtr<nsILoadInfo> channelLoadInfo;
1056 mChannel->GetLoadInfo(getter_AddRefs(channelLoadInfo));
1057
1058 nsCOMPtr<nsIPrincipal> principalToInherit;
1059 channelLoadInfo->GetPrincipalToInherit(getter_AddRefs(principalToInherit));
1060
1061 const RefPtr<nsHttpChannel> baseChannel = do_QueryObject(mChannel.get());
1062
1063 nsCOMPtr<nsILoadContext> loadContext;
1064 NS_QueryNotificationCallbacks(mChannel, loadContext);
1065 nsCOMPtr<nsILoadInfo> redirectLoadInfo;
1066
1067 // Only use CloneLoadInfoForRedirect if we have a load context,
1068 // since it internally tries to pull OriginAttributes from the
1069 // the load context and asserts if they don't match the load info.
1070 // We can end up without a load context if the channel has been aborted
1071 // and the callbacks have been cleared.
1072 if (baseChannel && loadContext) {
1073 redirectLoadInfo = baseChannel->CloneLoadInfoForRedirect(
1074 aArgs.uri(), nsIChannelEventSink::REDIRECT_INTERNAL);
1075 redirectLoadInfo->SetResultPrincipalURI(aArgs.uri());
1076
1077 // The clone process clears this, and then we fail tests..
1078 // docshell/test/mochitest/test_forceinheritprincipal_overrule_owner.html
1079 if (principalToInherit) {
1080 redirectLoadInfo->SetPrincipalToInherit(principalToInherit);
1081 }
1082 } else {
1083 redirectLoadInfo =
1084 static_cast<mozilla::net::LoadInfo*>(channelLoadInfo.get())->Clone();
1085
1086 nsCOMPtr<nsIPrincipal> uriPrincipal;
1087 nsIScriptSecurityManager* sm = nsContentUtils::GetSecurityManager();
1088 sm->GetChannelURIPrincipal(mChannel, getter_AddRefs(uriPrincipal));
1089
1090 nsCOMPtr<nsIRedirectHistoryEntry> entry =
1091 new nsRedirectHistoryEntry(uriPrincipal, nullptr, EmptyCString());
1092
1093 redirectLoadInfo->AppendRedirectHistoryEntry(entry, true);
1094 }
1095
1096 const Maybe<ClientInfo>& reservedClientInfo =
1097 channelLoadInfo->GetReservedClientInfo();
1098 if (reservedClientInfo) {
1099 redirectLoadInfo->SetReservedClientInfo(*reservedClientInfo);
1100 }
1101
1102 aArgs.registrarId() = mRedirectChannelId;
1103
1104 MOZ_ALWAYS_SUCCEEDS(
1105 ipc::LoadInfoToLoadInfoArgs(redirectLoadInfo, &aArgs.loadInfo()));
1106
1107 mChannel->GetOriginalURI(getter_AddRefs(aArgs.originalURI()));
1108
1109 // mChannel can be a nsHttpChannel as well as InterceptedHttpChannel so we
1110 // can't use baseChannel here.
1111 if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel)) {
1112 MOZ_ALWAYS_SUCCEEDS(httpChannel->GetChannelId(&aArgs.channelId()));
1113 }
1114
1115 aArgs.redirectMode() = nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW;
1116 nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
1117 do_QueryInterface(mChannel);
1118 if (httpChannelInternal) {
1119 MOZ_ALWAYS_SUCCEEDS(
1120 httpChannelInternal->GetRedirectMode(&aArgs.redirectMode()));
1121 }
1122
1123 if (baseChannel) {
1124 aArgs.init() =
1125 Some(baseChannel
1126 ->CloneReplacementChannelConfig(
1127 true, aRedirectFlags,
1128 HttpBaseChannel::ReplacementReason::DocumentChannel)
1129 .Serialize(aParent));
1130 }
1131
1132 uint32_t contentDispositionTemp;
1133 nsresult rv = mChannel->GetContentDisposition(&contentDispositionTemp);
1134 if (NS_SUCCEEDED(rv)) {
1135 aArgs.contentDisposition() = Some(contentDispositionTemp);
1136 }
1137
1138 nsString contentDispositionFilenameTemp;
1139 rv = mChannel->GetContentDispositionFilename(contentDispositionFilenameTemp);
1140 if (NS_SUCCEEDED(rv)) {
1141 aArgs.contentDispositionFilename() = Some(contentDispositionFilenameTemp);
1142 }
1143
1144 SetNeedToAddURIVisit(mChannel, false);
1145
1146 aArgs.newLoadFlags() = aLoadFlags;
1147 aArgs.redirectFlags() = aRedirectFlags;
1148 aArgs.redirectIdentifier() = mCrossProcessRedirectIdentifier;
1149 aArgs.properties() = do_QueryObject(mChannel.get());
1150 aArgs.srcdocData() = mSrcdocData;
1151 aArgs.baseUri() = mBaseURI;
1152 aArgs.loadStateLoadFlags() = mLoadStateLoadFlags;
1153 aArgs.loadStateLoadType() = mLoadStateLoadType;
1154 if (mSessionHistoryInfo) {
1155 aArgs.sessionHistoryInfo().emplace(
1156 mSessionHistoryInfo->mId, MakeUnique<mozilla::dom::SessionHistoryInfo>(
1157 *mSessionHistoryInfo->mInfo));
1158 }
1159 }
1160
MaybeTriggerProcessSwitch(bool * aWillSwitchToRemote)1161 bool DocumentLoadListener::MaybeTriggerProcessSwitch(
1162 bool* aWillSwitchToRemote) {
1163 MOZ_ASSERT(XRE_IsParentProcess());
1164 MOZ_DIAGNOSTIC_ASSERT(!mDoingProcessSwitch,
1165 "Already in the middle of switching?");
1166 MOZ_DIAGNOSTIC_ASSERT(mChannel);
1167 MOZ_DIAGNOSTIC_ASSERT(mParentChannelListener);
1168 MOZ_DIAGNOSTIC_ASSERT(aWillSwitchToRemote);
1169
1170 LOG(("DocumentLoadListener MaybeTriggerProcessSwitch [this=%p]", this));
1171
1172 // Get the BrowsingContext which will be switching processes.
1173 RefPtr<CanonicalBrowsingContext> browsingContext =
1174 mParentChannelListener->GetBrowsingContext();
1175 if (NS_WARN_IF(!browsingContext)) {
1176 LOG(("Process Switch Abort: no browsing context"));
1177 return false;
1178 }
1179 if (!browsingContext->IsContent()) {
1180 LOG(("Process Switch Abort: non-content browsing context"));
1181 return false;
1182 }
1183 if (browsingContext->GetParent() && !browsingContext->UseRemoteSubframes()) {
1184 LOG(("Process Switch Abort: remote subframes disabled"));
1185 return false;
1186 }
1187
1188 if (browsingContext->GetParentWindowContext() &&
1189 browsingContext->GetParentWindowContext()->IsInProcess()) {
1190 LOG(("Process Switch Abort: Subframe with in-process parent"));
1191 return false;
1192 }
1193
1194 // We currently can't switch processes for toplevel loads unless they're
1195 // loaded within a browser tab.
1196 // FIXME: Ideally we won't do this in the future.
1197 nsCOMPtr<nsIBrowser> browser;
1198 bool isPreloadSwitch = false;
1199 if (!browsingContext->GetParent()) {
1200 Element* browserElement = browsingContext->GetEmbedderElement();
1201 if (!browserElement) {
1202 LOG(("Process Switch Abort: cannot get browser element"));
1203 return false;
1204 }
1205 browser = browserElement->AsBrowser();
1206 if (!browser) {
1207 LOG(("Process Switch Abort: not loaded within nsIBrowser"));
1208 return false;
1209 }
1210 bool loadedInTab = false;
1211 if (NS_FAILED(browser->GetCanPerformProcessSwitch(&loadedInTab)) ||
1212 !loadedInTab) {
1213 LOG(("Process Switch Abort: browser is not loaded in a tab"));
1214 return false;
1215 }
1216
1217 // Leaving about:newtab from a used to be preloaded browser should run the
1218 // process selecting algorithm again.
1219 nsAutoString isPreloadBrowserStr;
1220 if (browserElement->GetAttr(kNameSpaceID_None, nsGkAtoms::preloadedState,
1221 isPreloadBrowserStr)) {
1222 if (isPreloadBrowserStr.EqualsLiteral("consumed")) {
1223 nsCOMPtr<nsIURI> originalURI;
1224 if (NS_SUCCEEDED(
1225 mChannel->GetOriginalURI(getter_AddRefs(originalURI)))) {
1226 if (!originalURI->GetSpecOrDefault().EqualsLiteral("about:newtab")) {
1227 LOG(("Process Switch: leaving preloaded browser"));
1228 isPreloadSwitch = true;
1229 browserElement->UnsetAttr(kNameSpaceID_None,
1230 nsGkAtoms::preloadedState, true);
1231 }
1232 }
1233 }
1234 }
1235 }
1236
1237 // Get information about the current document loaded in our BrowsingContext.
1238 nsCOMPtr<nsIPrincipal> currentPrincipal;
1239 if (RefPtr<WindowGlobalParent> wgp =
1240 browsingContext->GetCurrentWindowGlobal()) {
1241 currentPrincipal = wgp->DocumentPrincipal();
1242 }
1243 RefPtr<ContentParent> contentParent = browsingContext->GetContentParent();
1244 MOZ_ASSERT(!OtherPid() || contentParent,
1245 "Only PPDC is allowed to not have an existing ContentParent");
1246
1247 // Get the final principal, used to select which process to load into.
1248 nsCOMPtr<nsIPrincipal> resultPrincipal;
1249 nsresult rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
1250 mChannel, getter_AddRefs(resultPrincipal));
1251 if (NS_FAILED(rv)) {
1252 LOG(("Process Switch Abort: failed to get channel result principal"));
1253 return false;
1254 }
1255
1256 // Determine our COOP status, which will be used to determine our preferred
1257 // remote type.
1258 bool isCOOPSwitch = HasCrossOriginOpenerPolicyMismatch();
1259 nsILoadInfo::CrossOriginOpenerPolicy coop =
1260 nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
1261 if (!browsingContext->IsTop()) {
1262 coop = browsingContext->Top()->GetOpenerPolicy();
1263 } else if (nsCOMPtr<nsIHttpChannelInternal> httpChannel =
1264 do_QueryInterface(mChannel)) {
1265 MOZ_ALWAYS_SUCCEEDS(httpChannel->GetCrossOriginOpenerPolicy(&coop));
1266 }
1267
1268 nsAutoString currentRemoteType;
1269 if (contentParent) {
1270 currentRemoteType = contentParent->GetRemoteType();
1271 } else {
1272 currentRemoteType = VoidString();
1273 }
1274 nsAutoString preferredRemoteType = currentRemoteType;
1275 if (coop ==
1276 nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP) {
1277 // We want documents with SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP COOP
1278 // policy to be loaded in a separate process in which we can enable
1279 // high-resolution timers.
1280 nsAutoCString siteOrigin;
1281 resultPrincipal->GetSiteOrigin(siteOrigin);
1282 preferredRemoteType.Assign(
1283 NS_LITERAL_STRING(WITH_COOP_COEP_REMOTE_TYPE_PREFIX));
1284 preferredRemoteType.Append(NS_ConvertUTF8toUTF16(siteOrigin));
1285 } else if (isCOOPSwitch) {
1286 // If we're doing a COOP switch, we do not need any affinity to the current
1287 // remote type. Clear it back to the default value.
1288 preferredRemoteType.Assign(NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE));
1289 }
1290 MOZ_DIAGNOSTIC_ASSERT(!contentParent || !preferredRemoteType.IsEmpty(),
1291 "Unexpected empty remote type!");
1292
1293 LOG(
1294 ("DocumentLoadListener GetRemoteTypeForPrincipal "
1295 "[this=%p, contentParent=%s, preferredRemoteType=%s]",
1296 this, NS_ConvertUTF16toUTF8(currentRemoteType).get(),
1297 NS_ConvertUTF16toUTF8(preferredRemoteType).get()));
1298
1299 nsCOMPtr<nsIE10SUtils> e10sUtils =
1300 do_ImportModule("resource://gre/modules/E10SUtils.jsm", "E10SUtils");
1301 if (!e10sUtils) {
1302 LOG(("Process Switch Abort: Could not import E10SUtils"));
1303 return false;
1304 }
1305
1306 nsAutoString remoteType;
1307 rv = e10sUtils->GetRemoteTypeForPrincipal(
1308 resultPrincipal, mChannelCreationURI, browsingContext->UseRemoteTabs(),
1309 browsingContext->UseRemoteSubframes(), preferredRemoteType,
1310 currentPrincipal, browsingContext->GetParent(), remoteType);
1311 if (NS_WARN_IF(NS_FAILED(rv))) {
1312 LOG(("Process Switch Abort: getRemoteTypeForPrincipal threw an exception"));
1313 return false;
1314 }
1315
1316 LOG(("GetRemoteTypeForPrincipal -> current:%s remoteType:%s",
1317 NS_ConvertUTF16toUTF8(currentRemoteType).get(),
1318 NS_ConvertUTF16toUTF8(remoteType).get()));
1319
1320 // Check if a process switch is needed.
1321 if (currentRemoteType == remoteType && !isCOOPSwitch && !isPreloadSwitch) {
1322 LOG(("Process Switch Abort: type (%s) is compatible",
1323 NS_ConvertUTF16toUTF8(remoteType).get()));
1324 return false;
1325 }
1326 if (NS_WARN_IF(remoteType.IsEmpty())) {
1327 LOG(("Process Switch Abort: non-remote target process"));
1328 return false;
1329 }
1330
1331 *aWillSwitchToRemote = !remoteType.IsEmpty();
1332
1333 LOG(("Process Switch: Changing Remoteness from '%s' to '%s'",
1334 NS_ConvertUTF16toUTF8(currentRemoteType).get(),
1335 NS_ConvertUTF16toUTF8(remoteType).get()));
1336
1337 // XXX: This is super hacky, and we should be able to do something better.
1338 static uint64_t sNextCrossProcessRedirectIdentifier = 0;
1339 mCrossProcessRedirectIdentifier = ++sNextCrossProcessRedirectIdentifier;
1340 mDoingProcessSwitch = true;
1341
1342 RefPtr<DocumentLoadListener> self = this;
1343 // At this point, we're actually going to perform a process switch, which
1344 // involves calling into other logic.
1345 if (browsingContext->GetParent()) {
1346 LOG(("Process Switch: Calling ChangeFrameRemoteness"));
1347 // If we're switching a subframe, ask BrowsingContext to do it for us.
1348 MOZ_ASSERT(!isCOOPSwitch);
1349 browsingContext
1350 ->ChangeFrameRemoteness(remoteType, mCrossProcessRedirectIdentifier)
1351 ->Then(
1352 GetMainThreadSerialEventTarget(), __func__,
1353 [self](BrowserParent* aBrowserParent) {
1354 MOZ_ASSERT(self->mChannel,
1355 "Something went wrong, channel got cancelled");
1356 self->TriggerRedirectToRealChannel(
1357 Some(aBrowserParent->Manager()->ChildID()));
1358 },
1359 [self](nsresult aStatusCode) {
1360 MOZ_ASSERT(NS_FAILED(aStatusCode), "Status should be error");
1361 self->RedirectToRealChannelFinished(aStatusCode);
1362 });
1363 return true;
1364 }
1365
1366 LOG(("Process Switch: Calling nsIBrowser::PerformProcessSwitch"));
1367 // We're switching a toplevel BrowsingContext's process. This has to be done
1368 // using nsIBrowser.
1369 RefPtr<Promise> domPromise;
1370 browser->PerformProcessSwitch(remoteType, mCrossProcessRedirectIdentifier,
1371 isCOOPSwitch, getter_AddRefs(domPromise));
1372 MOZ_DIAGNOSTIC_ASSERT(domPromise,
1373 "PerformProcessSwitch didn't return a promise");
1374
1375 MozPromise<uint64_t, nsresult, true>::FromDomPromise(domPromise)
1376 ->Then(
1377 GetMainThreadSerialEventTarget(), __func__,
1378 [self](uint64_t aCpId) {
1379 MOZ_ASSERT(self->mChannel,
1380 "Something went wrong, channel got cancelled");
1381 self->TriggerRedirectToRealChannel(Some(aCpId));
1382 },
1383 [self](nsresult aStatusCode) {
1384 MOZ_ASSERT(NS_FAILED(aStatusCode), "Status should be error");
1385 self->RedirectToRealChannelFinished(aStatusCode);
1386 });
1387 return true;
1388 }
1389
EnsureBridge()1390 auto DocumentLoadListener::EnsureBridge() -> RefPtr<EnsureBridgePromise> {
1391 MOZ_ASSERT(mDocumentChannelBridge || mPendingDocumentChannelBridgeProcess);
1392 if (mDocumentChannelBridge) {
1393 MOZ_ASSERT(mBridgePromise.IsEmpty());
1394 return EnsureBridgePromise::CreateAndResolve(mDocumentChannelBridge,
1395 __func__);
1396 }
1397
1398 return mBridgePromise.Ensure(__func__);
1399 }
1400
1401 RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
RedirectToRealChannel(uint32_t aRedirectFlags,uint32_t aLoadFlags,const Maybe<uint64_t> & aDestinationProcess,nsTArray<ParentEndpoint> && aStreamFilterEndpoints)1402 DocumentLoadListener::RedirectToRealChannel(
1403 uint32_t aRedirectFlags, uint32_t aLoadFlags,
1404 const Maybe<uint64_t>& aDestinationProcess,
1405 nsTArray<ParentEndpoint>&& aStreamFilterEndpoints) {
1406 LOG(
1407 ("DocumentLoadListener RedirectToRealChannel [this=%p] "
1408 "aRedirectFlags=%" PRIx32 ", aLoadFlags=%" PRIx32,
1409 this, aRedirectFlags, aLoadFlags));
1410
1411 // TODO(djg): Add the last URI visit to history if success. Is there a better
1412 // place to handle this? Need access to the updated aLoadFlags.
1413 nsresult status = NS_OK;
1414 mChannel->GetStatus(&status);
1415 bool updateGHistory =
1416 nsDocShell::ShouldUpdateGlobalHistory(mLoadStateLoadType);
1417 if (NS_SUCCEEDED(status) && updateGHistory && !net::ChannelIsPost(mChannel)) {
1418 AddURIVisit(mChannel, aLoadFlags);
1419 }
1420
1421 if (aDestinationProcess || OtherPid()) {
1422 // Register the new channel and obtain id for it
1423 nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
1424 RedirectChannelRegistrar::GetOrCreate();
1425 MOZ_ASSERT(registrar);
1426 MOZ_ALWAYS_SUCCEEDS(
1427 registrar->RegisterChannel(mChannel, &mRedirectChannelId));
1428 }
1429
1430 if (aDestinationProcess) {
1431 dom::ContentParent* cp =
1432 dom::ContentProcessManager::GetSingleton()->GetContentProcessById(
1433 ContentParentId{*aDestinationProcess});
1434 if (!cp) {
1435 return PDocumentChannelParent::RedirectToRealChannelPromise::
1436 CreateAndReject(ipc::ResponseRejectReason::SendError, __func__);
1437 }
1438
1439 RedirectToRealChannelArgs args;
1440 SerializeRedirectData(args, !!aDestinationProcess, aRedirectFlags,
1441 aLoadFlags, cp);
1442 if (mTiming) {
1443 mTiming->Anonymize(args.uri());
1444 args.timing() = Some(std::move(mTiming));
1445 }
1446
1447 auto loadInfo = args.loadInfo();
1448
1449 if (loadInfo.isNothing()) {
1450 return PDocumentChannelParent::RedirectToRealChannelPromise::
1451 CreateAndReject(ipc::ResponseRejectReason::SendError, __func__);
1452 }
1453
1454 auto triggeringPrincipalOrErr =
1455 PrincipalInfoToPrincipal(loadInfo.ref().triggeringPrincipalInfo());
1456
1457 if (triggeringPrincipalOrErr.isOk()) {
1458 nsCOMPtr<nsIPrincipal> triggeringPrincipal =
1459 triggeringPrincipalOrErr.unwrap();
1460 cp->TransmitBlobDataIfBlobURL(args.uri(), triggeringPrincipal);
1461 }
1462
1463 return cp->SendCrossProcessRedirect(args,
1464 std::move(aStreamFilterEndpoints));
1465 }
1466
1467 return EnsureBridge()->Then(
1468 GetCurrentThreadSerialEventTarget(), __func__,
1469 [self = RefPtr<DocumentLoadListener>(this),
1470 endpoints = std::move(aStreamFilterEndpoints), aRedirectFlags,
1471 aLoadFlags](ADocumentChannelBridge* aBridge) mutable {
1472 if (self->mCancelled) {
1473 return PDocumentChannelParent::RedirectToRealChannelPromise::
1474 CreateAndResolve(NS_BINDING_ABORTED, __func__);
1475 }
1476 return aBridge->RedirectToRealChannel(std::move(endpoints),
1477 aRedirectFlags, aLoadFlags);
1478 },
1479 [](bool aDummy) {
1480 return PDocumentChannelParent::RedirectToRealChannelPromise::
1481 CreateAndReject(ipc::ResponseRejectReason::ActorDestroyed,
1482 __func__);
1483 });
1484 }
1485
TriggerRedirectToRealChannel(const Maybe<uint64_t> & aDestinationProcess)1486 void DocumentLoadListener::TriggerRedirectToRealChannel(
1487 const Maybe<uint64_t>& aDestinationProcess) {
1488 LOG((
1489 "DocumentLoadListener::TriggerRedirectToRealChannel [this=%p] "
1490 "aDestinationProcess=%" PRId64,
1491 this, aDestinationProcess ? int64_t(*aDestinationProcess) : int64_t(-1)));
1492 // This initiates replacing the current DocumentChannel with a
1493 // protocol specific 'real' channel, maybe in a different process than
1494 // the current DocumentChannelChild, if aDestinationProces is set.
1495 // It registers the current mChannel with the registrar to get an ID
1496 // so that the remote end can setup a new IPDL channel and lookup
1497 // the same underlying channel.
1498 // We expect this process to finish with FinishReplacementChannelSetup
1499 // (for both in-process and process switch cases), where we cleanup
1500 // the registrar and copy across any needed state to the replacing
1501 // IPDL parent object.
1502
1503 nsTArray<ParentEndpoint> parentEndpoints(mStreamFilterRequests.Length());
1504 if (!mStreamFilterRequests.IsEmpty()) {
1505 base::ProcessId pid = OtherPid();
1506 if (aDestinationProcess) {
1507 dom::ContentParent* cp =
1508 dom::ContentProcessManager::GetSingleton()->GetContentProcessById(
1509 ContentParentId(*aDestinationProcess));
1510 if (cp) {
1511 pid = cp->OtherPid();
1512 }
1513 }
1514
1515 for (StreamFilterRequest& request : mStreamFilterRequests) {
1516 ParentEndpoint parent;
1517 nsresult rv = extensions::PStreamFilter::CreateEndpoints(
1518 pid, request.mChildProcessId, &parent, &request.mChildEndpoint);
1519
1520 if (NS_FAILED(rv)) {
1521 request.mPromise->Reject(false, __func__);
1522 request.mPromise = nullptr;
1523 } else {
1524 parentEndpoints.AppendElement(std::move(parent));
1525 }
1526 }
1527 }
1528
1529 // If we didn't have any redirects, then we pass the REDIRECT_INTERNAL flag
1530 // for this channel switch so that it isn't recorded in session history etc.
1531 // If there were redirect(s), then we want this switch to be recorded as a
1532 // real one, since we have a new URI.
1533 uint32_t redirectFlags = 0;
1534 if (!mHaveVisibleRedirect) {
1535 redirectFlags = nsIChannelEventSink::REDIRECT_INTERNAL;
1536 }
1537
1538 uint32_t newLoadFlags = nsIRequest::LOAD_NORMAL;
1539 MOZ_ALWAYS_SUCCEEDS(mChannel->GetLoadFlags(&newLoadFlags));
1540 // We're pulling our flags from the inner channel, which may not have this
1541 // flag set on it. This is the case when loading a 'view-source' channel.
1542 newLoadFlags |= nsIChannel::LOAD_DOCUMENT_URI;
1543 if (!aDestinationProcess) {
1544 newLoadFlags |= nsIChannel::LOAD_REPLACE;
1545 }
1546
1547 // INHIBIT_PERSISTENT_CACHING is clearing during http redirects (from
1548 // both parent and content process channel instances), but only ever
1549 // re-added to the parent-side nsHttpChannel.
1550 // To match that behaviour, we want to explicitly avoid copying this flag
1551 // back to our newly created content side channel, otherwise it can
1552 // affect sub-resources loads in the same load group.
1553 nsCOMPtr<nsIURI> uri;
1554 mChannel->GetURI(getter_AddRefs(uri));
1555 if (uri && uri->SchemeIs("https")) {
1556 newLoadFlags &= ~nsIRequest::INHIBIT_PERSISTENT_CACHING;
1557 }
1558
1559 RefPtr<DocumentLoadListener> self = this;
1560 RedirectToRealChannel(redirectFlags, newLoadFlags, aDestinationProcess,
1561 std::move(parentEndpoints))
1562 ->Then(
1563 GetCurrentThreadSerialEventTarget(), __func__,
1564 [self, requests = std::move(mStreamFilterRequests)](
1565 const nsresult& aResponse) mutable {
1566 for (StreamFilterRequest& request : requests) {
1567 if (request.mPromise) {
1568 request.mPromise->Resolve(std::move(request.mChildEndpoint),
1569 __func__);
1570 request.mPromise = nullptr;
1571 }
1572 }
1573 self->RedirectToRealChannelFinished(aResponse);
1574 },
1575 [self](const mozilla::ipc::ResponseRejectReason) {
1576 self->RedirectToRealChannelFinished(NS_ERROR_FAILURE);
1577 });
1578 }
1579
1580 NS_IMETHODIMP
OnStartRequest(nsIRequest * aRequest)1581 DocumentLoadListener::OnStartRequest(nsIRequest* aRequest) {
1582 LOG(("DocumentLoadListener OnStartRequest [this=%p]", this));
1583 nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest);
1584 if (multiPartChannel) {
1585 multiPartChannel->GetBaseChannel(getter_AddRefs(mChannel));
1586 } else {
1587 mChannel = do_QueryInterface(aRequest);
1588 }
1589 MOZ_DIAGNOSTIC_ASSERT(mChannel);
1590 RefPtr<nsHttpChannel> httpChannel = do_QueryObject(mChannel);
1591
1592 if (!mDocumentChannelBridge && !mPendingDocumentChannelBridgeProcess) {
1593 return NS_ERROR_UNEXPECTED;
1594 }
1595
1596 // Enforce CSP frame-ancestors and x-frame-options checks which
1597 // might cancel the channel.
1598 nsContentSecurityUtils::PerformCSPFrameAncestorAndXFOCheck(mChannel);
1599
1600 // Generally we want to switch to a real channel even if the request failed,
1601 // since the listener might want to access protocol-specific data (like http
1602 // response headers) in its error handling.
1603 // An exception to this is when nsExtProtocolChannel handled the request and
1604 // returned NS_ERROR_NO_CONTENT, since creating a real one in the content
1605 // process will attempt to handle the URI a second time.
1606 nsresult status = NS_OK;
1607 aRequest->GetStatus(&status);
1608 if (status == NS_ERROR_NO_CONTENT) {
1609 DisconnectChildListeners(status, status);
1610 return NS_OK;
1611 }
1612
1613 mStreamListenerFunctions.AppendElement(StreamListenerFunction{
1614 VariantIndex<0>{}, OnStartRequestParams{aRequest}});
1615
1616 if (!mInitiatedRedirectToRealChannel) {
1617 mChannel->Suspend();
1618 } else {
1619 // This can be called multiple time if we have a multipart
1620 // decoder. Since we've already added the reqest to
1621 // mStreamListenerFunctions, we don't need to do anything else.
1622 return NS_OK;
1623 }
1624 mInitiatedRedirectToRealChannel = true;
1625
1626 // Determine if a new process needs to be spawned. If it does, this will
1627 // trigger a cross process switch, and we should hold off on redirecting to
1628 // the real channel.
1629 bool willBeRemote = false;
1630 if (status == NS_BINDING_ABORTED ||
1631 !MaybeTriggerProcessSwitch(&willBeRemote)) {
1632 TriggerRedirectToRealChannel();
1633
1634 // If we're not switching, then check if we're currently remote.
1635 if (GetBrowsingContext() && GetBrowsingContext()->GetContentParent()) {
1636 willBeRemote = true;
1637 }
1638 }
1639
1640 // If we're going to be delivering this channel to a remote content
1641 // process, then we want to install any required content conversions
1642 // in the content process.
1643 // The caller of this OnStartRequest will install a conversion
1644 // helper after we return if we haven't disabled conversion. Normally
1645 // HttpChannelParent::OnStartRequest would disable conversion, but we're
1646 // defering calling that until later. Manually disable it now to prevent the
1647 // converter from being installed (since we want the child to do it), and
1648 // also save the value so that when we do call
1649 // HttpChannelParent::OnStartRequest, we can have the value as it originally
1650 // was.
1651 if (httpChannel) {
1652 Unused << httpChannel->GetApplyConversion(&mOldApplyConversion);
1653 if (willBeRemote) {
1654 httpChannel->SetApplyConversion(false);
1655 }
1656 }
1657
1658 return NS_OK;
1659 }
1660
1661 NS_IMETHODIMP
OnStopRequest(nsIRequest * aRequest,nsresult aStatusCode)1662 DocumentLoadListener::OnStopRequest(nsIRequest* aRequest,
1663 nsresult aStatusCode) {
1664 LOG(("DocumentLoadListener OnStopRequest [this=%p]", this));
1665 mStreamListenerFunctions.AppendElement(StreamListenerFunction{
1666 VariantIndex<2>{}, OnStopRequestParams{aRequest, aStatusCode}});
1667
1668 // If we're not a multi-part channel, then we're finished and we don't
1669 // expect any further events. If we are, then this might be called again,
1670 // so wait for OnAfterLastPart instead.
1671 nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest);
1672 if (!multiPartChannel) {
1673 mIsFinished = true;
1674 }
1675
1676 mStreamFilterRequests.Clear();
1677
1678 return NS_OK;
1679 }
1680
1681 NS_IMETHODIMP
OnDataAvailable(nsIRequest * aRequest,nsIInputStream * aInputStream,uint64_t aOffset,uint32_t aCount)1682 DocumentLoadListener::OnDataAvailable(nsIRequest* aRequest,
1683 nsIInputStream* aInputStream,
1684 uint64_t aOffset, uint32_t aCount) {
1685 LOG(("DocumentLoadListener OnDataAvailable [this=%p]", this));
1686 // This isn't supposed to happen, since we suspended the channel, but
1687 // sometimes Suspend just doesn't work. This can happen when we're routing
1688 // through nsUnknownDecoder to sniff the content type, and it doesn't handle
1689 // being suspended. Let's just store the data and manually forward it to our
1690 // redirected channel when it's ready.
1691 nsCString data;
1692 nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount);
1693 NS_ENSURE_SUCCESS(rv, rv);
1694
1695 mStreamListenerFunctions.AppendElement(StreamListenerFunction{
1696 VariantIndex<1>{},
1697 OnDataAvailableParams{aRequest, data, aOffset, aCount}});
1698
1699 return NS_OK;
1700 }
1701
1702 //-----------------------------------------------------------------------------
1703 // DoucmentLoadListener::nsIMultiPartChannelListener
1704 //-----------------------------------------------------------------------------
1705
1706 NS_IMETHODIMP
OnAfterLastPart(nsresult aStatus)1707 DocumentLoadListener::OnAfterLastPart(nsresult aStatus) {
1708 LOG(("DocumentLoadListener OnAfterLastPart [this=%p]", this));
1709 if (!mInitiatedRedirectToRealChannel) {
1710 // if we get here, and we haven't initiated a redirect to a real
1711 // channel, then it means we never got OnStartRequest (maybe a problem?)
1712 // and we retargeted everything.
1713 LOG(("DocumentLoadListener Disconnecting child"));
1714 DisconnectChildListeners(NS_BINDING_RETARGETED, NS_OK);
1715 return NS_OK;
1716 }
1717 mStreamListenerFunctions.AppendElement(StreamListenerFunction{
1718 VariantIndex<3>{}, OnAfterLastPartParams{aStatus}});
1719 mIsFinished = true;
1720 return NS_OK;
1721 }
1722
1723 NS_IMETHODIMP
SetParentListener(mozilla::net::ParentChannelListener * listener)1724 DocumentLoadListener::SetParentListener(
1725 mozilla::net::ParentChannelListener* listener) {
1726 // We don't need this (do we?)
1727 return NS_OK;
1728 }
1729
1730 NS_IMETHODIMP
GetInterface(const nsIID & aIID,void ** result)1731 DocumentLoadListener::GetInterface(const nsIID& aIID, void** result) {
1732 RefPtr<CanonicalBrowsingContext> browsingContext =
1733 mParentChannelListener->GetBrowsingContext();
1734 if (aIID.Equals(NS_GET_IID(nsILoadContext)) && browsingContext) {
1735 browsingContext.forget(result);
1736 return NS_OK;
1737 }
1738
1739 return QueryInterface(aIID, result);
1740 }
1741
1742 // Rather than forwarding all these nsIParentChannel functions to the child,
1743 // we cache a list of them, and then ask the 'real' channel to forward them
1744 // for us after it's created.
1745 NS_IMETHODIMP
NotifyFlashPluginStateChanged(nsIHttpChannel::FlashPluginState aState)1746 DocumentLoadListener::NotifyFlashPluginStateChanged(
1747 nsIHttpChannel::FlashPluginState aState) {
1748 mIParentChannelFunctions.AppendElement(
1749 IParentChannelFunction{VariantIndex<0>{}, aState});
1750 return NS_OK;
1751 }
1752
1753 NS_IMETHODIMP
SetClassifierMatchedInfo(const nsACString & aList,const nsACString & aProvider,const nsACString & aFullHash)1754 DocumentLoadListener::SetClassifierMatchedInfo(const nsACString& aList,
1755 const nsACString& aProvider,
1756 const nsACString& aFullHash) {
1757 ClassifierMatchedInfoParams params;
1758 params.mList = aList;
1759 params.mProvider = aProvider;
1760 params.mFullHash = aFullHash;
1761
1762 mIParentChannelFunctions.AppendElement(
1763 IParentChannelFunction{VariantIndex<1>{}, std::move(params)});
1764 return NS_OK;
1765 }
1766
1767 NS_IMETHODIMP
SetClassifierMatchedTrackingInfo(const nsACString & aLists,const nsACString & aFullHash)1768 DocumentLoadListener::SetClassifierMatchedTrackingInfo(
1769 const nsACString& aLists, const nsACString& aFullHash) {
1770 ClassifierMatchedTrackingInfoParams params;
1771 params.mLists = aLists;
1772 params.mFullHashes = aFullHash;
1773
1774 mIParentChannelFunctions.AppendElement(
1775 IParentChannelFunction{VariantIndex<2>{}, std::move(params)});
1776 return NS_OK;
1777 }
1778
1779 NS_IMETHODIMP
NotifyClassificationFlags(uint32_t aClassificationFlags,bool aIsThirdParty)1780 DocumentLoadListener::NotifyClassificationFlags(uint32_t aClassificationFlags,
1781 bool aIsThirdParty) {
1782 mIParentChannelFunctions.AppendElement(IParentChannelFunction{
1783 VariantIndex<3>{},
1784 ClassificationFlagsParams{aClassificationFlags, aIsThirdParty}});
1785 return NS_OK;
1786 }
1787
1788 NS_IMETHODIMP
Delete()1789 DocumentLoadListener::Delete() {
1790 if (mDocumentChannelBridge) {
1791 mDocumentChannelBridge->Delete();
1792 }
1793 return NS_OK;
1794 }
1795
1796 NS_IMETHODIMP
AsyncOnChannelRedirect(nsIChannel * aOldChannel,nsIChannel * aNewChannel,uint32_t aFlags,nsIAsyncVerifyRedirectCallback * aCallback)1797 DocumentLoadListener::AsyncOnChannelRedirect(
1798 nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
1799 nsIAsyncVerifyRedirectCallback* aCallback) {
1800 LOG(("DocumentLoadListener AsyncOnChannelRedirect [this=%p, aFlags=%" PRIx32
1801 "]",
1802 this, aFlags));
1803 // We generally don't want to notify the content process about redirects,
1804 // so just update our channel and tell the callback that we're good to go.
1805 mChannel = aNewChannel;
1806
1807 // Since we're redirecting away from aOldChannel, we should check if it
1808 // had a COOP mismatch, since we want the final result for this to
1809 // include the state of all channels we redirected through.
1810 nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(aOldChannel);
1811 if (httpChannel) {
1812 bool isCOOPMismatch = false;
1813 Unused << NS_WARN_IF(NS_FAILED(
1814 httpChannel->HasCrossOriginOpenerPolicyMismatch(&isCOOPMismatch)));
1815 mHasCrossOriginOpenerPolicyMismatch |= isCOOPMismatch;
1816 }
1817
1818 // We don't need to confirm internal redirects or record any
1819 // history for them, so just immediately verify and return.
1820 if (aFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
1821 LOG(
1822 ("DocumentLoadListener AsyncOnChannelRedirect [this=%p] "
1823 "flags=REDIRECT_INTERNAL",
1824 this));
1825 aCallback->OnRedirectVerifyCallback(NS_OK);
1826 return NS_OK;
1827 }
1828
1829 if (!net::ChannelIsPost(aOldChannel)) {
1830 AddURIVisit(aOldChannel, 0);
1831
1832 nsCOMPtr<nsIURI> oldURI;
1833 aOldChannel->GetURI(getter_AddRefs(oldURI));
1834 nsDocShell::SaveLastVisit(aNewChannel, oldURI, aFlags);
1835 }
1836 mHaveVisibleRedirect |= true;
1837
1838 LOG(
1839 ("DocumentLoadListener AsyncOnChannelRedirect [this=%p] "
1840 "mHaveVisibleRedirect=%c",
1841 this, mHaveVisibleRedirect ? 'T' : 'F'));
1842
1843 // If this is a cross-origin redirect, then we should no longer allow
1844 // mixed content. The destination docshell checks this in its redirect
1845 // handling, but if we deliver to a new docshell (with a process switch)
1846 // then this doesn't happen.
1847 // Manually remove the allow mixed content flags.
1848 nsresult rv = nsContentUtils::CheckSameOrigin(aOldChannel, aNewChannel);
1849 if (NS_FAILED(rv)) {
1850 if (mLoadStateLoadType == LOAD_NORMAL_ALLOW_MIXED_CONTENT) {
1851 mLoadStateLoadType = LOAD_NORMAL;
1852 } else if (mLoadStateLoadType == LOAD_RELOAD_ALLOW_MIXED_CONTENT) {
1853 mLoadStateLoadType = LOAD_RELOAD_NORMAL;
1854 }
1855 MOZ_ASSERT(!LOAD_TYPE_HAS_FLAGS(
1856 mLoadStateLoadType, nsIWebNavigation::LOAD_FLAGS_ALLOW_MIXED_CONTENT));
1857 }
1858
1859 // We need the original URI of the current channel to use to open the real
1860 // channel in the content process. Unfortunately we overwrite the original
1861 // uri of the new channel with the original pre-redirect URI, so grab
1862 // a copy of it now.
1863 aNewChannel->GetOriginalURI(getter_AddRefs(mChannelCreationURI));
1864
1865 // Clear out our nsIParentChannel functions, since a normal parent
1866 // channel would actually redirect and not have those values on the new one.
1867 // We expect the URI classifier to run on the redirected channel with
1868 // the new URI and set these again.
1869 mIParentChannelFunctions.Clear();
1870
1871 #ifdef ANDROID
1872 nsCOMPtr<nsIURI> uriBeingLoaded =
1873 AntiTrackingUtils::MaybeGetDocumentURIBeingLoaded(mChannel);
1874 RefPtr<CanonicalBrowsingContext> bc =
1875 mParentChannelListener->GetBrowsingContext();
1876
1877 RefPtr<MozPromise<bool, bool, false>> promise;
1878 nsCOMPtr<nsIWidget> widget = bc->GetParentProcessWidgetContaining();
1879 RefPtr<nsWindow> window = nsWindow::From(widget);
1880
1881 if (window) {
1882 promise = window->OnLoadRequest(uriBeingLoaded,
1883 nsIBrowserDOMWindow::OPEN_CURRENTWINDOW,
1884 nsIWebNavigation::LOAD_FLAGS_IS_REDIRECT,
1885 nullptr, false, bc->IsTopContent());
1886 }
1887
1888 if (promise) {
1889 RefPtr<nsIAsyncVerifyRedirectCallback> cb = aCallback;
1890 promise->Then(
1891 GetCurrentThreadSerialEventTarget(), __func__,
1892 [=](const MozPromise<bool, bool, false>::ResolveOrRejectValue& aValue) {
1893 if (aValue.IsResolve()) {
1894 bool handled = aValue.ResolveValue();
1895 if (handled) {
1896 cb->OnRedirectVerifyCallback(NS_ERROR_ABORT);
1897 } else {
1898 cb->OnRedirectVerifyCallback(NS_OK);
1899 }
1900 }
1901 });
1902 } else
1903 #endif /* ANDROID */
1904 {
1905 aCallback->OnRedirectVerifyCallback(NS_OK);
1906 }
1907 return NS_OK;
1908 }
1909
1910 // This method returns the cached result of running the Cross-Origin-Opener
1911 // policy compare algorithm by calling ComputeCrossOriginOpenerPolicyMismatch
HasCrossOriginOpenerPolicyMismatch() const1912 bool DocumentLoadListener::HasCrossOriginOpenerPolicyMismatch() const {
1913 // If we found a COOP mismatch on an earlier channel and then
1914 // redirected away from that, we should use that result.
1915 if (mHasCrossOriginOpenerPolicyMismatch) {
1916 return true;
1917 }
1918
1919 nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(mChannel);
1920 if (!httpChannel) {
1921 // Not an nsIHttpChannelInternal assume it's okay to switch.
1922 return false;
1923 }
1924
1925 bool isCOOPMismatch = false;
1926 Unused << NS_WARN_IF(NS_FAILED(
1927 httpChannel->HasCrossOriginOpenerPolicyMismatch(&isCOOPMismatch)));
1928 return isCOOPMismatch;
1929 }
1930
AttachStreamFilter(base::ProcessId aChildProcessId)1931 auto DocumentLoadListener::AttachStreamFilter(base::ProcessId aChildProcessId)
1932 -> RefPtr<ChildEndpointPromise> {
1933 LOG(("DocumentLoadListener AttachStreamFilter [this=%p]", this));
1934
1935 StreamFilterRequest* request = mStreamFilterRequests.AppendElement();
1936 request->mPromise = new ChildEndpointPromise::Private(__func__);
1937 request->mChildProcessId = aChildProcessId;
1938 return request->mPromise;
1939 }
1940
1941 } // namespace net
1942 } // namespace mozilla
1943
1944 #undef LOG
1945