/* vim: set sw=2 ts=8 et tw=80 : */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef mozilla_net_DocumentLoadListener_h #define mozilla_net_DocumentLoadListener_h #include "mozilla/MozPromise.h" #include "mozilla/Variant.h" #include "mozilla/WeakPtr.h" #include "mozilla/ipc/Endpoint.h" #include "mozilla/dom/SessionHistoryEntry.h" #include "EarlyHintsService.h" #include "mozilla/net/NeckoCommon.h" #include "mozilla/net/NeckoParent.h" #include "mozilla/net/PDocumentChannelParent.h" #include "mozilla/net/ParentChannelListener.h" #include "nsDOMNavigationTiming.h" #include "nsIBrowser.h" #include "nsIChannelEventSink.h" #include "nsIEarlyHintObserver.h" #include "nsIInterfaceRequestor.h" #include "nsIMultiPartChannel.h" #include "nsIParentChannel.h" #include "nsIParentRedirectingChannel.h" #include "nsIProgressEventSink.h" #include "nsIRedirectResultListener.h" #define DOCUMENT_LOAD_LISTENER_IID \ { \ 0x3b393c56, 0x9e01, 0x11e9, { \ 0xa2, 0xa3, 0x2a, 0x2a, 0xe2, 0xdb, 0xcc, 0xe4 \ } \ } namespace mozilla { namespace dom { class CanonicalBrowsingContext; struct NavigationIsolationOptions; } // namespace dom namespace net { using ChildEndpointPromise = MozPromise, bool, true>; // If we've been asked to attach a stream filter to our channel, // then we return this promise and defer until we know the final // content process. At that point we setup Endpoints between // mStramFilterProcessId and the new content process, and send // the parent Endpoint to the new process. // Once we have confirmation of that being bound in the content // process, we resolve the promise the child Endpoint. struct StreamFilterRequest { StreamFilterRequest() = default; StreamFilterRequest(StreamFilterRequest&&) = default; ~StreamFilterRequest() { if (mPromise) { mPromise->Reject(false, __func__); } } RefPtr mPromise; mozilla::ipc::Endpoint mChildEndpoint; }; } // namespace net } // namespace mozilla MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::net::StreamFilterRequest) namespace mozilla { namespace net { class LoadInfo; /** * DocumentLoadListener represents a connecting document load for a * CanonicalBrowsingContext (in the parent process). * * It creates a network channel for the document load and then waits for it to * receive a response (after all redirects are resolved). It then decides where * to handle that load (could be in a different process from the initiator), * and then sets up a real network nsIChannel to deliver the data to the final * destination docshell, maybe through an nsIParentChannel/nsIChildChannel IPDL * layer. * * In the case where this was initiated from an nsDocShell, we also create an * nsIChannel to act as a placeholder within the docshell while this process * completes, and then notify the docshell of a 'redirect' when we replace this * channel with the real one. */ // TODO: We currently don't implement nsIProgressEventSink and forward those // to the child. Should we? We get the interface requested. class DocumentLoadListener : public nsIInterfaceRequestor, public nsIAsyncVerifyRedirectReadyCallback, public nsIParentChannel, public nsIChannelEventSink, public HttpChannelSecurityWarningReporter, public nsIMultiPartChannelListener, public nsIProgressEventSink, public nsIEarlyHintObserver { public: // See the comment on GetLoadingBrowsingContext for explanation of // aLoadingBrowsingContext. DocumentLoadListener(dom::CanonicalBrowsingContext* aLoadingBrowsingContext, bool aIsDocumentLoad); struct OpenPromiseSucceededType { nsTArray> mStreamFilterEndpoints; uint32_t mRedirectFlags; uint32_t mLoadFlags; uint32_t mEarlyHintLinkType; RefPtr mPromise; }; struct OpenPromiseFailedType { nsresult mStatus; nsresult mLoadGroupStatus; // This is set to true if the navigation in the content process should not // be cancelled, as the load is logically continuing within the current // browsing session, just within a different process or browsing context. bool mContinueNavigating = false; }; using OpenPromise = MozPromise; // Interface which may be provided when performing an or load // with `DocumentLoadListener`, to allow upgrading the Object load to a proper // Document load. struct ObjectUpgradeHandler : public SupportsWeakPtr { using ObjectUpgradePromise = MozPromise, nsresult, true /* isExclusive */>; // Upgrade an object load to be a potentially remote document. // // The returned promise will resolve with the BrowsingContext which has been // created in the or element to finish the load with. virtual RefPtr UpgradeObjectLoad() = 0; }; private: // Creates the channel, and then calls AsyncOpen on it. // The DocumentLoadListener will require additional process from the consumer // in order to complete the redirect to the end channel. This is done by // returning a RedirectToRealChannelPromise and then waiting for it to be // resolved or rejected accordingly. // Once that promise is resolved; the consumer no longer needs to hold a // reference to the DocumentLoadListener nor will the consumer required to be // used again. RefPtr Open(nsDocShellLoadState* aLoadState, LoadInfo* aLoadInfo, nsLoadFlags aLoadFlags, uint32_t aCacheKey, const Maybe& aChannelId, const TimeStamp& aAsyncOpenTime, nsDOMNavigationTiming* aTiming, Maybe&& aInfo, bool aUrgentStart, dom::ContentParent* aContentParent, nsresult* aRv); public: RefPtr OpenDocument( nsDocShellLoadState* aLoadState, uint32_t aCacheKey, const Maybe& aChannelId, const TimeStamp& aAsyncOpenTime, nsDOMNavigationTiming* aTiming, Maybe&& aInfo, Maybe aUriModified, Maybe aIsXFOError, dom::ContentParent* aContentParent, nsresult* aRv); RefPtr OpenObject( nsDocShellLoadState* aLoadState, uint32_t aCacheKey, const Maybe& aChannelId, const TimeStamp& aAsyncOpenTime, nsDOMNavigationTiming* aTiming, Maybe&& aInfo, uint64_t aInnerWindowId, nsLoadFlags aLoadFlags, nsContentPolicyType aContentPolicyType, bool aUrgentStart, dom::ContentParent* aContentParent, ObjectUpgradeHandler* aObjectUpgradeHandler, nsresult* aRv); // Creates a DocumentLoadListener entirely in the parent process and opens it, // and never needs a DocumentChannel to connect to an existing docshell. // Once we get a response it takes the 'process switch' path to find the right // process and docshell, and delivers the response there directly. static bool LoadInParent(dom::CanonicalBrowsingContext* aBrowsingContext, nsDocShellLoadState* aLoadState, bool aSetNavigating); // Creates a DocumentLoadListener directly in the parent process and opens it, // without needing an existing DocumentChannel. // If successful it registers a unique identifier (return in aOutIdent) to // keep it alive until a future DocumentChannel can attach to it, or we fail // and clean up. static bool SpeculativeLoadInParent( dom::CanonicalBrowsingContext* aBrowsingContext, nsDocShellLoadState* aLoadState); // Ensures that a load identifier allocated by OpenFromParent has // been deregistered if it hasn't already been claimed. // This also cancels the load. static void CleanupParentLoadAttempt(uint64_t aLoadIdent); // Looks up aLoadIdent to find the associated, cleans up the registration static RefPtr ClaimParentLoad(DocumentLoadListener** aListener, uint64_t aLoadIdent, Maybe aChannelId); // Called by the DocumentChannelParent if actor got destroyed or the parent // channel got deleted. void Abort(); NS_DECL_ISUPPORTS NS_DECL_NSIREQUESTOBSERVER NS_DECL_NSISTREAMLISTENER NS_DECL_NSIPARENTCHANNEL NS_DECL_NSIINTERFACEREQUESTOR NS_DECL_NSIASYNCVERIFYREDIRECTREADYCALLBACK NS_DECL_NSICHANNELEVENTSINK NS_DECL_NSIMULTIPARTCHANNELLISTENER NS_DECL_NSIPROGRESSEVENTSINK NS_DECL_NSIEARLYHINTOBSERVER // We suspend the underlying channel when replacing ourselves with // the real listener channel. // This helper resumes the underlying channel again, and manually // forwards any nsIStreamListener messages that arrived while we // were suspended (which might have failed). // Returns true if the channel was finished before we could resume it. bool ResumeSuspendedChannel(nsIStreamListener* aListener); NS_DECLARE_STATIC_IID_ACCESSOR(DOCUMENT_LOAD_LISTENER_IID) // Called by the DocumentChannel if cancelled. void Cancel(const nsresult& aStatusCode, const nsACString& aReason); nsIChannel* GetChannel() const { return mChannel; } uint32_t GetRedirectChannelId() const { return mRedirectChannelId; } nsresult ReportSecurityMessage(const nsAString& aMessageTag, const nsAString& aMessageCategory) override { ReportSecurityMessageParams params; params.mMessageTag = aMessageTag; params.mMessageCategory = aMessageCategory; mSecurityWarningFunctions.AppendElement( SecurityWarningFunction{VariantIndex<0>{}, std::move(params)}); return NS_OK; } nsresult LogBlockedCORSRequest(const nsAString& aMessage, const nsACString& aCategory, bool aIsWarning) override { LogBlockedCORSRequestParams params; params.mMessage = aMessage; params.mCategory = aCategory; params.mIsWarning = aIsWarning; mSecurityWarningFunctions.AppendElement( SecurityWarningFunction{VariantIndex<1>{}, std::move(params)}); return NS_OK; } nsresult LogMimeTypeMismatch(const nsACString& aMessageName, bool aWarning, const nsAString& aURL, const nsAString& aContentType) override { LogMimeTypeMismatchParams params; params.mMessageName = aMessageName; params.mWarning = aWarning; params.mURL = aURL; params.mContentType = aContentType; mSecurityWarningFunctions.AppendElement( SecurityWarningFunction{VariantIndex<2>{}, std::move(params)}); return NS_OK; } // The content process corresponding to this DocumentLoadListener, or nullptr // if connected to the parent process. dom::ContentParent* GetContentParent() const { return mContentParent; } // The process id of the content process that we are being called from // or 0 initiated from a parent process load. base::ProcessId OtherPid() const; [[nodiscard]] RefPtr AttachStreamFilter(); // EarlyHints aren't supported on ParentProcessDocumentChannels yet, allow // EarlyHints to be cancelled from there (Bug 1819886) void CancelEarlyHintPreloads(); // Gets the EarlyHint preloads for this document to pass them to the // ContentProcess. Registers them in the EarlyHintRegister void RegisterEarlyHintLinksAndGetConnectArgs( dom::ContentParentId aCpId, nsTArray& aOutLinks); // Serializes all data needed to setup the new replacement channel // in the content process into the RedirectToRealChannelArgs struct. void SerializeRedirectData(RedirectToRealChannelArgs& aArgs, bool aIsCrossProcess, uint32_t aRedirectFlags, uint32_t aLoadFlags, dom::ContentParent* aParent, nsTArray&& aEarlyHints, uint32_t aEarlyHintLinkType) const; uint64_t GetLoadIdentifier() const { return mLoadIdentifier; } uint32_t GetLoadType() const { return mLoadStateLoadType; } bool IsDownload() const { return mIsDownload; } bool IsLoadingJSURI() const { return mIsLoadingJSURI; } mozilla::dom::LoadingSessionHistoryInfo* GetLoadingSessionHistoryInfo() { return mLoadingSessionHistoryInfo.get(); } bool IsDocumentLoad() const { return mIsDocumentLoad; } // Determine what process switching behavior a browser element should have. enum ProcessBehavior : uint8_t { // Gecko won't automatically change which process this frame, or it's // subframes, are loaded in. PROCESS_BEHAVIOR_DISABLED, // If `useRemoteTabs` is enabled, Gecko will change which process this frame // is loaded in automatically, without calling `performProcessSwitch`. // When `useRemoteSubframes` is enabled, subframes will change processes. PROCESS_BEHAVIOR_STANDARD, // Gecko won't automatically change which process this frame is loaded, but // when `useRemoteSubframes` is enabled, subframes will change processes. // // NOTE: This configuration is included only for backwards compatibility, // and will be removed, as it can easily lead to invalid behavior. PROCESS_BEHAVIOR_SUBFRAME_ONLY, }; protected: virtual ~DocumentLoadListener(); private: RefPtr OpenInParent(nsDocShellLoadState* aLoadState, bool aSupportsRedirectToRealChannel); friend class ParentProcessDocumentOpenInfo; // Will reject the promise to notify the DLL consumer that we are done. // // If `aContinueNavigating` is true, the navigation in the content process // will not be aborted, as navigation is logically continuing in the existing // browsing session (e.g. due to a process switch or entering the bfcache). void DisconnectListeners(nsresult aStatus, nsresult aLoadGroupStatus, bool aContinueNavigating = false); // Called when we were created without a document channel, and creation has // failed, and won't ever be attached. void NotifyDocumentChannelFailed(); // Initiates the switch from DocumentChannel to the real protocol-specific // channel, and ensures that RedirectToRealChannelFinished is called when // this is complete. void TriggerRedirectToRealChannel( const Maybe& aDestinationProcess, nsTArray aStreamFilterRequests); // Called once the content-process side on setting up a replacement // channel is complete. May wait for the new parent channel to // finish, and then calls into FinishReplacementChannelSetup. void RedirectToRealChannelFinished(nsresult aRv); // Completes the replacement of the new channel. // This redirects the ParentChannelListener to forward any future // messages to the new channel, manually forwards any being held // by us, and resumes the underlying source channel. void FinishReplacementChannelSetup(nsresult aResult); // Called from `OnStartRequest` to make the decision about whether or not to // change process. This method will return `nullptr` if the current target // process is appropriate. // aWillSwitchToRemote is set to true if we initiate a process switch, // and that the new remote type will be something other than NOT_REMOTE bool MaybeTriggerProcessSwitch(bool* aWillSwitchToRemote); // Called when the process switch is going to happen, potentially // asynchronously, from `MaybeTriggerProcessSwitch`. // // aContext should be the target context for the navigation. This will either // be the loading BrowsingContext, the newly created BrowsingContext for an // object or embed element load, or a newly created tab for new tab load. // // If `aIsNewTab` is specified, the navigation in the original process will be // aborted immediately, rather than waiting for a process switch to happen and // the previous page to be unloaded or hidden. void TriggerProcessSwitch(dom::CanonicalBrowsingContext* aContext, const dom::NavigationIsolationOptions& aOptions, bool aIsNewTab = false); // A helper for TriggerRedirectToRealChannel that abstracts over // the same-process and cross-process switch cases and returns // a single promise to wait on. using ParentEndpoint = mozilla::ipc::Endpoint; RefPtr RedirectToRealChannel(uint32_t aRedirectFlags, uint32_t aLoadFlags, const Maybe& aDestinationProcess, nsTArray&& aStreamFilterEndpoints); // A helper for RedirectToRealChannel that handles the case where we started // from a content process and are process switching into the parent process. RefPtr RedirectToParentProcess(uint32_t aRedirectFlags, uint32_t aLoadFlags); // Return the Browsing Context that is performing the load. // For document loads, the BC is the one that the (sub)doc // will load into. For /, it's the embedder document's BC. dom::CanonicalBrowsingContext* GetLoadingBrowsingContext() const; // Return the Browsing Context that document is being loaded into. For // non-document loads, this will return nullptr. dom::CanonicalBrowsingContext* GetDocumentBrowsingContext() const; dom::CanonicalBrowsingContext* GetTopBrowsingContext() const; // Return the Window Context which which contains the element which the load // is being performed in. For toplevel loads, this will return `nullptr`. dom::WindowGlobalParent* GetParentWindowContext() const; void AddURIVisit(nsIChannel* aChannel, uint32_t aLoadFlags); bool HasCrossOriginOpenerPolicyMismatch() const; void ApplyPendingFunctions(nsIParentChannel* aChannel) const; void Disconnect(bool aContinueNavigating); void MaybeReportBlockedByURLClassifier(nsresult aStatus); // Returns true if a channel with aStatus will display // some sort of content (could be the actual channel data, // attempt a uri fixup and new load, or an error page). // Returns false if the docshell will ignore the load entirely. bool DocShellWillDisplayContent(nsresult aStatus); void FireStateChange(uint32_t aStateFlags, nsresult aStatus); // Returns true if this is a failed load, where we have successfully // created a fixed URI to attempt loading instead. // If successful, this calls DisconnectListeners to completely finish // the current load, and calls BrowsingContext::LoadURI to start the new one. bool MaybeHandleLoadErrorWithURIFixup(nsresult aStatus); // This defines a variant that describes all the attribute setters (and their // parameters) from nsIParentChannel // // SetClassifierMatchedInfo(const nsACString& aList, const nsACString& // aProvider, const nsACString& aFullHash) = 0; // SetClassifierMatchedTrackingInfo(const nsACString& aLists, const // nsACString& aFullHashes) = 0; NotifyClassificationFlags(uint32_t // aClassificationFlags, bool aIsThirdParty) = 0; struct ClassifierMatchedInfoParams { nsCString mList; nsCString mProvider; nsCString mFullHash; }; struct ClassifierMatchedTrackingInfoParams { nsCString mLists; nsCString mFullHashes; }; struct ClassificationFlagsParams { uint32_t mClassificationFlags; bool mIsThirdParty; }; using IParentChannelFunction = mozilla::Variant; // Store a list of all the attribute setters that have been called on this // channel, so that we can repeat them on the real channel that we redirect // to. nsTArray mIParentChannelFunctions; // This defines a variant this describes all the functions // from HttpChannelSecurityWarningReporter so that we can forward // them on to the real channel. struct ReportSecurityMessageParams { nsString mMessageTag; nsString mMessageCategory; }; struct LogBlockedCORSRequestParams { nsString mMessage; nsCString mCategory; bool mIsWarning; }; struct LogMimeTypeMismatchParams { nsCString mMessageName; bool mWarning = false; nsString mURL; nsString mContentType; }; using SecurityWarningFunction = mozilla::Variant; nsTArray mSecurityWarningFunctions; // TODO Backtrack this. // The set of nsIStreamListener functions that got called on this // listener, so that we can replay them onto the replacement channel's // listener. This should generally only be OnStartRequest, since we // Suspend() the channel at that point, but it can fail sometimes // so we have to support holding a list. nsTArray mStreamListenerFunctions; nsCOMPtr mChannel; Maybe mDocumentChannelId; // An instance of ParentChannelListener that we use as a listener // between mChannel (and any future redirected mChannels) and us. // This handles service worker interception, and retargetting // OnDataAvailable/OnStopRequest messages onto the listener that // replaces us. RefPtr mParentChannelListener; // Get the channel creation URI for constructing the channel in the content // process. See the function for more details. nsIURI* GetChannelCreationURI() const; // The original navigation timing information containing various timestamps // such as when the original load started. // This will be passed back to the new content process should a process // switch occurs. RefPtr mTiming; net::EarlyHintsService mEarlyHintsService; // An optional ObjectUpgradeHandler which can be used to upgrade an // or element to contain a nsFrameLoader, allowing us to switch them // into a different process. // // A weak pointer is held in order to avoid reference cycles. WeakPtr mObjectUpgradeHandler; // Used to identify an internal redirect in redirect chain. // True when we have seen at least one non-interal redirect. bool mHaveVisibleRedirect = false; // Pending stream filter requests which should be attached when redirecting to // the real channel. Moved into `TriggerRedirectToRealChannel` when the // connection is ready. nsTArray mStreamFilterRequests; nsString mSrcdocData; nsCOMPtr mBaseURI; mozilla::UniquePtr mLoadingSessionHistoryInfo; RefPtr mParentWindowContext; // Flags from nsDocShellLoadState::LoadFlags/Type that we want to make // available to the new docshell if we switch processes. uint32_t mLoadStateExternalLoadFlags = 0; uint32_t mLoadStateInternalLoadFlags = 0; uint32_t mLoadStateLoadType = 0; // Indicates if this load is a download. bool mIsDownload = false; // Indicates if we are loading a javascript URI. bool mIsLoadingJSURI = false; // Corresponding redirect channel registrar Id for the final channel that // we want to use when redirecting the child, or doing a process switch. // 0 means redirection is not started. uint64_t mRedirectChannelId = 0; // Set to true once we initiate the redirect to a real channel (either // via a process switch or a same-process redirect, and Suspend the // underlying channel. bool mInitiatedRedirectToRealChannel = false; // The value of GetApplyConversion on mChannel when OnStartRequest // was called. We override it to false to prevent a conversion // helper from being installed, but we need to restore the value // later. bool mOldApplyConversion = false; // Set to true if any previous channel that we redirected away // from had a COOP mismatch. bool mHasCrossOriginOpenerPolicyMismatch = false; // Set to true if we've received OnStopRequest, and shouldn't // setup a reference from the ParentChannelListener to the replacement // channel. bool mIsFinished = false; // The id of the currently pending load which is // passed to the childChannel in order to identify it in the new process. uint64_t mLoadIdentifier = 0; Maybe mOriginalUriString; // Parent-initiated loads do not support redirects to real channels. bool mSupportsRedirectToRealChannel = true; Maybe mRemoteTypeOverride; // The ContentParent which this channel is currently connected to, or nullptr // if connected to the parent process. RefPtr mContentParent; void RejectOpenPromise(nsresult aStatus, nsresult aLoadGroupStatus, bool aContinueNavigating, const char* aLocation) { // It is possible for mOpenPromise to not be set if AsyncOpen failed and // the DocumentChannel got canceled. if (!mOpenPromiseResolved && mOpenPromise) { mOpenPromise->Reject(OpenPromiseFailedType({aStatus, aLoadGroupStatus, aContinueNavigating}), aLocation); mOpenPromiseResolved = true; } } RefPtr mOpenPromise; bool mOpenPromiseResolved = false; const bool mIsDocumentLoad; }; NS_DEFINE_STATIC_IID_ACCESSOR(DocumentLoadListener, DOCUMENT_LOAD_LISTENER_IID) inline nsISupports* ToSupports(DocumentLoadListener* aObj) { return static_cast(aObj); } } // namespace net } // namespace mozilla #endif // mozilla_net_DocumentChannelParent_h