diff options
Diffstat (limited to '')
67 files changed, 13380 insertions, 0 deletions
diff --git a/netwerk/ipc/ChannelEventQueue.cpp b/netwerk/ipc/ChannelEventQueue.cpp new file mode 100644 index 0000000000..911deaa7d9 --- /dev/null +++ b/netwerk/ipc/ChannelEventQueue.cpp @@ -0,0 +1,227 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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/. */ + +#include "ChannelEventQueue.h" + +#include "mozilla/Assertions.h" +#include "mozilla/Unused.h" +#include "nsIChannel.h" +#include "mozilla/dom/Document.h" +#include "nsThreadUtils.h" + +namespace mozilla { +namespace net { + +ChannelEvent* ChannelEventQueue::TakeEvent() { + mMutex.AssertCurrentThreadOwns(); + MOZ_ASSERT(mFlushing); + + if (mSuspended || mEventQueue.IsEmpty()) { + return nullptr; + } + + UniquePtr<ChannelEvent> event(std::move(mEventQueue[0])); + mEventQueue.RemoveElementAt(0); + + return event.release(); +} + +void ChannelEventQueue::FlushQueue() { + // Events flushed could include destruction of channel (and our own + // destructor) unless we make sure its refcount doesn't drop to 0 while this + // method is running. + nsCOMPtr<nsISupports> kungFuDeathGrip; + { + MutexAutoLock lock(mMutex); + kungFuDeathGrip = mOwner; + } + mozilla::Unused << kungFuDeathGrip; // Not used in this function + +#ifdef DEBUG + { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(mFlushing); + } +#endif // DEBUG + + bool needResumeOnOtherThread = false; + + while (true) { + UniquePtr<ChannelEvent> event; + { + MutexAutoLock lock(mMutex); + event.reset(TakeEvent()); + if (!event) { + MOZ_ASSERT(mFlushing); + mFlushing = false; + MOZ_ASSERT(mEventQueue.IsEmpty() || (mSuspended || !!mForcedCount)); + break; + } + } + + nsCOMPtr<nsIEventTarget> target = event->GetEventTarget(); + MOZ_ASSERT(target); + + bool isCurrentThread = false; + nsresult rv = target->IsOnCurrentThread(&isCurrentThread); + if (NS_WARN_IF(NS_FAILED(rv))) { + // Simply run this event on current thread if we are not sure about it + // in release channel, or assert in Aurora/Nightly channel. + MOZ_DIAGNOSTIC_ASSERT(false); + isCurrentThread = true; + } + + if (!isCurrentThread) { + // Next event needs to run on another thread. Put it back to + // the front of the queue can try resume on that thread. + Suspend(); + PrependEvent(std::move(event)); + + needResumeOnOtherThread = true; + { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(mFlushing); + mFlushing = false; + MOZ_ASSERT(!mEventQueue.IsEmpty()); + } + break; + } + + event->Run(); + } // end of while(true) + + // The flush procedure is aborted because next event cannot be run on current + // thread. We need to resume the event processing right after flush procedure + // is finished. + // Note: we cannot call Resume() while "mFlushing == true" because + // CompleteResume will not trigger FlushQueue while there is an ongoing flush. + if (needResumeOnOtherThread) { + Resume(); + } +} + +void ChannelEventQueue::Suspend() { + MutexAutoLock lock(mMutex); + SuspendInternal(); +} + +void ChannelEventQueue::SuspendInternal() { + mMutex.AssertCurrentThreadOwns(); + + mSuspended = true; + mSuspendCount++; +} + +void ChannelEventQueue::Resume() { + MutexAutoLock lock(mMutex); + ResumeInternal(); +} + +void ChannelEventQueue::ResumeInternal() { + mMutex.AssertCurrentThreadOwns(); + + // Resuming w/o suspend: error in debug mode, ignore in build + MOZ_ASSERT(mSuspendCount > 0); + if (mSuspendCount <= 0) { + return; + } + + if (!--mSuspendCount) { + if (mEventQueue.IsEmpty() || !!mForcedCount) { + // Nothing in queue to flush or waiting for AutoEventEnqueuer to + // finish the force enqueue period, simply clear the flag. + mSuspended = false; + return; + } + + // Hold a strong reference of mOwner to avoid the channel release + // before CompleteResume was executed. + class CompleteResumeRunnable : public Runnable { + public: + explicit CompleteResumeRunnable(ChannelEventQueue* aQueue, + nsISupports* aOwner) + : Runnable("CompleteResumeRunnable"), + mQueue(aQueue), + mOwner(aOwner) {} + + NS_IMETHOD Run() override { + mQueue->CompleteResume(); + return NS_OK; + } + + private: + virtual ~CompleteResumeRunnable() = default; + + RefPtr<ChannelEventQueue> mQueue; + nsCOMPtr<nsISupports> mOwner; + }; + + if (!mOwner) { + return; + } + + // Worker thread requires a CancelableRunnable. + RefPtr<Runnable> event = new CompleteResumeRunnable(this, mOwner); + + nsCOMPtr<nsIEventTarget> target; + target = mEventQueue[0]->GetEventTarget(); + MOZ_ASSERT(target); + + Unused << NS_WARN_IF( + NS_FAILED(target->Dispatch(event.forget(), NS_DISPATCH_NORMAL))); + } +} + +bool ChannelEventQueue::MaybeSuspendIfEventsAreSuppressed() { + // We only ever need to suppress events on the main thread, since this is + // where content scripts can run. + if (!NS_IsMainThread()) { + return false; + } + + // Only suppress events for queues associated with XHRs, as these can cause + // content scripts to run. + if (mHasCheckedForXMLHttpRequest && !mForXMLHttpRequest) { + return false; + } + + mMutex.AssertCurrentThreadOwns(); + nsCOMPtr<nsIChannel> channel(do_QueryInterface(mOwner)); + if (!channel) { + return false; + } + + nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); + // Figure out if this is for an XHR, if we haven't done so already. + if (!mHasCheckedForXMLHttpRequest) { + nsContentPolicyType contentType = loadInfo->InternalContentPolicyType(); + mForXMLHttpRequest = + (contentType == nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST); + mHasCheckedForXMLHttpRequest = true; + + if (!mForXMLHttpRequest) { + return false; + } + } + + // Suspend the queue if the associated document has suppressed event handling, + // *and* it is not in the middle of a synchronous operation that might require + // XHR events to be processed (such as a synchronous XHR). + RefPtr<dom::Document> document; + loadInfo->GetLoadingDocument(getter_AddRefs(document)); + if (document && document->EventHandlingSuppressed() && + !document->IsInSyncOperation()) { + document->AddSuspendedChannelEventQueue(this); + SuspendInternal(); + return true; + } + + return false; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/ChannelEventQueue.h b/netwerk/ipc/ChannelEventQueue.h new file mode 100644 index 0000000000..1b61088e91 --- /dev/null +++ b/netwerk/ipc/ChannelEventQueue.h @@ -0,0 +1,383 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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_ChannelEventQueue_h +#define mozilla_net_ChannelEventQueue_h + +#include "nsTArray.h" +#include "nsIEventTarget.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Mutex.h" +#include "mozilla/RecursiveMutex.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" + +class nsISupports; + +namespace mozilla { +namespace net { + +class ChannelEvent { + public: + MOZ_COUNTED_DEFAULT_CTOR(ChannelEvent) + MOZ_COUNTED_DTOR_VIRTUAL(ChannelEvent) virtual void Run() = 0; + virtual already_AddRefed<nsIEventTarget> GetEventTarget() = 0; +}; + +// Note that MainThreadChannelEvent should not be used in child process since +// GetEventTarget() directly returns an unlabeled event target. +class MainThreadChannelEvent : public ChannelEvent { + public: + MOZ_COUNTED_DEFAULT_CTOR(MainThreadChannelEvent) + MOZ_COUNTED_DTOR_OVERRIDE(MainThreadChannelEvent) + + already_AddRefed<nsIEventTarget> GetEventTarget() override { + MOZ_ASSERT(XRE_IsParentProcess()); + + return do_AddRef(GetMainThreadSerialEventTarget()); + } +}; + +class ChannelFunctionEvent : public ChannelEvent { + public: + ChannelFunctionEvent( + std::function<already_AddRefed<nsIEventTarget>()>&& aGetEventTarget, + std::function<void()>&& aCallback) + : mGetEventTarget(std::move(aGetEventTarget)), + mCallback(std::move(aCallback)) {} + + void Run() override { mCallback(); } + already_AddRefed<nsIEventTarget> GetEventTarget() override { + return mGetEventTarget(); + } + + private: + const std::function<already_AddRefed<nsIEventTarget>()> mGetEventTarget; + const std::function<void()> mCallback; +}; + +// UnsafePtr is a work-around our static analyzer that requires all +// ref-counted objects to be captured in lambda via a RefPtr +// The ChannelEventQueue makes it safe to capture "this" by pointer only. +// This is required as work-around to prevent cycles until bug 1596295 +// is resolved. +template <typename T> +class UnsafePtr { + public: + explicit UnsafePtr(T* aPtr) : mPtr(aPtr) {} + + T& operator*() const { return *mPtr; } + T* operator->() const { + MOZ_ASSERT(mPtr, "dereferencing a null pointer"); + return mPtr; + } + operator T*() const& { return mPtr; } + explicit operator bool() const { return mPtr != nullptr; } + + private: + T* const mPtr; +}; + +class NeckoTargetChannelFunctionEvent : public ChannelFunctionEvent { + public: + template <typename T> + NeckoTargetChannelFunctionEvent(T* aChild, std::function<void()>&& aCallback) + : ChannelFunctionEvent( + [child = UnsafePtr<T>(aChild)]() { + MOZ_ASSERT(child); + return child->GetNeckoTarget(); + }, + std::move(aCallback)) {} +}; + +// Workaround for Necko re-entrancy dangers. We buffer IPDL messages in a +// queue if still dispatching previous one(s) to listeners/observers. +// Otherwise synchronous XMLHttpRequests and/or other code that spins the +// event loop (ex: IPDL rpc) could cause listener->OnDataAvailable (for +// instance) to be dispatched and called before mListener->OnStartRequest has +// completed. +// The ChannelEventQueue implementation ensures strict ordering of +// event execution across target threads. + +class ChannelEventQueue final { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ChannelEventQueue) + + public: + explicit ChannelEventQueue(nsISupports* owner) + : mSuspendCount(0), + mSuspended(false), + mForcedCount(0), + mFlushing(false), + mHasCheckedForXMLHttpRequest(false), + mForXMLHttpRequest(false), + mOwner(owner), + mMutex("ChannelEventQueue::mMutex"), + mRunningMutex("ChannelEventQueue::mRunningMutex") {} + + // Puts IPDL-generated channel event into queue, to be run later + // automatically when EndForcedQueueing and/or Resume is called. + // + // @param aCallback - the ChannelEvent + // @param aAssertionWhenNotQueued - this optional param will be used in an + // assertion when the event is executed directly. + inline void RunOrEnqueue(ChannelEvent* aCallback, + bool aAssertionWhenNotQueued = false); + + // Append ChannelEvent in front of the event queue. + inline void PrependEvent(UniquePtr<ChannelEvent>&& aEvent); + inline void PrependEvents(nsTArray<UniquePtr<ChannelEvent>>& aEvents); + + // After StartForcedQueueing is called, RunOrEnqueue() will start enqueuing + // events that will be run/flushed when EndForcedQueueing is called. + // - Note: queueing may still be required after EndForcedQueueing() (if the + // queue is suspended, etc): always call RunOrEnqueue() to avoid race + // conditions. + inline void StartForcedQueueing(); + inline void EndForcedQueueing(); + + // Suspend/resume event queue. RunOrEnqueue() will start enqueuing + // events and they will be run/flushed when resume is called. These should be + // called when the channel owning the event queue is suspended/resumed. + void Suspend(); + // Resume flushes the queue asynchronously, i.e. items in queue will be + // dispatched in a new event on the current thread. + void Resume(); + + void NotifyReleasingOwner() { + MutexAutoLock lock(mMutex); + mOwner = nullptr; + } + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + bool IsEmpty() { + MutexAutoLock lock(mMutex); + return mEventQueue.IsEmpty(); + } +#endif + + private: + // Private destructor, to discourage deletion outside of Release(): + ~ChannelEventQueue() = default; + + void SuspendInternal(); + void ResumeInternal(); + + bool MaybeSuspendIfEventsAreSuppressed() MOZ_REQUIRES(mMutex); + + inline void MaybeFlushQueue(); + void FlushQueue(); + inline void CompleteResume(); + + ChannelEvent* TakeEvent(); + + nsTArray<UniquePtr<ChannelEvent>> mEventQueue MOZ_GUARDED_BY(mMutex); + + uint32_t mSuspendCount MOZ_GUARDED_BY(mMutex); + bool mSuspended MOZ_GUARDED_BY(mMutex); + uint32_t mForcedCount // Support ForcedQueueing on multiple thread. + MOZ_GUARDED_BY(mMutex); + bool mFlushing MOZ_GUARDED_BY(mMutex); + + // Whether the queue is associated with an XHR. This is lazily instantiated + // the first time it is needed. These are MainThread-only. + bool mHasCheckedForXMLHttpRequest; + bool mForXMLHttpRequest; + + // Keep ptr to avoid refcount cycle: only grab ref during flushing. + nsISupports* mOwner MOZ_GUARDED_BY(mMutex); + + // For atomic mEventQueue operation and state update + Mutex mMutex; + + // To guarantee event execution order among threads + RecursiveMutex mRunningMutex MOZ_ACQUIRED_BEFORE(mMutex); + + friend class AutoEventEnqueuer; +}; + +inline void ChannelEventQueue::RunOrEnqueue(ChannelEvent* aCallback, + bool aAssertionWhenNotQueued) { + MOZ_ASSERT(aCallback); + // Events execution could be a destruction of the channel (and our own + // destructor) unless we make sure its refcount doesn't drop to 0 while this + // method is running. + nsCOMPtr<nsISupports> kungFuDeathGrip; + + // To avoid leaks. + UniquePtr<ChannelEvent> event(aCallback); + + // To guarantee that the running event and all the events generated within + // it will be finished before events on other threads. + RecursiveMutexAutoLock lock(mRunningMutex); + { + MutexAutoLock lock(mMutex); + kungFuDeathGrip = mOwner; // must be under the lock + + bool enqueue = !!mForcedCount || mSuspended || mFlushing || + !mEventQueue.IsEmpty() || + MaybeSuspendIfEventsAreSuppressed(); + // To ensure strict ordering of events across multiple threads we buffer the + // events for the below cases: + // a. event queuing is forced by AutoEventEnqueuer + // b. event queue is suspended + // c. an event is currently flushed/executed from the queue + // d. queue is non-empty (pending events on remote thread targets) + if (enqueue) { + mEventQueue.AppendElement(std::move(event)); + return; + } + + nsCOMPtr<nsIEventTarget> target = event->GetEventTarget(); + MOZ_ASSERT(target); + + bool isCurrentThread = false; + DebugOnly<nsresult> rv = target->IsOnCurrentThread(&isCurrentThread); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + if (!isCurrentThread) { + // Leverage Suspend/Resume mechanism to trigger flush procedure without + // creating a new one. + // The execution of further events in the queue is blocked until the + // target thread completes the execution of this event. + // A callback is dispatched to the target thread to flush events from the + // queue. This is done + // by ResumeInternal which dispatches a runnable + // (CompleteResumeRunnable) to the target thread. The target thread will + // call CompleteResume to flush the queue. All the events are run + // synchronously in their respective target threads. + SuspendInternal(); + mEventQueue.AppendElement(std::move(event)); + ResumeInternal(); + return; + } + } + + MOZ_RELEASE_ASSERT(!aAssertionWhenNotQueued); + // execute the event synchronously if we are not queuing it and + // the target thread is the current thread + event->Run(); +} + +inline void ChannelEventQueue::StartForcedQueueing() { + MutexAutoLock lock(mMutex); + ++mForcedCount; +} + +inline void ChannelEventQueue::EndForcedQueueing() { + bool tryFlush = false; + { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(mForcedCount > 0); + if (!--mForcedCount) { + tryFlush = true; + } + } + + if (tryFlush) { + MaybeFlushQueue(); + } +} + +inline void ChannelEventQueue::PrependEvent(UniquePtr<ChannelEvent>&& aEvent) { + MutexAutoLock lock(mMutex); + + // Prepending event while no queue flush foreseen might cause the following + // channel events not run. This assertion here guarantee there must be a + // queue flush, either triggered by Resume or EndForcedQueueing, to execute + // the added event. + MOZ_ASSERT(mSuspended || !!mForcedCount); + + mEventQueue.InsertElementAt(0, std::move(aEvent)); +} + +inline void ChannelEventQueue::PrependEvents( + nsTArray<UniquePtr<ChannelEvent>>& aEvents) { + MutexAutoLock lock(mMutex); + + // Prepending event while no queue flush foreseen might cause the following + // channel events not run. This assertion here guarantee there must be a + // queue flush, either triggered by Resume or EndForcedQueueing, to execute + // the added events. + MOZ_ASSERT(mSuspended || !!mForcedCount); + + mEventQueue.InsertElementsAt(0, aEvents.Length()); + + for (uint32_t i = 0; i < aEvents.Length(); i++) { + mEventQueue[i] = std::move(aEvents[i]); + } +} + +inline void ChannelEventQueue::CompleteResume() { + bool tryFlush = false; + { + MutexAutoLock lock(mMutex); + + // channel may have been suspended again since Resume fired event to call + // this. + if (!mSuspendCount) { + // we need to remain logically suspended (for purposes of queuing incoming + // messages) until this point, else new incoming messages could run before + // queued ones. + mSuspended = false; + tryFlush = true; + } + } + + if (tryFlush) { + MaybeFlushQueue(); + } +} + +inline void ChannelEventQueue::MaybeFlushQueue() { + // Don't flush if forced queuing on, we're already being flushed, or + // suspended, or there's nothing to flush + bool flushQueue = false; + + { + MutexAutoLock lock(mMutex); + flushQueue = !mForcedCount && !mFlushing && !mSuspended && + !mEventQueue.IsEmpty() && !MaybeSuspendIfEventsAreSuppressed(); + + // Only one thread is allowed to run FlushQueue at a time. + if (flushQueue) { + mFlushing = true; + } + } + + if (flushQueue) { + FlushQueue(); + } +} + +// Ensures that RunOrEnqueue() will be collecting events during its lifetime +// (letting caller know incoming IPDL msgs should be queued). Flushes the queue +// when it goes out of scope. +class MOZ_STACK_CLASS AutoEventEnqueuer { + public: + explicit AutoEventEnqueuer(ChannelEventQueue* queue) : mEventQueue(queue) { + { + // Probably not actually needed, since NotifyReleasingOwner should + // only happen after this, but safer to take it in case things change + MutexAutoLock lock(queue->mMutex); + mOwner = queue->mOwner; + } + mEventQueue->StartForcedQueueing(); + } + ~AutoEventEnqueuer() { mEventQueue->EndForcedQueueing(); } + + private: + RefPtr<ChannelEventQueue> mEventQueue; + // Ensure channel object lives longer than ChannelEventQueue. + nsCOMPtr<nsISupports> mOwner; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/ipc/DocumentChannel.cpp b/netwerk/ipc/DocumentChannel.cpp new file mode 100644 index 0000000000..a0d6a0c1f9 --- /dev/null +++ b/netwerk/ipc/DocumentChannel.cpp @@ -0,0 +1,484 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "mozilla/net/DocumentChannel.h" + +#include <inttypes.h> +#include <utility> +#include "mozIDOMWindow.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/LoadInfo.h" +#include "mozilla/Logging.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/Document.h" +#include "mozilla/net/DocumentChannelChild.h" +#include "mozilla/net/ParentProcessDocumentChannel.h" +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsDocShell.h" +#include "nsDocShellLoadState.h" +#include "nsHttpHandler.h" +#include "nsIContentPolicy.h" +#include "nsIInterfaceRequestor.h" +#include "nsILoadContext.h" +#include "nsILoadGroup.h" +#include "nsILoadInfo.h" +#include "nsIStreamListener.h" +#include "nsIURI.h" +#include "nsMimeTypes.h" +#include "nsNetUtil.h" +#include "nsPIDOMWindow.h" +#include "nsPIDOMWindowInlines.h" +#include "nsStringFwd.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" +#include "nscore.h" + +using namespace mozilla::dom; +using namespace mozilla::ipc; + +extern mozilla::LazyLogModule gDocumentChannelLog; +#define LOG(fmt) MOZ_LOG(gDocumentChannelLog, mozilla::LogLevel::Verbose, fmt) + +namespace mozilla { +namespace net { + +//----------------------------------------------------------------------------- +// DocumentChannel::nsISupports + +NS_IMPL_ADDREF(DocumentChannel) +NS_IMPL_RELEASE(DocumentChannel) + +NS_INTERFACE_MAP_BEGIN(DocumentChannel) + NS_INTERFACE_MAP_ENTRY(nsIRequest) + NS_INTERFACE_MAP_ENTRY(nsIChannel) + NS_INTERFACE_MAP_ENTRY(nsIIdentChannel) + NS_INTERFACE_MAP_ENTRY_CONCRETE(DocumentChannel) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRequest) +NS_INTERFACE_MAP_END + +DocumentChannel::DocumentChannel(nsDocShellLoadState* aLoadState, + net::LoadInfo* aLoadInfo, + nsLoadFlags aLoadFlags, uint32_t aCacheKey, + bool aUriModified, + bool aIsEmbeddingBlockedError) + : mLoadState(aLoadState), + mCacheKey(aCacheKey), + mLoadFlags(aLoadFlags), + mURI(aLoadState->URI()), + mLoadInfo(aLoadInfo), + mUriModified(aUriModified), + mIsEmbeddingBlockedError(aIsEmbeddingBlockedError) { + LOG(("DocumentChannel ctor [this=%p, uri=%s]", this, + aLoadState->URI()->GetSpecOrDefault().get())); + RefPtr<nsHttpHandler> handler = nsHttpHandler::GetInstance(); + uint64_t channelId; + Unused << handler->NewChannelId(channelId); + mChannelId = channelId; +} + +NS_IMETHODIMP +DocumentChannel::AsyncOpen(nsIStreamListener* aListener) { + MOZ_CRASH("If we get here, something is broken"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +void DocumentChannel::ShutdownListeners(nsresult aStatusCode) { + LOG(("DocumentChannel ShutdownListeners [this=%p, status=%" PRIx32 "]", this, + static_cast<uint32_t>(aStatusCode))); + mStatus = aStatusCode; + + nsCOMPtr<nsIStreamListener> listener = mListener; + if (listener) { + listener->OnStartRequest(this); + } + + mIsPending = false; + + listener = mListener; // it might have changed! + nsCOMPtr<nsILoadGroup> loadGroup = mLoadGroup; + + mListener = nullptr; + mLoadGroup = nullptr; + mCallbacks = nullptr; + + NS_DispatchToMainThread(NS_NewRunnableFunction( + "DocumentChannel::ShutdownListeners", [=, self = RefPtr{this}] { + if (listener) { + listener->OnStopRequest(self, aStatusCode); + } + + if (loadGroup) { + loadGroup->RemoveRequest(self, nullptr, aStatusCode); + } + })); + + DeleteIPDL(); +} + +void DocumentChannel::DisconnectChildListeners( + const nsresult& aStatus, const nsresult& aLoadGroupStatus) { + MOZ_ASSERT(NS_FAILED(aStatus)); + mStatus = aLoadGroupStatus; + // Make sure we remove from the load group before + // setting mStatus, as existing tests expect the + // status to be successful when we disconnect. + if (mLoadGroup) { + mLoadGroup->RemoveRequest(this, nullptr, aStatus); + mLoadGroup = nullptr; + } + + ShutdownListeners(aStatus); +} + +nsDocShell* DocumentChannel::GetDocShell() { + nsCOMPtr<nsILoadContext> loadContext; + NS_QueryNotificationCallbacks(this, loadContext); + if (!loadContext) { + return nullptr; + } + nsCOMPtr<mozIDOMWindowProxy> domWindow; + loadContext->GetAssociatedWindow(getter_AddRefs(domWindow)); + if (!domWindow) { + return nullptr; + } + auto* pDomWindow = nsPIDOMWindowOuter::From(domWindow); + nsIDocShell* docshell = pDomWindow->GetDocShell(); + return nsDocShell::Cast(docshell); +} + +static bool URIUsesDocChannel(nsIURI* aURI) { + if (SchemeIsJavascript(aURI)) { + return false; + } + + nsCString spec = aURI->GetSpecOrDefault(); + return !spec.EqualsLiteral("about:crashcontent"); +} + +bool DocumentChannel::CanUseDocumentChannel(nsIURI* aURI) { + // We want to use DocumentChannel if we're using a supported scheme. + return URIUsesDocChannel(aURI); +} + +/* static */ +already_AddRefed<DocumentChannel> DocumentChannel::CreateForDocument( + nsDocShellLoadState* aLoadState, class LoadInfo* aLoadInfo, + nsLoadFlags aLoadFlags, nsIInterfaceRequestor* aNotificationCallbacks, + uint32_t aCacheKey, bool aUriModified, bool aIsEmbeddingBlockedError) { + RefPtr<DocumentChannel> channel; + if (XRE_IsContentProcess()) { + channel = + new DocumentChannelChild(aLoadState, aLoadInfo, aLoadFlags, aCacheKey, + aUriModified, aIsEmbeddingBlockedError); + } else { + channel = new ParentProcessDocumentChannel( + aLoadState, aLoadInfo, aLoadFlags, aCacheKey, aUriModified, + aIsEmbeddingBlockedError); + } + channel->SetNotificationCallbacks(aNotificationCallbacks); + return channel.forget(); +} + +/* static */ +already_AddRefed<DocumentChannel> DocumentChannel::CreateForObject( + nsDocShellLoadState* aLoadState, class LoadInfo* aLoadInfo, + nsLoadFlags aLoadFlags, nsIInterfaceRequestor* aNotificationCallbacks) { + return CreateForDocument(aLoadState, aLoadInfo, aLoadFlags, + aNotificationCallbacks, 0, false, false); +} + +NS_IMETHODIMP DocumentChannel::SetCanceledReason(const nsACString& aReason) { + return SetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP DocumentChannel::GetCanceledReason(nsACString& aReason) { + return GetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP DocumentChannel::CancelWithReason(nsresult aStatus, + const nsACString& aReason) { + return CancelWithReasonImpl(aStatus, aReason); +} + +NS_IMETHODIMP +DocumentChannel::Cancel(nsresult aStatusCode) { + MOZ_CRASH("If we get here, something is broken"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +DocumentChannel::Suspend() { + MOZ_CRASH("If we get here, something is broken"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +DocumentChannel::Resume() { + MOZ_CRASH("If we get here, something is broken"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +//----------------------------------------------------------------------------- +// Remainder of nsIRequest/nsIChannel. +//----------------------------------------------------------------------------- + +NS_IMETHODIMP DocumentChannel::GetNotificationCallbacks( + nsIInterfaceRequestor** aCallbacks) { + nsCOMPtr<nsIInterfaceRequestor> callbacks(mCallbacks); + callbacks.forget(aCallbacks); + return NS_OK; +} + +NS_IMETHODIMP DocumentChannel::SetNotificationCallbacks( + nsIInterfaceRequestor* aNotificationCallbacks) { + mCallbacks = aNotificationCallbacks; + return NS_OK; +} + +NS_IMETHODIMP DocumentChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) { + nsCOMPtr<nsILoadGroup> loadGroup(mLoadGroup); + loadGroup.forget(aLoadGroup); + return NS_OK; +} + +NS_IMETHODIMP DocumentChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) { + mLoadGroup = aLoadGroup; + return NS_OK; +} + +NS_IMETHODIMP DocumentChannel::GetStatus(nsresult* aStatus) { + *aStatus = mStatus; + return NS_OK; +} + +NS_IMETHODIMP DocumentChannel::GetName(nsACString& aResult) { + if (!mURI) { + aResult.Truncate(); + return NS_OK; + } + nsCString spec; + nsresult rv = mURI->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + + aResult.AssignLiteral("documentchannel:"); + aResult.Append(spec); + return NS_OK; +} + +NS_IMETHODIMP DocumentChannel::IsPending(bool* aResult) { + *aResult = mIsPending; + return NS_OK; +} + +NS_IMETHODIMP DocumentChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) { + *aLoadFlags = mLoadFlags; + return NS_OK; +} + +NS_IMETHODIMP +DocumentChannel::GetTRRMode(nsIRequest::TRRMode* aTRRMode) { + return GetTRRModeImpl(aTRRMode); +} + +NS_IMETHODIMP +DocumentChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) { + return SetTRRModeImpl(aTRRMode); +} + +NS_IMETHODIMP DocumentChannel::SetLoadFlags(nsLoadFlags aLoadFlags) { + // Setting load flags for TYPE_OBJECT is OK, so long as the channel to parent + // isn't opened yet, or we're only setting the `LOAD_DOCUMENT_URI` flag. + auto contentPolicy = mLoadInfo->GetExternalContentPolicyType(); + if (contentPolicy == ExtContentPolicy::TYPE_OBJECT) { + if (mWasOpened) { + MOZ_DIAGNOSTIC_ASSERT( + aLoadFlags == (mLoadFlags | nsIChannel::LOAD_DOCUMENT_URI), + "After the channel has been opened, can only set the " + "`LOAD_DOCUMENT_URI` flag."); + } + mLoadFlags = aLoadFlags; + return NS_OK; + } + + MOZ_CRASH_UNSAFE_PRINTF( + "DocumentChannel::SetLoadFlags: Don't set flags after creation " + "(differing flags %x != %x)", + (mLoadFlags ^ aLoadFlags) & mLoadFlags, + (mLoadFlags ^ aLoadFlags) & aLoadFlags); +} + +NS_IMETHODIMP DocumentChannel::GetOriginalURI(nsIURI** aOriginalURI) { + nsCOMPtr<nsIURI> originalURI = + mLoadState->OriginalURI() ? mLoadState->OriginalURI() : mLoadState->URI(); + originalURI.forget(aOriginalURI); + return NS_OK; +} + +NS_IMETHODIMP DocumentChannel::SetOriginalURI(nsIURI* aOriginalURI) { + MOZ_CRASH("If we get here, something is broken"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP DocumentChannel::GetURI(nsIURI** aURI) { + nsCOMPtr<nsIURI> uri(mURI); + uri.forget(aURI); + return NS_OK; +} + +NS_IMETHODIMP DocumentChannel::GetOwner(nsISupports** aOwner) { + nsCOMPtr<nsISupports> owner(mOwner); + owner.forget(aOwner); + return NS_OK; +} + +NS_IMETHODIMP DocumentChannel::SetOwner(nsISupports* aOwner) { + mOwner = aOwner; + return NS_OK; +} + +NS_IMETHODIMP DocumentChannel::GetSecurityInfo( + nsITransportSecurityInfo** aSecurityInfo) { + *aSecurityInfo = nullptr; + return NS_OK; +} + +NS_IMETHODIMP DocumentChannel::GetContentType(nsACString& aContentType) { + // We may be trying to load HTML object data, and have determined that we're + // going to be performing a document load. In that case, fake the "text/html" + // content type for nsObjectLoadingContent. + if ((mLoadFlags & nsIRequest::LOAD_HTML_OBJECT_DATA) && + (mLoadFlags & nsIChannel::LOAD_DOCUMENT_URI)) { + aContentType = TEXT_HTML; + return NS_OK; + } + + NS_ERROR("If we get here, something is broken"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP DocumentChannel::SetContentType(const nsACString& aContentType) { + MOZ_CRASH("If we get here, something is broken"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP DocumentChannel::GetContentCharset(nsACString& aContentCharset) { + MOZ_CRASH("If we get here, something is broken"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP DocumentChannel::SetContentCharset( + const nsACString& aContentCharset) { + MOZ_CRASH("If we get here, something is broken"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP DocumentChannel::GetContentLength(int64_t* aContentLength) { + MOZ_CRASH("If we get here, something is broken"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP DocumentChannel::SetContentLength(int64_t aContentLength) { + MOZ_CRASH("If we get here, something is broken"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP DocumentChannel::Open(nsIInputStream** aStream) { + MOZ_CRASH("If we get here, something is broken"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP DocumentChannel::GetContentDisposition( + uint32_t* aContentDisposition) { + MOZ_CRASH("If we get here, something is broken"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP DocumentChannel::SetContentDisposition( + uint32_t aContentDisposition) { + MOZ_CRASH("If we get here, something is broken"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP DocumentChannel::GetContentDispositionFilename( + nsAString& aContentDispositionFilename) { + MOZ_CRASH("If we get here, something will be broken"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP DocumentChannel::SetContentDispositionFilename( + const nsAString& aContentDispositionFilename) { + MOZ_CRASH("If we get here, something will be broken"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP DocumentChannel::GetContentDispositionHeader( + nsACString& aContentDispositionHeader) { + MOZ_CRASH("If we get here, something is broken"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP DocumentChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) { + nsCOMPtr<nsILoadInfo> loadInfo(mLoadInfo); + loadInfo.forget(aLoadInfo); + return NS_OK; +} + +NS_IMETHODIMP DocumentChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) { + MOZ_CRASH("If we get here, something is broken"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP DocumentChannel::GetIsDocument(bool* aIsDocument) { + return NS_GetIsDocumentChannel(this, aIsDocument); +} + +NS_IMETHODIMP DocumentChannel::GetCanceled(bool* aCanceled) { + *aCanceled = mCanceled; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsIIdentChannel +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +DocumentChannel::GetChannelId(uint64_t* aChannelId) { + *aChannelId = mChannelId; + return NS_OK; +} + +NS_IMETHODIMP +DocumentChannel::SetChannelId(uint64_t aChannelId) { + mChannelId = aChannelId; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- + +uint64_t InnerWindowIDForExtantDoc(nsDocShell* docShell) { + if (!docShell) { + return 0; + } + + Document* doc = docShell->GetExtantDocument(); + if (!doc) { + return 0; + } + + return doc->InnerWindowID(); +} + +} // namespace net +} // namespace mozilla + +#undef LOG diff --git a/netwerk/ipc/DocumentChannel.h b/netwerk/ipc/DocumentChannel.h new file mode 100644 index 0000000000..f6a75d4f1b --- /dev/null +++ b/netwerk/ipc/DocumentChannel.h @@ -0,0 +1,120 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_DocumentChannel_h +#define mozilla_net_DocumentChannel_h + +#include "mozilla/dom/ClientInfo.h" +#include "mozilla/net/NeckoChannelParams.h" +#include "nsDOMNavigationTiming.h" +#include "nsIChannel.h" +#include "nsIChildChannel.h" + +class nsDocShell; + +#define DOCUMENT_CHANNEL_IID \ + { \ + 0x6977bc44, 0xb1db, 0x41b7, { \ + 0xb5, 0xc5, 0xe2, 0x13, 0x68, 0x22, 0xc9, 0x8f \ + } \ + } + +namespace mozilla { +namespace net { + +uint64_t InnerWindowIDForExtantDoc(nsDocShell* docShell); + +/** + * DocumentChannel is a protocol agnostic placeholder nsIChannel implementation + * that we use so that nsDocShell knows about a connecting load. It transfers + * all data into a DocumentLoadListener (running in the parent process), which + * will create the real channel for the connection, and decide which process to + * load the resulting document in. If the document is to be loaded in the + * current process, then we'll synthesize a redirect replacing this placeholder + * channel with the real one, otherwise the originating docshell will be removed + * during the process switch. + */ +class DocumentChannel : public nsIIdentChannel { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUEST + NS_DECL_NSICHANNEL + NS_DECL_NSIIDENTCHANNEL + + NS_DECLARE_STATIC_IID_ACCESSOR(DOCUMENT_CHANNEL_IID) + + void SetNavigationTiming(nsDOMNavigationTiming* aTiming) { + mTiming = aTiming; + } + + void SetInitialClientInfo(const Maybe<dom::ClientInfo>& aInfo) { + mInitialClientInfo = aInfo; + } + + void DisconnectChildListeners(const nsresult& aStatus, + const nsresult& aLoadGroupStatus); + + /** + * Will create the appropriate document channel: + * Either a DocumentChannelChild if called from the content process or + * a ParentProcessDocumentChannel if called from the parent process. + * This operation is infallible. + */ + static already_AddRefed<DocumentChannel> CreateForDocument( + nsDocShellLoadState* aLoadState, class LoadInfo* aLoadInfo, + nsLoadFlags aLoadFlags, nsIInterfaceRequestor* aNotificationCallbacks, + uint32_t aCacheKey, bool aUriModified, bool aIsEmbeddingBlockedError); + static already_AddRefed<DocumentChannel> CreateForObject( + nsDocShellLoadState* aLoadState, class LoadInfo* aLoadInfo, + nsLoadFlags aLoadFlags, nsIInterfaceRequestor* aNotificationCallbacks); + + static bool CanUseDocumentChannel(nsIURI* aURI); + + protected: + DocumentChannel(nsDocShellLoadState* aLoadState, class LoadInfo* aLoadInfo, + nsLoadFlags aLoadFlags, uint32_t aCacheKey, bool aUriModified, + bool aIsEmbeddingBlockedError); + + void ShutdownListeners(nsresult aStatusCode); + virtual void DeleteIPDL() {} + + nsDocShell* GetDocShell(); + + virtual ~DocumentChannel() = default; + + const RefPtr<nsDocShellLoadState> mLoadState; + const uint32_t mCacheKey; + + nsresult mStatus = NS_OK; + bool mCanceled = false; + bool mIsPending = false; + bool mWasOpened = false; + uint64_t mChannelId; + uint32_t mLoadFlags = LOAD_NORMAL; + const nsCOMPtr<nsIURI> mURI; + nsCOMPtr<nsILoadGroup> mLoadGroup; + nsCOMPtr<nsILoadInfo> mLoadInfo; + nsCOMPtr<nsIInterfaceRequestor> mCallbacks; + nsCOMPtr<nsIStreamListener> mListener; + nsCOMPtr<nsISupports> mOwner; + RefPtr<nsDOMNavigationTiming> mTiming; + Maybe<dom::ClientInfo> mInitialClientInfo; + // mUriModified is true if we're doing a history load and the URI of the + // session history had been modified by pushState/replaceState. + bool mUriModified = false; + // mIsEmbeddingBlockedError is true if we're handling a load error and the + // status of the failed channel is NS_ERROR_XFO_VIOLATION or + // NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION. + bool mIsEmbeddingBlockedError = false; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(DocumentChannel, DOCUMENT_CHANNEL_IID) + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_DocumentChannel_h diff --git a/netwerk/ipc/DocumentChannelChild.cpp b/netwerk/ipc/DocumentChannelChild.cpp new file mode 100644 index 0000000000..ead243eaa2 --- /dev/null +++ b/netwerk/ipc/DocumentChannelChild.cpp @@ -0,0 +1,483 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "DocumentChannelChild.h" + +#include "mozilla/dom/Document.h" +#include "mozilla/dom/RemoteType.h" +#include "mozilla/extensions/StreamFilterParent.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/net/HttpBaseChannel.h" +#include "mozilla/net/NeckoChild.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/StaticPrefs_fission.h" +#include "nsHashPropertyBag.h" +#include "nsIHttpChannelInternal.h" +#include "nsIObjectLoadingContent.h" +#include "nsIXULRuntime.h" +#include "nsIWritablePropertyBag.h" +#include "nsFrameLoader.h" +#include "nsFrameLoaderOwner.h" +#include "nsQueryObject.h" +#include "nsDocShellLoadState.h" + +using namespace mozilla::dom; +using namespace mozilla::ipc; + +extern mozilla::LazyLogModule gDocumentChannelLog; +#define LOG(fmt) MOZ_LOG(gDocumentChannelLog, mozilla::LogLevel::Verbose, fmt) + +namespace mozilla { +namespace net { + +//----------------------------------------------------------------------------- +// DocumentChannelChild::nsISupports + +NS_INTERFACE_MAP_BEGIN(DocumentChannelChild) + NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback) +NS_INTERFACE_MAP_END_INHERITING(DocumentChannel) + +NS_IMPL_ADDREF_INHERITED(DocumentChannelChild, DocumentChannel) +NS_IMPL_RELEASE_INHERITED(DocumentChannelChild, DocumentChannel) + +DocumentChannelChild::DocumentChannelChild(nsDocShellLoadState* aLoadState, + net::LoadInfo* aLoadInfo, + nsLoadFlags aLoadFlags, + uint32_t aCacheKey, + bool aUriModified, + bool aIsEmbeddingBlockedError) + : DocumentChannel(aLoadState, aLoadInfo, aLoadFlags, aCacheKey, + aUriModified, aIsEmbeddingBlockedError) { + mLoadingContext = nullptr; + LOG(("DocumentChannelChild ctor [this=%p, uri=%s]", this, + aLoadState->URI()->GetSpecOrDefault().get())); +} + +DocumentChannelChild::~DocumentChannelChild() { + LOG(("DocumentChannelChild dtor [this=%p]", this)); +} + +NS_IMETHODIMP +DocumentChannelChild::AsyncOpen(nsIStreamListener* aListener) { + nsresult rv = NS_OK; + + nsCOMPtr<nsIStreamListener> listener = aListener; + + NS_ENSURE_TRUE(gNeckoChild, NS_ERROR_FAILURE); + NS_ENSURE_ARG_POINTER(listener); + NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS); + NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED); + + // Port checked in parent, but duplicate here so we can return with error + // immediately, as we've done since before e10s. + rv = NS_CheckPortSafety(mURI); + NS_ENSURE_SUCCESS(rv, rv); + + bool isNotDownload = mLoadState->FileName().IsVoid(); + + // If not a download, add ourselves to the load group + if (isNotDownload && mLoadGroup) { + // During this call, we can re-enter back into the DocumentChannelChild to + // call SetNavigationTiming. + mLoadGroup->AddRequest(this, nullptr); + } + + if (mCanceled) { + // We may have been canceled already, either by on-modify-request + // listeners or by load group observers; in that case, don't create IPDL + // connection. See nsHttpChannel::AsyncOpen(). + return mStatus; + } + + gHttpHandler->OnOpeningDocumentRequest(this); + + RefPtr<nsDocShell> docShell = GetDocShell(); + if (!docShell) { + return NS_ERROR_FAILURE; + } + + // `loadingContext` is the BC that is initiating the resource load. + // For normal subdocument loads, the BC is the one that the subdoc will load + // into. For <object>/<embed> it's the embedder doc's BC. + RefPtr<BrowsingContext> loadingContext = docShell->GetBrowsingContext(); + if (!loadingContext || loadingContext->IsDiscarded()) { + return NS_ERROR_FAILURE; + } + mLoadingContext = loadingContext; + + Maybe<IPCClientInfo> ipcClientInfo; + if (mInitialClientInfo.isSome()) { + ipcClientInfo.emplace(mInitialClientInfo.ref().ToIPC()); + } + + DocumentChannelElementCreationArgs ipcElementCreationArgs; + switch (mLoadInfo->GetExternalContentPolicyType()) { + case ExtContentPolicy::TYPE_DOCUMENT: + case ExtContentPolicy::TYPE_SUBDOCUMENT: { + DocumentCreationArgs docArgs; + docArgs.uriModified() = mUriModified; + docArgs.isEmbeddingBlockedError() = mIsEmbeddingBlockedError; + + ipcElementCreationArgs = docArgs; + break; + } + + case ExtContentPolicy::TYPE_OBJECT: { + ObjectCreationArgs objectArgs; + objectArgs.embedderInnerWindowId() = InnerWindowIDForExtantDoc(docShell); + objectArgs.loadFlags() = mLoadFlags; + objectArgs.contentPolicyType() = mLoadInfo->InternalContentPolicyType(); + objectArgs.isUrgentStart() = UserActivation::IsHandlingUserInput(); + + ipcElementCreationArgs = objectArgs; + break; + } + + default: + MOZ_ASSERT_UNREACHABLE("unsupported content policy type"); + return NS_ERROR_FAILURE; + } + + switch (mLoadInfo->GetExternalContentPolicyType()) { + case ExtContentPolicy::TYPE_DOCUMENT: + case ExtContentPolicy::TYPE_SUBDOCUMENT: + MOZ_ALWAYS_SUCCEEDS(loadingContext->SetCurrentLoadIdentifier( + Some(mLoadState->GetLoadIdentifier()))); + break; + + default: + break; + } + + mLoadState->AssertProcessCouldTriggerLoadIfSystem(); + + DocumentChannelCreationArgs args( + mozilla::WrapNotNull(mLoadState), TimeStamp::Now(), mChannelId, mCacheKey, + mTiming, ipcClientInfo, ipcElementCreationArgs, + loadingContext->GetParentInitiatedNavigationEpoch()); + + gNeckoChild->SendPDocumentChannelConstructor(this, loadingContext, args); + + mIsPending = true; + mWasOpened = true; + mListener = listener; + + return NS_OK; +} + +IPCResult DocumentChannelChild::RecvFailedAsyncOpen( + const nsresult& aStatusCode) { + if (aStatusCode == NS_ERROR_RECURSIVE_DOCUMENT_LOAD) { + // This exists so that we are able to fire an error event + // for when there are too many recursive iframe or object loads. + // This is an incomplete solution, because right now we don't have a unified + // way of firing error events due to errors in document channel. + // This should be fixed in bug 1629201. + MOZ_DIAGNOSTIC_ASSERT(mLoadingContext); + if (RefPtr<Element> embedder = mLoadingContext->GetEmbedderElement()) { + if (RefPtr<nsFrameLoaderOwner> flo = do_QueryObject(embedder)) { + if (RefPtr<nsFrameLoader> fl = flo->GetFrameLoader()) { + fl->FireErrorEvent(); + } + } + } + } + ShutdownListeners(aStatusCode); + return IPC_OK(); +} + +IPCResult DocumentChannelChild::RecvDisconnectChildListeners( + const nsresult& aStatus, const nsresult& aLoadGroupStatus, + bool aContinueNavigating) { + // If this disconnect is not due to a process switch, perform the disconnect + // immediately. + if (!aContinueNavigating) { + DisconnectChildListeners(aStatus, aLoadGroupStatus); + return IPC_OK(); + } + + // Otherwise, the disconnect will occur later using some other mechanism, + // depending on what's happening to the loading DocShell. If this is a + // toplevel navigation, and this BrowsingContext enters the BFCache, we will + // cancel this channel when the PageHide event is firing, whereas if it does + // not enter BFCache (e.g. due to being an object, subframe or non-bfcached + // toplevel navigation), we will cancel this channel when the DocShell is + // destroyed. + nsDocShell* shell = GetDocShell(); + if (mLoadInfo->GetExternalContentPolicyType() == + ExtContentPolicy::TYPE_DOCUMENT && + shell) { + MOZ_ASSERT(shell->GetBrowsingContext()->IsTop()); + if (mozilla::SessionHistoryInParent() && + shell->GetBrowsingContext()->IsInBFCache()) { + DisconnectChildListeners(aStatus, aLoadGroupStatus); + } else { + // Tell the DocShell which channel to cancel if it enters the BFCache. + shell->SetChannelToDisconnectOnPageHide(mChannelId); + } + } + + return IPC_OK(); +} + +IPCResult DocumentChannelChild::RecvRedirectToRealChannel( + RedirectToRealChannelArgs&& aArgs, + nsTArray<Endpoint<extensions::PStreamFilterParent>>&& aEndpoints, + RedirectToRealChannelResolver&& aResolve) { + LOG(("DocumentChannelChild RecvRedirectToRealChannel [this=%p, uri=%s]", this, + aArgs.uri()->GetSpecOrDefault().get())); + + // The document that created the cspToInherit. + // This is used when deserializing LoadInfo from the parent + // process, since we can't serialize Documents directly. + // TODO: For a fission OOP iframe this will be unavailable, + // as will the loadingContext computed in LoadInfoArgsToLoadInfo. + // Figure out if we need these for cross-origin subdocs. + RefPtr<dom::Document> cspToInheritLoadingDocument; + nsCOMPtr<nsIContentSecurityPolicy> policy = mLoadState->Csp(); + if (policy) { + nsWeakPtr ctx = + static_cast<nsCSPContext*>(policy.get())->GetLoadingContext(); + cspToInheritLoadingDocument = do_QueryReferent(ctx); + } + nsCOMPtr<nsILoadInfo> loadInfo; + MOZ_ALWAYS_SUCCEEDS(LoadInfoArgsToLoadInfo(aArgs.loadInfo(), NOT_REMOTE_TYPE, + cspToInheritLoadingDocument, + getter_AddRefs(loadInfo))); + + mRedirectResolver = std::move(aResolve); + + nsCOMPtr<nsIChannel> newChannel; + MOZ_ASSERT((aArgs.loadStateInternalLoadFlags() & + nsDocShell::InternalLoad::INTERNAL_LOAD_FLAGS_IS_SRCDOC) || + aArgs.srcdocData().IsVoid()); + nsresult rv = nsDocShell::CreateRealChannelForDocument( + getter_AddRefs(newChannel), aArgs.uri(), loadInfo, nullptr, + aArgs.newLoadFlags(), aArgs.srcdocData(), aArgs.baseUri()); + if (newChannel) { + newChannel->SetLoadGroup(mLoadGroup); + } + + if (RefPtr<HttpBaseChannel> httpChannel = do_QueryObject(newChannel)) { + httpChannel->SetEarlyHints(std::move(aArgs.earlyHints())); + httpChannel->SetEarlyHintLinkType(aArgs.earlyHintLinkType()); + } + + // This is used to report any errors back to the parent by calling + // CrossProcessRedirectFinished. + auto scopeExit = MakeScopeExit([&]() { + mRedirectResolver(rv); + mRedirectResolver = nullptr; + }); + + if (NS_FAILED(rv)) { + return IPC_OK(); + } + + if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel)) { + rv = httpChannel->SetChannelId(aArgs.channelId()); + } + if (NS_FAILED(rv)) { + return IPC_OK(); + } + + rv = newChannel->SetOriginalURI(aArgs.originalURI()); + if (NS_FAILED(rv)) { + return IPC_OK(); + } + + if (nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = + do_QueryInterface(newChannel)) { + rv = httpChannelInternal->SetRedirectMode(aArgs.redirectMode()); + } + if (NS_FAILED(rv)) { + return IPC_OK(); + } + + newChannel->SetNotificationCallbacks(mCallbacks); + + if (aArgs.init()) { + HttpBaseChannel::ReplacementChannelConfig config(*aArgs.init()); + HttpBaseChannel::ConfigureReplacementChannel( + newChannel, config, + HttpBaseChannel::ReplacementReason::DocumentChannel); + } + + if (aArgs.contentDisposition()) { + newChannel->SetContentDisposition(*aArgs.contentDisposition()); + } + + if (aArgs.contentDispositionFilename()) { + newChannel->SetContentDispositionFilename( + *aArgs.contentDispositionFilename()); + } + + nsDocShell* docShell = GetDocShell(); + if (docShell && aArgs.loadingSessionHistoryInfo().isSome()) { + docShell->SetLoadingSessionHistoryInfo( + aArgs.loadingSessionHistoryInfo().ref()); + } + + // transfer any properties. This appears to be entirely a content-side + // interface and isn't copied across to the parent. Copying the values + // for this from this into the new actor will work, since the parent + // won't have the right details anyway. + // TODO: What about the process switch equivalent + // (ContentChild::RecvCrossProcessRedirect)? In that case there is no local + // existing actor in the destination process... We really need all information + // to go up to the parent, and then come down to the new child actor. + if (nsCOMPtr<nsIWritablePropertyBag> bag = do_QueryInterface(newChannel)) { + nsHashPropertyBag::CopyFrom(bag, aArgs.properties()); + } + + // connect parent. + nsCOMPtr<nsIChildChannel> childChannel = do_QueryInterface(newChannel); + if (childChannel) { + rv = childChannel->ConnectParent( + aArgs.registrarId()); // creates parent channel + if (NS_FAILED(rv)) { + return IPC_OK(); + } + } + mRedirectChannel = newChannel; + mStreamFilterEndpoints = std::move(aEndpoints); + + rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, + aArgs.redirectFlags(), + GetMainThreadSerialEventTarget()); + + if (NS_SUCCEEDED(rv)) { + scopeExit.release(); + } + + // scopeExit will call CrossProcessRedirectFinished(rv) here + return IPC_OK(); +} + +IPCResult DocumentChannelChild::RecvUpgradeObjectLoad( + UpgradeObjectLoadResolver&& aResolve) { + // We're doing a load for an <object> or <embed> element if we got here. + MOZ_ASSERT(mLoadFlags & nsIRequest::LOAD_HTML_OBJECT_DATA, + "Should have LOAD_HTML_OBJECT_DATA set"); + MOZ_ASSERT(!(mLoadFlags & nsIChannel::LOAD_DOCUMENT_URI), + "Shouldn't be a LOAD_DOCUMENT_URI load yet"); + MOZ_ASSERT(mLoadInfo->GetExternalContentPolicyType() == + ExtContentPolicy::TYPE_OBJECT, + "Should have the TYPE_OBJECT content policy type"); + + // If our load has already failed, or been cancelled, abort this attempt to + // upgade the load. + if (NS_FAILED(mStatus)) { + aResolve(nullptr); + return IPC_OK(); + } + + nsCOMPtr<nsIObjectLoadingContent> loadingContent; + NS_QueryNotificationCallbacks(this, loadingContent); + if (!loadingContent) { + return IPC_FAIL(this, "Channel is not for ObjectLoadingContent!"); + } + + // We're upgrading to a document channel now. Add the LOAD_DOCUMENT_URI flag + // after-the-fact. + mLoadFlags |= nsIChannel::LOAD_DOCUMENT_URI; + + RefPtr<BrowsingContext> browsingContext; + nsresult rv = loadingContent->UpgradeLoadToDocument( + this, getter_AddRefs(browsingContext)); + if (NS_FAILED(rv) || !browsingContext) { + // Oops! Looks like something went wrong, so let's bail out. + mLoadFlags &= ~nsIChannel::LOAD_DOCUMENT_URI; + aResolve(nullptr); + return IPC_OK(); + } + + aResolve(browsingContext); + return IPC_OK(); +} + +NS_IMETHODIMP +DocumentChannelChild::OnRedirectVerifyCallback(nsresult aStatusCode) { + LOG( + ("DocumentChannelChild OnRedirectVerifyCallback [this=%p, " + "aRv=0x%08" PRIx32 " ]", + this, static_cast<uint32_t>(aStatusCode))); + nsCOMPtr<nsIChannel> redirectChannel = std::move(mRedirectChannel); + RedirectToRealChannelResolver redirectResolver = std::move(mRedirectResolver); + + // If we've already shut down, then just notify the parent that + // we're done. + if (NS_FAILED(mStatus)) { + redirectChannel->SetNotificationCallbacks(nullptr); + redirectResolver(aStatusCode); + return NS_OK; + } + + nsresult rv = aStatusCode; + if (NS_SUCCEEDED(rv)) { + if (nsCOMPtr<nsIChildChannel> childChannel = + do_QueryInterface(redirectChannel)) { + rv = childChannel->CompleteRedirectSetup(mListener); + } else { + rv = redirectChannel->AsyncOpen(mListener); + } + } else { + redirectChannel->SetNotificationCallbacks(nullptr); + } + + for (auto& endpoint : mStreamFilterEndpoints) { + extensions::StreamFilterParent::Attach(redirectChannel, + std::move(endpoint)); + } + + redirectResolver(rv); + + if (NS_FAILED(rv)) { + ShutdownListeners(rv); + return NS_OK; + } + + if (mLoadGroup) { + mLoadGroup->RemoveRequest(this, nullptr, NS_BINDING_REDIRECTED); + } + mCallbacks = nullptr; + mListener = nullptr; + + // This calls NeckoChild::DeallocPDocumentChannel(), which deletes |this| if + // IPDL holds the last reference. Don't rely on |this| existing after here! + if (CanSend()) { + Send__delete__(this); + } + + return NS_OK; +} + +NS_IMETHODIMP +DocumentChannelChild::Cancel(nsresult aStatusCode) { + return CancelWithReason(aStatusCode, "DocumentChannelChild::Cancel"_ns); +} + +NS_IMETHODIMP DocumentChannelChild::CancelWithReason( + nsresult aStatusCode, const nsACString& aReason) { + if (mCanceled) { + return NS_OK; + } + + mCanceled = true; + if (CanSend()) { + SendCancel(aStatusCode, aReason); + } + + ShutdownListeners(aStatusCode); + + return NS_OK; +} + +} // namespace net +} // namespace mozilla + +#undef LOG diff --git a/netwerk/ipc/DocumentChannelChild.h b/netwerk/ipc/DocumentChannelChild.h new file mode 100644 index 0000000000..7b2461cbb6 --- /dev/null +++ b/netwerk/ipc/DocumentChannelChild.h @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_DocumentChannelChild_h +#define mozilla_net_DocumentChannelChild_h + +#include "mozilla/net/PDocumentChannelChild.h" +#include "mozilla/net/DocumentChannel.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "mozilla/dom/nsCSPContext.h" + +namespace mozilla { +namespace net { + +/** + * DocumentChannelChild is an implementation of DocumentChannel for nsDocShells + * in the content process, that uses PDocumentChannel to serialize everything + * across IPDL to the parent process. + */ +class DocumentChannelChild final : public DocumentChannel, + public nsIAsyncVerifyRedirectCallback, + public PDocumentChannelChild { + public: + DocumentChannelChild(nsDocShellLoadState* aLoadState, + class LoadInfo* aLoadInfo, nsLoadFlags aLoadFlags, + uint32_t aCacheKey, bool aUriModified, + bool aIsEmbeddingBlockedError); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK + + NS_IMETHOD AsyncOpen(nsIStreamListener* aListener) override; + NS_IMETHOD Cancel(nsresult aStatusCode) override; + NS_IMETHOD CancelWithReason(nsresult aStatusCode, + const nsACString& aReason) override; + + mozilla::ipc::IPCResult RecvFailedAsyncOpen(const nsresult& aStatusCode); + + mozilla::ipc::IPCResult RecvDisconnectChildListeners( + const nsresult& aStatus, const nsresult& aLoadGroupStatus, + bool aSwitchedProcess); + + mozilla::ipc::IPCResult RecvDeleteSelf(); + + mozilla::ipc::IPCResult RecvRedirectToRealChannel( + RedirectToRealChannelArgs&& aArgs, + nsTArray<Endpoint<extensions::PStreamFilterParent>>&& aEndpoints, + RedirectToRealChannelResolver&& aResolve); + + mozilla::ipc::IPCResult RecvUpgradeObjectLoad( + UpgradeObjectLoadResolver&& aResolve); + + private: + void DeleteIPDL() override { + if (CanSend()) { + Send__delete__(this); + } + } + + ~DocumentChannelChild(); + + nsCOMPtr<nsIChannel> mRedirectChannel; + + RedirectToRealChannelResolver mRedirectResolver; + nsTArray<Endpoint<extensions::PStreamFilterParent>> mStreamFilterEndpoints; + dom::BrowsingContext* mLoadingContext; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_DocumentChannelChild_h diff --git a/netwerk/ipc/DocumentChannelParent.cpp b/netwerk/ipc/DocumentChannelParent.cpp new file mode 100644 index 0000000000..a360f76bcb --- /dev/null +++ b/netwerk/ipc/DocumentChannelParent.cpp @@ -0,0 +1,160 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "DocumentChannelParent.h" + +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/ClientInfo.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/net/NeckoChannelParams.h" +#include "nsDocShellLoadState.h" + +extern mozilla::LazyLogModule gDocumentChannelLog; +#define LOG(fmt) MOZ_LOG(gDocumentChannelLog, mozilla::LogLevel::Verbose, fmt) + +using namespace mozilla::dom; + +namespace mozilla { +namespace net { + +DocumentChannelParent::DocumentChannelParent() { + LOG(("DocumentChannelParent ctor [this=%p]", this)); +} + +DocumentChannelParent::~DocumentChannelParent() { + LOG(("DocumentChannelParent dtor [this=%p]", this)); +} + +bool DocumentChannelParent::Init(dom::CanonicalBrowsingContext* aContext, + const DocumentChannelCreationArgs& aArgs) { + RefPtr<nsDocShellLoadState> loadState = aArgs.loadState(); + LOG(("DocumentChannelParent Init [this=%p, uri=%s]", this, + loadState->URI()->GetSpecOrDefault().get())); + if (aArgs.parentInitiatedNavigationEpoch() < + aContext->GetParentInitiatedNavigationEpoch()) { + nsresult rv = NS_BINDING_ABORTED; + return SendFailedAsyncOpen(rv); + } + + ContentParent* contentParent = + static_cast<ContentParent*>(Manager()->Manager()); + + RefPtr<DocumentLoadListener::OpenPromise> promise; + if (loadState->GetChannelInitialized()) { + promise = DocumentLoadListener::ClaimParentLoad( + getter_AddRefs(mDocumentLoadListener), loadState->GetLoadIdentifier(), + Some(aArgs.channelId())); + } + if (!promise) { + bool isDocumentLoad = + aArgs.elementCreationArgs().type() == + DocumentChannelElementCreationArgs::TDocumentCreationArgs; + mDocumentLoadListener = new DocumentLoadListener(aContext, isDocumentLoad); + + Maybe<ClientInfo> clientInfo; + if (aArgs.initialClientInfo().isSome()) { + clientInfo.emplace(ClientInfo(aArgs.initialClientInfo().ref())); + } + + nsresult rv = NS_ERROR_UNEXPECTED; + + if (isDocumentLoad) { + const DocumentCreationArgs& docArgs = aArgs.elementCreationArgs(); + + promise = mDocumentLoadListener->OpenDocument( + loadState, aArgs.cacheKey(), Some(aArgs.channelId()), + aArgs.asyncOpenTime(), aArgs.timing(), std::move(clientInfo), + Some(docArgs.uriModified()), Some(docArgs.isEmbeddingBlockedError()), + contentParent, &rv); + } else { + const ObjectCreationArgs& objectArgs = aArgs.elementCreationArgs(); + + promise = mDocumentLoadListener->OpenObject( + loadState, aArgs.cacheKey(), Some(aArgs.channelId()), + aArgs.asyncOpenTime(), aArgs.timing(), std::move(clientInfo), + objectArgs.embedderInnerWindowId(), objectArgs.loadFlags(), + objectArgs.contentPolicyType(), objectArgs.isUrgentStart(), + contentParent, this /* ObjectUpgradeHandler */, &rv); + } + + if (NS_FAILED(rv)) { + MOZ_ASSERT(!promise); + return SendFailedAsyncOpen(rv); + } + } + + RefPtr<DocumentChannelParent> self = this; + promise->Then( + GetCurrentSerialEventTarget(), __func__, + [self](DocumentLoadListener::OpenPromiseSucceededType&& aResolveValue) { + // The DLL is waiting for us to resolve the + // PDocumentChannel::RedirectToRealChannelPromise given as parameter. + auto promise = self->RedirectToRealChannel( + std::move(aResolveValue.mStreamFilterEndpoints), + aResolveValue.mRedirectFlags, aResolveValue.mLoadFlags, + aResolveValue.mEarlyHintLinkType); + // We chain the promise the DLL is waiting on to the one returned by + // RedirectToRealChannel. As soon as the promise returned is resolved + // or rejected, so will the DLL's promise. + promise->ChainTo(aResolveValue.mPromise.forget(), __func__); + self->mDocumentLoadListener = nullptr; + }, + [self](DocumentLoadListener::OpenPromiseFailedType&& aRejectValue) { + if (self->CanSend()) { + Unused << self->SendDisconnectChildListeners( + aRejectValue.mStatus, aRejectValue.mLoadGroupStatus, + aRejectValue.mContinueNavigating); + } + self->mDocumentLoadListener = nullptr; + }); + + return true; +} + +auto DocumentChannelParent::UpgradeObjectLoad() + -> RefPtr<ObjectUpgradePromise> { + return SendUpgradeObjectLoad()->Then( + GetCurrentSerialEventTarget(), __func__, + [](const UpgradeObjectLoadPromise::ResolveOrRejectValue& aValue) { + if (!aValue.IsResolve() || aValue.ResolveValue().IsNullOrDiscarded()) { + LOG(("DocumentChannelParent object load upgrade failed")); + return ObjectUpgradePromise::CreateAndReject(NS_ERROR_FAILURE, + __func__); + } + + return ObjectUpgradePromise::CreateAndResolve( + aValue.ResolveValue().get_canonical(), __func__); + }); +} + +RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise> +DocumentChannelParent::RedirectToRealChannel( + nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>>&& + aStreamFilterEndpoints, + uint32_t aRedirectFlags, uint32_t aLoadFlags, uint32_t aEarlyHintLinkType) { + if (!CanSend()) { + return PDocumentChannelParent::RedirectToRealChannelPromise:: + CreateAndReject(ResponseRejectReason::ChannelClosed, __func__); + } + + ContentParent* cp = static_cast<ContentParent*>(Manager()->Manager()); + nsTArray<EarlyHintConnectArgs> earlyHints; + mDocumentLoadListener->RegisterEarlyHintLinksAndGetConnectArgs(cp->ChildID(), + earlyHints); + + RedirectToRealChannelArgs args; + mDocumentLoadListener->SerializeRedirectData( + args, false, aRedirectFlags, aLoadFlags, std::move(earlyHints), + aEarlyHintLinkType); + return SendRedirectToRealChannel(args, std::move(aStreamFilterEndpoints)); +} + +} // namespace net +} // namespace mozilla + +#undef LOG diff --git a/netwerk/ipc/DocumentChannelParent.h b/netwerk/ipc/DocumentChannelParent.h new file mode 100644 index 0000000000..7537c95295 --- /dev/null +++ b/netwerk/ipc/DocumentChannelParent.h @@ -0,0 +1,68 @@ +/* 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_DocumentChannelParent_h +#define mozilla_net_DocumentChannelParent_h + +#include "mozilla/net/DocumentLoadListener.h" +#include "mozilla/net/PDocumentChannelParent.h" + +namespace mozilla { +namespace dom { +class CanonicalBrowsingContext; +} +namespace net { + +class EarlyHintConnectArgs; + +/** + * An actor that forwards all changes across to DocumentChannelChild, the + * nsIChannel implementation owned by a content process docshell. + */ +class DocumentChannelParent final + : public PDocumentChannelParent, + public DocumentLoadListener::ObjectUpgradeHandler { + public: + NS_INLINE_DECL_REFCOUNTING(DocumentChannelParent, override); + + explicit DocumentChannelParent(); + + bool Init(dom::CanonicalBrowsingContext* aContext, + const DocumentChannelCreationArgs& aArgs); + + // PDocumentChannelParent + ipc::IPCResult RecvCancel(const nsresult& aStatus, const nsCString& aReason) { + if (mDocumentLoadListener) { + mDocumentLoadListener->Cancel(aStatus, aReason); + } + return IPC_OK(); + } + void ActorDestroy(ActorDestroyReason aWhy) override { + if (mDocumentLoadListener) { + mDocumentLoadListener->Cancel(NS_BINDING_ABORTED, + "DocumentChannelParent::ActorDestroy"_ns); + } + } + + private: + RefPtr<ObjectUpgradePromise> UpgradeObjectLoad() override; + + RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise> + RedirectToRealChannel( + nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>>&& + aStreamFilterEndpoints, + uint32_t aRedirectFlags, uint32_t aLoadFlags, + uint32_t aEarlyHintLinkType); + + virtual ~DocumentChannelParent(); + + RefPtr<DocumentLoadListener> mDocumentLoadListener; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_DocumentChannelParent_h diff --git a/netwerk/ipc/DocumentLoadListener.cpp b/netwerk/ipc/DocumentLoadListener.cpp new file mode 100644 index 0000000000..ca1f59e884 --- /dev/null +++ b/netwerk/ipc/DocumentLoadListener.cpp @@ -0,0 +1,3027 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "DocumentLoadListener.h" + +#include "NeckoCommon.h" +#include "mozilla/AntiTrackingUtils.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/LoadInfo.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/RefPtr.h" +#include "mozilla/ResultVariant.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/StaticPrefs_extensions.h" +#include "mozilla/StaticPrefs_fission.h" +#include "mozilla/StaticPrefs_security.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/BrowsingContextGroup.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/ChildProcessChannelListener.h" +#include "mozilla/dom/ClientChannelHelper.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ContentProcessManager.h" +#include "mozilla/dom/ProcessIsolation.h" +#include "mozilla/dom/SessionHistoryEntry.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/dom/ipc/IdType.h" +#include "mozilla/net/CookieJarSettings.h" +#include "mozilla/net/HttpChannelParent.h" +#include "mozilla/net/RedirectChannelRegistrar.h" +#include "nsContentSecurityUtils.h" +#include "nsContentSecurityManager.h" +#include "nsDocShell.h" +#include "nsDocShellLoadState.h" +#include "nsDocShellLoadTypes.h" +#include "nsDOMNavigationTiming.h" +#include "nsDSURIContentListener.h" +#include "nsObjectLoadingContent.h" +#include "nsOpenWindowInfo.h" +#include "nsExternalHelperAppService.h" +#include "nsHttpChannel.h" +#include "nsIBrowser.h" +#include "nsIHttpChannelInternal.h" +#include "nsIStreamConverterService.h" +#include "nsIViewSourceChannel.h" +#include "nsImportModule.h" +#include "nsIXULRuntime.h" +#include "nsMimeTypes.h" +#include "nsQueryObject.h" +#include "nsRedirectHistoryEntry.h" +#include "nsSandboxFlags.h" +#include "nsSHistory.h" +#include "nsStringStream.h" +#include "nsURILoader.h" +#include "nsWebNavigationInfo.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/nsHTTPSOnlyUtils.h" +#include "mozilla/dom/ReferrerInfo.h" +#include "mozilla/dom/RemoteWebProgressRequest.h" +#include "mozilla/net/UrlClassifierFeatureFactory.h" +#include "mozilla/ExtensionPolicyService.h" + +#ifdef ANDROID +# include "mozilla/widget/nsWindow.h" +#endif /* ANDROID */ + +mozilla::LazyLogModule gDocumentChannelLog("DocumentChannel"); +#define LOG(fmt) MOZ_LOG(gDocumentChannelLog, mozilla::LogLevel::Verbose, fmt) + +extern mozilla::LazyLogModule gSHIPBFCacheLog; + +// Bug 136580: Limit to the number of nested content frames that can have the +// same URL. This is to stop content that is recursively loading +// itself. Note that "#foo" on the end of URL doesn't affect +// whether it's considered identical, but "?foo" or ";foo" are +// considered and compared. +// Limit this to 2, like chromium does. +static constexpr int kMaxSameURLContentFrames = 2; + +using namespace mozilla::dom; + +namespace mozilla { +namespace net { + +static ContentParentId GetContentProcessId(ContentParent* aContentParent) { + return aContentParent ? aContentParent->ChildID() : ContentParentId{0}; +} + +static void SetNeedToAddURIVisit(nsIChannel* aChannel, + bool aNeedToAddURIVisit) { + nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(aChannel)); + if (!props) { + return; + } + + props->SetPropertyAsBool(u"docshell.needToAddURIVisit"_ns, + aNeedToAddURIVisit); +} + +static auto SecurityFlagsForLoadInfo(nsDocShellLoadState* aLoadState) + -> nsSecurityFlags { + // TODO: Block copied from nsDocShell::DoURILoad, refactor out somewhere + nsSecurityFlags securityFlags = + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL; + + if (aLoadState->LoadType() == LOAD_ERROR_PAGE) { + securityFlags |= nsILoadInfo::SEC_LOAD_ERROR_PAGE; + } + + if (aLoadState->PrincipalToInherit()) { + bool isSrcdoc = aLoadState->HasInternalLoadFlags( + nsDocShell::INTERNAL_LOAD_FLAGS_IS_SRCDOC); + bool inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal( + aLoadState->PrincipalToInherit(), aLoadState->URI(), + true, // aInheritForAboutBlank + isSrcdoc); + + bool isData = SchemeIsData(aLoadState->URI()); + if (inheritAttrs && !isData) { + securityFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL; + } + } + + return securityFlags; +} + +// Construct a LoadInfo object to use when creating the internal channel for a +// Document/SubDocument load. +static auto CreateDocumentLoadInfo(CanonicalBrowsingContext* aBrowsingContext, + nsDocShellLoadState* aLoadState) + -> already_AddRefed<LoadInfo> { + uint32_t sandboxFlags = aBrowsingContext->GetSandboxFlags(); + RefPtr<LoadInfo> loadInfo; + + auto securityFlags = SecurityFlagsForLoadInfo(aLoadState); + + if (aBrowsingContext->GetParent()) { + loadInfo = LoadInfo::CreateForFrame( + aBrowsingContext, aLoadState->TriggeringPrincipal(), + aLoadState->GetEffectiveTriggeringRemoteType(), securityFlags, + sandboxFlags); + } else { + OriginAttributes attrs; + aBrowsingContext->GetOriginAttributes(attrs); + loadInfo = LoadInfo::CreateForDocument( + aBrowsingContext, aLoadState->URI(), aLoadState->TriggeringPrincipal(), + aLoadState->GetEffectiveTriggeringRemoteType(), attrs, securityFlags, + sandboxFlags); + } + + if (aLoadState->IsExemptFromHTTPSFirstMode()) { + uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus(); + httpsOnlyStatus |= nsILoadInfo::HTTPS_FIRST_EXEMPT_NEXT_LOAD; + loadInfo->SetHttpsOnlyStatus(httpsOnlyStatus); + } + + loadInfo->SetWasSchemelessInput(aLoadState->GetWasSchemelessInput()); + + loadInfo->SetTriggeringSandboxFlags(aLoadState->TriggeringSandboxFlags()); + loadInfo->SetTriggeringWindowId(aLoadState->TriggeringWindowId()); + loadInfo->SetTriggeringStorageAccess(aLoadState->TriggeringStorageAccess()); + loadInfo->SetHasValidUserGestureActivation( + aLoadState->HasValidUserGestureActivation()); + loadInfo->SetIsMetaRefresh(aLoadState->IsMetaRefresh()); + + return loadInfo.forget(); +} + +// Construct a LoadInfo object to use when creating the internal channel for an +// Object/Embed load. +static auto CreateObjectLoadInfo(nsDocShellLoadState* aLoadState, + uint64_t aInnerWindowId, + nsContentPolicyType aContentPolicyType, + uint32_t aSandboxFlags) + -> already_AddRefed<LoadInfo> { + RefPtr<WindowGlobalParent> wgp = + WindowGlobalParent::GetByInnerWindowId(aInnerWindowId); + MOZ_RELEASE_ASSERT(wgp); + + auto securityFlags = SecurityFlagsForLoadInfo(aLoadState); + + RefPtr<LoadInfo> loadInfo = LoadInfo::CreateForNonDocument( + wgp, wgp->DocumentPrincipal(), aContentPolicyType, securityFlags, + aSandboxFlags); + + loadInfo->SetHasValidUserGestureActivation( + aLoadState->HasValidUserGestureActivation()); + loadInfo->SetTriggeringSandboxFlags(aLoadState->TriggeringSandboxFlags()); + loadInfo->SetTriggeringWindowId(aLoadState->TriggeringWindowId()); + loadInfo->SetTriggeringStorageAccess(aLoadState->TriggeringStorageAccess()); + loadInfo->SetIsMetaRefresh(aLoadState->IsMetaRefresh()); + + return loadInfo.forget(); +} + +/** + * An extension to nsDocumentOpenInfo that we run in the parent process, so + * that we can make the decision to retarget to content handlers or the external + * helper app, before we make process switching decisions. + * + * This modifies the behaviour of nsDocumentOpenInfo so that it can do + * retargeting, but doesn't do stream conversion (but confirms that we will be + * able to do so later). + * + * We still run nsDocumentOpenInfo in the content process, but disable + * retargeting, so that it can only apply stream conversion, and then send data + * to the docshell. + */ +class ParentProcessDocumentOpenInfo final : public nsDocumentOpenInfo, + public nsIMultiPartChannelListener { + public: + ParentProcessDocumentOpenInfo(ParentChannelListener* aListener, + uint32_t aFlags, + mozilla::dom::BrowsingContext* aBrowsingContext, + bool aIsDocumentLoad) + : nsDocumentOpenInfo(aFlags, false), + mBrowsingContext(aBrowsingContext), + mListener(aListener), + mIsDocumentLoad(aIsDocumentLoad) { + LOG(("ParentProcessDocumentOpenInfo ctor [this=%p]", this)); + } + + NS_DECL_ISUPPORTS_INHERITED + + // The default content listener is always a docshell, so this manually + // implements the same checks, and if it succeeds, uses the parent + // channel listener so that we forward onto DocumentLoadListener. + bool TryDefaultContentListener(nsIChannel* aChannel, + const nsCString& aContentType) { + uint32_t canHandle = nsWebNavigationInfo::IsTypeSupported(aContentType); + if (canHandle != nsIWebNavigationInfo::UNSUPPORTED) { + m_targetStreamListener = mListener; + nsLoadFlags loadFlags = 0; + aChannel->GetLoadFlags(&loadFlags); + aChannel->SetLoadFlags(loadFlags | nsIChannel::LOAD_TARGETED); + return true; + } + return false; + } + + bool TryDefaultContentListener(nsIChannel* aChannel) override { + return TryDefaultContentListener(aChannel, mContentType); + } + + // Generally we only support stream converters that can tell + // use exactly what type they'll output. If we find one, then + // we just target to our default listener directly (without + // conversion), and the content process nsDocumentOpenInfo will + // run and do the actual conversion. + nsresult TryStreamConversion(nsIChannel* aChannel) override { + // The one exception is nsUnknownDecoder, which works in the parent + // (and we need to know what the content type is before we can + // decide if it will be handled in the parent), so we run that here. + if (mContentType.LowerCaseEqualsASCII(UNKNOWN_CONTENT_TYPE) || + mContentType.IsEmpty()) { + return nsDocumentOpenInfo::TryStreamConversion(aChannel); + } + + nsresult rv; + nsCOMPtr<nsIStreamConverterService> streamConvService = + do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv); + nsAutoCString str; + rv = streamConvService->ConvertedType(mContentType, aChannel, str); + NS_ENSURE_SUCCESS(rv, rv); + + // We only support passing data to the default content listener + // (docshell), and we don't supported chaining converters. + if (TryDefaultContentListener(aChannel, str)) { + mContentType = str; + return NS_OK; + } + // This is the same result as nsStreamConverterService uses when it + // can't find a converter + return NS_ERROR_FAILURE; + } + + nsresult TryExternalHelperApp(nsIExternalHelperAppService* aHelperAppService, + nsIChannel* aChannel) override { + RefPtr<nsIStreamListener> listener; + nsresult rv = aHelperAppService->CreateListener( + mContentType, aChannel, mBrowsingContext, false, nullptr, + getter_AddRefs(listener)); + if (NS_SUCCEEDED(rv)) { + m_targetStreamListener = listener; + } + return rv; + } + + nsDocumentOpenInfo* Clone() override { + mCloned = true; + return new ParentProcessDocumentOpenInfo(mListener, mFlags, + mBrowsingContext, mIsDocumentLoad); + } + + nsresult OnDocumentStartRequest(nsIRequest* request) { + LOG(("ParentProcessDocumentOpenInfo OnDocumentStartRequest [this=%p]", + this)); + + nsresult rv = nsDocumentOpenInfo::OnStartRequest(request); + + // If we didn't find a content handler, + // and we don't have a listener, then just forward to our + // default listener. This happens when the channel is in + // an error state, and we want to just forward that on to be + // handled in the content process. + if (NS_SUCCEEDED(rv) && !mUsedContentHandler && !m_targetStreamListener) { + m_targetStreamListener = mListener; + return m_targetStreamListener->OnStartRequest(request); + } + if (m_targetStreamListener != mListener) { + LOG( + ("ParentProcessDocumentOpenInfo targeted to non-default listener " + "[this=%p]", + this)); + // If this is the only part, then we can immediately tell our listener + // that it won't be getting any content and disconnect it. For multipart + // channels we have to wait until we've handled all parts before we know. + // This does mean that the content process can still Cancel() a multipart + // response while the response is being handled externally, but this + // matches the single-process behaviour. + // If we got cloned, then we don't need to do this, as only the last link + // needs to do it. + // Multi-part channels are guaranteed to call OnAfterLastPart, which we + // forward to the listeners, so it will handle disconnection at that + // point. + nsCOMPtr<nsIMultiPartChannel> multiPartChannel = + do_QueryInterface(request); + if (!multiPartChannel && !mCloned) { + DisconnectChildListeners(NS_FAILED(rv) ? rv : NS_BINDING_RETARGETED, + rv); + } + } + return rv; + } + + nsresult OnObjectStartRequest(nsIRequest* request) { + LOG(("ParentProcessDocumentOpenInfo OnObjectStartRequest [this=%p]", this)); + // Just redirect to the nsObjectLoadingContent in the content process. + m_targetStreamListener = mListener; + return m_targetStreamListener->OnStartRequest(request); + } + + NS_IMETHOD OnStartRequest(nsIRequest* request) override { + LOG(("ParentProcessDocumentOpenInfo OnStartRequest [this=%p]", this)); + + if (mIsDocumentLoad) { + return OnDocumentStartRequest(request); + } + + return OnObjectStartRequest(request); + } + + NS_IMETHOD OnAfterLastPart(nsresult aStatus) override { + mListener->OnAfterLastPart(aStatus); + return NS_OK; + } + + private: + virtual ~ParentProcessDocumentOpenInfo() { + LOG(("ParentProcessDocumentOpenInfo dtor [this=%p]", this)); + } + + void DisconnectChildListeners(nsresult aStatus, nsresult aLoadGroupStatus) { + // Tell the DocumentLoadListener to notify the content process that it's + // been entirely retargeted, and to stop waiting. + // Clear mListener's pointer to the DocumentLoadListener to break the + // reference cycle. + RefPtr<DocumentLoadListener> doc = do_GetInterface(ToSupports(mListener)); + MOZ_ASSERT(doc); + doc->DisconnectListeners(aStatus, aLoadGroupStatus); + mListener->SetListenerAfterRedirect(nullptr); + } + + RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext; + RefPtr<ParentChannelListener> mListener; + const bool mIsDocumentLoad; + + /** + * Set to true if we got cloned to create a chained listener. + */ + bool mCloned = false; +}; + +NS_IMPL_ADDREF_INHERITED(ParentProcessDocumentOpenInfo, nsDocumentOpenInfo) +NS_IMPL_RELEASE_INHERITED(ParentProcessDocumentOpenInfo, nsDocumentOpenInfo) + +NS_INTERFACE_MAP_BEGIN(ParentProcessDocumentOpenInfo) + NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannelListener) +NS_INTERFACE_MAP_END_INHERITING(nsDocumentOpenInfo) + +NS_IMPL_ADDREF(DocumentLoadListener) +NS_IMPL_RELEASE(DocumentLoadListener) + +NS_INTERFACE_MAP_BEGIN(DocumentLoadListener) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) + NS_INTERFACE_MAP_ENTRY(nsIStreamListener) + NS_INTERFACE_MAP_ENTRY(nsIParentChannel) + NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectReadyCallback) + NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink) + NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannelListener) + NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink) + NS_INTERFACE_MAP_ENTRY(nsIEarlyHintObserver) + NS_INTERFACE_MAP_ENTRY_CONCRETE(DocumentLoadListener) +NS_INTERFACE_MAP_END + +DocumentLoadListener::DocumentLoadListener( + CanonicalBrowsingContext* aLoadingBrowsingContext, bool aIsDocumentLoad) + : mIsDocumentLoad(aIsDocumentLoad) { + LOG(("DocumentLoadListener ctor [this=%p]", this)); + mParentChannelListener = + new ParentChannelListener(this, aLoadingBrowsingContext); +} + +DocumentLoadListener::~DocumentLoadListener() { + LOG(("DocumentLoadListener dtor [this=%p]", this)); +} + +void DocumentLoadListener::AddURIVisit(nsIChannel* aChannel, + uint32_t aLoadFlags) { + if (mLoadStateLoadType == LOAD_ERROR_PAGE || + mLoadStateLoadType == LOAD_BYPASS_HISTORY) { + return; + } + + nsCOMPtr<nsIURI> uri; + NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); + + nsCOMPtr<nsIURI> previousURI; + uint32_t previousFlags = 0; + if (mLoadStateLoadType & nsIDocShell::LOAD_CMD_RELOAD) { + previousURI = uri; + } else { + nsDocShell::ExtractLastVisit(aChannel, getter_AddRefs(previousURI), + &previousFlags); + } + + // Get the HTTP response code, if available. + uint32_t responseStatus = 0; + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel); + if (httpChannel) { + Unused << httpChannel->GetResponseStatus(&responseStatus); + } + + RefPtr<CanonicalBrowsingContext> browsingContext = + GetDocumentBrowsingContext(); + nsCOMPtr<nsIWidget> widget = + browsingContext->GetParentProcessWidgetContaining(); + + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); + + // Check if the URI has a http scheme and if either HSTS is enabled for this + // host, or if we were upgraded by HTTPS-Only/First. If this is the case, we + // want to mark this URI specially, as it will be followed shortly by an + // almost identical https history entry. That way the global history + // implementation can handle the visit appropriately (e.g. by marking it as + // `hidden`, so only the https url appears in default history views). + bool wasUpgraded = + uri->SchemeIs("http") && + (loadInfo->GetHstsStatus() || + (loadInfo->GetHttpsOnlyStatus() & + (nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST | + nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_NOT_REGISTERED | + nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_REGISTERED))); + + nsDocShell::InternalAddURIVisit(uri, previousURI, previousFlags, + responseStatus, browsingContext, widget, + mLoadStateLoadType, wasUpgraded); +} + +CanonicalBrowsingContext* DocumentLoadListener::GetLoadingBrowsingContext() + const { + return mParentChannelListener ? mParentChannelListener->GetBrowsingContext() + : nullptr; +} + +CanonicalBrowsingContext* DocumentLoadListener::GetDocumentBrowsingContext() + const { + return mIsDocumentLoad ? GetLoadingBrowsingContext() : nullptr; +} + +CanonicalBrowsingContext* DocumentLoadListener::GetTopBrowsingContext() const { + auto* loadingContext = GetLoadingBrowsingContext(); + return loadingContext ? loadingContext->Top() : nullptr; +} + +WindowGlobalParent* DocumentLoadListener::GetParentWindowContext() const { + return mParentWindowContext; +} + +bool CheckRecursiveLoad(CanonicalBrowsingContext* aLoadingContext, + nsDocShellLoadState* aLoadState, bool aIsDocumentLoad) { + // Bug 136580: Check for recursive frame loading excluding about:srcdoc URIs. + // srcdoc URIs require their contents to be specified inline, so it isn't + // possible for undesirable recursion to occur without the aid of a + // non-srcdoc URI, which this method will block normally. + // Besides, URI is not enough to guarantee uniqueness of srcdoc documents. + nsAutoCString buffer; + if (aLoadState->URI()->SchemeIs("about")) { + nsresult rv = aLoadState->URI()->GetPathQueryRef(buffer); + if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("srcdoc")) { + // Duplicates allowed up to depth limits + return true; + } + } + + RefPtr<WindowGlobalParent> parent; + if (!aIsDocumentLoad) { // object load + parent = aLoadingContext->GetCurrentWindowGlobal(); + } else { + parent = aLoadingContext->GetParentWindowContext(); + } + + int matchCount = 0; + CanonicalBrowsingContext* ancestorBC; + for (WindowGlobalParent* ancestorWGP = parent; ancestorWGP; + ancestorWGP = ancestorBC->GetParentWindowContext()) { + ancestorBC = ancestorWGP->BrowsingContext(); + MOZ_ASSERT(ancestorBC); + if (nsCOMPtr<nsIURI> parentURI = ancestorWGP->GetDocumentURI()) { + bool equal; + nsresult rv = aLoadState->URI()->EqualsExceptRef(parentURI, &equal); + NS_ENSURE_SUCCESS(rv, false); + + if (equal) { + matchCount++; + if (matchCount >= kMaxSameURLContentFrames) { + NS_WARNING( + "Too many nested content frames/objects have the same url " + "(recursion?) " + "so giving up"); + return false; + } + } + } + } + return true; +} + +// Check that the load state, potentially received from a child process, appears +// to be performing a load of the specified LoadingSessionHistoryInfo. +// Returns a Result<…> containing the SessionHistoryEntry found for the +// LoadingSessionHistoryInfo as success value if the validation succeeded, or a +// static (telemetry-safe) string naming what did not match as a failure value +// if the validation failed. +static Result<SessionHistoryEntry*, const char*> ValidateHistoryLoad( + CanonicalBrowsingContext* aLoadingContext, + nsDocShellLoadState* aLoadState) { + MOZ_ASSERT(SessionHistoryInParent()); + MOZ_ASSERT(aLoadState->LoadIsFromSessionHistory()); + + if (!aLoadState->GetLoadingSessionHistoryInfo()) { + return Err("Missing LoadingSessionHistoryInfo"); + } + + SessionHistoryEntry::LoadingEntry* loading = SessionHistoryEntry::GetByLoadId( + aLoadState->GetLoadingSessionHistoryInfo()->mLoadId); + if (!loading) { + return Err("Missing SessionHistoryEntry"); + } + + SessionHistoryInfo* snapshot = loading->mInfoSnapshotForValidation.get(); + // History loads do not inherit principal. + if (aLoadState->HasInternalLoadFlags( + nsDocShell::INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL)) { + return Err("LOAD_FLAGS_INHERIT_PRINCIPAL"); + } + + auto uriEq = [](nsIURI* a, nsIURI* b) -> bool { + bool eq = false; + return a == b || (a && b && NS_SUCCEEDED(a->Equals(b, &eq)) && eq); + }; + auto principalEq = [](nsIPrincipal* a, nsIPrincipal* b) -> bool { + return a == b || (a && b && a->Equals(b)); + }; + + // XXX: Needing to do all of this validation manually is kinda gross. + if (!uriEq(snapshot->GetURI(), aLoadState->URI())) { + return Err("URI"); + } + if (!uriEq(snapshot->GetOriginalURI(), aLoadState->OriginalURI())) { + return Err("OriginalURI"); + } + if (!aLoadState->ResultPrincipalURIIsSome() || + !uriEq(snapshot->GetResultPrincipalURI(), + aLoadState->ResultPrincipalURI())) { + return Err("ResultPrincipalURI"); + } + if (!uriEq(snapshot->GetUnstrippedURI(), aLoadState->GetUnstrippedURI())) { + return Err("UnstrippedURI"); + } + if (!principalEq(snapshot->GetTriggeringPrincipal(), + aLoadState->TriggeringPrincipal())) { + return Err("TriggeringPrincipal"); + } + if (!principalEq(snapshot->GetPrincipalToInherit(), + aLoadState->PrincipalToInherit())) { + return Err("PrincipalToInherit"); + } + if (!principalEq(snapshot->GetPartitionedPrincipalToInherit(), + aLoadState->PartitionedPrincipalToInherit())) { + return Err("PartitionedPrincipalToInherit"); + } + + // Everything matches! + return loading->mEntry; +} + +auto DocumentLoadListener::Open(nsDocShellLoadState* aLoadState, + LoadInfo* aLoadInfo, nsLoadFlags aLoadFlags, + uint32_t aCacheKey, + const Maybe<uint64_t>& aChannelId, + const TimeStamp& aAsyncOpenTime, + nsDOMNavigationTiming* aTiming, + Maybe<ClientInfo>&& aInfo, bool aUrgentStart, + dom::ContentParent* aContentParent, + nsresult* aRv) -> RefPtr<OpenPromise> { + auto* loadingContext = GetLoadingBrowsingContext(); + + MOZ_DIAGNOSTIC_ASSERT_IF(loadingContext->GetParent(), + loadingContext->GetParentWindowContext()); + + OriginAttributes attrs; + loadingContext->GetOriginAttributes(attrs); + + mLoadIdentifier = aLoadState->GetLoadIdentifier(); + // See description of mFileName in nsDocShellLoadState.h + mIsDownload = !aLoadState->FileName().IsVoid(); + mIsLoadingJSURI = net::SchemeIsJavascript(aLoadState->URI()); + + // Check for infinite recursive object or iframe loads + if (aLoadState->OriginalFrameSrc() || !mIsDocumentLoad) { + if (!CheckRecursiveLoad(loadingContext, aLoadState, mIsDocumentLoad)) { + *aRv = NS_ERROR_RECURSIVE_DOCUMENT_LOAD; + mParentChannelListener = nullptr; + return nullptr; + } + } + + auto* documentContext = GetDocumentBrowsingContext(); + + // If we are using SHIP and this load is from session history, validate that + // the load matches our local copy of the loading history entry. + // + // NOTE: Keep this check in-sync with the check in + // `nsDocShellLoadState::GetEffectiveTriggeringRemoteType()`! + RefPtr<SessionHistoryEntry> existingEntry; + if (SessionHistoryInParent() && aLoadState->LoadIsFromSessionHistory() && + aLoadState->LoadType() != LOAD_ERROR_PAGE) { + Result<SessionHistoryEntry*, const char*> result = + ValidateHistoryLoad(loadingContext, aLoadState); + if (result.isErr()) { + const char* mismatch = result.unwrapErr(); + LOG( + ("DocumentLoadListener::Open with invalid loading history entry " + "[this=%p, mismatch=%s]", + this, mismatch)); +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + MOZ_CRASH_UNSAFE_PRINTF( + "DocumentLoadListener::Open for invalid history entry due to " + "mismatch of '%s'", + mismatch); +#endif + *aRv = NS_ERROR_DOM_SECURITY_ERR; + mParentChannelListener = nullptr; + return nullptr; + } + + existingEntry = result.unwrap(); + if (!existingEntry->IsInSessionHistory() && + !documentContext->HasLoadingHistoryEntry(existingEntry)) { + SessionHistoryEntry::RemoveLoadId( + aLoadState->GetLoadingSessionHistoryInfo()->mLoadId); + LOG( + ("DocumentLoadListener::Open with disconnected history entry " + "[this=%p]", + this)); + + *aRv = NS_BINDING_ABORTED; + mParentChannelListener = nullptr; + mChannel = nullptr; + return nullptr; + } + } + + if (aLoadState->GetRemoteTypeOverride()) { + if (!mIsDocumentLoad || !NS_IsAboutBlank(aLoadState->URI()) || + !loadingContext->IsTopContent()) { + LOG( + ("DocumentLoadListener::Open with invalid remoteTypeOverride " + "[this=%p]", + this)); + *aRv = NS_ERROR_DOM_SECURITY_ERR; + mParentChannelListener = nullptr; + return nullptr; + } + + mRemoteTypeOverride = aLoadState->GetRemoteTypeOverride(); + } + + if (NS_WARN_IF(!loadingContext->IsOwnedByProcess( + GetContentProcessId(aContentParent)))) { + LOG( + ("DocumentLoadListener::Open called from non-current content process " + "[this=%p, current=%" PRIu64 ", caller=%" PRIu64 "]", + this, loadingContext->OwnerProcessId(), + uint64_t(GetContentProcessId(aContentParent)))); + *aRv = NS_BINDING_ABORTED; + mParentChannelListener = nullptr; + return nullptr; + } + + if (mIsDocumentLoad && loadingContext->IsContent() && + NS_WARN_IF(loadingContext->IsReplaced())) { + LOG( + ("DocumentLoadListener::Open called from replaced BrowsingContext " + "[this=%p, browserid=%" PRIx64 ", bcid=%" PRIx64 "]", + this, loadingContext->BrowserId(), loadingContext->Id())); + *aRv = NS_BINDING_ABORTED; + mParentChannelListener = nullptr; + return nullptr; + } + + if (!nsDocShell::CreateAndConfigureRealChannelForLoadState( + loadingContext, aLoadState, aLoadInfo, mParentChannelListener, + nullptr, attrs, aLoadFlags, aCacheKey, *aRv, + getter_AddRefs(mChannel))) { + LOG(("DocumentLoadListener::Open failed to create channel [this=%p]", + this)); + mParentChannelListener = nullptr; + return nullptr; + } + + if (documentContext && aLoadState->LoadType() != LOAD_ERROR_PAGE && + mozilla::SessionHistoryInParent()) { + // It's hard to know at this point whether session history will be enabled + // in the browsing context, so we always create an entry for a load here. + mLoadingSessionHistoryInfo = + documentContext->CreateLoadingSessionHistoryEntryForLoad( + aLoadState, existingEntry, mChannel); + MOZ_ASSERT(mLoadingSessionHistoryInfo); + } + + nsCOMPtr<nsIURI> uriBeingLoaded; + Unused << NS_WARN_IF( + NS_FAILED(mChannel->GetURI(getter_AddRefs(uriBeingLoaded)))); + + RefPtr<HttpBaseChannel> httpBaseChannel = do_QueryObject(mChannel, aRv); + if (uriBeingLoaded && httpBaseChannel) { + nsCOMPtr<nsIURI> topWindowURI; + if (mIsDocumentLoad && loadingContext->IsTop()) { + // If this is for the top level loading, the top window URI should be the + // URI which we are loading. + topWindowURI = uriBeingLoaded; + } else if (RefPtr<WindowGlobalParent> topWindow = AntiTrackingUtils:: + GetTopWindowExcludingExtensionAccessibleContentFrames( + loadingContext, uriBeingLoaded)) { + nsCOMPtr<nsIPrincipal> topWindowPrincipal = + topWindow->DocumentPrincipal(); + if (topWindowPrincipal && !topWindowPrincipal->GetIsNullPrincipal()) { + auto* basePrin = BasePrincipal::Cast(topWindowPrincipal); + basePrin->GetURI(getter_AddRefs(topWindowURI)); + } + } + httpBaseChannel->SetTopWindowURI(topWindowURI); + } + + nsCOMPtr<nsIIdentChannel> identChannel = do_QueryInterface(mChannel); + if (identChannel && aChannelId) { + Unused << identChannel->SetChannelId(*aChannelId); + } + mDocumentChannelId = aChannelId; + + RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel); + if (httpChannelImpl) { + httpChannelImpl->SetWarningReporter(this); + + if (mIsDocumentLoad && loadingContext->IsTop()) { + httpChannelImpl->SetEarlyHintObserver(this); + } + } + + nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(mChannel); + if (timedChannel) { + timedChannel->SetAsyncOpen(aAsyncOpenTime); + } + + if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel)) { + Unused << httpChannel->SetRequestContextID( + loadingContext->GetRequestContextId()); + + nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(httpChannel)); + if (cos && aUrgentStart) { + cos->AddClassFlags(nsIClassOfService::UrgentStart); + } + } + + // Setup a ClientChannelHelper to watch for redirects, and copy + // across any serviceworker related data between channels as needed. + AddClientChannelHelperInParent(mChannel, std::move(aInfo)); + + if (documentContext && !documentContext->StartDocumentLoad(this)) { + LOG(("DocumentLoadListener::Open failed StartDocumentLoad [this=%p]", + this)); + *aRv = NS_BINDING_ABORTED; + mParentChannelListener = nullptr; + mChannel = nullptr; + return nullptr; + } + + // Recalculate the openFlags, matching the logic in use in Content process. + // NOTE: The only case not handled here to mirror Content process is + // redirecting to re-use the channel. + MOZ_ASSERT(!aLoadState->GetPendingRedirectedChannel()); + uint32_t openFlags = + nsDocShell::ComputeURILoaderFlags(loadingContext, aLoadState->LoadType()); + + RefPtr<ParentProcessDocumentOpenInfo> openInfo = + new ParentProcessDocumentOpenInfo(mParentChannelListener, openFlags, + loadingContext, mIsDocumentLoad); + openInfo->Prepare(); + +#ifdef ANDROID + RefPtr<MozPromise<bool, bool, false>> promise; + if (documentContext && aLoadState->LoadType() != LOAD_ERROR_PAGE && + !(aLoadState->HasInternalLoadFlags( + nsDocShell::INTERNAL_LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE)) && + !(aLoadState->LoadType() & LOAD_HISTORY)) { + nsCOMPtr<nsIWidget> widget = + documentContext->GetParentProcessWidgetContaining(); + RefPtr<nsWindow> window = nsWindow::From(widget); + + if (window) { + promise = window->OnLoadRequest( + aLoadState->URI(), nsIBrowserDOMWindow::OPEN_CURRENTWINDOW, + aLoadState->InternalLoadFlags(), aLoadState->TriggeringPrincipal(), + aLoadState->HasValidUserGestureActivation(), + documentContext->IsTopContent()); + } + } + + if (promise) { + RefPtr<DocumentLoadListener> self = this; + promise->Then( + GetCurrentSerialEventTarget(), __func__, + [=](const MozPromise<bool, bool, false>::ResolveOrRejectValue& aValue) { + if (aValue.IsResolve()) { + bool handled = aValue.ResolveValue(); + if (handled) { + self->DisconnectListeners(NS_ERROR_ABORT, NS_ERROR_ABORT); + mParentChannelListener = nullptr; + } else { + nsresult rv = mChannel->AsyncOpen(openInfo); + if (NS_FAILED(rv)) { + self->DisconnectListeners(rv, rv); + mParentChannelListener = nullptr; + } + } + } + }); + } else +#endif /* ANDROID */ + { + *aRv = mChannel->AsyncOpen(openInfo); + if (NS_FAILED(*aRv)) { + LOG(("DocumentLoadListener::Open failed AsyncOpen [this=%p rv=%" PRIx32 + "]", + this, static_cast<uint32_t>(*aRv))); + if (documentContext) { + documentContext->EndDocumentLoad(false); + } + mParentChannelListener = nullptr; + return nullptr; + } + } + + // HTTPS-Only Mode fights potential timeouts caused by upgrades. Instantly + // after opening the document channel we have to kick off countermeasures. + nsHTTPSOnlyUtils::PotentiallyFireHttpRequestToShortenTimout(this); + + nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo(); + loadInfo->SetChannelCreationOriginalURI(aLoadState->URI()); + + mContentParent = aContentParent; + mLoadStateExternalLoadFlags = aLoadState->LoadFlags(); + mLoadStateInternalLoadFlags = aLoadState->InternalLoadFlags(); + mLoadStateLoadType = aLoadState->LoadType(); + mTiming = aTiming; + mSrcdocData = aLoadState->SrcdocData(); + mBaseURI = aLoadState->BaseURI(); + mOriginalUriString = aLoadState->GetOriginalURIString(); + if (documentContext) { + mParentWindowContext = documentContext->GetParentWindowContext(); + } else { + mParentWindowContext = + WindowGlobalParent::GetByInnerWindowId(aLoadInfo->GetInnerWindowID()); + MOZ_RELEASE_ASSERT(mParentWindowContext->GetBrowsingContext() == + GetLoadingBrowsingContext(), + "mismatched parent window context?"); + } + + // For content-initiated loads, this flag is set in nsDocShell::LoadURI. + // For parent-initiated loads, we have to set it here. + // Below comment is copied from nsDocShell::LoadURI - + // If we have a system triggering principal, we can assume that this load was + // triggered by some UI in the browser chrome, such as the URL bar or + // bookmark bar. This should count as a user interaction for the current sh + // entry, so that the user may navigate back to the current entry, from the + // entry that is going to be added as part of this load. + if (!mSupportsRedirectToRealChannel && aLoadState->TriggeringPrincipal() && + aLoadState->TriggeringPrincipal()->IsSystemPrincipal()) { + WindowContext* topWc = loadingContext->GetTopWindowContext(); + if (topWc && !topWc->IsDiscarded()) { + MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(true)); + } + } + + *aRv = NS_OK; + mOpenPromise = new OpenPromise::Private(__func__); + // We make the promise use direct task dispatch in order to reduce the number + // of event loops iterations. + mOpenPromise->UseDirectTaskDispatch(__func__); + return mOpenPromise; +} + +auto DocumentLoadListener::OpenDocument( + nsDocShellLoadState* aLoadState, uint32_t aCacheKey, + const Maybe<uint64_t>& aChannelId, const TimeStamp& aAsyncOpenTime, + nsDOMNavigationTiming* aTiming, Maybe<dom::ClientInfo>&& aInfo, + Maybe<bool> aUriModified, Maybe<bool> aIsEmbeddingBlockedError, + dom::ContentParent* aContentParent, nsresult* aRv) -> RefPtr<OpenPromise> { + LOG(("DocumentLoadListener [%p] OpenDocument [uri=%s]", this, + aLoadState->URI()->GetSpecOrDefault().get())); + + MOZ_ASSERT(mIsDocumentLoad); + + RefPtr<CanonicalBrowsingContext> browsingContext = + GetDocumentBrowsingContext(); + + // If this is a top-level load, then rebuild the LoadInfo from scratch, + // since the goal is to be able to initiate loads in the parent, where the + // content process won't have provided us with an existing one. + RefPtr<LoadInfo> loadInfo = + CreateDocumentLoadInfo(browsingContext, aLoadState); + + nsLoadFlags loadFlags = aLoadState->CalculateChannelLoadFlags( + browsingContext, std::move(aUriModified), + std::move(aIsEmbeddingBlockedError)); + + // Keep track of navigation for the Bounce Tracking Protection. + if (browsingContext->IsTopContent()) { + RefPtr<BounceTrackingState> bounceTrackingState = + browsingContext->GetBounceTrackingState(); + + // Not every browsing context has a BounceTrackingState. It's also null when + // the feature is disabled. + if (bounceTrackingState) { + nsCOMPtr<nsIPrincipal> triggeringPrincipal; + nsresult rv = + loadInfo->GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal)); + + if (!NS_WARN_IF(NS_FAILED(rv))) { + DebugOnly<nsresult> rv = bounceTrackingState->OnStartNavigation( + triggeringPrincipal, loadInfo->GetHasValidUserGestureActivation()); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "BounceTrackingState::OnStartNavigation failed"); + } + } + } + + return Open(aLoadState, loadInfo, loadFlags, aCacheKey, aChannelId, + aAsyncOpenTime, aTiming, std::move(aInfo), false, aContentParent, + aRv); +} + +auto DocumentLoadListener::OpenObject( + nsDocShellLoadState* aLoadState, uint32_t aCacheKey, + const Maybe<uint64_t>& aChannelId, const TimeStamp& aAsyncOpenTime, + nsDOMNavigationTiming* aTiming, Maybe<dom::ClientInfo>&& aInfo, + uint64_t aInnerWindowId, nsLoadFlags aLoadFlags, + nsContentPolicyType aContentPolicyType, bool aUrgentStart, + dom::ContentParent* aContentParent, + ObjectUpgradeHandler* aObjectUpgradeHandler, nsresult* aRv) + -> RefPtr<OpenPromise> { + LOG(("DocumentLoadListener [%p] OpenObject [uri=%s]", this, + aLoadState->URI()->GetSpecOrDefault().get())); + + MOZ_ASSERT(!mIsDocumentLoad); + + auto sandboxFlags = aLoadState->TriggeringSandboxFlags(); + + RefPtr<LoadInfo> loadInfo = CreateObjectLoadInfo( + aLoadState, aInnerWindowId, aContentPolicyType, sandboxFlags); + + mObjectUpgradeHandler = aObjectUpgradeHandler; + + return Open(aLoadState, loadInfo, aLoadFlags, aCacheKey, aChannelId, + aAsyncOpenTime, aTiming, std::move(aInfo), aUrgentStart, + aContentParent, aRv); +} + +auto DocumentLoadListener::OpenInParent(nsDocShellLoadState* aLoadState, + bool aSupportsRedirectToRealChannel) + -> RefPtr<OpenPromise> { + MOZ_ASSERT(mIsDocumentLoad); + + // We currently only support passing nullptr for aLoadInfo for + // top level browsing contexts. + auto* browsingContext = GetDocumentBrowsingContext(); + if (!browsingContext->IsTopContent() || + !browsingContext->GetContentParent()) { + LOG(("DocumentLoadListener::OpenInParent failed because of subdoc")); + return nullptr; + } + + // Clone because this mutates the load flags in the load state, which + // breaks nsDocShells expectations of being able to do it. + RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(*aLoadState); + loadState->CalculateLoadURIFlags(); + + RefPtr<nsDOMNavigationTiming> timing = new nsDOMNavigationTiming(nullptr); + timing->NotifyNavigationStart( + browsingContext->IsActive() + ? nsDOMNavigationTiming::DocShellState::eActive + : nsDOMNavigationTiming::DocShellState::eInactive); + + const mozilla::dom::LoadingSessionHistoryInfo* loadingInfo = + loadState->GetLoadingSessionHistoryInfo(); + + uint32_t cacheKey = 0; + auto loadType = aLoadState->LoadType(); + if (loadType == LOAD_HISTORY || loadType == LOAD_RELOAD_NORMAL || + loadType == LOAD_RELOAD_CHARSET_CHANGE || + loadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE || + loadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE) { + if (loadingInfo) { + cacheKey = loadingInfo->mInfo.GetCacheKey(); + } + } + + // Loads start in the content process might have exposed a channel id to + // observers, so we need to preserve the value in the parent. That can't have + // happened here, so Nothing() is fine. + Maybe<uint64_t> channelId = Nothing(); + + // Initial client info is only relevant for subdocument loads, which we're + // not supporting yet. + Maybe<dom::ClientInfo> initialClientInfo; + + mSupportsRedirectToRealChannel = aSupportsRedirectToRealChannel; + + // This is a top-level load, so rebuild the LoadInfo from scratch, + // since in the parent the + // content process won't have provided us with an existing one. + RefPtr<LoadInfo> loadInfo = + CreateDocumentLoadInfo(browsingContext, aLoadState); + + nsLoadFlags loadFlags = loadState->CalculateChannelLoadFlags( + browsingContext, + Some(loadingInfo && loadingInfo->mInfo.GetURIWasModified()), Nothing()); + + nsresult rv; + return Open(loadState, loadInfo, loadFlags, cacheKey, channelId, + TimeStamp::Now(), timing, std::move(initialClientInfo), false, + browsingContext->GetContentParent(), &rv); +} + +base::ProcessId DocumentLoadListener::OtherPid() const { + return mContentParent ? mContentParent->OtherPid() : base::ProcessId{0}; +} + +void DocumentLoadListener::FireStateChange(uint32_t aStateFlags, + nsresult aStatus) { + nsCOMPtr<nsIChannel> request = GetChannel(); + + RefPtr<BrowsingContextWebProgress> webProgress = + GetLoadingBrowsingContext()->GetWebProgress(); + + if (webProgress) { + NS_DispatchToMainThread( + NS_NewRunnableFunction("DocumentLoadListener::FireStateChange", [=]() { + webProgress->OnStateChange(webProgress, request, aStateFlags, + aStatus); + })); + } +} + +static void SetNavigating(CanonicalBrowsingContext* aBrowsingContext, + bool aNavigating) { + nsCOMPtr<nsIBrowser> browser; + if (RefPtr<Element> currentElement = aBrowsingContext->GetEmbedderElement()) { + browser = currentElement->AsBrowser(); + } + + if (!browser) { + return; + } + + NS_DispatchToMainThread(NS_NewRunnableFunction( + "DocumentLoadListener::SetNavigating", + [browser, aNavigating]() { browser->SetIsNavigating(aNavigating); })); +} + +/* static */ bool DocumentLoadListener::LoadInParent( + CanonicalBrowsingContext* aBrowsingContext, nsDocShellLoadState* aLoadState, + bool aSetNavigating) { + SetNavigating(aBrowsingContext, aSetNavigating); + + RefPtr<DocumentLoadListener> load = + new DocumentLoadListener(aBrowsingContext, true); + RefPtr<DocumentLoadListener::OpenPromise> promise = load->OpenInParent( + aLoadState, /* aSupportsRedirectToRealChannel */ false); + if (!promise) { + SetNavigating(aBrowsingContext, false); + return false; + } + + // We passed false for aSupportsRedirectToRealChannel, so we should always + // take the process switching path, and reject this promise. + promise->Then( + GetCurrentSerialEventTarget(), __func__, + [load](DocumentLoadListener::OpenPromise::ResolveOrRejectValue&& aValue) { + MOZ_ASSERT(aValue.IsReject()); + DocumentLoadListener::OpenPromiseFailedType& rejectValue = + aValue.RejectValue(); + if (!rejectValue.mContinueNavigating) { + // If we're not switching the load to a new process, then it is + // finished (and failed), and we should fire a state change to notify + // observers. Normally the docshell would fire this, and it would get + // filtered out by BrowserParent if needed. + load->FireStateChange(nsIWebProgressListener::STATE_STOP | + nsIWebProgressListener::STATE_IS_WINDOW | + nsIWebProgressListener::STATE_IS_NETWORK, + rejectValue.mStatus); + } + }); + + load->FireStateChange(nsIWebProgressListener::STATE_START | + nsIWebProgressListener::STATE_IS_DOCUMENT | + nsIWebProgressListener::STATE_IS_REQUEST | + nsIWebProgressListener::STATE_IS_WINDOW | + nsIWebProgressListener::STATE_IS_NETWORK, + NS_OK); + SetNavigating(aBrowsingContext, false); + return true; +} + +/* static */ +bool DocumentLoadListener::SpeculativeLoadInParent( + dom::CanonicalBrowsingContext* aBrowsingContext, + nsDocShellLoadState* aLoadState) { + LOG(("DocumentLoadListener::OpenFromParent")); + + RefPtr<DocumentLoadListener> listener = + new DocumentLoadListener(aBrowsingContext, true); + + auto promise = listener->OpenInParent(aLoadState, true); + if (promise) { + // Create an entry in the redirect channel registrar to + // allocate an identifier for this load. + nsCOMPtr<nsIRedirectChannelRegistrar> registrar = + RedirectChannelRegistrar::GetOrCreate(); + uint64_t loadIdentifier = aLoadState->GetLoadIdentifier(); + DebugOnly<nsresult> rv = + registrar->RegisterChannel(nullptr, loadIdentifier); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + // Register listener (as an nsIParentChannel) under our new identifier. + rv = registrar->LinkChannels(loadIdentifier, listener, nullptr); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + } + return !!promise; +} + +void DocumentLoadListener::CleanupParentLoadAttempt(uint64_t aLoadIdent) { + nsCOMPtr<nsIRedirectChannelRegistrar> registrar = + RedirectChannelRegistrar::GetOrCreate(); + + nsCOMPtr<nsIParentChannel> parentChannel; + registrar->GetParentChannel(aLoadIdent, getter_AddRefs(parentChannel)); + RefPtr<DocumentLoadListener> loadListener = do_QueryObject(parentChannel); + + if (loadListener) { + // If the load listener is still registered, then we must have failed + // to connect DocumentChannel into it. Better cancel it! + loadListener->NotifyDocumentChannelFailed(); + } + + registrar->DeregisterChannels(aLoadIdent); +} + +auto DocumentLoadListener::ClaimParentLoad(DocumentLoadListener** aListener, + uint64_t aLoadIdent, + Maybe<uint64_t> aChannelId) + -> RefPtr<OpenPromise> { + nsCOMPtr<nsIRedirectChannelRegistrar> registrar = + RedirectChannelRegistrar::GetOrCreate(); + + nsCOMPtr<nsIParentChannel> parentChannel; + registrar->GetParentChannel(aLoadIdent, getter_AddRefs(parentChannel)); + RefPtr<DocumentLoadListener> loadListener = do_QueryObject(parentChannel); + registrar->DeregisterChannels(aLoadIdent); + + if (!loadListener) { + // The parent went away unexpectedly. + *aListener = nullptr; + return nullptr; + } + + loadListener->mDocumentChannelId = aChannelId; + + MOZ_DIAGNOSTIC_ASSERT(loadListener->mOpenPromise); + loadListener.forget(aListener); + + return (*aListener)->mOpenPromise; +} + +void DocumentLoadListener::NotifyDocumentChannelFailed() { + LOG(("DocumentLoadListener NotifyDocumentChannelFailed [this=%p]", this)); + // There's been no calls to ClaimParentLoad, and so no listeners have been + // attached to mOpenPromise yet. As such we can run Then() on it. + mOpenPromise->Then( + GetMainThreadSerialEventTarget(), __func__, + [](DocumentLoadListener::OpenPromiseSucceededType&& aResolveValue) { + aResolveValue.mPromise->Resolve(NS_BINDING_ABORTED, __func__); + }, + []() {}); + + Cancel(NS_BINDING_ABORTED, + "DocumentLoadListener::NotifyDocumentChannelFailed"_ns); +} + +void DocumentLoadListener::Disconnect(bool aContinueNavigating) { + LOG(("DocumentLoadListener Disconnect [this=%p, aContinueNavigating=%d]", + this, aContinueNavigating)); + // The nsHttpChannel may have a reference to this parent, release it + // to avoid circular references. + RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel); + if (httpChannelImpl) { + httpChannelImpl->SetWarningReporter(nullptr); + httpChannelImpl->SetEarlyHintObserver(nullptr); + } + + // Don't cancel ongoing early hints when continuing to load the web page. + // Early hints are loaded earlier in the code and shouldn't get cancelled + // here. See also: Bug 1765652 + if (!aContinueNavigating) { + mEarlyHintsService.Cancel("DocumentLoadListener::Disconnect"_ns); + } + + if (auto* ctx = GetDocumentBrowsingContext()) { + ctx->EndDocumentLoad(aContinueNavigating); + } +} + +void DocumentLoadListener::Cancel(const nsresult& aStatusCode, + const nsACString& aReason) { + LOG( + ("DocumentLoadListener Cancel [this=%p, " + "aStatusCode=%" PRIx32 " ]", + this, static_cast<uint32_t>(aStatusCode))); + if (mOpenPromiseResolved) { + return; + } + if (mChannel) { + mChannel->CancelWithReason(aStatusCode, aReason); + } + + DisconnectListeners(aStatusCode, aStatusCode); +} + +void DocumentLoadListener::DisconnectListeners(nsresult aStatus, + nsresult aLoadGroupStatus, + bool aContinueNavigating) { + LOG( + ("DocumentLoadListener DisconnectListener [this=%p, " + "aStatus=%" PRIx32 ", aLoadGroupStatus=%" PRIx32 + ", aContinueNavigating=%d]", + this, static_cast<uint32_t>(aStatus), + static_cast<uint32_t>(aLoadGroupStatus), aContinueNavigating)); + + RejectOpenPromise(aStatus, aLoadGroupStatus, aContinueNavigating, __func__); + + Disconnect(aContinueNavigating); + + // Clear any pending stream filter requests. If we're going to be sending a + // response to the content process due to a navigation, our caller will have + // already stashed the array to be passed to `TriggerRedirectToRealChannel`, + // so it's safe for us to clear here. + // TODO: If we retargeted the stream to a non-default handler (e.g. to trigger + // a download), we currently never attach a stream filter. Should we attach a + // stream filter in those situations as well? + mStreamFilterRequests.Clear(); +} + +void DocumentLoadListener::RedirectToRealChannelFinished(nsresult aRv) { + LOG( + ("DocumentLoadListener RedirectToRealChannelFinished [this=%p, " + "aRv=%" PRIx32 " ]", + this, static_cast<uint32_t>(aRv))); + if (NS_FAILED(aRv)) { + FinishReplacementChannelSetup(aRv); + return; + } + + // Wait for background channel ready on target channel + nsCOMPtr<nsIRedirectChannelRegistrar> redirectReg = + RedirectChannelRegistrar::GetOrCreate(); + MOZ_ASSERT(redirectReg); + + nsCOMPtr<nsIParentChannel> redirectParentChannel; + redirectReg->GetParentChannel(mRedirectChannelId, + getter_AddRefs(redirectParentChannel)); + if (!redirectParentChannel) { + FinishReplacementChannelSetup(NS_ERROR_FAILURE); + return; + } + + nsCOMPtr<nsIParentRedirectingChannel> redirectingParent = + do_QueryInterface(redirectParentChannel); + if (!redirectingParent) { + // Continue verification procedure if redirecting to non-Http protocol + FinishReplacementChannelSetup(NS_OK); + return; + } + + // Ask redirected channel if verification can proceed. + // ReadyToVerify will be invoked when redirected channel is ready. + redirectingParent->ContinueVerification(this); +} + +NS_IMETHODIMP +DocumentLoadListener::ReadyToVerify(nsresult aResultCode) { + FinishReplacementChannelSetup(aResultCode); + return NS_OK; +} + +void DocumentLoadListener::FinishReplacementChannelSetup(nsresult aResult) { + LOG( + ("DocumentLoadListener FinishReplacementChannelSetup [this=%p, " + "aResult=%x]", + this, int(aResult))); + + auto endDocumentLoad = MakeScopeExit([&]() { + if (auto* ctx = GetDocumentBrowsingContext()) { + ctx->EndDocumentLoad(false); + } + }); + mStreamFilterRequests.Clear(); + + nsCOMPtr<nsIRedirectChannelRegistrar> registrar = + RedirectChannelRegistrar::GetOrCreate(); + MOZ_ASSERT(registrar); + + nsCOMPtr<nsIParentChannel> redirectChannel; + nsresult rv = registrar->GetParentChannel(mRedirectChannelId, + getter_AddRefs(redirectChannel)); + if (NS_FAILED(rv) || !redirectChannel) { + aResult = NS_ERROR_FAILURE; + } + + // Release all previously registered channels, they are no longer needed to + // be kept in the registrar from this moment. + registrar->DeregisterChannels(mRedirectChannelId); + mRedirectChannelId = 0; + if (NS_FAILED(aResult)) { + if (redirectChannel) { + redirectChannel->Delete(); + } + mChannel->Cancel(aResult); + mChannel->Resume(); + return; + } + + MOZ_ASSERT( + !SameCOMIdentity(redirectChannel, static_cast<nsIParentChannel*>(this))); + + redirectChannel->SetParentListener(mParentChannelListener); + + ApplyPendingFunctions(redirectChannel); + + if (!ResumeSuspendedChannel(redirectChannel)) { + nsCOMPtr<nsILoadGroup> loadGroup; + mChannel->GetLoadGroup(getter_AddRefs(loadGroup)); + if (loadGroup) { + // We added ourselves to the load group, but attempting + // to resume has notified us that the channel is already + // finished. Better remove ourselves from the loadgroup + // again. The only time the channel will be in a loadgroup + // is if we're connected to the parent process. + nsresult status = NS_OK; + mChannel->GetStatus(&status); + loadGroup->RemoveRequest(mChannel, nullptr, status); + } + } +} + +void DocumentLoadListener::ApplyPendingFunctions( + nsIParentChannel* aChannel) const { + // We stored the values from all nsIParentChannel functions called since we + // couldn't handle them. Copy them across to the real channel since it + // should know what to do. + + nsCOMPtr<nsIParentChannel> parentChannel = aChannel; + for (const auto& variant : mIParentChannelFunctions) { + variant.match( + [parentChannel](const ClassifierMatchedInfoParams& aParams) { + parentChannel->SetClassifierMatchedInfo( + aParams.mList, aParams.mProvider, aParams.mFullHash); + }, + [parentChannel](const ClassifierMatchedTrackingInfoParams& aParams) { + parentChannel->SetClassifierMatchedTrackingInfo(aParams.mLists, + aParams.mFullHashes); + }, + [parentChannel](const ClassificationFlagsParams& aParams) { + parentChannel->NotifyClassificationFlags(aParams.mClassificationFlags, + aParams.mIsThirdParty); + }); + } + + RefPtr<HttpChannelSecurityWarningReporter> reporter; + if (RefPtr<HttpChannelParent> httpParent = do_QueryObject(aChannel)) { + reporter = httpParent; + } else if (RefPtr<nsHttpChannel> httpChannel = do_QueryObject(aChannel)) { + reporter = httpChannel->GetWarningReporter(); + } + if (reporter) { + for (const auto& variant : mSecurityWarningFunctions) { + variant.match( + [reporter](const ReportSecurityMessageParams& aParams) { + Unused << reporter->ReportSecurityMessage(aParams.mMessageTag, + aParams.mMessageCategory); + }, + [reporter](const LogBlockedCORSRequestParams& aParams) { + Unused << reporter->LogBlockedCORSRequest( + aParams.mMessage, aParams.mCategory, aParams.mIsWarning); + }, + [reporter](const LogMimeTypeMismatchParams& aParams) { + Unused << reporter->LogMimeTypeMismatch( + aParams.mMessageName, aParams.mWarning, aParams.mURL, + aParams.mContentType); + }); + } + } +} + +bool DocumentLoadListener::ResumeSuspendedChannel( + nsIStreamListener* aListener) { + LOG(("DocumentLoadListener ResumeSuspendedChannel [this=%p]", this)); + RefPtr<nsHttpChannel> httpChannel = do_QueryObject(mChannel); + if (httpChannel) { + httpChannel->SetApplyConversion(mOldApplyConversion); + } + + if (!mIsFinished) { + mParentChannelListener->SetListenerAfterRedirect(aListener); + } + + // If we failed to suspend the channel, then we might have received + // some messages while the redirected was being handled. + // Manually send them on now. + nsTArray<StreamListenerFunction> streamListenerFunctions = + std::move(mStreamListenerFunctions); + if (!aListener) { + streamListenerFunctions.Clear(); + } + + ForwardStreamListenerFunctions(std::move(streamListenerFunctions), aListener); + + // We don't expect to get new stream listener functions added + // via re-entrancy. If this ever happens, we should understand + // exactly why before allowing it. + NS_ASSERTION(mStreamListenerFunctions.IsEmpty(), + "Should not have added new stream listener function!"); + + mChannel->Resume(); + + // Our caller will invoke `EndDocumentLoad` for us. + + return !mIsFinished; +} + +void DocumentLoadListener::CancelEarlyHintPreloads() { + mEarlyHintsService.Cancel("DocumentLoadListener::CancelEarlyHintPreloads"_ns); +} + +void DocumentLoadListener::RegisterEarlyHintLinksAndGetConnectArgs( + dom::ContentParentId aCpId, nsTArray<EarlyHintConnectArgs>& aOutLinks) { + mEarlyHintsService.RegisterLinksAndGetConnectArgs(aCpId, aOutLinks); +} + +void DocumentLoadListener::SerializeRedirectData( + RedirectToRealChannelArgs& aArgs, bool aIsCrossProcess, + uint32_t aRedirectFlags, uint32_t aLoadFlags, + nsTArray<EarlyHintConnectArgs>&& aEarlyHints, + uint32_t aEarlyHintLinkType) const { + aArgs.uri() = GetChannelCreationURI(); + aArgs.loadIdentifier() = mLoadIdentifier; + aArgs.earlyHints() = std::move(aEarlyHints); + aArgs.earlyHintLinkType() = aEarlyHintLinkType; + + // I previously used HttpBaseChannel::CloneLoadInfoForRedirect, but that + // clears the principal to inherit, which fails tests (probably because this + // 'redirect' is usually just an implementation detail). It's also http + // only, and mChannel can be anything that we redirected to. + nsCOMPtr<nsILoadInfo> channelLoadInfo = mChannel->LoadInfo(); + nsCOMPtr<nsIPrincipal> principalToInherit; + channelLoadInfo->GetPrincipalToInherit(getter_AddRefs(principalToInherit)); + + const RefPtr<nsHttpChannel> baseChannel = do_QueryObject(mChannel); + + nsCOMPtr<nsILoadContext> loadContext; + NS_QueryNotificationCallbacks(mChannel, loadContext); + nsCOMPtr<nsILoadInfo> redirectLoadInfo; + + // Only use CloneLoadInfoForRedirect if we have a load context, + // since it internally tries to pull OriginAttributes from the + // the load context and asserts if they don't match the load info. + // We can end up without a load context if the channel has been aborted + // and the callbacks have been cleared. + if (baseChannel && loadContext) { + redirectLoadInfo = baseChannel->CloneLoadInfoForRedirect( + aArgs.uri(), nsIChannelEventSink::REDIRECT_INTERNAL); + redirectLoadInfo->SetResultPrincipalURI(aArgs.uri()); + + // The clone process clears this, and then we fail tests.. + // docshell/test/mochitest/test_forceinheritprincipal_overrule_owner.html + if (principalToInherit) { + redirectLoadInfo->SetPrincipalToInherit(principalToInherit); + } + } else { + redirectLoadInfo = + static_cast<mozilla::net::LoadInfo*>(channelLoadInfo.get())->Clone(); + + redirectLoadInfo->AppendRedirectHistoryEntry(mChannel, true); + } + + const Maybe<ClientInfo>& reservedClientInfo = + channelLoadInfo->GetReservedClientInfo(); + if (reservedClientInfo) { + redirectLoadInfo->SetReservedClientInfo(*reservedClientInfo); + } + + aArgs.registrarId() = mRedirectChannelId; + +#ifdef DEBUG + // We only set the granularFingerprintingProtection field when opening http + // channels. So, we mark the field as set here if the channel is not a + // nsHTTPChannel to pass the assertion check for getting this field in below + // LoadInfoToLoadInfoArgs() call. + if (!baseChannel) { + static_cast<mozilla::net::LoadInfo*>(redirectLoadInfo.get()) + ->MarkOverriddenFingerprintingSettingsAsSet(); + } +#endif + + MOZ_ALWAYS_SUCCEEDS( + ipc::LoadInfoToLoadInfoArgs(redirectLoadInfo, &aArgs.loadInfo())); + + mChannel->GetOriginalURI(getter_AddRefs(aArgs.originalURI())); + + // mChannel can be a nsHttpChannel as well as InterceptedHttpChannel so we + // can't use baseChannel here. + if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel)) { + MOZ_ALWAYS_SUCCEEDS(httpChannel->GetChannelId(&aArgs.channelId())); + } + + aArgs.redirectMode() = nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW; + nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = + do_QueryInterface(mChannel); + if (httpChannelInternal) { + MOZ_ALWAYS_SUCCEEDS( + httpChannelInternal->GetRedirectMode(&aArgs.redirectMode())); + } + + if (baseChannel) { + aArgs.init() = + Some(baseChannel + ->CloneReplacementChannelConfig( + true, aRedirectFlags, + HttpBaseChannel::ReplacementReason::DocumentChannel) + .Serialize()); + } + + uint32_t contentDispositionTemp; + nsresult rv = mChannel->GetContentDisposition(&contentDispositionTemp); + if (NS_SUCCEEDED(rv)) { + aArgs.contentDisposition() = Some(contentDispositionTemp); + } + + nsString contentDispositionFilenameTemp; + rv = mChannel->GetContentDispositionFilename(contentDispositionFilenameTemp); + if (NS_SUCCEEDED(rv)) { + aArgs.contentDispositionFilename() = Some(contentDispositionFilenameTemp); + } + + SetNeedToAddURIVisit(mChannel, false); + + aArgs.newLoadFlags() = aLoadFlags; + aArgs.redirectFlags() = aRedirectFlags; + aArgs.properties() = do_QueryObject(mChannel); + aArgs.srcdocData() = mSrcdocData; + aArgs.baseUri() = mBaseURI; + aArgs.loadStateExternalLoadFlags() = mLoadStateExternalLoadFlags; + aArgs.loadStateInternalLoadFlags() = mLoadStateInternalLoadFlags; + aArgs.loadStateLoadType() = mLoadStateLoadType; + aArgs.originalUriString() = mOriginalUriString; + if (mLoadingSessionHistoryInfo) { + aArgs.loadingSessionHistoryInfo().emplace(*mLoadingSessionHistoryInfo); + } +} + +static bool IsFirstLoadInWindow(nsIChannel* aChannel) { + if (nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aChannel)) { + bool tmp = false; + nsresult rv = + props->GetPropertyAsBool(u"docshell.newWindowTarget"_ns, &tmp); + return NS_SUCCEEDED(rv) && tmp; + } + return false; +} + +// Get where the document loaded by this nsIChannel should be rendered. This +// will be `OPEN_CURRENTWINDOW` unless we're loading an attachment which would +// normally open in an external program, but we're instead choosing to render +// internally. +static int32_t GetWhereToOpen(nsIChannel* aChannel, bool aIsDocumentLoad) { + // Ignore content disposition for loads from an object or embed element. + if (!aIsDocumentLoad) { + return nsIBrowserDOMWindow::OPEN_CURRENTWINDOW; + } + + // Always continue in the same window if we're not loading an attachment. + uint32_t disposition = nsIChannel::DISPOSITION_INLINE; + if (NS_FAILED(aChannel->GetContentDisposition(&disposition)) || + disposition != nsIChannel::DISPOSITION_ATTACHMENT) { + return nsIBrowserDOMWindow::OPEN_CURRENTWINDOW; + } + + // If the channel is for a new window target, continue in the same window. + if (IsFirstLoadInWindow(aChannel)) { + return nsIBrowserDOMWindow::OPEN_CURRENTWINDOW; + } + + // Respect the user's preferences with browser.link.open_newwindow + // FIXME: There should probably be a helper for this, as the logic is + // duplicated in a few places. + int32_t where = Preferences::GetInt("browser.link.open_newwindow", + nsIBrowserDOMWindow::OPEN_NEWTAB); + if (where == nsIBrowserDOMWindow::OPEN_CURRENTWINDOW || + where == nsIBrowserDOMWindow::OPEN_NEWWINDOW || + where == nsIBrowserDOMWindow::OPEN_NEWTAB) { + return where; + } + // NOTE: nsIBrowserDOMWindow::OPEN_NEWTAB_BACKGROUND is not allowed as a pref + // value. + return nsIBrowserDOMWindow::OPEN_NEWTAB; +} + +static DocumentLoadListener::ProcessBehavior GetProcessSwitchBehavior( + Element* aBrowserElement) { + if (aBrowserElement->HasAttribute(u"maychangeremoteness"_ns)) { + return DocumentLoadListener::ProcessBehavior::PROCESS_BEHAVIOR_STANDARD; + } + nsCOMPtr<nsIBrowser> browser = aBrowserElement->AsBrowser(); + bool isRemoteBrowser = false; + browser->GetIsRemoteBrowser(&isRemoteBrowser); + if (isRemoteBrowser) { + return DocumentLoadListener::ProcessBehavior:: + PROCESS_BEHAVIOR_SUBFRAME_ONLY; + } + return DocumentLoadListener::ProcessBehavior::PROCESS_BEHAVIOR_DISABLED; +} + +static bool ContextCanProcessSwitch(CanonicalBrowsingContext* aBrowsingContext, + WindowGlobalParent* aParentWindow, + bool aSwitchToNewTab) { + if (NS_WARN_IF(!aBrowsingContext)) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, + ("Process Switch Abort: no browsing context")); + return false; + } + if (!aBrowsingContext->IsContent()) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, + ("Process Switch Abort: non-content browsing context")); + return false; + } + + // If we're switching into a new tab, we can skip the remaining checks, as + // we're not actually changing the process of aBrowsingContext, so whether or + // not it is allowed to process switch isn't relevant. + if (aSwitchToNewTab) { + return true; + } + + if (aParentWindow && !aBrowsingContext->UseRemoteSubframes()) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, + ("Process Switch Abort: remote subframes disabled")); + return false; + } + + if (aParentWindow && aParentWindow->IsInProcess()) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, + ("Process Switch Abort: Subframe with in-process parent")); + return false; + } + + // Determine what process switching behaviour is being requested by the root + // <browser> element. + Element* browserElement = aBrowsingContext->Top()->GetEmbedderElement(); + if (!browserElement) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, + ("Process Switch Abort: cannot get embedder element")); + return false; + } + nsCOMPtr<nsIBrowser> browser = browserElement->AsBrowser(); + if (!browser) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, + ("Process Switch Abort: not loaded within nsIBrowser")); + return false; + } + + DocumentLoadListener::ProcessBehavior processBehavior = + GetProcessSwitchBehavior(browserElement); + + // Check if the process switch we're considering is disabled by the + // <browser>'s process behavior. + if (processBehavior == + DocumentLoadListener::ProcessBehavior::PROCESS_BEHAVIOR_DISABLED) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, + ("Process Switch Abort: switch disabled by <browser>")); + return false; + } + if (!aParentWindow && processBehavior == + DocumentLoadListener::ProcessBehavior:: + PROCESS_BEHAVIOR_SUBFRAME_ONLY) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, + ("Process Switch Abort: toplevel switch disabled by <browser>")); + return false; + } + + return true; +} + +static RefPtr<dom::BrowsingContextCallbackReceivedPromise> SwitchToNewTab( + CanonicalBrowsingContext* aLoadingBrowsingContext, int32_t aWhere) { + MOZ_ASSERT(aWhere == nsIBrowserDOMWindow::OPEN_NEWTAB || + aWhere == nsIBrowserDOMWindow::OPEN_NEWTAB_BACKGROUND || + aWhere == nsIBrowserDOMWindow::OPEN_NEWWINDOW, + "Unsupported open location"); + + auto promise = + MakeRefPtr<dom::BrowsingContextCallbackReceivedPromise::Private>( + __func__); + + // Get the nsIBrowserDOMWindow for the given BrowsingContext's tab. + nsCOMPtr<nsIBrowserDOMWindow> browserDOMWindow = + aLoadingBrowsingContext->GetBrowserDOMWindow(); + if (NS_WARN_IF(!browserDOMWindow)) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, + ("Process Switch Abort: Unable to get nsIBrowserDOMWindow")); + promise->Reject(NS_ERROR_FAILURE, __func__); + return promise; + } + + // Open a new content tab by calling into frontend. We don't need to worry + // about the triggering principal or CSP, as createContentWindow doesn't + // actually start loading anything, but use a null principal anyway in case + // something changes. + nsCOMPtr<nsIPrincipal> triggeringPrincipal = + NullPrincipal::Create(aLoadingBrowsingContext->OriginAttributesRef()); + + RefPtr<nsOpenWindowInfo> openInfo = new nsOpenWindowInfo(); + openInfo->mBrowsingContextReadyCallback = + new nsBrowsingContextReadyCallback(promise); + openInfo->mOriginAttributes = aLoadingBrowsingContext->OriginAttributesRef(); + openInfo->mParent = aLoadingBrowsingContext; + openInfo->mForceNoOpener = true; + openInfo->mIsRemote = true; + + // Do the actual work to open a new tab or window async. + nsresult rv = NS_DispatchToMainThread(NS_NewRunnableFunction( + "DocumentLoadListener::SwitchToNewTab", + [browserDOMWindow, openInfo, aWhere, triggeringPrincipal, promise] { + RefPtr<BrowsingContext> bc; + nsresult rv = browserDOMWindow->CreateContentWindow( + /* uri */ nullptr, openInfo, aWhere, + nsIBrowserDOMWindow::OPEN_NO_REFERRER, triggeringPrincipal, + /* csp */ nullptr, getter_AddRefs(bc)); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, + ("Process Switch Abort: CreateContentWindow threw")); + promise->Reject(rv, __func__); + } + if (bc) { + promise->Resolve(bc, __func__); + } + })); + if (NS_WARN_IF(NS_FAILED(rv))) { + promise->Reject(NS_ERROR_UNEXPECTED, __func__); + } + return promise; +} + +bool DocumentLoadListener::MaybeTriggerProcessSwitch( + bool* aWillSwitchToRemote) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_DIAGNOSTIC_ASSERT(mChannel); + MOZ_DIAGNOSTIC_ASSERT(mParentChannelListener); + MOZ_DIAGNOSTIC_ASSERT(aWillSwitchToRemote); + + MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, + ("DocumentLoadListener MaybeTriggerProcessSwitch [this=%p, uri=%s, " + "browserid=%" PRIx64 "]", + this, GetChannelCreationURI()->GetSpecOrDefault().get(), + GetLoadingBrowsingContext()->Top()->BrowserId())); + + // If we're doing an <object>/<embed> load, we may be doing a document load at + // this point. We never need to do a process switch for a non-document + // <object> or <embed> load. + if (!mIsDocumentLoad) { + if (!mChannel->IsDocument()) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, + ("Process Switch Abort: non-document load")); + return false; + } + nsresult status; + if (!nsObjectLoadingContent::IsSuccessfulRequest(mChannel, &status)) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, + ("Process Switch Abort: error page")); + return false; + } + } + + // Check if we should handle this load in a different tab or window. + int32_t where = GetWhereToOpen(mChannel, mIsDocumentLoad); + bool switchToNewTab = where != nsIBrowserDOMWindow::OPEN_CURRENTWINDOW; + + // Get the loading BrowsingContext. This may not be the context which will be + // switching processes when switching to a new tab, and in the case of an + // <object> or <embed> element, as we don't create the final context until + // after process selection. + // + // - /!\ WARNING /!\ - + // Don't use `browsingContext->IsTop()` in this method! It will behave + // incorrectly for non-document loads such as `<object>` or `<embed>`. + // Instead, check whether or not `parentWindow` is null. + RefPtr<CanonicalBrowsingContext> browsingContext = + GetLoadingBrowsingContext(); + // If switching to a new tab, the final BC isn't a frame. + RefPtr<WindowGlobalParent> parentWindow = + switchToNewTab ? nullptr : GetParentWindowContext(); + if (!ContextCanProcessSwitch(browsingContext, parentWindow, switchToNewTab)) { + return false; + } + + if (!browsingContext->IsOwnedByProcess(GetContentProcessId(mContentParent))) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Error, + ("Process Switch Abort: context no longer owned by creator")); + Cancel(NS_BINDING_ABORTED, + "Process Switch Abort: context no longer owned by creator"_ns); + return false; + } + + if (browsingContext->IsReplaced()) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, + ("Process Switch Abort: replaced browsing context")); + Cancel(NS_BINDING_ABORTED, + "Process Switch Abort: replaced browsing context"_ns); + return false; + } + + nsAutoCString currentRemoteType(NOT_REMOTE_TYPE); + if (mContentParent) { + currentRemoteType = mContentParent->GetRemoteType(); + } + + auto optionsResult = IsolationOptionsForNavigation( + browsingContext->Top(), switchToNewTab ? nullptr : parentWindow.get(), + GetChannelCreationURI(), mChannel, currentRemoteType, + HasCrossOriginOpenerPolicyMismatch(), switchToNewTab, mLoadStateLoadType, + mDocumentChannelId, mRemoteTypeOverride); + if (optionsResult.isErr()) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Error, + ("Process Switch Abort: CheckIsolationForNavigation Failed with %s", + GetStaticErrorName(optionsResult.inspectErr()))); + Cancel(optionsResult.unwrapErr(), + "Process Switch Abort: CheckIsolationForNavigation Failed"_ns); + return false; + } + + NavigationIsolationOptions options = optionsResult.unwrap(); + + if (options.mTryUseBFCache) { + MOZ_ASSERT(!parentWindow, "Can only BFCache toplevel windows"); + MOZ_ASSERT(!switchToNewTab, "Can't BFCache for a tab switch"); + bool sameOrigin = false; + if (auto* wgp = browsingContext->GetCurrentWindowGlobal()) { + nsCOMPtr<nsIPrincipal> resultPrincipal; + MOZ_ALWAYS_SUCCEEDS( + nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal( + mChannel, getter_AddRefs(resultPrincipal))); + sameOrigin = + wgp->DocumentPrincipal()->EqualsConsideringDomain(resultPrincipal); + } + + // We only reset the window name for content. + mLoadingSessionHistoryInfo->mForceMaybeResetName.emplace( + StaticPrefs::privacy_window_name_update_enabled() && + browsingContext->IsContent() && !sameOrigin); + } + + MOZ_LOG( + gProcessIsolationLog, LogLevel::Verbose, + ("CheckIsolationForNavigation -> current:(%s) remoteType:(%s) replace:%d " + "group:%" PRIx64 " bfcache:%d shentry:%p newTab:%d", + currentRemoteType.get(), options.mRemoteType.get(), + options.mReplaceBrowsingContext, options.mSpecificGroupId, + options.mTryUseBFCache, options.mActiveSessionHistoryEntry.get(), + switchToNewTab)); + + // Check if a process switch is needed. + if (currentRemoteType == options.mRemoteType && + !options.mReplaceBrowsingContext && !switchToNewTab) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Info, + ("Process Switch Abort: type (%s) is compatible", + options.mRemoteType.get())); + return false; + } + + if (NS_WARN_IF(parentWindow && options.mRemoteType.IsEmpty())) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Error, + ("Process Switch Abort: non-remote target process for subframe")); + return false; + } + + *aWillSwitchToRemote = !options.mRemoteType.IsEmpty(); + + // If we've decided to re-target this load into a new tab or window (see + // `GetWhereToOpen`), do so before performing a process switch. This will + // require creating the new <browser> to load in, which may be performed + // async. + if (switchToNewTab) { + SwitchToNewTab(browsingContext, where) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [self = RefPtr{this}, + options](const RefPtr<BrowsingContext>& aBrowsingContext) mutable { + if (aBrowsingContext->IsDiscarded()) { + MOZ_LOG( + gProcessIsolationLog, LogLevel::Error, + ("Process Switch: Got invalid new-tab BrowsingContext")); + self->RedirectToRealChannelFinished(NS_ERROR_FAILURE); + return; + } + + MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, + ("Process Switch: Redirected load to new tab")); + self->TriggerProcessSwitch(aBrowsingContext->Canonical(), options, + /* aIsNewTab */ true); + }, + [self = RefPtr{this}](const CopyableErrorResult&) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Error, + ("Process Switch: SwitchToNewTab failed")); + self->RedirectToRealChannelFinished(NS_ERROR_FAILURE); + }); + return true; + } + + // If we're doing a document load, we can immediately perform a process + // switch. + if (mIsDocumentLoad) { + TriggerProcessSwitch(browsingContext, options); + return true; + } + + // We're not doing a document load, which means we must be performing an + // object load. We need a BrowsingContext to perform the switch in, so will + // trigger an upgrade. + if (!mObjectUpgradeHandler) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, + ("Process Switch Abort: no object upgrade handler")); + return false; + } + + if (!StaticPrefs::fission_remoteObjectEmbed()) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, + ("Process Switch Abort: remote <object>/<embed> disabled")); + return false; + } + + mObjectUpgradeHandler->UpgradeObjectLoad()->Then( + GetMainThreadSerialEventTarget(), __func__, + [self = RefPtr{this}, options, parentWindow]( + const RefPtr<CanonicalBrowsingContext>& aBrowsingContext) mutable { + if (aBrowsingContext->IsDiscarded() || + parentWindow != aBrowsingContext->GetParentWindowContext()) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Error, + ("Process Switch: Got invalid BrowsingContext from object " + "upgrade!")); + self->RedirectToRealChannelFinished(NS_ERROR_FAILURE); + return; + } + + MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, + ("Process Switch: Upgraded Object to Document Load")); + self->TriggerProcessSwitch(aBrowsingContext, options); + }, + [self = RefPtr{this}](nsresult aStatusCode) { + MOZ_ASSERT(NS_FAILED(aStatusCode), "Status should be error"); + self->RedirectToRealChannelFinished(aStatusCode); + }); + return true; +} + +void DocumentLoadListener::TriggerProcessSwitch( + CanonicalBrowsingContext* aContext, + const NavigationIsolationOptions& aOptions, bool aIsNewTab) { + MOZ_DIAGNOSTIC_ASSERT(aIsNewTab || aContext->IsOwnedByProcess( + GetContentProcessId(mContentParent)), + "not owned by creator process anymore?"); + if (MOZ_LOG_TEST(gProcessIsolationLog, LogLevel::Info)) { + nsCString currentRemoteType = "INVALID"_ns; + aContext->GetCurrentRemoteType(currentRemoteType, IgnoreErrors()); + + MOZ_LOG(gProcessIsolationLog, LogLevel::Info, + ("Process Switch: Changing Remoteness from '%s' to '%s'", + currentRemoteType.get(), aOptions.mRemoteType.get())); + } + + // Stash our stream filter requests to pass to TriggerRedirectToRealChannel, + // as the call to `DisconnectListeners` will clear our list. + nsTArray<StreamFilterRequest> streamFilterRequests = + std::move(mStreamFilterRequests); + + // We're now committing to a process switch, so we can disconnect from + // the listeners in the old process. + // As the navigation is continuing, we don't actually want to cancel the + // request in the old process unless we're redirecting the load into a new + // tab. + DisconnectListeners(NS_BINDING_ABORTED, NS_BINDING_ABORTED, !aIsNewTab); + + MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, + ("Process Switch: Calling ChangeRemoteness")); + aContext->ChangeRemoteness(aOptions, mLoadIdentifier) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [self = RefPtr{this}, requests = std::move(streamFilterRequests)]( + BrowserParent* aBrowserParent) mutable { + MOZ_ASSERT(self->mChannel, + "Something went wrong, channel got cancelled"); + self->TriggerRedirectToRealChannel( + Some(aBrowserParent ? aBrowserParent->Manager() : nullptr), + std::move(requests)); + }, + [self = RefPtr{this}](nsresult aStatusCode) { + MOZ_ASSERT(NS_FAILED(aStatusCode), "Status should be error"); + self->RedirectToRealChannelFinished(aStatusCode); + }); +} + +RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise> +DocumentLoadListener::RedirectToParentProcess(uint32_t aRedirectFlags, + uint32_t aLoadFlags) { + // This is largely the same as ContentChild::RecvCrossProcessRedirect, + // except without needing to deserialize or create an nsIChildChannel. + + RefPtr<nsDocShellLoadState> loadState; + nsDocShellLoadState::CreateFromPendingChannel( + mChannel, mLoadIdentifier, mRedirectChannelId, getter_AddRefs(loadState)); + + loadState->SetLoadFlags(mLoadStateExternalLoadFlags); + loadState->SetInternalLoadFlags(mLoadStateInternalLoadFlags); + loadState->SetLoadType(mLoadStateLoadType); + if (mLoadingSessionHistoryInfo) { + loadState->SetLoadingSessionHistoryInfo(*mLoadingSessionHistoryInfo); + } + + // This is poorly named now. + RefPtr<ChildProcessChannelListener> processListener = + ChildProcessChannelListener::GetSingleton(); + + auto promise = + MakeRefPtr<PDocumentChannelParent::RedirectToRealChannelPromise::Private>( + __func__); + promise->UseDirectTaskDispatch(__func__); + auto resolve = [promise](nsresult aResult) { + promise->Resolve(aResult, __func__); + }; + + nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>> endpoints; + processListener->OnChannelReady(loadState, mLoadIdentifier, + std::move(endpoints), mTiming, + std::move(resolve)); + + return promise; +} + +RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise> +DocumentLoadListener::RedirectToRealChannel( + uint32_t aRedirectFlags, uint32_t aLoadFlags, + const Maybe<ContentParent*>& aDestinationProcess, + nsTArray<ParentEndpoint>&& aStreamFilterEndpoints) { + LOG( + ("DocumentLoadListener RedirectToRealChannel [this=%p] " + "aRedirectFlags=%" PRIx32 ", aLoadFlags=%" PRIx32, + this, aRedirectFlags, aLoadFlags)); + + if (mIsDocumentLoad) { + // TODO(djg): Add the last URI visit to history if success. Is there a + // better place to handle this? Need access to the updated aLoadFlags. + nsresult status = NS_OK; + mChannel->GetStatus(&status); + bool updateGHistory = + nsDocShell::ShouldUpdateGlobalHistory(mLoadStateLoadType); + if (NS_SUCCEEDED(status) && updateGHistory && + !net::ChannelIsPost(mChannel)) { + AddURIVisit(mChannel, aLoadFlags); + } + } + + // Register the new channel and obtain id for it + nsCOMPtr<nsIRedirectChannelRegistrar> registrar = + RedirectChannelRegistrar::GetOrCreate(); + MOZ_ASSERT(registrar); + nsCOMPtr<nsIChannel> chan = mChannel; + if (nsCOMPtr<nsIViewSourceChannel> vsc = do_QueryInterface(chan)) { + chan = vsc->GetInnerChannel(); + } + mRedirectChannelId = nsContentUtils::GenerateLoadIdentifier(); + MOZ_ALWAYS_SUCCEEDS(registrar->RegisterChannel(chan, mRedirectChannelId)); + + if (aDestinationProcess) { + RefPtr<ContentParent> cp = *aDestinationProcess; + if (!cp) { + MOZ_ASSERT(aStreamFilterEndpoints.IsEmpty()); + return RedirectToParentProcess(aRedirectFlags, aLoadFlags); + } + + if (!cp->CanSend()) { + return PDocumentChannelParent::RedirectToRealChannelPromise:: + CreateAndReject(ipc::ResponseRejectReason::SendError, __func__); + } + + nsTArray<EarlyHintConnectArgs> ehArgs; + mEarlyHintsService.RegisterLinksAndGetConnectArgs(cp->ChildID(), ehArgs); + + RedirectToRealChannelArgs args; + SerializeRedirectData(args, /* aIsCrossProcess */ true, aRedirectFlags, + aLoadFlags, std::move(ehArgs), + mEarlyHintsService.LinkType()); + if (mTiming) { + mTiming->Anonymize(args.uri()); + args.timing() = std::move(mTiming); + } + + cp->TransmitBlobDataIfBlobURL(args.uri()); + + if (CanonicalBrowsingContext* bc = GetDocumentBrowsingContext()) { + if (bc->IsTop() && bc->IsActive()) { + nsContentUtils::RequestGeckoTaskBurst(); + } + } + + return cp->SendCrossProcessRedirect(args, + std::move(aStreamFilterEndpoints)); + } + + if (mOpenPromiseResolved) { + LOG( + ("DocumentLoadListener RedirectToRealChannel [this=%p] " + "promise already resolved. Aborting.", + this)); + // The promise has already been resolved or aborted, so we have no way to + // return a promise again to the listener which would cancel the operation. + // Reject the promise immediately. + return PDocumentChannelParent::RedirectToRealChannelPromise:: + CreateAndResolve(NS_BINDING_ABORTED, __func__); + } + + // This promise will be passed on the promise listener which will + // resolve this promise for us. + auto promise = + MakeRefPtr<PDocumentChannelParent::RedirectToRealChannelPromise::Private>( + __func__); + + mOpenPromise->Resolve( + OpenPromiseSucceededType({std::move(aStreamFilterEndpoints), + aRedirectFlags, aLoadFlags, + mEarlyHintsService.LinkType(), promise}), + __func__); + + // There is no way we could come back here if the promise had been resolved + // previously. But for clarity and to avoid all doubt, we set this boolean to + // true. + mOpenPromiseResolved = true; + + return promise; +} + +void DocumentLoadListener::TriggerRedirectToRealChannel( + const Maybe<ContentParent*>& aDestinationProcess, + nsTArray<StreamFilterRequest> aStreamFilterRequests) { + LOG(( + "DocumentLoadListener::TriggerRedirectToRealChannel [this=%p] " + "aDestinationProcess=%" PRId64, + this, aDestinationProcess ? int64_t(*aDestinationProcess) : int64_t(-1))); + // This initiates replacing the current DocumentChannel with a + // protocol specific 'real' channel, maybe in a different process than + // the current DocumentChannelChild, if aDestinationProces is set. + // It registers the current mChannel with the registrar to get an ID + // so that the remote end can setup a new IPDL channel and lookup + // the same underlying channel. + // We expect this process to finish with FinishReplacementChannelSetup + // (for both in-process and process switch cases), where we cleanup + // the registrar and copy across any needed state to the replacing + // IPDL parent object. + + nsTArray<ParentEndpoint> parentEndpoints(aStreamFilterRequests.Length()); + if (!aStreamFilterRequests.IsEmpty()) { + ContentParent* cp = aDestinationProcess.valueOr(mContentParent); + base::ProcessId pid = cp ? cp->OtherPid() : base::ProcessId{0}; + + for (StreamFilterRequest& request : aStreamFilterRequests) { + if (!pid) { + request.mPromise->Reject(false, __func__); + request.mPromise = nullptr; + continue; + } + ParentEndpoint parent; + nsresult rv = extensions::PStreamFilter::CreateEndpoints( + &parent, &request.mChildEndpoint); + + if (NS_FAILED(rv)) { + request.mPromise->Reject(false, __func__); + request.mPromise = nullptr; + } else { + parentEndpoints.AppendElement(std::move(parent)); + } + } + } + + // If we didn't have any redirects, then we pass the REDIRECT_INTERNAL flag + // for this channel switch so that it isn't recorded in session history etc. + // If there were redirect(s), then we want this switch to be recorded as a + // real one, since we have a new URI. + uint32_t redirectFlags = 0; + if (!mHaveVisibleRedirect) { + redirectFlags = nsIChannelEventSink::REDIRECT_INTERNAL; + } + + uint32_t newLoadFlags = nsIRequest::LOAD_NORMAL; + MOZ_ALWAYS_SUCCEEDS(mChannel->GetLoadFlags(&newLoadFlags)); + // We're pulling our flags from the inner channel, which may not have this + // flag set on it. This is the case when loading a 'view-source' channel. + if (mIsDocumentLoad || aDestinationProcess) { + newLoadFlags |= nsIChannel::LOAD_DOCUMENT_URI; + } + if (!aDestinationProcess) { + newLoadFlags |= nsIChannel::LOAD_REPLACE; + } + + // INHIBIT_PERSISTENT_CACHING is clearing during http redirects (from + // both parent and content process channel instances), but only ever + // re-added to the parent-side nsHttpChannel. + // To match that behaviour, we want to explicitly avoid copying this flag + // back to our newly created content side channel, otherwise it can + // affect sub-resources loads in the same load group. + nsCOMPtr<nsIURI> uri; + mChannel->GetURI(getter_AddRefs(uri)); + if (uri && uri->SchemeIs("https")) { + newLoadFlags &= ~nsIRequest::INHIBIT_PERSISTENT_CACHING; + } + + RefPtr<DocumentLoadListener> self = this; + RedirectToRealChannel(redirectFlags, newLoadFlags, aDestinationProcess, + std::move(parentEndpoints)) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [self, requests = std::move(aStreamFilterRequests)]( + const nsresult& aResponse) mutable { + for (StreamFilterRequest& request : requests) { + if (request.mPromise) { + request.mPromise->Resolve(std::move(request.mChildEndpoint), + __func__); + request.mPromise = nullptr; + } + } + self->RedirectToRealChannelFinished(aResponse); + }, + [self](const mozilla::ipc::ResponseRejectReason) { + self->RedirectToRealChannelFinished(NS_ERROR_FAILURE); + }); +} + +void DocumentLoadListener::MaybeReportBlockedByURLClassifier(nsresult aStatus) { + auto* browsingContext = GetDocumentBrowsingContext(); + if (!browsingContext || browsingContext->IsTop() || + !StaticPrefs::privacy_trackingprotection_testing_report_blocked_node()) { + return; + } + + if (!UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aStatus)) { + return; + } + + RefPtr<WindowGlobalParent> parent = browsingContext->GetParentWindowContext(); + if (parent) { + Unused << parent->SendAddBlockedFrameNodeByClassifier(browsingContext); + } +} + +bool DocumentLoadListener::DocShellWillDisplayContent(nsresult aStatus) { + if (NS_SUCCEEDED(aStatus)) { + return true; + } + + // Always return errored loads to the <object> or <embed> element's process, + // as load errors will not be rendered as documents. + if (!mIsDocumentLoad) { + return false; + } + + // nsDocShell attempts urifixup on some failure types, + // but also of those also display an error page if we don't + // succeed with fixup, so we don't need to check for it + // here. + + auto* loadingContext = GetLoadingBrowsingContext(); + + bool isInitialDocument = true; + if (WindowGlobalParent* currentWindow = + loadingContext->GetCurrentWindowGlobal()) { + isInitialDocument = currentWindow->IsInitialDocument(); + } + + nsresult rv = nsDocShell::FilterStatusForErrorPage( + aStatus, mChannel, mLoadStateLoadType, loadingContext->IsTop(), + loadingContext->GetUseErrorPages(), isInitialDocument, nullptr); + + if (NS_SUCCEEDED(rv)) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, + ("Skipping process switch, as DocShell will not display content " + "(status: %s) %s", + GetStaticErrorName(aStatus), + GetChannelCreationURI()->GetSpecOrDefault().get())); + } + + // If filtering returned a failure code, then an error page will + // be display for that code, so return true; + return NS_FAILED(rv); +} + +bool DocumentLoadListener::MaybeHandleLoadErrorWithURIFixup(nsresult aStatus) { + RefPtr<CanonicalBrowsingContext> bc = GetDocumentBrowsingContext(); + if (!bc) { + return false; + } + + nsCOMPtr<nsIInputStream> newPostData; + bool wasSchemelessInput = false; + nsCOMPtr<nsIURI> newURI = nsDocShell::AttemptURIFixup( + mChannel, aStatus, mOriginalUriString, mLoadStateLoadType, bc->IsTop(), + mLoadStateInternalLoadFlags & + nsDocShell::INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP, + bc->UsePrivateBrowsing(), true, getter_AddRefs(newPostData), + &wasSchemelessInput); + + // Since aStatus will be NS_OK for 4xx and 5xx error codes we + // have to check each request which was upgraded by https-first. + // If an error (including 4xx and 5xx) occured, then let's check if + // we can downgrade the scheme to HTTP again. + bool isHTTPSFirstFixup = false; + if (!newURI) { + newURI = nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(mChannel, + aStatus); + isHTTPSFirstFixup = true; + } + + if (!newURI) { + return false; + } + + // If we got a new URI, then we should initiate a load with that. + // Notify the listeners that this load is complete (with a code that + // won't trigger an error page), and then start the new one. + DisconnectListeners(NS_BINDING_ABORTED, NS_BINDING_ABORTED); + + RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(newURI); + nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo(); + + nsCOMPtr<nsIContentSecurityPolicy> cspToInherit = loadInfo->GetCspToInherit(); + loadState->SetCsp(cspToInherit); + + nsCOMPtr<nsIPrincipal> triggeringPrincipal = loadInfo->TriggeringPrincipal(); + loadState->SetTriggeringPrincipal(triggeringPrincipal); + + loadState->SetPostDataStream(newPostData); + + // Record whether the protocol was added through a fixup. + loadState->SetWasSchemelessInput(wasSchemelessInput); + + if (isHTTPSFirstFixup) { + // We have to exempt the load from HTTPS-First to prevent a + // upgrade-downgrade loop. + loadState->SetIsExemptFromHTTPSFirstMode(true); + } + + // Ensure to set referrer information in the fallback channel equally to the + // not-upgraded original referrer info. + // + // A simply copy of the referrer info from the upgraded one leads to problems. + // For example: + // 1. https://some-site.com redirects to http://other-site.com with referrer + // policy + // "no-referrer-when-downgrade". + // 2. https-first upgrades the redirection, so redirects to + // https://other-site.com, + // according to referrer policy the referrer will be send (https-> https) + // 3. Assume other-site.com is not supporting https, https-first performs + // fall- + // back. + // If the referrer info from the upgraded channel gets copied into the + // http fallback channel, the referrer info would contain the referrer + // (https://some-site.com). That would violate the policy + // "no-referrer-when-downgrade". A recreation of the original referrer info + // would ensure us that the referrer is set according to the referrer policy. + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel); + if (httpChannel) { + nsCOMPtr<nsIReferrerInfo> referrerInfo = httpChannel->GetReferrerInfo(); + if (referrerInfo) { + ReferrerPolicy referrerPolicy = referrerInfo->ReferrerPolicy(); + nsCOMPtr<nsIURI> originalReferrer = referrerInfo->GetOriginalReferrer(); + if (originalReferrer) { + // Create new ReferrerInfo with the original referrer and the referrer + // policy. + nsCOMPtr<nsIReferrerInfo> newReferrerInfo = + new ReferrerInfo(originalReferrer, referrerPolicy); + loadState->SetReferrerInfo(newReferrerInfo); + } + } + } + + bc->LoadURI(loadState, false); + return true; +} + +NS_IMETHODIMP +DocumentLoadListener::OnStartRequest(nsIRequest* aRequest) { + LOG(("DocumentLoadListener OnStartRequest [this=%p]", this)); + + nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest); + if (multiPartChannel) { + multiPartChannel->GetBaseChannel(getter_AddRefs(mChannel)); + } else { + mChannel = do_QueryInterface(aRequest); + } + MOZ_DIAGNOSTIC_ASSERT(mChannel); + + if (mHaveVisibleRedirect && GetDocumentBrowsingContext() && + mLoadingSessionHistoryInfo) { + mLoadingSessionHistoryInfo = + GetDocumentBrowsingContext()->ReplaceLoadingSessionHistoryEntryForLoad( + mLoadingSessionHistoryInfo.get(), mChannel); + } + + RefPtr<nsHttpChannel> httpChannel = do_QueryObject(mChannel); + + // Enforce CSP frame-ancestors and x-frame-options checks which + // might cancel the channel. + nsContentSecurityUtils::PerformCSPFrameAncestorAndXFOCheck(mChannel); + + // HTTPS-Only Mode tries to upgrade connections to https. Once loading + // is in progress we set that flag so that timeout counter measures + // do not kick in. + if (httpChannel) { + nsCOMPtr<nsILoadInfo> loadInfo = httpChannel->LoadInfo(); + bool isPrivateWin = loadInfo->GetOriginAttributes().mPrivateBrowsingId > 0; + if (nsHTTPSOnlyUtils::IsHttpsOnlyModeEnabled(isPrivateWin)) { + uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus(); + httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_TOP_LEVEL_LOAD_IN_PROGRESS; + loadInfo->SetHttpsOnlyStatus(httpsOnlyStatus); + } + + if (mLoadingSessionHistoryInfo && + nsDocShell::ShouldDiscardLayoutState(httpChannel)) { + mLoadingSessionHistoryInfo->mInfo.SetSaveLayoutStateFlag(false); + } + } + + auto* loadingContext = GetLoadingBrowsingContext(); + if (!loadingContext || loadingContext->IsDiscarded()) { + Cancel(NS_ERROR_UNEXPECTED, "No valid LoadingBrowsingContext."_ns); + return NS_ERROR_UNEXPECTED; + } + + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + Cancel(NS_ERROR_ILLEGAL_DURING_SHUTDOWN, + "Aborting OnStartRequest after shutdown started."_ns); + return NS_OK; + } + + // Block top-level data URI navigations if triggered by the web. Logging is + // performed in AllowTopLevelNavigationToDataURI. + if (!nsContentSecurityManager::AllowTopLevelNavigationToDataURI(mChannel)) { + mChannel->Cancel(NS_ERROR_DOM_BAD_URI); + if (loadingContext) { + RefPtr<MaybeCloseWindowHelper> maybeCloseWindowHelper = + new MaybeCloseWindowHelper(loadingContext); + // If a new window was opened specifically for this request, close it + // after blocking the navigation. + maybeCloseWindowHelper->SetShouldCloseWindow( + IsFirstLoadInWindow(mChannel)); + Unused << maybeCloseWindowHelper->MaybeCloseWindow(); + } + DisconnectListeners(NS_ERROR_DOM_BAD_URI, NS_ERROR_DOM_BAD_URI); + return NS_OK; + } + + // Generally we want to switch to a real channel even if the request failed, + // since the listener might want to access protocol-specific data (like http + // response headers) in its error handling. + // An exception to this is when nsExtProtocolChannel handled the request and + // returned NS_ERROR_NO_CONTENT, since creating a real one in the content + // process will attempt to handle the URI a second time. + nsresult status = NS_OK; + aRequest->GetStatus(&status); + if (status == NS_ERROR_NO_CONTENT) { + DisconnectListeners(status, status); + return NS_OK; + } + + // PerformCSPFrameAncestorAndXFOCheck may cancel a moz-extension request that + // needs to be handled here. Without this, the resource would be loaded and + // not blocked when the real channel is created in the content process. + if (status == NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION && !httpChannel) { + DisconnectListeners(status, status); + return NS_OK; + } + + // If this was a failed load and we want to try fixing the uri, then + // this will initiate a new load (and disconnect this one), and we don't + // need to do anything else. + if (MaybeHandleLoadErrorWithURIFixup(status)) { + return NS_OK; + } + + mStreamListenerFunctions.AppendElement(StreamListenerFunction{ + VariantIndex<0>{}, OnStartRequestParams{aRequest}}); + + if (mOpenPromiseResolved || mInitiatedRedirectToRealChannel) { + // I we have already resolved the promise, there's no point to continue + // attempting a process switch or redirecting to the real channel. + // We can also have multiple calls to OnStartRequest when dealing with + // multi-part content, but only want to redirect once. + return NS_OK; + } + + // Keep track of server responses resulting in a document for the Bounce + // Tracking Protection. + if (mIsDocumentLoad && GetParentWindowContext() == nullptr && + loadingContext->IsTopContent()) { + RefPtr<BounceTrackingState> bounceTrackingState = + loadingContext->GetBounceTrackingState(); + + // Not every browsing context has a BounceTrackingState. It's also null when + // the feature is disabled. + if (bounceTrackingState) { + DebugOnly<nsresult> rv = + bounceTrackingState->OnDocumentStartRequest(mChannel); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "BounceTrackingState::OnDocumentStartRequest failed."); + } + } + + mChannel->Suspend(); + + mInitiatedRedirectToRealChannel = true; + + MaybeReportBlockedByURLClassifier(status); + + // Determine if a new process needs to be spawned. If it does, this will + // trigger a cross process switch, and we should hold off on redirecting to + // the real channel. + // If the channel has failed, and the docshell isn't going to display an + // error page for that failure, then don't allow process switching, since + // we just want to keep our existing document. + bool willBeRemote = false; + if (!DocShellWillDisplayContent(status) || + !MaybeTriggerProcessSwitch(&willBeRemote)) { + // We're not going to be doing a process switch, so redirect to the real + // channel within our current process. + nsTArray<StreamFilterRequest> streamFilterRequests = + std::move(mStreamFilterRequests); + if (!mSupportsRedirectToRealChannel) { + RefPtr<BrowserParent> browserParent = loadingContext->GetBrowserParent(); + if (browserParent->Manager() != mContentParent) { + LOG( + ("DocumentLoadListener::RedirectToRealChannel failed because " + "browsingContext no longer owned by creator")); + Cancel(NS_BINDING_ABORTED, + "DocumentLoadListener::RedirectToRealChannel failed because " + "browsingContext no longer owned by creator"_ns); + return NS_OK; + } + MOZ_DIAGNOSTIC_ASSERT( + browserParent->GetBrowsingContext() == loadingContext, + "make sure the load is going to the right place"); + + // If the existing process is right for this load, but the bridge doesn't + // support redirects, then we need to do it manually, by faking a process + // switch. + DisconnectListeners(NS_BINDING_ABORTED, NS_BINDING_ABORTED, + /* aContinueNavigating */ true); + + // Notify the docshell that it should load using the newly connected + // channel + browserParent->ResumeLoad(mLoadIdentifier); + + // Use the current process ID to run the 'process switch' path and connect + // the channel into the current process. + TriggerRedirectToRealChannel(Some(mContentParent), + std::move(streamFilterRequests)); + } else { + TriggerRedirectToRealChannel(Nothing(), std::move(streamFilterRequests)); + } + + // If we're not switching, then check if we're currently remote. + if (mContentParent) { + willBeRemote = true; + } + } + + if (httpChannel) { + uint32_t responseStatus = 0; + Unused << httpChannel->GetResponseStatus(&responseStatus); + mEarlyHintsService.FinalResponse(responseStatus); + } else { + mEarlyHintsService.Cancel( + "DocumentLoadListener::OnStartRequest: no httpChannel"_ns); + } + + // If we're going to be delivering this channel to a remote content + // process, then we want to install any required content conversions + // in the content process. + // The caller of this OnStartRequest will install a conversion + // helper after we return if we haven't disabled conversion. Normally + // HttpChannelParent::OnStartRequest would disable conversion, but we're + // defering calling that until later. Manually disable it now to prevent the + // converter from being installed (since we want the child to do it), and + // also save the value so that when we do call + // HttpChannelParent::OnStartRequest, we can have the value as it originally + // was. + if (httpChannel) { + Unused << httpChannel->GetApplyConversion(&mOldApplyConversion); + if (willBeRemote) { + httpChannel->SetApplyConversion(false); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +DocumentLoadListener::OnStopRequest(nsIRequest* aRequest, + nsresult aStatusCode) { + LOG(("DocumentLoadListener OnStopRequest [this=%p]", this)); + mStreamListenerFunctions.AppendElement(StreamListenerFunction{ + VariantIndex<2>{}, OnStopRequestParams{aRequest, aStatusCode}}); + + // If we're not a multi-part channel, then we're finished and we don't + // expect any further events. If we are, then this might be called again, + // so wait for OnAfterLastPart instead. + nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest); + if (!multiPartChannel) { + mIsFinished = true; + } + + mStreamFilterRequests.Clear(); + + return NS_OK; +} + +NS_IMETHODIMP +DocumentLoadListener::OnDataAvailable(nsIRequest* aRequest, + nsIInputStream* aInputStream, + uint64_t aOffset, uint32_t aCount) { + LOG(("DocumentLoadListener OnDataAvailable [this=%p]", this)); + // This isn't supposed to happen, since we suspended the channel, but + // sometimes Suspend just doesn't work. This can happen when we're routing + // through nsUnknownDecoder to sniff the content type, and it doesn't handle + // being suspended. Let's just store the data and manually forward it to our + // redirected channel when it's ready. + nsCString data; + nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount); + NS_ENSURE_SUCCESS(rv, rv); + + mStreamListenerFunctions.AppendElement(StreamListenerFunction{ + VariantIndex<1>{}, + OnDataAvailableParams{aRequest, std::move(data), aOffset, aCount}}); + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// DoucmentLoadListener::nsIMultiPartChannelListener +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +DocumentLoadListener::OnAfterLastPart(nsresult aStatus) { + LOG(("DocumentLoadListener OnAfterLastPart [this=%p]", this)); + if (!mInitiatedRedirectToRealChannel) { + // if we get here, and we haven't initiated a redirect to a real + // channel, then it means we never got OnStartRequest (maybe a problem?) + // and we retargeted everything. + LOG(("DocumentLoadListener Disconnecting child")); + DisconnectListeners(NS_BINDING_RETARGETED, NS_OK); + return NS_OK; + } + mStreamListenerFunctions.AppendElement(StreamListenerFunction{ + VariantIndex<3>{}, OnAfterLastPartParams{aStatus}}); + mIsFinished = true; + return NS_OK; +} + +NS_IMETHODIMP +DocumentLoadListener::GetInterface(const nsIID& aIID, void** result) { + RefPtr<CanonicalBrowsingContext> browsingContext = + GetLoadingBrowsingContext(); + if (aIID.Equals(NS_GET_IID(nsILoadContext)) && browsingContext) { + browsingContext.forget(result); + return NS_OK; + } + + return QueryInterface(aIID, result); +} + +//////////////////////////////////////////////////////////////////////////////// +// nsIParentChannel +//////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP +DocumentLoadListener::SetParentListener( + mozilla::net::ParentChannelListener* listener) { + // We don't need this (do we?) + return NS_OK; +} + +NS_IMETHODIMP +DocumentLoadListener::SetClassifierMatchedInfo(const nsACString& aList, + const nsACString& aProvider, + const nsACString& aFullHash) { + ClassifierMatchedInfoParams params; + params.mList = aList; + params.mProvider = aProvider; + params.mFullHash = aFullHash; + + mIParentChannelFunctions.AppendElement( + IParentChannelFunction{VariantIndex<0>{}, std::move(params)}); + return NS_OK; +} + +NS_IMETHODIMP +DocumentLoadListener::SetClassifierMatchedTrackingInfo( + const nsACString& aLists, const nsACString& aFullHash) { + ClassifierMatchedTrackingInfoParams params; + params.mLists = aLists; + params.mFullHashes = aFullHash; + + mIParentChannelFunctions.AppendElement( + IParentChannelFunction{VariantIndex<1>{}, std::move(params)}); + return NS_OK; +} + +NS_IMETHODIMP +DocumentLoadListener::NotifyClassificationFlags(uint32_t aClassificationFlags, + bool aIsThirdParty) { + mIParentChannelFunctions.AppendElement(IParentChannelFunction{ + VariantIndex<2>{}, + ClassificationFlagsParams{aClassificationFlags, aIsThirdParty}}); + return NS_OK; +} + +NS_IMETHODIMP +DocumentLoadListener::Delete() { + MOZ_ASSERT_UNREACHABLE("This method is unused"); + return NS_OK; +} + +NS_IMETHODIMP +DocumentLoadListener::GetRemoteType(nsACString& aRemoteType) { + // FIXME: The remote type here should be pulled from the remote process used + // to create this DLL, not from the current `browsingContext`. + RefPtr<CanonicalBrowsingContext> browsingContext = + GetDocumentBrowsingContext(); + if (!browsingContext) { + return NS_ERROR_UNEXPECTED; + } + + ErrorResult error; + browsingContext->GetCurrentRemoteType(aRemoteType, error); + if (error.Failed()) { + aRemoteType = NOT_REMOTE_TYPE; + } + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsIChannelEventSink +//////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP +DocumentLoadListener::AsyncOnChannelRedirect( + nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags, + nsIAsyncVerifyRedirectCallback* aCallback) { + LOG(("DocumentLoadListener::AsyncOnChannelRedirect [this=%p flags=%" PRIu32 + "]", + this, aFlags)); + // We generally don't want to notify the content process about redirects, + // so just update our channel and tell the callback that we're good to go. + mChannel = aNewChannel; + + // We need the original URI of the current channel to use to open the real + // channel in the content process. Unfortunately we overwrite the original + // uri of the new channel with the original pre-redirect URI, so grab + // a copy of it now and save it on the loadInfo corresponding to the + // new channel. + nsCOMPtr<nsILoadInfo> loadInfoFromChannel = mChannel->LoadInfo(); + MOZ_ASSERT(loadInfoFromChannel); + nsCOMPtr<nsIURI> uri; + mChannel->GetOriginalURI(getter_AddRefs(uri)); + loadInfoFromChannel->SetChannelCreationOriginalURI(uri); + + // Since we're redirecting away from aOldChannel, we should check if it + // had a COOP mismatch, since we want the final result for this to + // include the state of all channels we redirected through. + nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(aOldChannel); + if (httpChannel) { + bool isCOOPMismatch = false; + Unused << NS_WARN_IF(NS_FAILED( + httpChannel->HasCrossOriginOpenerPolicyMismatch(&isCOOPMismatch))); + mHasCrossOriginOpenerPolicyMismatch |= isCOOPMismatch; + } + + // If HTTPS-Only mode is enabled, we need to check whether the exception-flag + // needs to be removed or set, by asking the PermissionManager. + nsHTTPSOnlyUtils::TestSitePermissionAndPotentiallyAddExemption(mChannel); + + // We don't need to confirm internal redirects or record any + // history for them, so just immediately verify and return. + if (aFlags & nsIChannelEventSink::REDIRECT_INTERNAL) { + LOG( + ("DocumentLoadListener AsyncOnChannelRedirect [this=%p] " + "flags=REDIRECT_INTERNAL", + this)); + aCallback->OnRedirectVerifyCallback(NS_OK); + return NS_OK; + } + + // Cancel cross origin redirects as described by whatwg: + // > Note: [The early hint reponse] is discarded if it is succeeded by a + // > cross-origin redirect. + // https://html.spec.whatwg.org/multipage/semantics.html#early-hints + nsCOMPtr<nsIURI> oldURI; + aOldChannel->GetURI(getter_AddRefs(oldURI)); + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + nsresult rv = ssm->CheckSameOriginURI(oldURI, uri, false, false); + if (NS_FAILED(rv)) { + mEarlyHintsService.Cancel( + "DocumentLoadListener::AsyncOnChannelRedirect: cors redirect"_ns); + } + + if (GetDocumentBrowsingContext()) { + if (!net::ChannelIsPost(aOldChannel)) { + AddURIVisit(aOldChannel, 0); + nsDocShell::SaveLastVisit(aNewChannel, oldURI, aFlags); + } + } + mHaveVisibleRedirect |= true; + + LOG( + ("DocumentLoadListener AsyncOnChannelRedirect [this=%p] " + "mHaveVisibleRedirect=%c", + this, mHaveVisibleRedirect ? 'T' : 'F')); + + // Clear out our nsIParentChannel functions, since a normal parent + // channel would actually redirect and not have those values on the new one. + // We expect the URI classifier to run on the redirected channel with + // the new URI and set these again. + mIParentChannelFunctions.Clear(); + + // If we had a remote type override, ensure it's been cleared after a + // redirect, as it can't apply anymore. + mRemoteTypeOverride.reset(); + +#ifdef ANDROID + nsCOMPtr<nsIURI> uriBeingLoaded = + AntiTrackingUtils::MaybeGetDocumentURIBeingLoaded(mChannel); + + RefPtr<MozPromise<bool, bool, false>> promise; + RefPtr<CanonicalBrowsingContext> bc = + mParentChannelListener->GetBrowsingContext(); + nsCOMPtr<nsIWidget> widget = + bc ? bc->GetParentProcessWidgetContaining() : nullptr; + RefPtr<nsWindow> window = nsWindow::From(widget); + + if (window) { + promise = window->OnLoadRequest(uriBeingLoaded, + nsIBrowserDOMWindow::OPEN_CURRENTWINDOW, + nsIWebNavigation::LOAD_FLAGS_IS_REDIRECT, + nullptr, false, bc->IsTopContent()); + } + + if (promise) { + RefPtr<nsIAsyncVerifyRedirectCallback> cb = aCallback; + promise->Then( + GetCurrentSerialEventTarget(), __func__, + [=](const MozPromise<bool, bool, false>::ResolveOrRejectValue& aValue) { + if (aValue.IsResolve()) { + bool handled = aValue.ResolveValue(); + if (handled) { + cb->OnRedirectVerifyCallback(NS_ERROR_ABORT); + } else { + cb->OnRedirectVerifyCallback(NS_OK); + } + } + }); + } else +#endif /* ANDROID */ + { + aCallback->OnRedirectVerifyCallback(NS_OK); + } + return NS_OK; +} + +nsIURI* DocumentLoadListener::GetChannelCreationURI() const { + nsCOMPtr<nsILoadInfo> channelLoadInfo = mChannel->LoadInfo(); + + nsCOMPtr<nsIURI> uri; + channelLoadInfo->GetChannelCreationOriginalURI(getter_AddRefs(uri)); + if (uri) { + // See channelCreationOriginalURI for more info. We use this instead of the + // originalURI of the channel to help us avoid the situation when we use + // the URI of a redirect that has failed to happen. + return uri; + } + + // Otherwise, get the original URI from the channel. + mChannel->GetOriginalURI(getter_AddRefs(uri)); + return uri; +} + +// This method returns the cached result of running the Cross-Origin-Opener +// policy compare algorithm by calling ComputeCrossOriginOpenerPolicyMismatch +bool DocumentLoadListener::HasCrossOriginOpenerPolicyMismatch() const { + // If we found a COOP mismatch on an earlier channel and then + // redirected away from that, we should use that result. + if (mHasCrossOriginOpenerPolicyMismatch) { + return true; + } + + nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(mChannel); + if (!httpChannel) { + // Not an nsIHttpChannelInternal assume it's okay to switch. + return false; + } + + bool isCOOPMismatch = false; + Unused << NS_WARN_IF(NS_FAILED( + httpChannel->HasCrossOriginOpenerPolicyMismatch(&isCOOPMismatch))); + return isCOOPMismatch; +} + +auto DocumentLoadListener::AttachStreamFilter() + -> RefPtr<ChildEndpointPromise> { + LOG(("DocumentLoadListener AttachStreamFilter [this=%p]", this)); + + StreamFilterRequest* request = mStreamFilterRequests.AppendElement(); + request->mPromise = new ChildEndpointPromise::Private(__func__); + return request->mPromise; +} + +NS_IMETHODIMP DocumentLoadListener::OnProgress(nsIRequest* aRequest, + int64_t aProgress, + int64_t aProgressMax) { + return NS_OK; +} + +NS_IMETHODIMP DocumentLoadListener::OnStatus(nsIRequest* aRequest, + nsresult aStatus, + const char16_t* aStatusArg) { + nsCOMPtr<nsIChannel> channel = mChannel; + + RefPtr<BrowsingContextWebProgress> webProgress = + GetLoadingBrowsingContext()->GetWebProgress(); + const nsString message(aStatusArg); + + if (webProgress) { + NS_DispatchToMainThread( + NS_NewRunnableFunction("DocumentLoadListener::OnStatus", [=]() { + webProgress->OnStatusChange(webProgress, channel, aStatus, + message.get()); + })); + } + return NS_OK; +} + +NS_IMETHODIMP DocumentLoadListener::EarlyHint(const nsACString& aLinkHeader, + const nsACString& aReferrerPolicy, + const nsACString& aCSPHeader) { + LOG(("DocumentLoadListener::EarlyHint.\n")); + mEarlyHintsService.EarlyHint(aLinkHeader, GetChannelCreationURI(), mChannel, + aReferrerPolicy, aCSPHeader, + GetLoadingBrowsingContext()); + return NS_OK; +} + +} // namespace net +} // namespace mozilla + +#undef LOG diff --git a/netwerk/ipc/DocumentLoadListener.h b/netwerk/ipc/DocumentLoadListener.h new file mode 100644 index 0000000000..98aacc98f8 --- /dev/null +++ b/netwerk/ipc/DocumentLoadListener.h @@ -0,0 +1,630 @@ +/* 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<mozilla::ipc::Endpoint<extensions::PStreamFilterChild>, 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<ChildEndpointPromise::Private> mPromise; + mozilla::ipc::Endpoint<extensions::PStreamFilterChild> 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<mozilla::ipc::Endpoint<extensions::PStreamFilterParent>> + mStreamFilterEndpoints; + uint32_t mRedirectFlags; + uint32_t mLoadFlags; + uint32_t mEarlyHintLinkType; + RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise::Private> + 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<OpenPromiseSucceededType, OpenPromiseFailedType, true>; + + // Interface which may be provided when performing an <object> or <embed> load + // with `DocumentLoadListener`, to allow upgrading the Object load to a proper + // Document load. + struct ObjectUpgradeHandler : public SupportsWeakPtr { + using ObjectUpgradePromise = + MozPromise<RefPtr<dom::CanonicalBrowsingContext>, 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 <object> or <embed> element to finish the load with. + virtual RefPtr<ObjectUpgradePromise> 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<OpenPromise> Open(nsDocShellLoadState* aLoadState, LoadInfo* aLoadInfo, + nsLoadFlags aLoadFlags, uint32_t aCacheKey, + const Maybe<uint64_t>& aChannelId, + const TimeStamp& aAsyncOpenTime, + nsDOMNavigationTiming* aTiming, + Maybe<dom::ClientInfo>&& aInfo, bool aUrgentStart, + dom::ContentParent* aContentParent, nsresult* aRv); + + public: + RefPtr<OpenPromise> OpenDocument( + nsDocShellLoadState* aLoadState, uint32_t aCacheKey, + const Maybe<uint64_t>& aChannelId, const TimeStamp& aAsyncOpenTime, + nsDOMNavigationTiming* aTiming, Maybe<dom::ClientInfo>&& aInfo, + Maybe<bool> aUriModified, Maybe<bool> aIsEmbeddingBlockedError, + dom::ContentParent* aContentParent, nsresult* aRv); + + RefPtr<OpenPromise> OpenObject( + nsDocShellLoadState* aLoadState, uint32_t aCacheKey, + const Maybe<uint64_t>& aChannelId, const TimeStamp& aAsyncOpenTime, + nsDOMNavigationTiming* aTiming, Maybe<dom::ClientInfo>&& 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<OpenPromise> ClaimParentLoad(DocumentLoadListener** aListener, + uint64_t aLoadIdent, + Maybe<uint64_t> 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<ChildEndpointPromise> 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<EarlyHintConnectArgs>& 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, + nsTArray<EarlyHintConnectArgs>&& 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<OpenPromise> 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<dom::ContentParent*>& aDestinationProcess, + nsTArray<StreamFilterRequest> 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<extensions::PStreamFilterParent>; + RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise> + RedirectToRealChannel(uint32_t aRedirectFlags, uint32_t aLoadFlags, + const Maybe<dom::ContentParent*>& aDestinationProcess, + nsTArray<ParentEndpoint>&& 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<PDocumentChannelParent::RedirectToRealChannelPromise> + 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 <object>/<embed>, 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<ClassifierMatchedInfoParams, + ClassifierMatchedTrackingInfoParams, + ClassificationFlagsParams>; + + // 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<IParentChannelFunction> 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<ReportSecurityMessageParams, LogBlockedCORSRequestParams, + LogMimeTypeMismatchParams>; + nsTArray<SecurityWarningFunction> 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<StreamListenerFunction> mStreamListenerFunctions; + + nsCOMPtr<nsIChannel> mChannel; + + Maybe<uint64_t> 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<ParentChannelListener> 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<nsDOMNavigationTiming> mTiming; + + net::EarlyHintsService mEarlyHintsService; + + // An optional ObjectUpgradeHandler which can be used to upgrade an <object> + // or <embed> 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<ObjectUpgradeHandler> 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<StreamFilterRequest> mStreamFilterRequests; + + nsString mSrcdocData; + nsCOMPtr<nsIURI> mBaseURI; + + mozilla::UniquePtr<mozilla::dom::LoadingSessionHistoryInfo> + mLoadingSessionHistoryInfo; + + RefPtr<dom::WindowGlobalParent> 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<nsCString> mOriginalUriString; + + // Parent-initiated loads do not support redirects to real channels. + bool mSupportsRedirectToRealChannel = true; + + Maybe<nsCString> mRemoteTypeOverride; + + // The ContentParent which this channel is currently connected to, or nullptr + // if connected to the parent process. + RefPtr<dom::ContentParent> 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<OpenPromise::Private> 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<nsIInterfaceRequestor*>(aObj); +} + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_DocumentChannelParent_h diff --git a/netwerk/ipc/InputChannelThrottleQueueChild.cpp b/netwerk/ipc/InputChannelThrottleQueueChild.cpp new file mode 100644 index 0000000000..ef7a916b79 --- /dev/null +++ b/netwerk/ipc/InputChannelThrottleQueueChild.cpp @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set ts=4 sw=4 sts=4 et cin: */ +/* 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/. */ + +#include "InputChannelThrottleQueueChild.h" +#include "nsThreadUtils.h" + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS_INHERITED0(InputChannelThrottleQueueChild, ThrottleQueue) + +NS_IMETHODIMP +InputChannelThrottleQueueChild::RecordRead(uint32_t aBytesRead) { + ThrottleQueue::RecordRead(aBytesRead); + + RefPtr<InputChannelThrottleQueueChild> self = this; + NS_DispatchToMainThread(NS_NewRunnableFunction( + "InputChannelThrottleQueueChild::RecordRead", [self, aBytesRead]() { + if (self->CanSend()) { + Unused << self->SendRecordRead(aBytesRead); + } + })); + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/InputChannelThrottleQueueChild.h b/netwerk/ipc/InputChannelThrottleQueueChild.h new file mode 100644 index 0000000000..a758dac417 --- /dev/null +++ b/netwerk/ipc/InputChannelThrottleQueueChild.h @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 InputChannelThrottleQueueChild_h__ +#define InputChannelThrottleQueueChild_h__ + +#include "mozilla/net/PInputChannelThrottleQueueChild.h" +#include "mozilla/net/ThrottleQueue.h" +#include "nsISupportsImpl.h" + +namespace mozilla { +namespace net { + +class InputChannelThrottleQueueChild final + : public PInputChannelThrottleQueueChild, + public ThrottleQueue { + public: + friend class PInputChannelThrottleQueueChild; + NS_DECL_ISUPPORTS_INHERITED + + explicit InputChannelThrottleQueueChild() = default; + NS_IMETHOD RecordRead(uint32_t aBytesRead) override; + + private: + virtual ~InputChannelThrottleQueueChild() = default; +}; + +} // namespace net +} // namespace mozilla + +#endif // InputChannelThrottleQueueChild_h__ diff --git a/netwerk/ipc/InputChannelThrottleQueueParent.cpp b/netwerk/ipc/InputChannelThrottleQueueParent.cpp new file mode 100644 index 0000000000..d1e6e82f9f --- /dev/null +++ b/netwerk/ipc/InputChannelThrottleQueueParent.cpp @@ -0,0 +1,125 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set ts=4 sw=4 sts=4 et cin: */ +/* 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/. */ + +#include "InputChannelThrottleQueueParent.h" +#include "mozilla/net/SocketProcessParent.h" +#include "nsIOService.h" + +namespace mozilla { +namespace net { + +NS_IMPL_ADDREF(InputChannelThrottleQueueParent) +NS_INTERFACE_MAP_BEGIN(InputChannelThrottleQueueParent) + NS_INTERFACE_MAP_ENTRY(nsIInputChannelThrottleQueue) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY_CONCRETE(InputChannelThrottleQueueParent) +NS_INTERFACE_MAP_END + +NS_IMETHODIMP_(MozExternalRefCountType) +InputChannelThrottleQueueParent::Release(void) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); + + if (!nsAutoRefCnt::isThreadSafe) { + NS_ASSERT_OWNINGTHREAD(InputChannelThrottleQueueParent); + } + + nsrefcnt count = --mRefCnt; + NS_LOG_RELEASE(this, count, "InputChannelThrottleQueueParent"); + + if (count == 0) { + if (!nsAutoRefCnt::isThreadSafe) { + NS_ASSERT_OWNINGTHREAD(InputChannelThrottleQueueParent); + } + + mRefCnt = 1; /* stabilize */ + delete (this); + return 0; + } + + // When ref count goes down to 1 (held internally by IPDL), it means that + // we are done with this ThrottleQueue. We should send a delete message + // to delete the InputChannelThrottleQueueChild in socket process. + if (count == 1 && CanSend()) { + mozilla::Unused << Send__delete__(this); + return 1; + } + return count; +} + +mozilla::ipc::IPCResult InputChannelThrottleQueueParent::RecvRecordRead( + const uint32_t& aBytesRead) { + mBytesProcessed += aBytesRead; + return IPC_OK(); +} + +NS_IMETHODIMP +InputChannelThrottleQueueParent::RecordRead(uint32_t aBytesRead) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +InputChannelThrottleQueueParent::Available(uint32_t aRemaining, + uint32_t* aAvailable) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +InputChannelThrottleQueueParent::Init(uint32_t aMeanBytesPerSecond, + uint32_t aMaxBytesPerSecond) { + // Can be called on any thread. + if (aMeanBytesPerSecond == 0 || aMaxBytesPerSecond == 0 || + aMaxBytesPerSecond < aMeanBytesPerSecond) { + return NS_ERROR_ILLEGAL_VALUE; + } + + mMeanBytesPerSecond = aMeanBytesPerSecond; + mMaxBytesPerSecond = aMaxBytesPerSecond; + + RefPtr<InputChannelThrottleQueueParent> self = this; + gIOService->CallOrWaitForSocketProcess( + [self, meanBytesPerSecond(mMeanBytesPerSecond), + maxBytesPerSecond(mMaxBytesPerSecond)] { + Unused << SocketProcessParent::GetSingleton() + ->SendPInputChannelThrottleQueueConstructor( + self, meanBytesPerSecond, maxBytesPerSecond); + }); + + return NS_OK; +} + +NS_IMETHODIMP +InputChannelThrottleQueueParent::BytesProcessed(uint64_t* aResult) { + *aResult = mBytesProcessed; + return NS_OK; +} + +NS_IMETHODIMP +InputChannelThrottleQueueParent::WrapStream(nsIInputStream* aInputStream, + nsIAsyncInputStream** aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +InputChannelThrottleQueueParent::GetMeanBytesPerSecond( + uint32_t* aMeanBytesPerSecond) { + NS_ENSURE_ARG(aMeanBytesPerSecond); + + *aMeanBytesPerSecond = mMeanBytesPerSecond; + return NS_OK; +} + +NS_IMETHODIMP +InputChannelThrottleQueueParent::GetMaxBytesPerSecond( + uint32_t* aMaxBytesPerSecond) { + NS_ENSURE_ARG(aMaxBytesPerSecond); + + *aMaxBytesPerSecond = mMaxBytesPerSecond; + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/InputChannelThrottleQueueParent.h b/netwerk/ipc/InputChannelThrottleQueueParent.h new file mode 100644 index 0000000000..74d1a00bd7 --- /dev/null +++ b/netwerk/ipc/InputChannelThrottleQueueParent.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 InputChannelThrottleQueueParent_h__ +#define InputChannelThrottleQueueParent_h__ + +#include "nsISupportsImpl.h" +#include "nsIThrottledInputChannel.h" +#include "mozilla/net/PInputChannelThrottleQueueParent.h" + +namespace mozilla { +namespace net { + +#define INPUT_CHANNEL_THROTTLE_QUEUE_PARENT_IID \ + { \ + 0x4f151655, 0x70b3, 0x4350, { \ + 0x9b, 0xd9, 0xe3, 0x2b, 0xe5, 0xeb, 0xb2, 0x9e \ + } \ + } + +class InputChannelThrottleQueueParent final + : public PInputChannelThrottleQueueParent, + public nsIInputChannelThrottleQueue { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIINPUTCHANNELTHROTTLEQUEUE + NS_DECLARE_STATIC_IID_ACCESSOR(INPUT_CHANNEL_THROTTLE_QUEUE_PARENT_IID) + + friend class PInputChannelThrottleQueueParent; + + explicit InputChannelThrottleQueueParent() = default; + mozilla::ipc::IPCResult RecvRecordRead(const uint32_t& aBytesRead); + void ActorDestroy(ActorDestroyReason aWhy) override {} + + private: + virtual ~InputChannelThrottleQueueParent() = default; + + uint64_t mBytesProcessed{0}; + uint32_t mMeanBytesPerSecond{0}; + uint32_t mMaxBytesPerSecond{0}; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(InputChannelThrottleQueueParent, + INPUT_CHANNEL_THROTTLE_QUEUE_PARENT_IID) + +} // namespace net +} // namespace mozilla + +#endif // InputChannelThrottleQueueParent_h__ diff --git a/netwerk/ipc/NeckoChannelParams.ipdlh b/netwerk/ipc/NeckoChannelParams.ipdlh new file mode 100644 index 0000000000..44af8d4808 --- /dev/null +++ b/netwerk/ipc/NeckoChannelParams.ipdlh @@ -0,0 +1,623 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 ft=c: */ + +/* 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/. */ + +include protocol PHttpChannel; +include ClientIPCTypes; +include URIParams; +include IPCServiceWorkerDescriptor; +include IPCStream; +include PBackgroundSharedTypes; +include DOMTypes; +include ProtocolTypes; + +include "mozilla/dom/FetchIPCTypes.h"; +include "mozilla/dom/PropertyBagUtils.h"; +include "mozilla/dom/ReferrerInfoUtils.h"; +include "mozilla/ipc/URIUtils.h"; +include "mozilla/net/CacheInfoIPCTypes.h"; +include "mozilla/AntiTrackingIPCUtils.h"; +include "mozilla/net/ClassOfService.h"; + +using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h"; +using mozilla::net::RequestHeaderTuples from "mozilla/net/PHttpChannelParams.h"; +using struct nsHttpAtom from "nsHttp.h"; +using class mozilla::net::nsHttpResponseHead from "nsHttpResponseHead.h"; +using class mozilla::TimeStamp from "mozilla/TimeStamp.h"; +[RefCounted] using class nsIPropertyBag2 from "nsIPropertyBag2.h"; +[RefCounted] using class nsDOMNavigationTiming from "nsDOMNavigationTiming.h"; +[RefCounted] using class nsDocShellLoadState from "nsDocShellLoadState.h"; +using nsContentPolicyType from "nsIContentPolicy.h"; +using mozilla::net::PreferredAlternativeDataDeliveryTypeIPC from "nsICacheInfoChannel.h"; +using nsILoadInfo::CrossOriginEmbedderPolicy from "nsILoadInfo.h"; +using nsILoadInfo::StoragePermissionState from "nsILoadInfo.h"; +using struct mozilla::dom::LoadingSessionHistoryInfo from "mozilla/dom/SessionHistoryEntry.h"; +using mozilla::dom::RequestMode from "mozilla/dom/RequestBinding.h"; +using mozilla::net::LinkHeader from "nsNetUtil.h"; + +namespace mozilla { +namespace net { + +//----------------------------------------------------------------------------- +// CookieJarSettings IPDL structs +//----------------------------------------------------------------------------- + +struct CookiePermissionData +{ + PrincipalInfo principalInfo; + uint32_t cookiePermission; +}; + +struct CookieJarSettingsArgs +{ + // Copy of the cookie jar settings for the top-level document. + uint32_t cookieBehavior; + bool isFirstPartyIsolated; + bool shouldResistFingerprinting; + bool isOnContentBlockingAllowList; + CookiePermissionData[] cookiePermissions; + bool isFixed; + nsString partitionKey; + bool hasFingerprintingRandomizationKey; + uint8_t[] fingerprintingRandomizationKey; +}; + +//----------------------------------------------------------------------------- +// Preferrer alternative data type +//----------------------------------------------------------------------------- + +struct PreferredAlternativeDataTypeParams +{ + nsCString type; + nsCString contentType; + PreferredAlternativeDataDeliveryTypeIPC deliverAltData; +}; + +//----------------------------------------------------------------------------- +// LoadInfo IPDL structs +//----------------------------------------------------------------------------- + +struct RedirectHistoryEntryInfo +{ + PrincipalInfo principalInfo; + URIParams? referrerUri; + nsCString remoteAddress; +}; + +struct InterceptionInfoArg +{ + PrincipalInfo? triggeringPrincipalInfo; + nsContentPolicyType contentPolicyType; + RedirectHistoryEntryInfo[] redirectChain; + bool fromThirdParty; +}; + +struct LoadInfoArgs +{ + PrincipalInfo? requestingPrincipalInfo; + PrincipalInfo triggeringPrincipalInfo; + PrincipalInfo? principalToInheritInfo; + PrincipalInfo? topLevelPrincipalInfo; + URIParams? resultPrincipalURI; + nsCString triggeringRemoteType; + nsID sandboxedNullPrincipalID; + uint32_t securityFlags; + uint32_t sandboxFlags; + uint32_t triggeringSandboxFlags; + uint64_t triggeringWindowId; + bool triggeringStorageAccess; + nsContentPolicyType contentPolicyType; + uint32_t tainting; + bool blockAllMixedContent; + bool upgradeInsecureRequests; + bool browserUpgradeInsecureRequests; + bool browserDidUpgradeInsecureRequests; + bool browserWouldUpgradeInsecureRequests; + bool forceAllowDataURI; + bool allowInsecureRedirectToDataURI; + bool skipContentPolicyCheckForWebRequest; + bool originalFrameSrcLoad; + bool forceInheritPrincipalDropped; + uint64_t innerWindowID; + uint64_t browsingContextID; + uint64_t frameBrowsingContextID; + bool initialSecurityCheckDone; + bool isInThirdPartyContext; + bool? isThirdPartyContextToTopWindow; + bool isFormSubmission; + bool sendCSPViolationEvents; + OriginAttributes originAttributes; + RedirectHistoryEntryInfo[] redirectChainIncludingInternalRedirects; + RedirectHistoryEntryInfo[] redirectChain; + bool hasInjectedCookieForCookieBannerHandling; + bool wasSchemelessInput; + + /** + * ClientInfo structure representing the window or worker that triggered + * this network request. May be Nothing if its a system internal request. + */ + IPCClientInfo? clientInfo; + + /** + * Non-subresource requests will result in the creation of a window or + * worker client. The reserved and initial ClientInfo values represent + * this resulting client. An initial ClientInfo represents an initial + * about:blank window that will be re-used while a reserved ClientInfo + * represents a to-be-newly-created window/worker. + */ + IPCClientInfo? reservedClientInfo; + IPCClientInfo? initialClientInfo; + + /** + * Subresource loads may have a controller set based on their owning + * window/worker client. We must send this across IPC to support + * performing interception in the parent. + */ + IPCServiceWorkerDescriptor? controller; + + nsCString[] corsUnsafeHeaders; + bool forcePreflight; + bool isPreflight; + bool loadTriggeredFromExternal; + bool serviceWorkerTaintingSynthesized; + bool documentHasUserInteracted; + bool allowListFutureDocumentsCreatedFromThisRedirectChain; + bool needForCheckingAntiTrackingHeuristic; + nsString cspNonce; + nsString integrityMetadata; + bool skipContentSniffing; + uint32_t httpsOnlyStatus; + bool hstsStatus; + bool hasValidUserGestureActivation; + bool allowDeprecatedSystemRequests; + bool isInDevToolsContext; + bool parserCreatedScript; + bool isFromProcessingFrameAttributes; + bool isMediaRequest; + bool isMediaInitialRequest; + bool isFromObjectOrEmbed; + CookieJarSettingsArgs cookieJarSettings; + uint32_t requestBlockingReason; + CSPInfo? cspToInheritInfo; + StoragePermissionState storagePermission; + uint64_t? overriddenFingerprintingSettings; + bool isMetaRefresh; + CrossOriginEmbedderPolicy loadingEmbedderPolicy; + bool originTrialCoepCredentiallessEnabledForTopLevel; + nullable nsIURI unstrippedURI; + InterceptionInfoArg? interceptionInfo; +}; + +/** + * This structure is used to carry selected properties of a LoadInfo + * object to child processes to merge LoadInfo changes from the parent + * process. We don't want to use LoadInfoArgs for that since it's + * too huge and we only care about small subpart of properties anyway. + */ +struct ParentLoadInfoForwarderArgs +{ + // WebExtextensions' WebRequest API allows extensions to intercept and + // redirect a channel to a data URI. This modifications happens in + // the parent and needs to be mirrored to the child so that security + // checks can pass. + bool allowInsecureRedirectToDataURI; + + // The ServiceWorker controller that may be set in the parent when + // interception occurs. + IPCServiceWorkerDescriptor? controller; + + // The service worker may synthesize a Response with a particular + // tainting value. + uint32_t tainting; + + // This flag is used for any browsing context where we should not sniff + // the content type. E.g if an iframe has the XCTO nosniff header, then + // that flag is set to true so we skip content sniffing for that browsing + bool skipContentSniffing; + + uint32_t httpsOnlyStatus; + + bool hstsStatus; + + // Returns true if at the time of the loadinfo construction the document + // that triggered this load has the bit hasValidTransientUserGestureActivation + // set or the load was triggered from External. (Mostly this bool is used + // in the context of Sec-Fetch-User.) + bool hasValidUserGestureActivation; + + // The SystemPrincipal is disallowed to make requests to the public web + // and all requests will be cancelled. Setting this flag to true prevents + // the request from being cancelled. + bool allowDeprecatedSystemRequests; + + bool isInDevToolsContext; + + // Only ever returns true if the loadinfo is of TYPE_SCRIPT and + // the script was created by the HTML parser. + bool parserCreatedScript; + + // Sandbox Flags of the Document that triggered the load + uint32_t triggeringSandboxFlags; + + // Window ID and UsingStorageAccess of the Document that triggered the load. + // Used by the Storage Access API to determine if SubDocument loads should + // be partitioned or not. + uint64_t triggeringWindowId; + bool triggeringStorageAccess; + + // We must also note that the tainting value was explicitly set + // by the service worker. + bool serviceWorkerTaintingSynthesized; + + bool documentHasUserInteracted; + bool allowListFutureDocumentsCreatedFromThisRedirectChain; + + CookieJarSettingsArgs? cookieJarSettings; + + uint32_t requestBlockingReason; + + StoragePermissionState storagePermission; + + uint64_t? overriddenFingerprintingSettings; + + bool isMetaRefresh; + + bool? isThirdPartyContextToTopWindow; + + bool isInThirdPartyContext; + + nullable nsIURI unstrippedURI; + + // IMPORTANT: when you add new properites here you must also update + // LoadInfoToParentLoadInfoForwarder and MergeParentLoadInfoForwarder + // in BackgroundUtils.cpp/.h! +}; + +/** + * This structure is used to carry selected properties of a LoadInfo + * object to the parent process that might have changed in the child + * during a redirect. We don't want to use LoadInfoArgs for that since + * it's too huge and we only care about small subpart of properties + * anyway. + */ +struct ChildLoadInfoForwarderArgs +{ + // The reserved and initial ClientInfo values may change during a + // redirect if the new channel is cross-origin to the old channel. + IPCClientInfo? reservedClientInfo; + IPCClientInfo? initialClientInfo; + + // The ServiceWorker controller may be cleared in the child during + // a redirect. + IPCServiceWorkerDescriptor? controller; + + uint32_t requestBlockingReason; +}; + +//----------------------------------------------------------------------------- +// HTTP IPDL structs +//----------------------------------------------------------------------------- + +struct CorsPreflightArgs +{ + nsCString[] unsafeHeaders; +}; + +struct HttpChannelOpenArgs +{ + nullable nsIURI uri; + // - TODO: bug 571161: unclear if any HTTP channel clients ever + // set originalURI != uri (about:credits?); also not clear if + // chrome channel would ever need to know. Get rid of next arg? + nullable nsIURI original; + nullable nsIURI doc; + nullable nsIReferrerInfo referrerInfo; + nullable nsIURI apiRedirectTo; + nullable nsIURI topWindowURI; + RequestHeaderTuples requestHeaders; + PreferredAlternativeDataTypeParams[] preferredAlternativeTypes; + TimeStamp launchServiceWorkerStart; + TimeStamp launchServiceWorkerEnd; + TimeStamp dispatchFetchEventStart; + TimeStamp dispatchFetchEventEnd; + TimeStamp handleFetchEventStart; + TimeStamp handleFetchEventEnd; + TimeStamp navigationStartTimeStamp; + uint64_t startPos; + uint64_t requestContextID; + uint64_t channelId; + uint64_t contentWindowId; + uint64_t browserId; + uint64_t earlyHintPreloaderId; + nsCString requestMethod; + ClassOfService classOfService; + nsCString entityID; + nsCString appCacheClientID; + CorsPreflightArgs? preflightArgs; + nsCString contentTypeHint; + nsString integrityMetadata; + IPCStream? uploadStream; + LoadInfoArgs loadInfo; + uint32_t loadFlags; + uint32_t thirdPartyFlags; + uint32_t tlsFlags; + uint32_t cacheKey; + uint32_t initialRwin; + uint32_t redirectMode; + int16_t priority; + bool uploadStreamHasHeaders; + bool allowSTS; + bool resumeAt; + bool allowSpdy; + bool allowHttp3; + bool allowAltSvc; + bool beConservative; + bool bypassProxy; + bool blockAuthPrompt; + bool allowStaleCacheContent; + RequestMode requestMode; + bool forceValidateCacheContent; + bool preferCacheLoadOverBypass; + bool forceMainDocumentChannel; + uint8_t redirectionLimit; + nsString classicScriptHintCharset; + nsString documentCharacterSet; + bool isUserAgentHeaderModified; +}; + +struct HttpChannelConnectArgs +{ + uint32_t registrarId; +}; + +union HttpChannelCreationArgs +{ + HttpChannelOpenArgs; // For AsyncOpen: the common case. + HttpChannelConnectArgs; // Used for redirected-to channels +}; + +struct ProxyInfoCloneArgs +{ + nsCString type; + nsCString host; + int32_t port; + nsCString username; + nsCString password; + uint32_t flags; + uint32_t timeout; + uint32_t resolveFlags; + nsCString proxyAuthorizationHeader; + nsCString connectionIsolationKey; +}; + +struct HttpConnectionInfoCloneArgs +{ + nsCString host; + int32_t port; + nsCString npnToken; + nsCString username; + OriginAttributes originAttributes; + bool endToEndSSL; + nsCString routedHost; + int32_t routedPort; + bool anonymous; + bool aPrivate; // use prefix to avoid code generation error + bool insecureScheme; + bool noSpdy; + bool beConservative; + bool bypassProxy; + bool anonymousAllowClientCert; + bool fallbackConnection; + uint32_t tlsFlags; + bool isolated; + bool isTrrServiceChannel; + uint8_t trrMode; + bool isIPv4Disabled; + bool isIPv6Disabled; + nsCString topWindowOrigin; + bool isHttp3; + bool webTransport; + bool hasIPHintAddress; + nsCString echConfig; + ProxyInfoCloneArgs[] proxyInfo; +}; + +struct ConsoleReportCollected { + uint32_t errorFlags; + nsCString category; + uint32_t propertiesFile; + nsCString sourceFileURI; + uint32_t lineNumber; + uint32_t columnNumber; + nsCString messageName; + nsString[] stringParams; +}; + +struct CookieStruct +{ + nsCString name; + nsCString value; + nsCString host; + nsCString path; + int64_t expiry; + int64_t lastAccessed; + int64_t creationTime; + bool isHttpOnly; + bool isSession; + bool isSecure; + bool isPartitioned; + int32_t sameSite; + int32_t rawSameSite; + uint8_t schemeMap; +}; + +struct DocumentCreationArgs { + bool uriModified; + bool isEmbeddingBlockedError; +}; + +struct ObjectCreationArgs { + uint64_t embedderInnerWindowId; + uint32_t loadFlags; + nsContentPolicyType contentPolicyType; + bool isUrgentStart; +}; + +union DocumentChannelElementCreationArgs { + DocumentCreationArgs; + ObjectCreationArgs; +}; + +struct DocumentChannelCreationArgs { + nsDocShellLoadState loadState; + TimeStamp asyncOpenTime; + uint64_t channelId; + uint32_t cacheKey; + nullable nsDOMNavigationTiming timing; + IPCClientInfo? initialClientInfo; + DocumentChannelElementCreationArgs elementCreationArgs; + uint64_t parentInitiatedNavigationEpoch; +}; + +struct EarlyHintConnectArgs { + LinkHeader link; + uint64_t earlyHintPreloaderId; +}; + +struct RedirectToRealChannelArgs { + uint32_t registrarId; + nullable nsIURI uri; + uint32_t newLoadFlags; + ReplacementChannelConfigInit? init; + LoadInfoArgs loadInfo; + uint64_t channelId; + nullable nsIURI originalURI; + uint32_t redirectMode; + uint32_t redirectFlags; + uint32_t? contentDisposition; + nsString? contentDispositionFilename; + nullable nsIPropertyBag2 properties; + uint32_t loadStateExternalLoadFlags; + uint32_t loadStateInternalLoadFlags; + uint32_t loadStateLoadType; + nullable nsDOMNavigationTiming timing; + nsString srcdocData; + nullable nsIURI baseUri; + LoadingSessionHistoryInfo? loadingSessionHistoryInfo; + uint64_t loadIdentifier; + nsCString? originalUriString; + EarlyHintConnectArgs[] earlyHints; + uint32_t earlyHintLinkType; +}; + +struct TimingStructArgs { + TimeStamp domainLookupStart; + TimeStamp domainLookupEnd; + TimeStamp connectStart; + TimeStamp tcpConnectEnd; + TimeStamp secureConnectionStart; + TimeStamp connectEnd; + TimeStamp requestStart; + TimeStamp responseStart; + TimeStamp responseEnd; + TimeStamp transactionPending; +}; + +struct ResourceTimingStructArgs { + TimeStamp domainLookupStart; + TimeStamp domainLookupEnd; + TimeStamp connectStart; + TimeStamp tcpConnectEnd; + TimeStamp secureConnectionStart; + TimeStamp connectEnd; + TimeStamp requestStart; + TimeStamp responseStart; + TimeStamp responseEnd; + TimeStamp fetchStart; + TimeStamp redirectStart; + TimeStamp redirectEnd; + uint64_t transferSize; + uint64_t encodedBodySize; + + // Not actually part of resource timing, but not part of the transaction + // timings either. These need to be passed to HttpChannelChild along with + // the rest of the timings so the timing information in the child is complete. + TimeStamp cacheReadStart; + TimeStamp cacheReadEnd; + + TimeStamp transactionPending; +}; + +struct HttpActivity +{ + nsCString host; + int32_t port; + bool endToEndSSL; +}; + +struct HttpConnectionActivity +{ + nsCString connInfoKey; + nsCString host; + int32_t port; + bool ssl; + bool hasECH; + bool isHttp3; +}; + +union HttpActivityArgs +{ + uint64_t; + HttpActivity; + HttpConnectionActivity; +}; + +struct TransactionObserverResult +{ + bool versionOk; + bool authOk; + nsresult closeReason; +}; + +struct SpeculativeConnectionOverriderArgs { + uint32_t parallelSpeculativeConnectLimit; + bool ignoreIdle; + bool isFromPredictor; + bool allow1918; +}; + +//----------------------------------------------------------------------------- +// GIO IPDL structs +//----------------------------------------------------------------------------- + +struct GIOChannelOpenArgs +{ + URIParams uri; + uint64_t startPos; + nsCString entityID; + IPCStream? uploadStream; + LoadInfoArgs loadInfo; + uint32_t loadFlags; +}; + +struct GIOChannelConnectArgs +{ + uint32_t channelId; +}; + +union GIOChannelCreationArgs +{ + GIOChannelOpenArgs; // For AsyncOpen: the common case. + GIOChannelConnectArgs; // Used for redirected-to channels +}; + +struct RemoteStreamInfo { + nullable nsIInputStream inputStream; + nsCString contentType; + int64_t contentLength; +}; +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/NeckoChild.cpp b/netwerk/ipc/NeckoChild.cpp new file mode 100644 index 0000000000..fe2b3df0bf --- /dev/null +++ b/netwerk/ipc/NeckoChild.cpp @@ -0,0 +1,337 @@ + +/* 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/. */ + +#include "nsHttp.h" +#include "mozilla/net/NeckoChild.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/net/HttpChannelChild.h" +#include "mozilla/net/ChildDNSService.h" +#include "mozilla/net/CookieServiceChild.h" +#include "mozilla/net/DataChannelChild.h" +#ifdef MOZ_WIDGET_GTK +# include "mozilla/net/GIOChannelChild.h" +#endif +#include "mozilla/net/FileChannelChild.h" +#include "mozilla/net/WebSocketChannelChild.h" +#include "mozilla/net/WebSocketEventListenerChild.h" +#include "mozilla/net/DNSRequestChild.h" +#include "mozilla/net/IPCTransportProvider.h" +#include "mozilla/dom/network/TCPSocketChild.h" +#include "mozilla/dom/network/TCPServerSocketChild.h" +#include "mozilla/dom/network/UDPSocketChild.h" +#include "mozilla/net/AltDataOutputStreamChild.h" +#include "mozilla/net/SocketProcessBridgeChild.h" +#ifdef MOZ_WEBRTC +# include "mozilla/net/StunAddrsRequestChild.h" +# include "mozilla/net/WebrtcTCPSocketChild.h" +#endif + +#include "SerializedLoadContext.h" +#include "nsGlobalWindowInner.h" +#include "nsIOService.h" +#include "nsINetworkPredictor.h" +#include "nsINetworkPredictorVerifier.h" +#include "nsINetworkLinkService.h" +#include "nsQueryObject.h" +#include "mozilla/ipc/URIUtils.h" +#include "nsNetUtil.h" +#include "SimpleChannel.h" + +using mozilla::dom::TCPServerSocketChild; +using mozilla::dom::TCPSocketChild; +using mozilla::dom::UDPSocketChild; + +namespace mozilla { +namespace net { + +PNeckoChild* gNeckoChild = nullptr; + +// C++ file contents + +NeckoChild::~NeckoChild() { + // Send__delete__(gNeckoChild); + gNeckoChild = nullptr; +} + +void NeckoChild::InitNeckoChild() { + if (!IsNeckoChild()) { + MOZ_ASSERT(false, "InitNeckoChild called by non-child!"); + return; + } + + if (!gNeckoChild) { + mozilla::dom::ContentChild* cpc = + mozilla::dom::ContentChild::GetSingleton(); + NS_ASSERTION(cpc, "Content Protocol is NULL!"); + if (NS_WARN_IF(cpc->IsShuttingDown())) { + return; + } + RefPtr<NeckoChild> child = new NeckoChild(); + gNeckoChild = cpc->SendPNeckoConstructor(child); + NS_ASSERTION(gNeckoChild, "PNecko Protocol init failed!"); + } +} + +PStunAddrsRequestChild* NeckoChild::AllocPStunAddrsRequestChild() { + // We don't allocate here: instead we always use IPDL constructor that takes + // an existing object + MOZ_ASSERT_UNREACHABLE( + "AllocPStunAddrsRequestChild should not be called " + "on child"); + return nullptr; +} + +bool NeckoChild::DeallocPStunAddrsRequestChild(PStunAddrsRequestChild* aActor) { +#ifdef MOZ_WEBRTC + StunAddrsRequestChild* p = static_cast<StunAddrsRequestChild*>(aActor); + p->ReleaseIPDLReference(); +#endif + return true; +} + +PWebrtcTCPSocketChild* NeckoChild::AllocPWebrtcTCPSocketChild( + const Maybe<TabId>& tabId) { + // We don't allocate here: instead we always use IPDL constructor that takes + // an existing object + MOZ_ASSERT_UNREACHABLE( + "AllocPWebrtcTCPSocketChild should not be called on" + " child"); + return nullptr; +} + +bool NeckoChild::DeallocPWebrtcTCPSocketChild(PWebrtcTCPSocketChild* aActor) { +#ifdef MOZ_WEBRTC + WebrtcTCPSocketChild* child = static_cast<WebrtcTCPSocketChild*>(aActor); + child->ReleaseIPDLReference(); +#endif + return true; +} + +PAltDataOutputStreamChild* NeckoChild::AllocPAltDataOutputStreamChild( + const nsACString& type, const int64_t& predictedSize, + PHttpChannelChild* channel) { + // We don't allocate here: see HttpChannelChild::OpenAlternativeOutputStream() + MOZ_ASSERT_UNREACHABLE("AllocPAltDataOutputStreamChild should not be called"); + return nullptr; +} + +bool NeckoChild::DeallocPAltDataOutputStreamChild( + PAltDataOutputStreamChild* aActor) { + AltDataOutputStreamChild* child = + static_cast<AltDataOutputStreamChild*>(aActor); + child->ReleaseIPDLReference(); + return true; +} + +#ifdef MOZ_WIDGET_GTK +PGIOChannelChild* NeckoChild::AllocPGIOChannelChild( + PBrowserChild* aBrowser, const SerializedLoadContext& aSerialized, + const GIOChannelCreationArgs& aOpenArgs) { + // We don't allocate here: see GIOChannelChild::AsyncOpen() + MOZ_CRASH("AllocPGIOChannelChild should not be called"); + return nullptr; +} + +bool NeckoChild::DeallocPGIOChannelChild(PGIOChannelChild* channel) { + MOZ_ASSERT(IsNeckoChild(), "DeallocPGIOChannelChild called by non-child!"); + + GIOChannelChild* child = static_cast<GIOChannelChild*>(channel); + child->ReleaseIPDLReference(); + return true; +} +#endif + +PCookieServiceChild* NeckoChild::AllocPCookieServiceChild() { + // We don't allocate here: see CookieService::GetSingleton() + MOZ_ASSERT_UNREACHABLE("AllocPCookieServiceChild should not be called"); + return nullptr; +} + +bool NeckoChild::DeallocPCookieServiceChild(PCookieServiceChild* cs) { + NS_ASSERTION(IsNeckoChild(), + "DeallocPCookieServiceChild called by non-child!"); + + CookieServiceChild* p = static_cast<CookieServiceChild*>(cs); + p->Release(); + return true; +} + +PWebSocketChild* NeckoChild::AllocPWebSocketChild( + PBrowserChild* browser, const SerializedLoadContext& aSerialized, + const uint32_t& aSerial) { + MOZ_ASSERT_UNREACHABLE("AllocPWebSocketChild should not be called"); + return nullptr; +} + +bool NeckoChild::DeallocPWebSocketChild(PWebSocketChild* child) { + WebSocketChannelChild* p = static_cast<WebSocketChannelChild*>(child); + p->ReleaseIPDLReference(); + return true; +} + +PWebSocketEventListenerChild* NeckoChild::AllocPWebSocketEventListenerChild( + const uint64_t& aInnerWindowID) { + RefPtr<WebSocketEventListenerChild> c = new WebSocketEventListenerChild( + aInnerWindowID, GetMainThreadSerialEventTarget()); + return c.forget().take(); +} + +bool NeckoChild::DeallocPWebSocketEventListenerChild( + PWebSocketEventListenerChild* aActor) { + RefPtr<WebSocketEventListenerChild> c = + dont_AddRef(static_cast<WebSocketEventListenerChild*>(aActor)); + MOZ_ASSERT(c); + return true; +} + +PSimpleChannelChild* NeckoChild::AllocPSimpleChannelChild( + const uint32_t& channelId) { + MOZ_ASSERT_UNREACHABLE("Should never get here"); + return nullptr; +} + +bool NeckoChild::DeallocPSimpleChannelChild(PSimpleChannelChild* child) { + static_cast<SimpleChannelChild*>(child)->Release(); + return true; +} + +PTCPSocketChild* NeckoChild::AllocPTCPSocketChild(const nsAString& host, + const uint16_t& port) { + TCPSocketChild* p = new TCPSocketChild(host, port, nullptr); + p->AddIPDLReference(); + return p; +} + +bool NeckoChild::DeallocPTCPSocketChild(PTCPSocketChild* child) { + TCPSocketChild* p = static_cast<TCPSocketChild*>(child); + p->ReleaseIPDLReference(); + return true; +} + +PTCPServerSocketChild* NeckoChild::AllocPTCPServerSocketChild( + const uint16_t& aLocalPort, const uint16_t& aBacklog, + const bool& aUseArrayBuffers) { + MOZ_ASSERT_UNREACHABLE("AllocPTCPServerSocket should not be called"); + return nullptr; +} + +bool NeckoChild::DeallocPTCPServerSocketChild(PTCPServerSocketChild* child) { + TCPServerSocketChild* p = static_cast<TCPServerSocketChild*>(child); + p->ReleaseIPDLReference(); + return true; +} + +PUDPSocketChild* NeckoChild::AllocPUDPSocketChild(nsIPrincipal* aPrincipal, + const nsACString& aFilter) { + MOZ_ASSERT_UNREACHABLE("AllocPUDPSocket should not be called"); + return nullptr; +} + +bool NeckoChild::DeallocPUDPSocketChild(PUDPSocketChild* child) { + UDPSocketChild* p = static_cast<UDPSocketChild*>(child); + p->ReleaseIPDLReference(); + return true; +} + +PTransportProviderChild* NeckoChild::AllocPTransportProviderChild() { + // This refcount is transferred to the receiver of the message that + // includes the PTransportProviderChild actor. + RefPtr<TransportProviderChild> res = new TransportProviderChild(); + + return res.forget().take(); +} + +bool NeckoChild::DeallocPTransportProviderChild( + PTransportProviderChild* aActor) { + return true; +} + +/* Predictor Messages */ +mozilla::ipc::IPCResult NeckoChild::RecvPredOnPredictPrefetch( + nsIURI* aURI, const uint32_t& aHttpStatus) { + MOZ_ASSERT(NS_IsMainThread(), + "PredictorChild::RecvOnPredictPrefetch " + "off main thread."); + if (!aURI) { + return IPC_FAIL(this, "aURI is null"); + } + + // Get the current predictor + nsresult rv = NS_OK; + nsCOMPtr<nsINetworkPredictorVerifier> predictor = + do_GetService("@mozilla.org/network/predictor;1", &rv); + NS_ENSURE_SUCCESS(rv, IPC_FAIL_NO_REASON(this)); + + predictor->OnPredictPrefetch(aURI, aHttpStatus); + return IPC_OK(); +} + +mozilla::ipc::IPCResult NeckoChild::RecvPredOnPredictPreconnect(nsIURI* aURI) { + MOZ_ASSERT(NS_IsMainThread(), + "PredictorChild::RecvOnPredictPreconnect " + "off main thread."); + if (!aURI) { + return IPC_FAIL(this, "aURI is null"); + } + // Get the current predictor + nsresult rv = NS_OK; + nsCOMPtr<nsINetworkPredictorVerifier> predictor = + do_GetService("@mozilla.org/network/predictor;1", &rv); + NS_ENSURE_SUCCESS(rv, IPC_FAIL_NO_REASON(this)); + + predictor->OnPredictPreconnect(aURI); + return IPC_OK(); +} + +mozilla::ipc::IPCResult NeckoChild::RecvPredOnPredictDNS(nsIURI* aURI) { + MOZ_ASSERT(NS_IsMainThread(), + "PredictorChild::RecvOnPredictDNS off " + "main thread."); + if (!aURI) { + return IPC_FAIL(this, "aURI is null"); + } + // Get the current predictor + nsresult rv = NS_OK; + nsCOMPtr<nsINetworkPredictorVerifier> predictor = + do_GetService("@mozilla.org/network/predictor;1", &rv); + NS_ENSURE_SUCCESS(rv, IPC_FAIL_NO_REASON(this)); + + predictor->OnPredictDNS(aURI); + return IPC_OK(); +} + +mozilla::ipc::IPCResult NeckoChild::RecvSpeculativeConnectRequest() { + nsCOMPtr<nsIObserverService> obsService = services::GetObserverService(); + if (obsService) { + obsService->NotifyObservers(nullptr, "speculative-connect-request", + nullptr); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult NeckoChild::RecvNetworkChangeNotification( + nsCString const& type) { + nsCOMPtr<nsIObserverService> obsService = services::GetObserverService(); + if (obsService) { + obsService->NotifyObservers(nullptr, NS_NETWORK_LINK_TOPIC, + NS_ConvertUTF8toUTF16(type).get()); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult NeckoChild::RecvSetTRRDomain(const nsCString& domain) { + RefPtr<net::ChildDNSService> dnsServiceChild = + dont_AddRef(net::ChildDNSService::GetSingleton()); + if (dnsServiceChild) { + dnsServiceChild->SetTRRDomain(domain); + } + return IPC_OK(); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/NeckoChild.h b/netwerk/ipc/NeckoChild.h new file mode 100644 index 0000000000..2434c02126 --- /dev/null +++ b/netwerk/ipc/NeckoChild.h @@ -0,0 +1,93 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_NeckoChild_h +#define mozilla_net_NeckoChild_h + +#include "mozilla/net/PNeckoChild.h" +#include "mozilla/net/NeckoCommon.h" + +namespace mozilla { +namespace net { + +// Header file contents +class NeckoChild : public PNeckoChild { + friend class PNeckoChild; + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NeckoChild, override) + + NeckoChild() = default; + + static void InitNeckoChild(); + + protected: + virtual ~NeckoChild(); + + PStunAddrsRequestChild* AllocPStunAddrsRequestChild(); + bool DeallocPStunAddrsRequestChild(PStunAddrsRequestChild* aActor); + + PWebrtcTCPSocketChild* AllocPWebrtcTCPSocketChild(const Maybe<TabId>& tabId); + bool DeallocPWebrtcTCPSocketChild(PWebrtcTCPSocketChild* aActor); + + PAltDataOutputStreamChild* AllocPAltDataOutputStreamChild( + const nsACString& type, const int64_t& predictedSize, + PHttpChannelChild* channel); + bool DeallocPAltDataOutputStreamChild(PAltDataOutputStreamChild* aActor); + + PCookieServiceChild* AllocPCookieServiceChild(); + bool DeallocPCookieServiceChild(PCookieServiceChild*); +#ifdef MOZ_WIDGET_GTK + PGIOChannelChild* AllocPGIOChannelChild( + PBrowserChild* aBrowser, const SerializedLoadContext& aSerialized, + const GIOChannelCreationArgs& aOpenArgs); + bool DeallocPGIOChannelChild(PGIOChannelChild*); +#endif + PWebSocketChild* AllocPWebSocketChild(PBrowserChild*, + const SerializedLoadContext&, + const uint32_t&); + bool DeallocPWebSocketChild(PWebSocketChild*); + PTCPSocketChild* AllocPTCPSocketChild(const nsAString& host, + const uint16_t& port); + bool DeallocPTCPSocketChild(PTCPSocketChild*); + PTCPServerSocketChild* AllocPTCPServerSocketChild( + const uint16_t& aLocalPort, const uint16_t& aBacklog, + const bool& aUseArrayBuffers); + bool DeallocPTCPServerSocketChild(PTCPServerSocketChild*); + PUDPSocketChild* AllocPUDPSocketChild(nsIPrincipal* aPrincipal, + const nsACString& aFilter); + bool DeallocPUDPSocketChild(PUDPSocketChild*); + PSimpleChannelChild* AllocPSimpleChannelChild(const uint32_t& channelId); + bool DeallocPSimpleChannelChild(PSimpleChannelChild* child); + PTransportProviderChild* AllocPTransportProviderChild(); + bool DeallocPTransportProviderChild(PTransportProviderChild* aActor); + PWebSocketEventListenerChild* AllocPWebSocketEventListenerChild( + const uint64_t& aInnerWindowID); + bool DeallocPWebSocketEventListenerChild(PWebSocketEventListenerChild*); + + /* Predictor Messsages */ + mozilla::ipc::IPCResult RecvPredOnPredictPrefetch( + nsIURI* aURI, const uint32_t& aHttpStatus); + mozilla::ipc::IPCResult RecvPredOnPredictPreconnect(nsIURI* aURI); + mozilla::ipc::IPCResult RecvPredOnPredictDNS(nsIURI* aURI); + + mozilla::ipc::IPCResult RecvSpeculativeConnectRequest(); + mozilla::ipc::IPCResult RecvNetworkChangeNotification(nsCString const& type); + + mozilla::ipc::IPCResult RecvSetTRRDomain(const nsCString& domain); +}; + +/** + * Reference to the PNecko Child protocol. + * Null if this is not a content process. + */ +extern PNeckoChild* gNeckoChild; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_NeckoChild_h diff --git a/netwerk/ipc/NeckoCommon.cpp b/netwerk/ipc/NeckoCommon.cpp new file mode 100644 index 0000000000..9ad4483d32 --- /dev/null +++ b/netwerk/ipc/NeckoCommon.cpp @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "NeckoCommon.h" + +#include "nsIInputStream.h" +#include "nsIMultiPartChannel.h" +#include "nsIParentChannel.h" +#include "nsStringStream.h" + +namespace mozilla::net { + +nsresult ForwardStreamListenerFunctions(nsTArray<StreamListenerFunction> aCalls, + nsIStreamListener* aParent) { + nsresult rv = NS_OK; + for (auto& variant : aCalls) { + variant.match( + [&](OnStartRequestParams& aParams) { + rv = aParent->OnStartRequest(aParams.request); + if (NS_FAILED(rv)) { + aParams.request->Cancel(rv); + } + }, + [&](OnDataAvailableParams& aParams) { + // Don't deliver OnDataAvailable if we've + // already failed. + if (NS_FAILED(rv)) { + return; + } + nsCOMPtr<nsIInputStream> stringStream; + rv = NS_NewCStringInputStream(getter_AddRefs(stringStream), + std::move(aParams.data)); + if (NS_SUCCEEDED(rv)) { + rv = aParent->OnDataAvailable(aParams.request, stringStream, + aParams.offset, aParams.count); + } + if (NS_FAILED(rv)) { + aParams.request->Cancel(rv); + } + }, + [&](OnStopRequestParams& aParams) { + if (NS_SUCCEEDED(rv)) { + aParent->OnStopRequest(aParams.request, aParams.status); + } else { + aParent->OnStopRequest(aParams.request, rv); + } + rv = NS_OK; + }, + [&](OnAfterLastPartParams& aParams) { + nsCOMPtr<nsIMultiPartChannelListener> multiListener = + do_QueryInterface(aParent); + if (multiListener) { + if (NS_SUCCEEDED(rv)) { + multiListener->OnAfterLastPart(aParams.status); + } else { + multiListener->OnAfterLastPart(rv); + } + } + }); + } + return rv; +} + +} // namespace mozilla::net diff --git a/netwerk/ipc/NeckoCommon.h b/netwerk/ipc/NeckoCommon.h new file mode 100644 index 0000000000..57f770db68 --- /dev/null +++ b/netwerk/ipc/NeckoCommon.h @@ -0,0 +1,144 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_NeckoCommon_h +#define mozilla_net_NeckoCommon_h + +#include "mozilla/Preferences.h" +#include "mozilla/Variant.h" +#include "nsIRequest.h" +#include "nsPrintfCString.h" +#include "nsXULAppAPI.h" +#include "prenv.h" + +class nsIStreamListener; + +namespace mozilla { +namespace dom { +class BrowserChild; +} // namespace dom +} // namespace mozilla + +#if defined(DEBUG) +# define NECKO_ERRORS_ARE_FATAL_DEFAULT true +#else +# define NECKO_ERRORS_ARE_FATAL_DEFAULT false +#endif + +// TODO: Eventually remove NECKO_MAYBE_ABORT and DROP_DEAD (bug 575494). +// Still useful for catching listener interfaces we don't yet support across +// processes, etc. + +#define NECKO_MAYBE_ABORT(msg) \ + do { \ + bool abort = NECKO_ERRORS_ARE_FATAL_DEFAULT; \ + const char* e = PR_GetEnv("NECKO_ERRORS_ARE_FATAL"); \ + if (e) abort = (*e == '0') ? false : true; \ + if (abort) { \ + msg.AppendLiteral( \ + " (set NECKO_ERRORS_ARE_FATAL=0 in your environment " \ + "to convert this error into a warning.)"); \ + MOZ_CRASH_UNSAFE(msg.get()); \ + } else { \ + msg.AppendLiteral( \ + " (set NECKO_ERRORS_ARE_FATAL=1 in your environment " \ + "to convert this warning into a fatal error.)"); \ + NS_WARNING(msg.get()); \ + } \ + } while (0) + +#define DROP_DEAD() \ + do { \ + nsPrintfCString msg("NECKO ERROR: '%s' UNIMPLEMENTED", __FUNCTION__); \ + NECKO_MAYBE_ABORT(msg); \ + return NS_ERROR_NOT_IMPLEMENTED; \ + } while (0) + +#define ENSURE_CALLED_BEFORE_ASYNC_OPEN() \ + do { \ + if (LoadIsPending() || LoadWasOpened()) { \ + nsPrintfCString msg("'%s' called after AsyncOpen: %s +%d", __FUNCTION__, \ + __FILE__, __LINE__); \ + NECKO_MAYBE_ABORT(msg); \ + } \ + NS_ENSURE_TRUE(!LoadIsPending(), NS_ERROR_IN_PROGRESS); \ + NS_ENSURE_TRUE(!LoadWasOpened(), NS_ERROR_ALREADY_OPENED); \ + } while (0) + +// Fails call if made after request observers (on-modify-request, etc) have been +// called + +#define ENSURE_CALLED_BEFORE_CONNECT() \ + do { \ + if (LoadRequestObserversCalled()) { \ + nsPrintfCString msg("'%s' called too late: %s +%d", __FUNCTION__, \ + __FILE__, __LINE__); \ + NECKO_MAYBE_ABORT(msg); \ + if (LoadIsPending()) return NS_ERROR_IN_PROGRESS; \ + MOZ_ASSERT(LoadWasOpened()); \ + return NS_ERROR_ALREADY_OPENED; \ + } \ + } while (0) + +namespace mozilla { +namespace net { + +inline bool IsNeckoChild() { + static bool didCheck = false; + static bool amChild = false; + + if (!didCheck) { + didCheck = true; + amChild = (XRE_GetProcessType() == GeckoProcessType_Content); + } + return amChild; +} + +inline bool IsSocketProcessChild() { + static bool amChild = (XRE_GetProcessType() == GeckoProcessType_Socket); + return amChild; +} + +class HttpChannelSecurityWarningReporter : public nsISupports { + public: + [[nodiscard]] virtual nsresult ReportSecurityMessage( + const nsAString& aMessageTag, const nsAString& aMessageCategory) = 0; + [[nodiscard]] virtual nsresult LogBlockedCORSRequest( + const nsAString& aMessage, const nsACString& aCategory, + bool aIsWarning = false) = 0; + [[nodiscard]] virtual nsresult LogMimeTypeMismatch( + const nsACString& aMessageName, bool aWarning, const nsAString& aURL, + const nsAString& aContentType) = 0; +}; + +struct OnStartRequestParams { + nsCOMPtr<nsIRequest> request; +}; +struct OnDataAvailableParams { + nsCOMPtr<nsIRequest> request; + nsCString data; + uint64_t offset; + uint32_t count; +}; +struct OnStopRequestParams { + nsCOMPtr<nsIRequest> request; + nsresult status; +}; +struct OnAfterLastPartParams { + nsresult status; +}; +using StreamListenerFunction = + mozilla::Variant<OnStartRequestParams, OnDataAvailableParams, + OnStopRequestParams, OnAfterLastPartParams>; + +nsresult ForwardStreamListenerFunctions(nsTArray<StreamListenerFunction> aCalls, + nsIStreamListener* aParent); + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_NeckoCommon_h diff --git a/netwerk/ipc/NeckoMessageUtils.h b/netwerk/ipc/NeckoMessageUtils.h new file mode 100644 index 0000000000..a15398330e --- /dev/null +++ b/netwerk/ipc/NeckoMessageUtils.h @@ -0,0 +1,190 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_NeckoMessageUtils_h +#define mozilla_net_NeckoMessageUtils_h + +#include "mozilla/DebugOnly.h" + +#include "ipc/EnumSerializer.h" +#include "ipc/IPCMessageUtils.h" +#include "nsExceptionHandler.h" +#include "nsIHttpChannel.h" +#include "nsPrintfCString.h" +#include "nsString.h" +#include "prio.h" +#include "mozilla/net/DNS.h" +#include "ipc/IPCMessageUtilsSpecializations.h" +#include "nsITRRSkipReason.h" +#include "nsIDNSService.h" + +namespace IPC { + +// nsIPermissionManager utilities + +struct Permission { + nsCString origin, type; + uint32_t capability, expireType; + int64_t expireTime; + + Permission() : capability(0), expireType(0), expireTime(0) {} + + Permission(const nsCString& aOrigin, const nsACString& aType, + const uint32_t aCapability, const uint32_t aExpireType, + const int64_t aExpireTime) + : origin(aOrigin), + type(aType), + capability(aCapability), + expireType(aExpireType), + expireTime(aExpireTime) {} + + bool operator==(const Permission& aOther) const { + return aOther.origin == origin && aOther.type == type && + aOther.capability == capability && aOther.expireType == expireType && + aOther.expireTime == expireTime; + } +}; + +template <> +struct ParamTraits<Permission> { + static void Write(MessageWriter* aWriter, const Permission& aParam) { + WriteParam(aWriter, aParam.origin); + WriteParam(aWriter, aParam.type); + WriteParam(aWriter, aParam.capability); + WriteParam(aWriter, aParam.expireType); + WriteParam(aWriter, aParam.expireTime); + } + + static bool Read(MessageReader* aReader, Permission* aResult) { + return ReadParam(aReader, &aResult->origin) && + ReadParam(aReader, &aResult->type) && + ReadParam(aReader, &aResult->capability) && + ReadParam(aReader, &aResult->expireType) && + ReadParam(aReader, &aResult->expireTime); + } +}; + +template <> +struct ParamTraits<mozilla::net::NetAddr> { + static void Write(MessageWriter* aWriter, + const mozilla::net::NetAddr& aParam) { + WriteParam(aWriter, aParam.raw.family); + if (aParam.raw.family == AF_UNSPEC) { + aWriter->WriteBytes(aParam.raw.data, sizeof(aParam.raw.data)); + } else if (aParam.raw.family == AF_INET) { + WriteParam(aWriter, aParam.inet.port); + WriteParam(aWriter, aParam.inet.ip); + } else if (aParam.raw.family == AF_INET6) { + WriteParam(aWriter, aParam.inet6.port); + WriteParam(aWriter, aParam.inet6.flowinfo); + WriteParam(aWriter, aParam.inet6.ip.u64[0]); + WriteParam(aWriter, aParam.inet6.ip.u64[1]); + WriteParam(aWriter, aParam.inet6.scope_id); +#if defined(XP_UNIX) + } else if (aParam.raw.family == AF_LOCAL) { + // Train's already off the rails: let's get a stack trace at least... + MOZ_CRASH( + "Error: please post stack trace to " + "https://bugzilla.mozilla.org/show_bug.cgi?id=661158"); + aWriter->WriteBytes(aParam.local.path, sizeof(aParam.local.path)); +#endif + } else { + if (XRE_IsParentProcess()) { + nsPrintfCString msg("%d", aParam.raw.family); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::UnknownNetAddrSocketFamily, msg); + } + + MOZ_CRASH("Unknown socket family"); + } + } + + static bool Read(MessageReader* aReader, mozilla::net::NetAddr* aResult) { + if (!ReadParam(aReader, &aResult->raw.family)) return false; + + if (aResult->raw.family == AF_UNSPEC) { + return aReader->ReadBytesInto(&aResult->raw.data, + sizeof(aResult->raw.data)); + } else if (aResult->raw.family == AF_INET) { + return ReadParam(aReader, &aResult->inet.port) && + ReadParam(aReader, &aResult->inet.ip); + } else if (aResult->raw.family == AF_INET6) { + return ReadParam(aReader, &aResult->inet6.port) && + ReadParam(aReader, &aResult->inet6.flowinfo) && + ReadParam(aReader, &aResult->inet6.ip.u64[0]) && + ReadParam(aReader, &aResult->inet6.ip.u64[1]) && + ReadParam(aReader, &aResult->inet6.scope_id); +#if defined(XP_UNIX) + } else if (aResult->raw.family == AF_LOCAL) { + return aReader->ReadBytesInto(&aResult->local.path, + sizeof(aResult->local.path)); +#endif + } + + /* We've been tricked by some socket family we don't know about! */ + return false; + } +}; + +template <> +struct ParamTraits<nsIRequest::TRRMode> { + static void Write(MessageWriter* aWriter, const nsIRequest::TRRMode& aParam) { + WriteParam(aWriter, (uint8_t)aParam); + } + static bool Read(MessageReader* aReader, nsIRequest::TRRMode* aResult) { + uint8_t mode; + if (!ReadParam(aReader, &mode)) { + return false; + } + // TODO: sanity check + *aResult = static_cast<nsIRequest::TRRMode>(mode); + return true; + } +}; + +template <> +struct ParamTraits<nsITRRSkipReason::value> { + static void Write(MessageWriter* aWriter, + const nsITRRSkipReason::value& aParam) { + WriteParam(aWriter, (uint8_t)aParam); + } + static bool Read(MessageReader* aReader, nsITRRSkipReason::value* aResult) { + uint8_t reason; + if (!ReadParam(aReader, &reason)) { + return false; + } + // TODO: sanity check + *aResult = static_cast<nsITRRSkipReason::value>(reason); + return true; + } +}; + +template <> +struct ParamTraits<nsIDNSService::DNSFlags> + : public BitFlagsEnumSerializer< + nsIDNSService::DNSFlags, nsIDNSService::DNSFlags::ALL_DNSFLAGS_BITS> { +}; + +template <> +struct ParamTraits<nsIDNSService::ResolverMode> { + static void Write(MessageWriter* aWriter, + const nsIDNSService::ResolverMode& aParam) { + WriteParam(aWriter, (uint8_t)aParam); + } + static bool Read(MessageReader* aReader, + nsIDNSService::ResolverMode* aResult) { + uint8_t mode; + if (!ReadParam(aReader, &mode)) { + return false; + } + // TODO: sanity check + *aResult = static_cast<nsIDNSService::ResolverMode>(mode); + return true; + } +}; + +} // namespace IPC + +#endif // mozilla_net_NeckoMessageUtils_h diff --git a/netwerk/ipc/NeckoParent.cpp b/netwerk/ipc/NeckoParent.cpp new file mode 100644 index 0000000000..102b46a378 --- /dev/null +++ b/netwerk/ipc/NeckoParent.cpp @@ -0,0 +1,889 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsHttp.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/ContentPrincipal.h" +#include "mozilla/ipc/IPCStreamUtils.h" +#include "mozilla/net/ExtensionProtocolHandler.h" +#include "mozilla/net/PageThumbProtocolHandler.h" +#include "mozilla/net/NeckoParent.h" +#include "mozilla/net/HttpChannelParent.h" +#include "mozilla/net/CookieServiceParent.h" +#include "mozilla/net/WebSocketChannelParent.h" +#include "mozilla/net/WebSocketEventListenerParent.h" +#include "mozilla/net/DataChannelParent.h" +#ifdef MOZ_WIDGET_GTK +# include "mozilla/net/GIOChannelParent.h" +#endif +#include "mozilla/net/DocumentChannelParent.h" +#include "mozilla/net/SimpleChannelParent.h" +#include "mozilla/net/AltDataOutputStreamParent.h" +#include "mozilla/Unused.h" +#include "mozilla/net/FileChannelParent.h" +#include "mozilla/net/DNSRequestParent.h" +#include "mozilla/net/IPCTransportProvider.h" +#include "mozilla/net/RemoteStreamGetter.h" +#include "mozilla/net/RequestContextService.h" +#include "mozilla/net/SocketProcessParent.h" +#include "mozilla/net/PSocketProcessBridgeParent.h" +#ifdef MOZ_WEBRTC +# include "mozilla/net/StunAddrsRequestParent.h" +# include "mozilla/net/WebrtcTCPSocketParent.h" +#endif +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/MaybeDiscarded.h" +#include "mozilla/dom/network/TCPSocketParent.h" +#include "mozilla/dom/network/TCPServerSocketParent.h" +#include "mozilla/dom/network/UDPSocketParent.h" +#ifdef MOZ_PLACES +# include "mozilla/places/PageIconProtocolHandler.h" +#endif +#include "mozilla/LoadContext.h" +#include "mozilla/MozPromise.h" +#include "nsPrintfCString.h" +#include "mozilla/dom/HTMLDNSPrefetch.h" +#include "nsEscape.h" +#include "SerializedLoadContext.h" +#include "nsAuthInformationHolder.h" +#include "nsINetworkPredictor.h" +#include "nsINetworkPredictorVerifier.h" +#include "nsISpeculativeConnect.h" +#include "nsHttpHandler.h" +#include "nsNetUtil.h" +#include "nsIOService.h" + +using IPC::SerializedLoadContext; +using mozilla::dom::BrowserParent; +using mozilla::dom::ContentParent; +using mozilla::dom::TCPServerSocketParent; +using mozilla::dom::TCPSocketParent; +using mozilla::dom::UDPSocketParent; +using mozilla::ipc::LoadInfoArgsToLoadInfo; +using mozilla::ipc::PrincipalInfo; +#ifdef MOZ_PLACES +using mozilla::places::PageIconProtocolHandler; +#endif + +namespace mozilla { +namespace net { + +// C++ file contents +NeckoParent::NeckoParent() : mSocketProcessBridgeInited(false) { + // Init HTTP protocol handler now since we need atomTable up and running very + // early (IPDL argument handling for PHttpChannel constructor needs it) so + // normal init (during 1st Http channel request) isn't early enough. + nsCOMPtr<nsIProtocolHandler> proto = + do_GetService("@mozilla.org/network/protocol;1?name=http"); +} + +static PBOverrideStatus PBOverrideStatusFromLoadContext( + const SerializedLoadContext& aSerialized) { + if (!aSerialized.IsNotNull() && aSerialized.IsPrivateBitValid()) { + return (aSerialized.mOriginAttributes.mPrivateBrowsingId > 0) + ? kPBOverride_Private + : kPBOverride_NotPrivate; + } + return kPBOverride_Unset; +} + +static already_AddRefed<nsIPrincipal> GetRequestingPrincipal( + const LoadInfoArgs& aLoadInfoArgs) { + const Maybe<PrincipalInfo>& optionalPrincipalInfo = + aLoadInfoArgs.requestingPrincipalInfo(); + + if (optionalPrincipalInfo.isNothing()) { + return nullptr; + } + + const PrincipalInfo& principalInfo = optionalPrincipalInfo.ref(); + + auto principalOrErr = PrincipalInfoToPrincipal(principalInfo); + return principalOrErr.isOk() ? principalOrErr.unwrap().forget() : nullptr; +} + +static already_AddRefed<nsIPrincipal> GetRequestingPrincipal( + const HttpChannelCreationArgs& aArgs) { + if (aArgs.type() != HttpChannelCreationArgs::THttpChannelOpenArgs) { + return nullptr; + } + + const HttpChannelOpenArgs& args = aArgs.get_HttpChannelOpenArgs(); + return GetRequestingPrincipal(args.loadInfo()); +} + +const char* NeckoParent::GetValidatedOriginAttributes( + const SerializedLoadContext& aSerialized, PContentParent* aContent, + nsIPrincipal* aRequestingPrincipal, OriginAttributes& aAttrs) { + if (!aSerialized.IsNotNull()) { + // If serialized is null, we cannot validate anything. We have to assume + // that this requests comes from a SystemPrincipal. + aAttrs = OriginAttributes(false); + } else { + aAttrs = aSerialized.mOriginAttributes; + } + return nullptr; +} + +const char* NeckoParent::CreateChannelLoadContext( + PBrowserParent* aBrowser, PContentParent* aContent, + const SerializedLoadContext& aSerialized, + nsIPrincipal* aRequestingPrincipal, nsCOMPtr<nsILoadContext>& aResult) { + OriginAttributes attrs; + const char* error = GetValidatedOriginAttributes(aSerialized, aContent, + aRequestingPrincipal, attrs); + if (error) { + return error; + } + + // if !UsingNeckoIPCSecurity(), we may not have a LoadContext to set. This is + // the common case for most xpcshell tests. + if (aSerialized.IsNotNull()) { + attrs.SyncAttributesWithPrivateBrowsing( + aSerialized.mOriginAttributes.mPrivateBrowsingId > 0); + + RefPtr<BrowserParent> browserParent = BrowserParent::GetFrom(aBrowser); + dom::Element* topFrameElement = nullptr; + if (browserParent) { + topFrameElement = browserParent->GetOwnerElement(); + } + aResult = new LoadContext(aSerialized, topFrameElement, attrs); + } + + return nullptr; +} + +void NeckoParent::ActorDestroy(ActorDestroyReason aWhy) { + // Nothing needed here. Called right before destructor since this is a + // non-refcounted class. +} + +already_AddRefed<PHttpChannelParent> NeckoParent::AllocPHttpChannelParent( + PBrowserParent* aBrowser, const SerializedLoadContext& aSerialized, + const HttpChannelCreationArgs& aOpenArgs) { + nsCOMPtr<nsIPrincipal> requestingPrincipal = + GetRequestingPrincipal(aOpenArgs); + + nsCOMPtr<nsILoadContext> loadContext; + const char* error = CreateChannelLoadContext( + aBrowser, Manager(), aSerialized, requestingPrincipal, loadContext); + if (error) { + printf_stderr( + "NeckoParent::AllocPHttpChannelParent: " + "FATAL error: %s: KILLING CHILD PROCESS\n", + error); + return nullptr; + } + PBOverrideStatus overrideStatus = + PBOverrideStatusFromLoadContext(aSerialized); + RefPtr<HttpChannelParent> p = new HttpChannelParent( + BrowserParent::GetFrom(aBrowser), loadContext, overrideStatus); + return p.forget(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvPHttpChannelConstructor( + PHttpChannelParent* aActor, PBrowserParent* aBrowser, + const SerializedLoadContext& aSerialized, + const HttpChannelCreationArgs& aOpenArgs) { + HttpChannelParent* p = static_cast<HttpChannelParent*>(aActor); + if (!p->Init(aOpenArgs)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +PStunAddrsRequestParent* NeckoParent::AllocPStunAddrsRequestParent() { +#ifdef MOZ_WEBRTC + StunAddrsRequestParent* p = new StunAddrsRequestParent(); + p->AddRef(); + return p; +#else + return nullptr; +#endif +} + +bool NeckoParent::DeallocPStunAddrsRequestParent( + PStunAddrsRequestParent* aActor) { +#ifdef MOZ_WEBRTC + StunAddrsRequestParent* p = static_cast<StunAddrsRequestParent*>(aActor); + p->Release(); +#endif + return true; +} + +PWebrtcTCPSocketParent* NeckoParent::AllocPWebrtcTCPSocketParent( + const Maybe<TabId>& aTabId) { +#ifdef MOZ_WEBRTC + WebrtcTCPSocketParent* parent = new WebrtcTCPSocketParent(aTabId); + parent->AddRef(); + return parent; +#else + return nullptr; +#endif +} + +bool NeckoParent::DeallocPWebrtcTCPSocketParent( + PWebrtcTCPSocketParent* aActor) { +#ifdef MOZ_WEBRTC + WebrtcTCPSocketParent* parent = static_cast<WebrtcTCPSocketParent*>(aActor); + parent->Release(); +#endif + return true; +} + +PAltDataOutputStreamParent* NeckoParent::AllocPAltDataOutputStreamParent( + const nsACString& type, const int64_t& predictedSize, + PHttpChannelParent* channel) { + HttpChannelParent* chan = static_cast<HttpChannelParent*>(channel); + nsCOMPtr<nsIAsyncOutputStream> stream; + nsresult rv = chan->OpenAlternativeOutputStream(type, predictedSize, + getter_AddRefs(stream)); + AltDataOutputStreamParent* parent = new AltDataOutputStreamParent(stream); + parent->AddRef(); + // If the return value was not NS_OK, the error code will be sent + // asynchronously to the child, after receiving the first message. + parent->SetError(rv); + return parent; +} + +bool NeckoParent::DeallocPAltDataOutputStreamParent( + PAltDataOutputStreamParent* aActor) { + AltDataOutputStreamParent* parent = + static_cast<AltDataOutputStreamParent*>(aActor); + parent->Release(); + return true; +} + +already_AddRefed<PDocumentChannelParent> +NeckoParent::AllocPDocumentChannelParent( + const dom::MaybeDiscarded<dom::BrowsingContext>& aContext, + const DocumentChannelCreationArgs& args) { + RefPtr<DocumentChannelParent> p = new DocumentChannelParent(); + return p.forget(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvPDocumentChannelConstructor( + PDocumentChannelParent* aActor, + const dom::MaybeDiscarded<dom::BrowsingContext>& aContext, + const DocumentChannelCreationArgs& aArgs) { + DocumentChannelParent* p = static_cast<DocumentChannelParent*>(aActor); + + if (aContext.IsNullOrDiscarded()) { + Unused << p->SendFailedAsyncOpen(NS_ERROR_FAILURE); + return IPC_OK(); + } + + if (!p->Init(aContext.get_canonical(), aArgs)) { + return IPC_FAIL(this, "Couldn't initialize DocumentChannel"); + } + + return IPC_OK(); +} + +PCookieServiceParent* NeckoParent::AllocPCookieServiceParent() { + return new CookieServiceParent(); +} + +bool NeckoParent::DeallocPCookieServiceParent(PCookieServiceParent* cs) { + delete cs; + return true; +} + +PWebSocketParent* NeckoParent::AllocPWebSocketParent( + PBrowserParent* browser, const SerializedLoadContext& serialized, + const uint32_t& aSerial) { + nsCOMPtr<nsILoadContext> loadContext; + const char* error = CreateChannelLoadContext(browser, Manager(), serialized, + nullptr, loadContext); + if (error) { + printf_stderr( + "NeckoParent::AllocPWebSocketParent: " + "FATAL error: %s: KILLING CHILD PROCESS\n", + error); + return nullptr; + } + + RefPtr<BrowserParent> browserParent = BrowserParent::GetFrom(browser); + PBOverrideStatus overrideStatus = PBOverrideStatusFromLoadContext(serialized); + WebSocketChannelParent* p = new WebSocketChannelParent( + browserParent, loadContext, overrideStatus, aSerial); + p->AddRef(); + return p; +} + +bool NeckoParent::DeallocPWebSocketParent(PWebSocketParent* actor) { + WebSocketChannelParent* p = static_cast<WebSocketChannelParent*>(actor); + p->Release(); + return true; +} + +PWebSocketEventListenerParent* NeckoParent::AllocPWebSocketEventListenerParent( + const uint64_t& aInnerWindowID) { + RefPtr<WebSocketEventListenerParent> c = + new WebSocketEventListenerParent(aInnerWindowID); + return c.forget().take(); +} + +bool NeckoParent::DeallocPWebSocketEventListenerParent( + PWebSocketEventListenerParent* aActor) { + RefPtr<WebSocketEventListenerParent> c = + dont_AddRef(static_cast<WebSocketEventListenerParent*>(aActor)); + MOZ_ASSERT(c); + return true; +} + +already_AddRefed<PDataChannelParent> NeckoParent::AllocPDataChannelParent( + const uint32_t& channelId) { + RefPtr<DataChannelParent> p = new DataChannelParent(); + return p.forget(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvPDataChannelConstructor( + PDataChannelParent* actor, const uint32_t& channelId) { + DataChannelParent* p = static_cast<DataChannelParent*>(actor); + DebugOnly<bool> rv = p->Init(channelId); + MOZ_ASSERT(rv); + return IPC_OK(); +} + +#ifdef MOZ_WIDGET_GTK +static already_AddRefed<nsIPrincipal> GetRequestingPrincipal( + const GIOChannelCreationArgs& aArgs) { + if (aArgs.type() != GIOChannelCreationArgs::TGIOChannelOpenArgs) { + return nullptr; + } + + const GIOChannelOpenArgs& args = aArgs.get_GIOChannelOpenArgs(); + return GetRequestingPrincipal(args.loadInfo()); +} + +PGIOChannelParent* NeckoParent::AllocPGIOChannelParent( + PBrowserParent* aBrowser, const SerializedLoadContext& aSerialized, + const GIOChannelCreationArgs& aOpenArgs) { + nsCOMPtr<nsIPrincipal> requestingPrincipal = + GetRequestingPrincipal(aOpenArgs); + + nsCOMPtr<nsILoadContext> loadContext; + const char* error = CreateChannelLoadContext( + aBrowser, Manager(), aSerialized, requestingPrincipal, loadContext); + if (error) { + printf_stderr( + "NeckoParent::AllocPGIOChannelParent: " + "FATAL error: %s: KILLING CHILD PROCESS\n", + error); + return nullptr; + } + PBOverrideStatus overrideStatus = + PBOverrideStatusFromLoadContext(aSerialized); + GIOChannelParent* p = new GIOChannelParent(BrowserParent::GetFrom(aBrowser), + loadContext, overrideStatus); + p->AddRef(); + return p; +} + +bool NeckoParent::DeallocPGIOChannelParent(PGIOChannelParent* channel) { + GIOChannelParent* p = static_cast<GIOChannelParent*>(channel); + p->Release(); + return true; +} + +mozilla::ipc::IPCResult NeckoParent::RecvPGIOChannelConstructor( + PGIOChannelParent* actor, PBrowserParent* aBrowser, + const SerializedLoadContext& aSerialized, + const GIOChannelCreationArgs& aOpenArgs) { + GIOChannelParent* p = static_cast<GIOChannelParent*>(actor); + DebugOnly<bool> rv = p->Init(aOpenArgs); + MOZ_ASSERT(rv); + return IPC_OK(); +} +#endif + +PSimpleChannelParent* NeckoParent::AllocPSimpleChannelParent( + const uint32_t& channelId) { + RefPtr<SimpleChannelParent> p = new SimpleChannelParent(); + return p.forget().take(); +} + +bool NeckoParent::DeallocPSimpleChannelParent(PSimpleChannelParent* actor) { + RefPtr<SimpleChannelParent> p = + dont_AddRef(actor).downcast<SimpleChannelParent>(); + return true; +} + +mozilla::ipc::IPCResult NeckoParent::RecvPSimpleChannelConstructor( + PSimpleChannelParent* actor, const uint32_t& channelId) { + SimpleChannelParent* p = static_cast<SimpleChannelParent*>(actor); + MOZ_ALWAYS_TRUE(p->Init(channelId)); + return IPC_OK(); +} + +already_AddRefed<PFileChannelParent> NeckoParent::AllocPFileChannelParent( + const uint32_t& channelId) { + RefPtr<FileChannelParent> p = new FileChannelParent(); + return p.forget(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvPFileChannelConstructor( + PFileChannelParent* actor, const uint32_t& channelId) { + FileChannelParent* p = static_cast<FileChannelParent*>(actor); + DebugOnly<bool> rv = p->Init(channelId); + MOZ_ASSERT(rv); + return IPC_OK(); +} + +PTCPSocketParent* NeckoParent::AllocPTCPSocketParent( + const nsAString& /* host */, const uint16_t& /* port */) { + // We actually don't need host/port to construct a TCPSocketParent since + // TCPSocketParent will maintain an internal nsIDOMTCPSocket instance which + // can be delegated to get the host/port. + TCPSocketParent* p = new TCPSocketParent(); + p->AddIPDLReference(); + return p; +} + +bool NeckoParent::DeallocPTCPSocketParent(PTCPSocketParent* actor) { + TCPSocketParent* p = static_cast<TCPSocketParent*>(actor); + p->ReleaseIPDLReference(); + return true; +} + +PTCPServerSocketParent* NeckoParent::AllocPTCPServerSocketParent( + const uint16_t& aLocalPort, const uint16_t& aBacklog, + const bool& aUseArrayBuffers) { + TCPServerSocketParent* p = + new TCPServerSocketParent(this, aLocalPort, aBacklog, aUseArrayBuffers); + p->AddIPDLReference(); + return p; +} + +mozilla::ipc::IPCResult NeckoParent::RecvPTCPServerSocketConstructor( + PTCPServerSocketParent* aActor, const uint16_t& aLocalPort, + const uint16_t& aBacklog, const bool& aUseArrayBuffers) { + static_cast<TCPServerSocketParent*>(aActor)->Init(); + return IPC_OK(); +} + +bool NeckoParent::DeallocPTCPServerSocketParent(PTCPServerSocketParent* actor) { + TCPServerSocketParent* p = static_cast<TCPServerSocketParent*>(actor); + p->ReleaseIPDLReference(); + return true; +} + +PUDPSocketParent* NeckoParent::AllocPUDPSocketParent( + nsIPrincipal* /* unused */, const nsACString& /* unused */) { + RefPtr<UDPSocketParent> p = new UDPSocketParent(this); + + return p.forget().take(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvPUDPSocketConstructor( + PUDPSocketParent* aActor, nsIPrincipal* aPrincipal, + const nsACString& aFilter) { + if (!static_cast<UDPSocketParent*>(aActor)->Init(aPrincipal, aFilter)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +bool NeckoParent::DeallocPUDPSocketParent(PUDPSocketParent* actor) { + UDPSocketParent* p = static_cast<UDPSocketParent*>(actor); + p->Release(); + return true; +} + +already_AddRefed<PDNSRequestParent> NeckoParent::AllocPDNSRequestParent( + const nsACString& aHost, const nsACString& aTrrServer, const int32_t& aPort, + const uint16_t& aType, const OriginAttributes& aOriginAttributes, + const nsIDNSService::DNSFlags& aFlags) { + RefPtr<DNSRequestHandler> handler = new DNSRequestHandler(); + RefPtr<DNSRequestParent> actor = new DNSRequestParent(handler); + return actor.forget(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvPDNSRequestConstructor( + PDNSRequestParent* aActor, const nsACString& aHost, + const nsACString& aTrrServer, const int32_t& aPort, const uint16_t& aType, + const OriginAttributes& aOriginAttributes, + const nsIDNSService::DNSFlags& aFlags) { + RefPtr<DNSRequestParent> actor = static_cast<DNSRequestParent*>(aActor); + RefPtr<DNSRequestHandler> handler = + actor->GetDNSRequest()->AsDNSRequestHandler(); + handler->DoAsyncResolve(aHost, aTrrServer, aPort, aType, aOriginAttributes, + aFlags); + return IPC_OK(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvSpeculativeConnect( + nsIURI* aURI, nsIPrincipal* aPrincipal, + Maybe<OriginAttributes>&& aOriginAttributes, const bool& aAnonymous) { + nsCOMPtr<nsISpeculativeConnect> speculator(gIOService); + nsCOMPtr<nsIPrincipal> principal(aPrincipal); + if (!aURI) { + return IPC_FAIL(this, "aURI must not be null"); + } + if (aURI && speculator) { + if (aOriginAttributes) { + speculator->SpeculativeConnectWithOriginAttributesNative( + aURI, std::move(aOriginAttributes.ref()), nullptr, aAnonymous); + } else { + speculator->SpeculativeConnect(aURI, principal, nullptr, aAnonymous); + } + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvHTMLDNSPrefetch( + const nsAString& hostname, const bool& isHttps, + const OriginAttributes& aOriginAttributes, + const nsIDNSService::DNSFlags& flags) { + dom::HTMLDNSPrefetch::Prefetch(hostname, isHttps, aOriginAttributes, flags); + return IPC_OK(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvCancelHTMLDNSPrefetch( + const nsAString& hostname, const bool& isHttps, + const OriginAttributes& aOriginAttributes, + const nsIDNSService::DNSFlags& flags, const nsresult& reason) { + dom::HTMLDNSPrefetch::CancelPrefetch(hostname, isHttps, aOriginAttributes, + flags, reason); + return IPC_OK(); +} + +PTransportProviderParent* NeckoParent::AllocPTransportProviderParent() { + RefPtr<TransportProviderParent> res = new TransportProviderParent(); + return res.forget().take(); +} + +bool NeckoParent::DeallocPTransportProviderParent( + PTransportProviderParent* aActor) { + RefPtr<TransportProviderParent> provider = + dont_AddRef(static_cast<TransportProviderParent*>(aActor)); + return true; +} + +/* Predictor Messages */ +mozilla::ipc::IPCResult NeckoParent::RecvPredPredict( + nsIURI* aTargetURI, nsIURI* aSourceURI, const uint32_t& aReason, + const OriginAttributes& aOriginAttributes, const bool& hasVerifier) { + // Get the current predictor + nsresult rv = NS_OK; + nsCOMPtr<nsINetworkPredictor> predictor = + do_GetService("@mozilla.org/network/predictor;1", &rv); + NS_ENSURE_SUCCESS(rv, IPC_OK()); + + nsCOMPtr<nsINetworkPredictorVerifier> verifier; + if (hasVerifier) { + verifier = do_QueryInterface(predictor); + } + predictor->PredictNative(aTargetURI, aSourceURI, aReason, aOriginAttributes, + verifier); + return IPC_OK(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvPredLearn( + nsIURI* aTargetURI, nsIURI* aSourceURI, const uint32_t& aReason, + const OriginAttributes& aOriginAttributes) { + // Get the current predictor + nsresult rv = NS_OK; + nsCOMPtr<nsINetworkPredictor> predictor = + do_GetService("@mozilla.org/network/predictor;1", &rv); + NS_ENSURE_SUCCESS(rv, IPC_OK()); + + predictor->LearnNative(aTargetURI, aSourceURI, aReason, aOriginAttributes); + return IPC_OK(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvPredReset() { + // Get the current predictor + nsresult rv = NS_OK; + nsCOMPtr<nsINetworkPredictor> predictor = + do_GetService("@mozilla.org/network/predictor;1", &rv); + NS_ENSURE_SUCCESS(rv, IPC_OK()); + + predictor->Reset(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvRequestContextLoadBegin( + const uint64_t& rcid) { + nsCOMPtr<nsIRequestContextService> rcsvc = + RequestContextService::GetOrCreate(); + if (!rcsvc) { + return IPC_OK(); + } + nsCOMPtr<nsIRequestContext> rc; + rcsvc->GetRequestContext(rcid, getter_AddRefs(rc)); + if (rc) { + rc->BeginLoad(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvRequestContextAfterDOMContentLoaded( + const uint64_t& rcid) { + nsCOMPtr<nsIRequestContextService> rcsvc = + RequestContextService::GetOrCreate(); + if (!rcsvc) { + return IPC_OK(); + } + nsCOMPtr<nsIRequestContext> rc; + rcsvc->GetRequestContext(rcid, getter_AddRefs(rc)); + if (rc) { + rc->DOMContentLoaded(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvRemoveRequestContext( + const uint64_t& rcid) { + nsCOMPtr<nsIRequestContextService> rcsvc = + RequestContextService::GetOrCreate(); + if (!rcsvc) { + return IPC_OK(); + } + + rcsvc->RemoveRequestContext(rcid); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvGetExtensionStream( + nsIURI* aURI, GetExtensionStreamResolver&& aResolve) { + if (!aURI) { + return IPC_FAIL(this, "aURI must not be null"); + } + + RefPtr<ExtensionProtocolHandler> ph(ExtensionProtocolHandler::GetSingleton()); + MOZ_ASSERT(ph); + + // Ask the ExtensionProtocolHandler to give us a new input stream for + // this URI. The request comes from an ExtensionProtocolHandler in the + // child process, but is not guaranteed to be a valid moz-extension URI, + // and not guaranteed to represent a resource that the child should be + // allowed to access. The ExtensionProtocolHandler is responsible for + // validating the request. Specifically, only URI's for local files that + // an extension is allowed to access via moz-extension URI's should be + // accepted. + nsCOMPtr<nsIInputStream> inputStream; + bool terminateSender = true; + auto inputStreamOrReason = ph->NewStream(aURI, &terminateSender); + if (inputStreamOrReason.isOk()) { + inputStream = inputStreamOrReason.unwrap(); + } + + // If NewStream failed, we send back an invalid stream to the child so + // it can handle the error. MozPromise rejection is reserved for channel + // errors/disconnects. + aResolve(inputStream); + + if (terminateSender) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvGetExtensionFD( + nsIURI* aURI, GetExtensionFDResolver&& aResolve) { + if (!aURI) { + return IPC_FAIL(this, "aURI must not be null"); + } + + RefPtr<ExtensionProtocolHandler> ph(ExtensionProtocolHandler::GetSingleton()); + MOZ_ASSERT(ph); + + // Ask the ExtensionProtocolHandler to give us a new input stream for + // this URI. The request comes from an ExtensionProtocolHandler in the + // child process, but is not guaranteed to be a valid moz-extension URI, + // and not guaranteed to represent a resource that the child should be + // allowed to access. The ExtensionProtocolHandler is responsible for + // validating the request. Specifically, only URI's for local files that + // an extension is allowed to access via moz-extension URI's should be + // accepted. + bool terminateSender = true; + auto result = ph->NewFD(aURI, &terminateSender, aResolve); + + if (result.isErr() && terminateSender) { + return IPC_FAIL_NO_REASON(this); + } + + if (result.isErr()) { + FileDescriptor invalidFD; + aResolve(invalidFD); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvInitSocketProcessBridge( + InitSocketProcessBridgeResolver&& aResolver) { + MOZ_ASSERT(NS_IsMainThread()); + + // Initing the socket process bridge must be async here in order to + // wait for the socket process launch before executing. + auto task = [self = RefPtr{this}, resolver = std::move(aResolver)]() { + // The content process might be already destroyed. + if (!self->CanSend()) { + return; + } + + Endpoint<PSocketProcessBridgeChild> invalidEndpoint; + if (NS_WARN_IF(self->mSocketProcessBridgeInited)) { + resolver(std::move(invalidEndpoint)); + return; + } + + SocketProcessParent* parent = SocketProcessParent::GetSingleton(); + if (NS_WARN_IF(!parent)) { + resolver(std::move(invalidEndpoint)); + return; + } + + Endpoint<PSocketProcessBridgeParent> parentEndpoint; + Endpoint<PSocketProcessBridgeChild> childEndpoint; + if (NS_WARN_IF(NS_FAILED(PSocketProcessBridge::CreateEndpoints( + parent->OtherPid(), self->Manager()->OtherPid(), &parentEndpoint, + &childEndpoint)))) { + resolver(std::move(invalidEndpoint)); + return; + } + + if (NS_WARN_IF(!parent->SendInitSocketProcessBridgeParent( + self->Manager()->OtherPid(), std::move(parentEndpoint)))) { + resolver(std::move(invalidEndpoint)); + return; + } + + resolver(std::move(childEndpoint)); + self->mSocketProcessBridgeInited = true; + }; + gIOService->CallOrWaitForSocketProcess(std::move(task)); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvResetSocketProcessBridge() { + // SendResetSocketProcessBridge is called from + // SocketProcessBridgeChild::ActorDestroy if the socket process + // crashes. This is necessary in order to properly initialize the + // restarted socket process. + mSocketProcessBridgeInited = false; + return IPC_OK(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvEnsureHSTSData( + EnsureHSTSDataResolver&& aResolver) { + auto callback = [aResolver{std::move(aResolver)}](bool aResult) { + aResolver(aResult); + }; + RefPtr<HSTSDataCallbackWrapper> wrapper = + new HSTSDataCallbackWrapper(std::move(callback)); + gHttpHandler->EnsureHSTSDataReadyNative(wrapper); + return IPC_OK(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvGetPageThumbStream( + nsIURI* aURI, const LoadInfoArgs& aLoadInfoArgs, + GetPageThumbStreamResolver&& aResolver) { + // Only the privileged about content process is allowed to access + // things over the moz-page-thumb protocol. Any other content process + // that tries to send this should have been blocked via the + // ScriptSecurityManager, but if somehow the process has been tricked into + // sending this message, we send IPC_FAIL in order to crash that + // likely-compromised content process. + if (static_cast<ContentParent*>(Manager())->GetRemoteType() != + PRIVILEGEDABOUT_REMOTE_TYPE) { + return IPC_FAIL(this, "Wrong process type"); + } + + RefPtr<PageThumbProtocolHandler> ph(PageThumbProtocolHandler::GetSingleton()); + MOZ_ASSERT(ph); + + // Ask the PageThumbProtocolHandler to give us a new input stream for + // this URI. The request comes from a PageThumbProtocolHandler in the + // child process, but is not guaranteed to be a valid moz-page-thumb URI, + // and not guaranteed to represent a resource that the child should be + // allowed to access. The PageThumbProtocolHandler is responsible for + // validating the request. + nsCOMPtr<nsIInputStream> inputStream; + bool terminateSender = true; + auto inputStreamPromise = ph->NewStream(aURI, &terminateSender); + + if (terminateSender) { + return IPC_FAIL(this, "Malformed moz-page-thumb request"); + } + + inputStreamPromise->Then( + GetMainThreadSerialEventTarget(), __func__, + [aResolver](const RemoteStreamInfo& aInfo) { aResolver(Some(aInfo)); }, + [aResolver](nsresult aRv) { + // If NewStream failed, we send back an invalid stream to the child so + // it can handle the error. MozPromise rejection is reserved for channel + // errors/disconnects. + Unused << NS_WARN_IF(NS_FAILED(aRv)); + aResolver(Nothing()); + }); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvGetPageIconStream( + nsIURI* aURI, const LoadInfoArgs& aLoadInfoArgs, + GetPageIconStreamResolver&& aResolver) { +#ifdef MOZ_PLACES + const nsACString& remoteType = + ContentParent::Cast(Manager())->GetRemoteType(); + + // Only the privileged about content process is allowed to access + // things over the page-icon protocol. Any other content process + // that tries to send this should have been blocked via the + // ScriptSecurityManager, but if somehow the process has been tricked into + // sending this message, we send IPC_FAIL in order to crash that + // likely-compromised content process. + if (remoteType != PRIVILEGEDABOUT_REMOTE_TYPE) { + return IPC_FAIL(this, "Wrong process type"); + } + + nsCOMPtr<nsILoadInfo> loadInfo; + nsresult rv = mozilla::ipc::LoadInfoArgsToLoadInfo(aLoadInfoArgs, remoteType, + getter_AddRefs(loadInfo)); + if (NS_FAILED(rv)) { + return IPC_FAIL(this, "Page-icon request must include loadInfo"); + } + + RefPtr<PageIconProtocolHandler> ph(PageIconProtocolHandler::GetSingleton()); + MOZ_ASSERT(ph); + + nsCOMPtr<nsIInputStream> inputStream; + bool terminateSender = true; + auto inputStreamPromise = ph->NewStream(aURI, loadInfo, &terminateSender); + + if (terminateSender) { + return IPC_FAIL(this, "Malformed page-icon request"); + } + + inputStreamPromise->Then( + GetMainThreadSerialEventTarget(), __func__, + [aResolver](const RemoteStreamInfo& aInfo) { aResolver(Some(aInfo)); }, + [aResolver](nsresult aRv) { + // If NewStream failed, we send back an invalid stream to the child so + // it can handle the error. MozPromise rejection is reserved for channel + // errors/disconnects. + Unused << NS_WARN_IF(NS_FAILED(aRv)); + aResolver(Nothing()); + }); + + return IPC_OK(); +#else + return IPC_FAIL(this, "page-icon: protocol unavailable"); +#endif +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/NeckoParent.h b/netwerk/ipc/NeckoParent.h new file mode 100644 index 0000000000..9c7ffa4636 --- /dev/null +++ b/netwerk/ipc/NeckoParent.h @@ -0,0 +1,217 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "mozilla/BasePrincipal.h" +#include "mozilla/net/PNeckoParent.h" +#include "mozilla/net/NeckoCommon.h" +#include "nsIAuthPrompt2.h" +#include "nsINetworkPredictor.h" +#include "nsNetUtil.h" + +#ifndef mozilla_net_NeckoParent_h +# define mozilla_net_NeckoParent_h + +namespace mozilla { +namespace net { + +// Used to override channel Private Browsing status if needed. +enum PBOverrideStatus { + kPBOverride_Unset = 0, + kPBOverride_Private, + kPBOverride_NotPrivate +}; + +// Header file contents +class NeckoParent : public PNeckoParent { + friend class PNeckoParent; + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NeckoParent, override) + + NeckoParent(); + + [[nodiscard]] static const char* GetValidatedOriginAttributes( + const SerializedLoadContext& aSerialized, PContentParent* aBrowser, + nsIPrincipal* aRequestingPrincipal, mozilla::OriginAttributes& aAttrs); + + /* + * Creates LoadContext for parent-side of an e10s channel. + * + * PContentParent corresponds to the process that is requesting the load. + * + * Returns null if successful, or an error string if failed. + */ + [[nodiscard]] static const char* CreateChannelLoadContext( + PBrowserParent* aBrowser, PContentParent* aContent, + const SerializedLoadContext& aSerialized, + nsIPrincipal* aRequestingPrincipal, nsCOMPtr<nsILoadContext>& aResult); + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + PCookieServiceParent* AllocPCookieServiceParent(); + virtual mozilla::ipc::IPCResult RecvPCookieServiceConstructor( + PCookieServiceParent* aActor) override { + return PNeckoParent::RecvPCookieServiceConstructor(aActor); + } + + protected: + virtual ~NeckoParent() = default; + + bool mSocketProcessBridgeInited; + + already_AddRefed<PHttpChannelParent> AllocPHttpChannelParent( + PBrowserParent*, const SerializedLoadContext&, + const HttpChannelCreationArgs& aOpenArgs); + virtual mozilla::ipc::IPCResult RecvPHttpChannelConstructor( + PHttpChannelParent* aActor, PBrowserParent* aBrowser, + const SerializedLoadContext& aSerialized, + const HttpChannelCreationArgs& aOpenArgs) override; + + PStunAddrsRequestParent* AllocPStunAddrsRequestParent(); + bool DeallocPStunAddrsRequestParent(PStunAddrsRequestParent* aActor); + + PWebrtcTCPSocketParent* AllocPWebrtcTCPSocketParent( + const Maybe<TabId>& aTabId); + bool DeallocPWebrtcTCPSocketParent(PWebrtcTCPSocketParent* aActor); + + PAltDataOutputStreamParent* AllocPAltDataOutputStreamParent( + const nsACString& type, const int64_t& predictedSize, + PHttpChannelParent* channel); + bool DeallocPAltDataOutputStreamParent(PAltDataOutputStreamParent* aActor); + + bool DeallocPCookieServiceParent(PCookieServiceParent*); + PWebSocketParent* AllocPWebSocketParent( + PBrowserParent* browser, const SerializedLoadContext& aSerialized, + const uint32_t& aSerial); + bool DeallocPWebSocketParent(PWebSocketParent*); + PTCPSocketParent* AllocPTCPSocketParent(const nsAString& host, + const uint16_t& port); + + already_AddRefed<PDocumentChannelParent> AllocPDocumentChannelParent( + const dom::MaybeDiscarded<dom::BrowsingContext>& aContext, + const DocumentChannelCreationArgs& args); + virtual mozilla::ipc::IPCResult RecvPDocumentChannelConstructor( + PDocumentChannelParent* aActor, + const dom::MaybeDiscarded<dom::BrowsingContext>& aContext, + const DocumentChannelCreationArgs& aArgs) override; + bool DeallocPDocumentChannelParent(PDocumentChannelParent* channel); + + bool DeallocPTCPSocketParent(PTCPSocketParent*); + PTCPServerSocketParent* AllocPTCPServerSocketParent( + const uint16_t& aLocalPort, const uint16_t& aBacklog, + const bool& aUseArrayBuffers); + virtual mozilla::ipc::IPCResult RecvPTCPServerSocketConstructor( + PTCPServerSocketParent*, const uint16_t& aLocalPort, + const uint16_t& aBacklog, const bool& aUseArrayBuffers) override; + bool DeallocPTCPServerSocketParent(PTCPServerSocketParent*); + PUDPSocketParent* AllocPUDPSocketParent(nsIPrincipal* aPrincipal, + const nsACString& aFilter); + virtual mozilla::ipc::IPCResult RecvPUDPSocketConstructor( + PUDPSocketParent*, nsIPrincipal* aPrincipal, + const nsACString& aFilter) override; + bool DeallocPUDPSocketParent(PUDPSocketParent*); + already_AddRefed<PDNSRequestParent> AllocPDNSRequestParent( + const nsACString& aHost, const nsACString& aTrrServer, + const int32_t& aPort, const uint16_t& aType, + const OriginAttributes& aOriginAttributes, + const nsIDNSService::DNSFlags& aFlags); + virtual mozilla::ipc::IPCResult RecvPDNSRequestConstructor( + PDNSRequestParent* actor, const nsACString& aHost, + const nsACString& trrServer, const int32_t& aPort, const uint16_t& type, + const OriginAttributes& aOriginAttributes, + const nsIDNSService::DNSFlags& flags) override; + mozilla::ipc::IPCResult RecvSpeculativeConnect( + nsIURI* aURI, nsIPrincipal* aPrincipal, + Maybe<OriginAttributes>&& aOriginAttributes, const bool& aAnonymous); + mozilla::ipc::IPCResult RecvHTMLDNSPrefetch( + const nsAString& hostname, const bool& isHttps, + const OriginAttributes& aOriginAttributes, + const nsIDNSService::DNSFlags& flags); + mozilla::ipc::IPCResult RecvCancelHTMLDNSPrefetch( + const nsAString& hostname, const bool& isHttps, + const OriginAttributes& aOriginAttributes, + const nsIDNSService::DNSFlags& flags, const nsresult& reason); + PWebSocketEventListenerParent* AllocPWebSocketEventListenerParent( + const uint64_t& aInnerWindowID); + bool DeallocPWebSocketEventListenerParent(PWebSocketEventListenerParent*); + + already_AddRefed<PDataChannelParent> AllocPDataChannelParent( + const uint32_t& channelId); + + virtual mozilla::ipc::IPCResult RecvPDataChannelConstructor( + PDataChannelParent* aActor, const uint32_t& channelId) override; +# ifdef MOZ_WIDGET_GTK + PGIOChannelParent* AllocPGIOChannelParent( + PBrowserParent* aBrowser, const SerializedLoadContext& aSerialized, + const GIOChannelCreationArgs& aOpenArgs); + bool DeallocPGIOChannelParent(PGIOChannelParent* channel); + + virtual mozilla::ipc::IPCResult RecvPGIOChannelConstructor( + PGIOChannelParent* aActor, PBrowserParent* aBrowser, + const SerializedLoadContext& aSerialized, + const GIOChannelCreationArgs& aOpenArgs) override; +# endif + PSimpleChannelParent* AllocPSimpleChannelParent(const uint32_t& channelId); + bool DeallocPSimpleChannelParent(PSimpleChannelParent* actor); + + virtual mozilla::ipc::IPCResult RecvPSimpleChannelConstructor( + PSimpleChannelParent* aActor, const uint32_t& channelId) override; + + already_AddRefed<PFileChannelParent> AllocPFileChannelParent( + const uint32_t& channelId); + + virtual mozilla::ipc::IPCResult RecvPFileChannelConstructor( + PFileChannelParent* aActor, const uint32_t& channelId) override; + + PTransportProviderParent* AllocPTransportProviderParent(); + bool DeallocPTransportProviderParent(PTransportProviderParent* aActor); + + /* Predictor Messages */ + mozilla::ipc::IPCResult RecvPredPredict( + nsIURI* aTargetURI, nsIURI* aSourceURI, + const PredictorPredictReason& aReason, + const OriginAttributes& aOriginAttributes, const bool& hasVerifier); + + mozilla::ipc::IPCResult RecvPredLearn( + nsIURI* aTargetURI, nsIURI* aSourceURI, + const PredictorPredictReason& aReason, + const OriginAttributes& aOriginAttributes); + mozilla::ipc::IPCResult RecvPredReset(); + + mozilla::ipc::IPCResult RecvRequestContextLoadBegin(const uint64_t& rcid); + mozilla::ipc::IPCResult RecvRequestContextAfterDOMContentLoaded( + const uint64_t& rcid); + mozilla::ipc::IPCResult RecvRemoveRequestContext(const uint64_t& rcid); + + /* WebExtensions */ + mozilla::ipc::IPCResult RecvGetExtensionStream( + nsIURI* aURI, GetExtensionStreamResolver&& aResolve); + + mozilla::ipc::IPCResult RecvGetExtensionFD(nsIURI* aURI, + GetExtensionFDResolver&& aResolve); + + /* Page thumbnails remote resource loading */ + mozilla::ipc::IPCResult RecvGetPageThumbStream( + nsIURI* aURI, const LoadInfoArgs& aLoadInfoArgs, + GetPageThumbStreamResolver&& aResolve); + + /* Page icon remote resource loading */ + mozilla::ipc::IPCResult RecvGetPageIconStream( + nsIURI* aURI, const LoadInfoArgs& aLoadInfoArgs, + GetPageIconStreamResolver&& aResolve); + + mozilla::ipc::IPCResult RecvInitSocketProcessBridge( + InitSocketProcessBridgeResolver&& aResolver); + mozilla::ipc::IPCResult RecvResetSocketProcessBridge(); + + mozilla::ipc::IPCResult RecvEnsureHSTSData( + EnsureHSTSDataResolver&& aResolver); +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_NeckoParent_h diff --git a/netwerk/ipc/NeckoTargetHolder.cpp b/netwerk/ipc/NeckoTargetHolder.cpp new file mode 100644 index 0000000000..90d80ce7d5 --- /dev/null +++ b/netwerk/ipc/NeckoTargetHolder.cpp @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "NeckoTargetHolder.h" + +#include "nsContentUtils.h" + +namespace mozilla { +namespace net { + +already_AddRefed<nsISerialEventTarget> NeckoTargetHolder::GetNeckoTarget() { + nsCOMPtr<nsISerialEventTarget> target = mNeckoTarget; + + if (!target) { + target = GetMainThreadSerialEventTarget(); + } + return target.forget(); +} + +nsresult NeckoTargetHolder::Dispatch(already_AddRefed<nsIRunnable>&& aRunnable, + uint32_t aDispatchFlags) { + if (mNeckoTarget) { + return mNeckoTarget->Dispatch(std::move(aRunnable), aDispatchFlags); + } + + nsCOMPtr<nsISerialEventTarget> mainThreadTarget = + GetMainThreadSerialEventTarget(); + MOZ_ASSERT(mainThreadTarget); + + return mainThreadTarget->Dispatch(std::move(aRunnable), aDispatchFlags); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/NeckoTargetHolder.h b/netwerk/ipc/NeckoTargetHolder.h new file mode 100644 index 0000000000..254d6f138f --- /dev/null +++ b/netwerk/ipc/NeckoTargetHolder.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_NeckoTargetHolder_h +#define mozilla_net_NeckoTargetHolder_h + +#include "nsIEventTarget.h" +#include "nsThreadUtils.h" + +namespace mozilla { +namespace net { + +// A helper class that implements GetNeckoTarget(). Basically, all e10s child +// channels should inherit this class in order to get a labeled event target. +class NeckoTargetHolder { + public: + explicit NeckoTargetHolder(nsISerialEventTarget* aNeckoTarget) + : mNeckoTarget(aNeckoTarget) {} + + protected: + virtual ~NeckoTargetHolder() = default; + // Get event target for processing network events. + virtual already_AddRefed<nsISerialEventTarget> GetNeckoTarget(); + // When |mNeckoTarget| is not null, use it to dispatch the runnable. + // Otherwise, dispatch the runnable to the main thread. + nsresult Dispatch(already_AddRefed<nsIRunnable>&& aRunnable, + uint32_t aDispatchFlags = NS_DISPATCH_NORMAL); + + // EventTarget for labeling networking events. + nsCOMPtr<nsISerialEventTarget> mNeckoTarget; +}; + +} // namespace net +} // namespace mozilla + +#endif diff --git a/netwerk/ipc/PDataChannel.ipdl b/netwerk/ipc/PDataChannel.ipdl new file mode 100644 index 0000000000..52d8642ff7 --- /dev/null +++ b/netwerk/ipc/PDataChannel.ipdl @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */ + +/* 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/. */ + +include protocol PNecko; + +namespace mozilla { +namespace net { + +[ChildImpl=virtual] +async protocol PDataChannel +{ + manager PNecko; + +parent: + // Note: channels are opened during construction, so no open method here: + // see PNecko.ipdl + async __delete__(); +}; + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/PDocumentChannel.ipdl b/netwerk/ipc/PDocumentChannel.ipdl new file mode 100644 index 0000000000..1e40052a25 --- /dev/null +++ b/netwerk/ipc/PDocumentChannel.ipdl @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */ + +/* 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/. */ + +include protocol PNecko; +include protocol PStreamFilter; +include InputStreamParams; +include PBackgroundSharedTypes; +include NeckoChannelParams; +include IPCServiceWorkerDescriptor; +include IPCStream; +include DOMTypes; + +include "mozilla/net/NeckoMessageUtils.h"; + +using class mozilla::net::nsHttpHeaderArray from "nsHttpHeaderArray.h"; +using mozilla::net::NetAddr from "mozilla/net/DNS.h"; + +namespace mozilla { +namespace net { + +protocol PDocumentChannel +{ + manager PNecko; + +parent: + + async Cancel(nsresult status, nsCString reason); + + async __delete__(); + +child: + + // Used to cancel child channel if we hit errors during creating and + // AsyncOpen of nsHttpChannel on the parent. + async FailedAsyncOpen(nsresult status); + + // This message is sent to a child that has been redirected to another process. + // As a consequence, it should cleanup the channel listeners and remove the + // request from the loadGroup. + // aStatus must be an error result. + // aLoadGroupReason is used as mStatus when we remove the child channel from + // the loadgroup (but aStatus is passed as the parameter to RemoveRequest). + // We do this so we can remove using NS_BINDING_RETARGETED, but still have the + // channel not be in an error state. + async DisconnectChildListeners(nsresult aStatus, nsresult aLoadGroupReason, bool aContinueNavigating); + + // Triggers replacing this DocumentChannel with a 'real' channel (like PHttpChannel), + // and notifies the listener via a redirect to the new channel. + async RedirectToRealChannel(RedirectToRealChannelArgs args, Endpoint<PStreamFilterParent>[] aEndpoint) + returns (nsresult rv); + + // May only be called on a DocumentChannel created by nsObjectLoadingContent + // for an object or embed element load. + // + // Promotes the load from an object load to a proper document load, and + // returns the `BrowsingContext` which should be used to host the final load. + async UpgradeObjectLoad() returns (MaybeDiscardedBrowsingContext frameContext); +}; + +} // namespace net +} // namespace mozilla + diff --git a/netwerk/ipc/PFileChannel.ipdl b/netwerk/ipc/PFileChannel.ipdl new file mode 100644 index 0000000000..8f86fc81b1 --- /dev/null +++ b/netwerk/ipc/PFileChannel.ipdl @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=2 sts=2 et tw=80 ft=cpp : */ + +/* 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/. */ + +include protocol PNecko; + +namespace mozilla { +namespace net { + +/* Used to facilitate http redirects to file:// - see + * https://bugzilla.mozilla.org/show_bug.cgi?id=1345094 + */ +[ChildImpl=virtual] +async protocol PFileChannel +{ + manager PNecko; + +parent: + // Note: channels are opened during construction, so no open method here: + // see PNecko.ipdl + async __delete__(); +}; + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/PInputChannelThrottleQueue.ipdl b/netwerk/ipc/PInputChannelThrottleQueue.ipdl new file mode 100644 index 0000000000..b95d9cc77f --- /dev/null +++ b/netwerk/ipc/PInputChannelThrottleQueue.ipdl @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */ + +/* 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/. */ + +include protocol PSocketProcess; + +namespace mozilla { +namespace net { + +protocol PInputChannelThrottleQueue +{ + manager PSocketProcess; + +parent: + async RecordRead(uint32_t aBytesRead); + +child: + async __delete__(); +}; + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/PNecko.ipdl b/netwerk/ipc/PNecko.ipdl new file mode 100644 index 0000000000..9eb9b5ca27 --- /dev/null +++ b/netwerk/ipc/PNecko.ipdl @@ -0,0 +1,178 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */ + +/* 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/. */ + +include protocol PContent; +include protocol PHttpChannel; +include protocol PCookieService; +include protocol PBrowser; +#ifdef MOZ_WIDGET_GTK +include protocol PGIOChannel; +#endif +include protocol PWebSocket; +include protocol PWebSocketEventListener; +include protocol PTCPSocket; +include protocol PTCPServerSocket; +include protocol PUDPSocket; +include protocol PDNSRequest; +include protocol PDataChannel; +include protocol PSimpleChannel; +include protocol PTransportProvider; +include protocol PStunAddrsRequest; +include protocol PFileChannel; +include protocol PWebrtcTCPSocket; +include protocol PSocketProcessBridge; +include protocol PDocumentChannel; + +include IPCStream; +include NeckoChannelParams; +include protocol PAltDataOutputStream; + +include "mozilla/dom/PermissionMessageUtils.h"; + +using mozilla::dom::MaybeDiscardedBrowsingContext from "mozilla/dom/BrowsingContext.h"; +using class IPC::SerializedLoadContext from "SerializedLoadContext.h"; +using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h"; +using nsIDNSService::DNSFlags from "nsIDNSService.h"; +[RefCounted] using class nsIInputStream from "mozilla/ipc/IPCStreamUtils.h"; +[RefCounted] using class nsIURI from "mozilla/ipc/URIUtils.h"; +[RefCounted] using class nsIPrincipal from "nsIPrincipal.h"; + + +namespace mozilla { +namespace net { + +//------------------------------------------------------------------- +[NestedUpTo=inside_cpow] sync protocol PNecko +{ + manager PContent; + manages PHttpChannel; + manages PCookieService; + manages PWebSocket; + manages PWebSocketEventListener; + manages PTCPSocket; + manages PTCPServerSocket; + manages PUDPSocket; + manages PDNSRequest; + manages PDataChannel; +#ifdef MOZ_WIDGET_GTK + manages PGIOChannel; +#endif + manages PSimpleChannel; + manages PFileChannel; + manages PTransportProvider; + manages PAltDataOutputStream; + manages PStunAddrsRequest; + manages PWebrtcTCPSocket; + manages PDocumentChannel; + +parent: + async __delete__(); + + [Nested=inside_cpow] async PCookieService(); + async PHttpChannel(nullable PBrowser browser, + SerializedLoadContext loadContext, + HttpChannelCreationArgs args); + + async PWebSocket(nullable PBrowser browser, SerializedLoadContext loadContext, + uint32_t aSerialID); + async PTCPServerSocket(uint16_t localPort, uint16_t backlog, bool useArrayBuffers); + async PUDPSocket(nullable nsIPrincipal principal, nsCString filter); + + async PDNSRequest(nsCString hostName, nsCString trrServer, int32_t port, + uint16_t type, OriginAttributes originAttributes, + DNSFlags flags); + + async PDocumentChannel(MaybeDiscardedBrowsingContext browsingContext, + DocumentChannelCreationArgs args); + + async PWebSocketEventListener(uint64_t aInnerWindowID); + + /* Predictor Methods */ + async PredPredict(nullable nsIURI targetURI, nullable nsIURI sourceURI, + uint32_t reason, OriginAttributes originAttributes, + bool hasVerifier); + async PredLearn(nullable nsIURI targetURI, nullable nsIURI sourceURI, + uint32_t reason, OriginAttributes originAttributes); + async PredReset(); + + async SpeculativeConnect(nullable nsIURI uri, + nullable nsIPrincipal principal, + OriginAttributes? originAttributes, + bool anonymous); + async HTMLDNSPrefetch(nsString hostname, bool isHttps, + OriginAttributes originAttributes, DNSFlags flags); + async CancelHTMLDNSPrefetch(nsString hostname, bool isHttps, + OriginAttributes originAttributes, + DNSFlags flags, nsresult reason); + + /** + * channelId is used to establish a connection between redirect channels in + * the parent and the child when we're redirecting to a data: URI. + */ + async PDataChannel(uint32_t channelId); +#ifdef MOZ_WIDGET_GTK + async PGIOChannel(nullable PBrowser browser, SerializedLoadContext loadContext, GIOChannelCreationArgs args); +#endif + async PSimpleChannel(uint32_t channelId); + async PFileChannel(uint32_t channelId); + + async RequestContextLoadBegin(uint64_t rcid); + async RequestContextAfterDOMContentLoaded(uint64_t rcid); + async RemoveRequestContext(uint64_t rcid); + + async PAltDataOutputStream(nsCString type, int64_t predictedSize, PHttpChannel channel); + + async PStunAddrsRequest(); + + /* tabId is only required for web-proxy support, which isn't always needed */ + async PWebrtcTCPSocket(TabId? tabId); + + /** + * WebExtension-specific remote resource loading + */ + async GetExtensionStream(nullable nsIURI uri) returns (nullable nsIInputStream stream); + async GetExtensionFD(nullable nsIURI uri) returns (FileDescriptor fd); + + async InitSocketProcessBridge() + returns (Endpoint<PSocketProcessBridgeChild> endpoint); + async ResetSocketProcessBridge(); + + async EnsureHSTSData() + returns (bool result); + + /** + * Page thumbnails remote resource loading + */ + async GetPageThumbStream(nullable nsIURI uri, LoadInfoArgs loadInfo) returns (RemoteStreamInfo? info); + async GetPageIconStream(nullable nsIURI uri, LoadInfoArgs loadInfo) returns (RemoteStreamInfo? info); + +child: + /* Predictor Methods */ + async PredOnPredictPrefetch(nullable nsIURI uri, uint32_t httpStatus); + async PredOnPredictPreconnect(nullable nsIURI uri); + async PredOnPredictDNS(nullable nsIURI uri); + + async SpeculativeConnectRequest(); + + // Using medium high priority to deliver this notification possibly sooner than we + // enter poll() on the child process with infinite timeout. + [Priority=mediumhigh] async NetworkChangeNotification(nsCString type); + + async PTransportProvider(); + + async SetTRRDomain(nsCString domain); + +both: + // Actually we need PTCPSocket() for parent. But ipdl disallows us having different + // signatures on parent and child. So when constructing the parent side object, we just + // leave host/port unused. + async PTCPSocket(nsString host, uint16_t port); +}; + + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/PProxyAutoConfig.ipdl b/netwerk/ipc/PProxyAutoConfig.ipdl new file mode 100644 index 0000000000..7018243fa6 --- /dev/null +++ b/netwerk/ipc/PProxyAutoConfig.ipdl @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +namespace mozilla { +namespace net { + +[ChildProc=Socket] +sync protocol PProxyAutoConfig +{ +child: + async ConfigurePAC(nsCString aPACURI, nsCString aPACScriptData, + bool aIncludePath, uint32_t aExtraHeapSize); + async GetProxyForURI(nsCString aTestURI, nsCString aTestHost) + returns (nsresult aStatus, nsCString aResult); +}; + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/PProxyConfigLookup.ipdl b/netwerk/ipc/PProxyConfigLookup.ipdl new file mode 100644 index 0000000000..9aaaf5fbcb --- /dev/null +++ b/netwerk/ipc/PProxyConfigLookup.ipdl @@ -0,0 +1,21 @@ +/* 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/. */ + +include protocol PSocketProcess; + +include NeckoChannelParams; + +namespace mozilla { +namespace net { + +async protocol PProxyConfigLookup +{ + manager PSocketProcess; + +child: + async __delete__(ProxyInfoCloneArgs[] aProxyInfo, nsresult aResult); +}; + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/PSimpleChannel.ipdl b/netwerk/ipc/PSimpleChannel.ipdl new file mode 100644 index 0000000000..cae7fe8f1b --- /dev/null +++ b/netwerk/ipc/PSimpleChannel.ipdl @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */ + +/* 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/. */ + +include protocol PNecko; + +namespace mozilla { +namespace net { + +[ManualDealloc, ChildImpl=virtual] +async protocol PSimpleChannel +{ + manager PNecko; + +parent: + // Note: channels are opened during construction, so no open method here: + // see PNecko.ipdl + async __delete__(); +}; + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/PSocketProcess.ipdl b/netwerk/ipc/PSocketProcess.ipdl new file mode 100644 index 0000000000..ede6397fa4 --- /dev/null +++ b/netwerk/ipc/PSocketProcess.ipdl @@ -0,0 +1,222 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +include protocol PDNSRequest; +include protocol PSocketProcessBridge; +include protocol PProfiler; +include protocol PWebrtcTCPSocket; +include protocol PHttpTransaction; +include protocol PHttpConnectionMgr; +include protocol PInputChannelThrottleQueue; +include protocol PAltService; +include protocol PAltSvcTransaction; +include protocol PTRRService; +include protocol PProxyConfigLookup; +include protocol PNativeDNSResolverOverride; +include protocol PProxyAutoConfig; +include protocol PSocketProcessBackground; + +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) +include protocol PSandboxTesting; +#endif + +include MemoryReportTypes; +include NeckoChannelParams; +include PrefsTypes; + +include "mozilla/ipc/ByteBufUtils.h"; + +using mozilla::dom::NativeThreadId from "mozilla/dom/NativeThreadId.h"; +using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h"; +using mozilla::Telemetry::HistogramAccumulation from "mozilla/TelemetryComms.h"; +using mozilla::Telemetry::KeyedHistogramAccumulation from "mozilla/TelemetryComms.h"; +using mozilla::Telemetry::ScalarAction from "mozilla/TelemetryComms.h"; +using mozilla::Telemetry::KeyedScalarAction from "mozilla/TelemetryComms.h"; +using mozilla::Telemetry::ChildEventData from "mozilla/TelemetryComms.h"; +using mozilla::Telemetry::DiscardedData from "mozilla/TelemetryComms.h"; +using base::ProcessId from "base/process.h"; +using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h"; +using PRTime from "prtime.h"; +[RefCounted] using class nsIURI from "mozilla/ipc/URIUtils.h"; +using struct nsID from "nsID.h"; +using mozilla::net::SocketInfo from "mozilla/net/DashboardTypes.h"; +using mozilla::net::DNSCacheEntries from "mozilla/net/DashboardTypes.h"; +using mozilla::net::HttpRetParams from "mozilla/net/DashboardTypes.h"; +using nsIDNSService::DNSFlags from "nsIDNSService.h"; + +#if defined(XP_WIN) +[MoveOnly] using mozilla::UntrustedModulesData from "mozilla/UntrustedModulesData.h"; +[MoveOnly] using mozilla::ModulePaths from "mozilla/UntrustedModulesData.h"; +[MoveOnly] using mozilla::ModulesMapResult from "mozilla/UntrustedModulesData.h"; +#endif // defined(XP_WIN) + +namespace mozilla { +namespace net { + +struct HttpHandlerInitArgs { + nsCString mLegacyAppName; + nsCString mLegacyAppVersion; + nsCString mPlatform; + nsCString mOscpu; + nsCString mMisc; + nsCString mProduct; + nsCString mProductSub; + nsCString mAppName; + nsCString mAppVersion; + nsCString mCompatFirefox; + nsCString mCompatDevice; + nsCString mDeviceModelId; +}; + +struct SocketDataArgs +{ + uint64_t totalSent; + uint64_t totalRecv; + SocketInfo[] info; +}; + +struct SocketPorcessInitAttributes { + bool mOffline; + bool mConnectivity; + bool mInitSandbox; +#if defined(XP_WIN) + bool mIsReadyForBackgroundProcessing; +#endif + FileDescriptor? mSandboxBroker; +}; + +[NeedsOtherPid, ChildProc=Socket] +sync protocol PSocketProcess +{ + manages PDNSRequest; + manages PWebrtcTCPSocket; + manages PHttpTransaction; + manages PHttpConnectionMgr; + manages PInputChannelThrottleQueue; + manages PAltService; + manages PAltSvcTransaction; + manages PTRRService; + manages PProxyConfigLookup; + manages PNativeDNSResolverOverride; + +parent: + async InitCrashReporter(NativeThreadId threadId); + async AddMemoryReport(MemoryReport aReport); + // Messages for sending telemetry to parent process. + async AccumulateChildHistograms(HistogramAccumulation[] accumulations); + async AccumulateChildKeyedHistograms(KeyedHistogramAccumulation[] accumulations); + async UpdateChildScalars(ScalarAction[] actions); + async UpdateChildKeyedScalars(KeyedScalarAction[] actions); + async RecordChildEvents(ChildEventData[] events); + async RecordDiscardedData(DiscardedData data); + + /* tabId is only required for web-proxy support, which isn't always needed */ + async PWebrtcTCPSocket(TabId? tabId); + async ObserveHttpActivity(HttpActivityArgs aActivityArgs, + uint32_t aActivityType, + uint32_t aActivitySubtype, + PRTime aTimestamp, + uint64_t aExtraSizeData, + nsCString aExtraStringData); + async InitSocketBackground(Endpoint<PSocketProcessBackgroundParent> aEndpoint); + async PAltService(); + async PProxyConfigLookup(nullable nsIURI aUri, uint32_t aFlags); + async CachePushCheck(nullable nsIURI aPushedURL, + OriginAttributes aOriginAttributes, + nsCString aRequestString) + returns (bool aAccepted); + + async ExcludeHttp2OrHttp3(HttpConnectionInfoCloneArgs aArgs); + + async OnConsoleMessage(nsString aMessage); + + // Sent from time-to-time to limit the amount of telemetry vulnerable to loss + // Buffer contains bincoded Rust structs. + // https://firefox-source-docs.mozilla.org/toolkit/components/glean/dev/ipc.html + async FOGData(ByteBuf buf); + +#if defined(XP_WIN) + async GetModulesTrust(ModulePaths aModPaths, bool aRunAtNormalPriority) + returns (ModulesMapResult? modMapResult); +#endif // defined(XP_WIN) + +child: + async Init(SocketPorcessInitAttributes aAttributes); + async PreferenceUpdate(Pref pref); + async RequestMemoryReport(uint32_t generation, + bool anonymize, + bool minimizeMemoryUsage, + FileDescriptor? DMDFile) + returns (uint32_t aGeneration); + async SetOffline(bool offline); + async SetConnectivity(bool connectivity); + async InitLinuxSandbox(FileDescriptor? sandboxBroker); + async InitSocketProcessBridgeParent(ProcessId processId, Endpoint<PSocketProcessBridgeParent> endpoint); + async InitProfiler(Endpoint<PProfilerChild> aEndpoint); +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) + async InitSandboxTesting(Endpoint<PSandboxTestingChild> aEndpoint); +#endif + // test-only + async SocketProcessTelemetryPing(); + + async PHttpTransaction(); + async PHttpConnectionMgr(HttpHandlerInitArgs aArgs); + async UpdateDeviceModelId(nsCString aModelId); + + async OnHttpActivityDistributorActivated(bool aIsActivated); + async OnHttpActivityDistributorObserveProxyResponse(bool aIsEnabled); + async OnHttpActivityDistributorObserveConnection(bool aIsEnabled); + async PInputChannelThrottleQueue(uint32_t meanBytesPerSecond, + uint32_t maxBytesPerSecond); + async PAltSvcTransaction(HttpConnectionInfoCloneArgs aConnInfo, + uint32_t aCaps); + async ClearSessionCache() returns (void_t ok); + async PTRRService(bool aCaptiveIsPassed, + bool aParentalControlEnabled, + nsCString[] aDNSSuffixList); + async PNativeDNSResolverOverride(); + async NotifyObserver(nsCString aTopic, nsString aData); + + async GetSocketData() + returns (SocketDataArgs data); + async GetDNSCacheEntries() + returns (DNSCacheEntries[] entries); + async GetHttpConnectionData() + returns (HttpRetParams[] params); + + async InitProxyAutoConfigChild(Endpoint<PProxyAutoConfigChild> endpoint); + + async RecheckIPConnectivity(); + async RecheckDNS(); + + // Tells the Socket process to flush any pending telemetry. + // Used in tests and ping assembly. Buffer contains bincoded Rust structs. + // https://firefox-source-docs.mozilla.org/toolkit/components/glean/dev/ipc.html + async FlushFOGData() returns (ByteBuf buf); + + // Test-only method. + // Asks the Socket process to trigger test-only instrumentation. + // The unused returned value is to have a promise we can await. + async TestTriggerMetrics() returns (bool unused); + +#if defined(XP_WIN) + async GetUntrustedModulesData() returns (UntrustedModulesData? data); + + /** + * This method is used to notifty a child process to start + * processing module loading events in UntrustedModulesProcessor. + * This should be called when the parent process has gone idle. + */ + async UnblockUntrustedModulesThread(); +#endif // defined(XP_WIN) + +both: + async PDNSRequest(nsCString hostName, nsCString trrServer, int32_t port, + uint16_t type, OriginAttributes originAttributes, + DNSFlags flags); +}; + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/PSocketProcessBackground.ipdl b/netwerk/ipc/PSocketProcessBackground.ipdl new file mode 100644 index 0000000000..8c703a4ae2 --- /dev/null +++ b/netwerk/ipc/PSocketProcessBackground.ipdl @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 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/. */ + +include protocol PVerifySSLServerCert; +include protocol PSelectTLSClientAuthCert; +include protocol PIPCClientCerts; +include protocol PWebSocketConnection; + +include PSMIPCTypes; + +using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h"; + +namespace mozilla { +namespace net { + +/** + * PSocketProcessBackground is the top level IPC protocol between + * a background task queue in the socket process and the parent process. + * We use this to create several IPC protocols between the socket process + * and the parent process. + */ +[ChildProc=Socket] +sync protocol PSocketProcessBackground +{ +parent: + async InitVerifySSLServerCert( + Endpoint<PVerifySSLServerCertParent> aEndpoint, + ByteArray[] aPeerCertChain, + nsCString aHostName, + int32_t aPort, + OriginAttributes aOriginAttributes, + ByteArray? aStapledOCSPResponse, + ByteArray? aSctsFromTLSExtension, + DelegatedCredentialInfoArg? aDcInfo, + uint32_t aProviderFlags, + uint32_t aCertVerifierFlags); + + async InitSelectTLSClientAuthCert( + Endpoint<PSelectTLSClientAuthCertParent> aEndpoint, + nsCString aHostName, + OriginAttributes aOriginAttributes, + int32_t aPort, + uint32_t aProviderFlags, + uint32_t aProviderTlsFlags, + ByteArray aServerCertBytes, + ByteArray[] aCANames, + uint64_t aBrowserId); + + async InitIPCClientCerts(Endpoint<PIPCClientCertsParent> aEndpoint); + + async InitWebSocketConnection(Endpoint<PWebSocketConnectionParent> aEndpoint, + uint32_t aListenerId); +}; + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/PSocketProcessBridge.ipdl b/netwerk/ipc/PSocketProcessBridge.ipdl new file mode 100644 index 0000000000..36c808bfdc --- /dev/null +++ b/netwerk/ipc/PSocketProcessBridge.ipdl @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 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/. */ + +include protocol PBackgroundDataBridge; + +#ifdef MOZ_WEBRTC +include protocol PMediaTransport; +#endif // MOZ_WEBRTC + + +namespace mozilla { +namespace net { + +/** + * PSocketProcessBridge is the IPC protocol between the main thread in + * the content process and the main thread in the socket process. + * We use this to create PBackgroundDataBridge and PMediaTransport protocols + * between content process and socket process. + * Once created, PSocketProcessBridgeChild is the actor that lives in + * content process and PSocketProcessBridgeParent lives in + * socket process. + */ +[NeedsOtherPid, ParentProc=Socket, ChildProc=Content] +sync protocol PSocketProcessBridge +{ +parent: + /** + * For setting up PBackgroundDataBridge protocol, we use this message to + * create a background task queue and the BackgroundDataBridgeParent actor in + * socket process. + */ + async InitBackgroundDataBridge(Endpoint<PBackgroundDataBridgeParent> aEndpoint, + uint64_t aChannelID); + +#ifdef MOZ_WEBRTC + /** + * Similar to the above, this message is for PMediaTransport. + */ + async InitMediaTransport(Endpoint<PMediaTransportParent> aEndpoint); +#endif // MOZ_WEBRTC + +}; + +} +} diff --git a/netwerk/ipc/ParentChannelWrapper.cpp b/netwerk/ipc/ParentChannelWrapper.cpp new file mode 100644 index 0000000000..3b89e3e38f --- /dev/null +++ b/netwerk/ipc/ParentChannelWrapper.cpp @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "ParentChannelWrapper.h" +#include "mozilla/net/HttpBaseChannel.h" +#include "mozilla/net/UrlClassifierCommon.h" +#include "mozilla/net/RedirectChannelRegistrar.h" +#include "nsIViewSourceChannel.h" +#include "nsNetUtil.h" +#include "nsQueryObject.h" +#include "mozilla/dom/RemoteType.h" + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS(ParentChannelWrapper, nsIParentChannel, nsIStreamListener, + nsIRequestObserver); + +void ParentChannelWrapper::Register(uint64_t aRegistrarId) { + nsCOMPtr<nsIRedirectChannelRegistrar> registrar = + RedirectChannelRegistrar::GetOrCreate(); + nsCOMPtr<nsIChannel> dummy; + MOZ_ALWAYS_SUCCEEDS( + NS_LinkRedirectChannels(aRegistrarId, this, getter_AddRefs(dummy))); + +#ifdef DEBUG + // The channel registered with the RedirectChannelRegistrar will be the inner + // channel when dealing with view-source loads. + if (nsCOMPtr<nsIViewSourceChannel> viewSource = do_QueryInterface(mChannel)) { + MOZ_ASSERT(dummy == viewSource->GetInnerChannel()); + } else { + MOZ_ASSERT(dummy == mChannel); + } +#endif +} + +//////////////////////////////////////////////////////////////////////////////// +// nsIParentChannel +//////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP +ParentChannelWrapper::SetParentListener( + mozilla::net::ParentChannelListener* listener) { + return NS_OK; +} + +NS_IMETHODIMP +ParentChannelWrapper::SetClassifierMatchedInfo(const nsACString& aList, + const nsACString& aProvider, + const nsACString& aFullHash) { + nsCOMPtr<nsIClassifiedChannel> classifiedChannel = + do_QueryInterface(mChannel); + if (classifiedChannel) { + classifiedChannel->SetMatchedInfo(aList, aProvider, aFullHash); + } + return NS_OK; +} + +NS_IMETHODIMP +ParentChannelWrapper::SetClassifierMatchedTrackingInfo( + const nsACString& aLists, const nsACString& aFullHash) { + nsCOMPtr<nsIClassifiedChannel> classifiedChannel = + do_QueryInterface(mChannel); + if (classifiedChannel) { + nsTArray<nsCString> lists, fullhashes; + for (const nsACString& token : aLists.Split(',')) { + lists.AppendElement(token); + } + for (const nsACString& token : aFullHash.Split(',')) { + fullhashes.AppendElement(token); + } + classifiedChannel->SetMatchedTrackingInfo(lists, fullhashes); + } + return NS_OK; +} + +NS_IMETHODIMP +ParentChannelWrapper::NotifyClassificationFlags(uint32_t aClassificationFlags, + bool aIsThirdParty) { + UrlClassifierCommon::SetClassificationFlagsHelper( + mChannel, aClassificationFlags, aIsThirdParty); + return NS_OK; +} + +NS_IMETHODIMP +ParentChannelWrapper::Delete() { return NS_OK; } + +NS_IMETHODIMP +ParentChannelWrapper::GetRemoteType(nsACString& aRemoteType) { + aRemoteType = NOT_REMOTE_TYPE; + return NS_OK; +} + +} // namespace net +} // namespace mozilla + +#undef LOG diff --git a/netwerk/ipc/ParentChannelWrapper.h b/netwerk/ipc/ParentChannelWrapper.h new file mode 100644 index 0000000000..8d7d6dd73d --- /dev/null +++ b/netwerk/ipc/ParentChannelWrapper.h @@ -0,0 +1,39 @@ +/* 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_ParentChannelWrapper_h +#define mozilla_net_ParentChannelWrapper_h + +#include "nsIParentChannel.h" +#include "nsIStreamListener.h" + +namespace mozilla { +namespace net { + +class ParentChannelWrapper : public nsIParentChannel { + public: + ParentChannelWrapper(nsIChannel* aChannel, nsIStreamListener* aListener) + : mChannel(aChannel), mListener(aListener) {} + + // Registers this nsIParentChannel wrapper with the RedirectChannelRegistrar + // and holds a reference. + void Register(uint64_t aRegistrarId); + + NS_DECL_ISUPPORTS + NS_DECL_NSIPARENTCHANNEL + NS_FORWARD_NSISTREAMLISTENER(mListener->) + NS_FORWARD_NSIREQUESTOBSERVER(mListener->) + + private: + virtual ~ParentChannelWrapper() = default; + const nsCOMPtr<nsIChannel> mChannel; + const nsCOMPtr<nsIStreamListener> mListener; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_ParentChannelWrapper_h diff --git a/netwerk/ipc/ParentProcessDocumentChannel.cpp b/netwerk/ipc/ParentProcessDocumentChannel.cpp new file mode 100644 index 0000000000..ed9decac72 --- /dev/null +++ b/netwerk/ipc/ParentProcessDocumentChannel.cpp @@ -0,0 +1,314 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "ParentProcessDocumentChannel.h" + +#include "mozilla/extensions/StreamFilterParent.h" +#include "mozilla/net/ParentChannelWrapper.h" +#include "mozilla/net/UrlClassifierCommon.h" +#include "mozilla/StaticPrefs_extensions.h" +#include "nsCRT.h" +#include "nsDocShell.h" +#include "nsIObserverService.h" +#include "nsIClassifiedChannel.h" +#include "nsIXULRuntime.h" +#include "nsHttpHandler.h" +#include "nsDocShellLoadState.h" + +extern mozilla::LazyLogModule gDocumentChannelLog; +#define LOG(fmt) MOZ_LOG(gDocumentChannelLog, mozilla::LogLevel::Verbose, fmt) + +namespace mozilla { +namespace net { + +using RedirectToRealChannelPromise = + typename PDocumentChannelParent::RedirectToRealChannelPromise; + +NS_IMPL_ISUPPORTS_INHERITED(ParentProcessDocumentChannel, DocumentChannel, + nsIAsyncVerifyRedirectCallback, nsIObserver) + +ParentProcessDocumentChannel::ParentProcessDocumentChannel( + nsDocShellLoadState* aLoadState, class LoadInfo* aLoadInfo, + nsLoadFlags aLoadFlags, uint32_t aCacheKey, bool aUriModified, + bool aIsEmbeddingBlockedError) + : DocumentChannel(aLoadState, aLoadInfo, aLoadFlags, aCacheKey, + aUriModified, aIsEmbeddingBlockedError) { + LOG(("ParentProcessDocumentChannel ctor [this=%p]", this)); +} + +ParentProcessDocumentChannel::~ParentProcessDocumentChannel() { + LOG(("ParentProcessDocumentChannel dtor [this=%p]", this)); +} + +RefPtr<RedirectToRealChannelPromise> +ParentProcessDocumentChannel::RedirectToRealChannel( + nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>>&& + aStreamFilterEndpoints, + uint32_t aRedirectFlags, uint32_t aLoadFlags, + const nsTArray<EarlyHintConnectArgs>& aEarlyHints) { + LOG(("ParentProcessDocumentChannel RedirectToRealChannel [this=%p]", this)); + nsCOMPtr<nsIChannel> channel = mDocumentLoadListener->GetChannel(); + channel->SetLoadFlags(aLoadFlags); + channel->SetNotificationCallbacks(mCallbacks); + + if (mLoadGroup) { + channel->SetLoadGroup(mLoadGroup); + } + + if (XRE_IsE10sParentProcess()) { + nsCOMPtr<nsIURI> uri; + MOZ_ALWAYS_SUCCEEDS(NS_GetFinalChannelURI(channel, getter_AddRefs(uri))); + if (!nsDocShell::CanLoadInParentProcess(uri)) { + nsAutoCString msg; + uri->GetSpec(msg); + msg.Insert( + "Attempt to load a non-authorised load in the parent process: ", 0); + NS_ASSERTION(false, msg.get()); + return RedirectToRealChannelPromise::CreateAndResolve( + NS_ERROR_CONTENT_BLOCKED, __func__); + } + } + mStreamFilterEndpoints = std::move(aStreamFilterEndpoints); + + if (mDocumentLoadListener->IsDocumentLoad() && + mozilla::SessionHistoryInParent() && GetDocShell() && + mDocumentLoadListener->GetLoadingSessionHistoryInfo()) { + GetDocShell()->SetLoadingSessionHistoryInfo( + *mDocumentLoadListener->GetLoadingSessionHistoryInfo()); + } + + RefPtr<RedirectToRealChannelPromise> p = mPromise.Ensure(__func__); + // We make the promise use direct task dispatch in order to reduce the number + // of event loops iterations. + mPromise.UseDirectTaskDispatch(__func__); + + nsresult rv = + gHttpHandler->AsyncOnChannelRedirect(this, channel, aRedirectFlags); + if (NS_FAILED(rv)) { + LOG( + ("ParentProcessDocumentChannel RedirectToRealChannel " + "AsyncOnChannelRedirect failed [this=%p " + "aRv=%d]", + this, int(rv))); + OnRedirectVerifyCallback(rv); + } + + return p; +} + +NS_IMETHODIMP +ParentProcessDocumentChannel::OnRedirectVerifyCallback(nsresult aResult) { + LOG( + ("ParentProcessDocumentChannel OnRedirectVerifyCallback [this=%p " + "aResult=%d]", + this, int(aResult))); + + MOZ_ASSERT(mDocumentLoadListener); + + if (NS_FAILED(aResult)) { + Cancel(aResult); + } else if (mCanceled) { + aResult = NS_ERROR_ABORT; + } else { + const nsCOMPtr<nsIChannel> channel = mDocumentLoadListener->GetChannel(); + mLoadGroup->AddRequest(channel, nullptr); + // Adding the channel to the loadgroup could have triggered a status + // change with an observer being called destroying the docShell, resulting + // in the PPDC to be canceled. + if (mCanceled) { + aResult = NS_ERROR_ABORT; + } else { + mLoadGroup->RemoveRequest(this, nullptr, NS_BINDING_REDIRECTED); + for (auto& endpoint : mStreamFilterEndpoints) { + extensions::StreamFilterParent::Attach(channel, std::move(endpoint)); + } + + RefPtr<ParentChannelWrapper> wrapper = + new ParentChannelWrapper(channel, mListener); + + wrapper->Register(mDocumentLoadListener->GetRedirectChannelId()); + } + } + + mPromise.Resolve(aResult, __func__); + + return NS_OK; +} + +NS_IMETHODIMP ParentProcessDocumentChannel::AsyncOpen( + nsIStreamListener* aListener) { + LOG(("ParentProcessDocumentChannel AsyncOpen [this=%p]", this)); + auto docShell = RefPtr<nsDocShell>(GetDocShell()); + MOZ_ASSERT(docShell); + + bool isDocumentLoad = mLoadInfo->GetExternalContentPolicyType() != + ExtContentPolicy::TYPE_OBJECT; + + mDocumentLoadListener = MakeRefPtr<DocumentLoadListener>( + docShell->GetBrowsingContext()->Canonical(), isDocumentLoad); + LOG(("Created PPDocumentChannel with listener=%p", + mDocumentLoadListener.get())); + + // Add observers. + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) { + MOZ_ALWAYS_SUCCEEDS(observerService->AddObserver( + this, NS_HTTP_ON_MODIFY_REQUEST_TOPIC, false)); + } + + gHttpHandler->OnOpeningDocumentRequest(this); + + if (isDocumentLoad) { + // Return value of setting synced field should be checked. See bug 1656492. + Unused << GetDocShell()->GetBrowsingContext()->SetCurrentLoadIdentifier( + Some(mLoadState->GetLoadIdentifier())); + } + + nsresult rv = NS_OK; + Maybe<dom::ClientInfo> initialClientInfo = mInitialClientInfo; + + RefPtr<DocumentLoadListener::OpenPromise> promise; + if (isDocumentLoad) { + promise = mDocumentLoadListener->OpenDocument( + mLoadState, mCacheKey, Some(mChannelId), TimeStamp::Now(), mTiming, + std::move(initialClientInfo), Some(mUriModified), + Some(mIsEmbeddingBlockedError), nullptr /* ContentParent */, &rv); + } else { + promise = mDocumentLoadListener->OpenObject( + mLoadState, mCacheKey, Some(mChannelId), TimeStamp::Now(), mTiming, + std::move(initialClientInfo), InnerWindowIDForExtantDoc(docShell), + mLoadFlags, mLoadInfo->InternalContentPolicyType(), + dom::UserActivation::IsHandlingUserInput(), nullptr /* ContentParent */, + nullptr /* ObjectUpgradeHandler */, &rv); + } + + if (NS_FAILED(rv)) { + MOZ_ASSERT(!promise); + mDocumentLoadListener = nullptr; + RemoveObserver(); + return rv; + } + + mListener = aListener; + if (mLoadGroup) { + mLoadGroup->AddRequest(this, nullptr); + } + + RefPtr<ParentProcessDocumentChannel> self = this; + promise->Then( + GetCurrentSerialEventTarget(), __func__, + [self](DocumentLoadListener::OpenPromiseSucceededType&& aResolveValue) { + self->mDocumentLoadListener->CancelEarlyHintPreloads(); + nsTArray<EarlyHintConnectArgs> earlyHints; + + // The DLL is waiting for us to resolve the + // RedirectToRealChannelPromise given as parameter. + RefPtr<RedirectToRealChannelPromise> p = + self->RedirectToRealChannel( + std::move(aResolveValue.mStreamFilterEndpoints), + aResolveValue.mRedirectFlags, aResolveValue.mLoadFlags, + earlyHints) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [self](RedirectToRealChannelPromise::ResolveOrRejectValue&& + aValue) -> RefPtr<RedirectToRealChannelPromise> { + MOZ_ASSERT(aValue.IsResolve()); + nsresult rv = aValue.ResolveValue(); + if (NS_FAILED(rv)) { + self->DisconnectChildListeners(rv, rv); + } + self->mLoadGroup = nullptr; + self->mListener = nullptr; + self->mCallbacks = nullptr; + self->RemoveObserver(); + auto p = + MakeRefPtr<RedirectToRealChannelPromise::Private>( + __func__); + p->UseDirectTaskDispatch(__func__); + p->ResolveOrReject(std::move(aValue), __func__); + return p; + }); + // We chain the promise the DLL is waiting on to the one returned by + // RedirectToRealChannel. As soon as the promise returned is + // resolved or rejected, so will the DLL's promise. + p->ChainTo(aResolveValue.mPromise.forget(), __func__); + }, + [self](DocumentLoadListener::OpenPromiseFailedType&& aRejectValue) { + // If this is a normal failure, then we want to disconnect our listeners + // and notify them of the failure. If this is a process switch, then we + // can just ignore it silently, and trust that the switch will shut down + // our docshell and cancel us when it's ready. + if (!aRejectValue.mContinueNavigating) { + self->DisconnectChildListeners(aRejectValue.mStatus, + aRejectValue.mLoadGroupStatus); + } + self->RemoveObserver(); + }); + return NS_OK; +} + +NS_IMETHODIMP ParentProcessDocumentChannel::Cancel(nsresult aStatus) { + return CancelWithReason(aStatus, "ParentProcessDocumentChannel::Cancel"_ns); +} + +NS_IMETHODIMP ParentProcessDocumentChannel::CancelWithReason( + nsresult aStatusCode, const nsACString& aReason) { + LOG(("ParentProcessDocumentChannel CancelWithReason [this=%p]", this)); + if (mCanceled) { + return NS_OK; + } + + mCanceled = true; + // This will force the DocumentListener to abort the promise if there's one + // pending. + mDocumentLoadListener->Cancel(aStatusCode, aReason); + + return NS_OK; +} + +void ParentProcessDocumentChannel::RemoveObserver() { + if (nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService()) { + observerService->RemoveObserver(this, NS_HTTP_ON_MODIFY_REQUEST_TOPIC); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// nsIObserver +//////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP +ParentProcessDocumentChannel::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + MOZ_ASSERT(NS_IsMainThread()); + + if (mRequestObserversCalled) { + // We have already emitted the event, we don't want to emit it again. + // We only care about forwarding the first NS_HTTP_ON_MODIFY_REQUEST_TOPIC + // encountered. + return NS_OK; + } + nsCOMPtr<nsIChannel> channel = do_QueryInterface(aSubject); + if (!channel || mDocumentLoadListener->GetChannel() != channel) { + // Not a channel we are interested with. + return NS_OK; + } + LOG(("DocumentChannelParent Observe [this=%p aChannel=%p]", this, + channel.get())); + if (!nsCRT::strcmp(aTopic, NS_HTTP_ON_MODIFY_REQUEST_TOPIC)) { + mRequestObserversCalled = true; + gHttpHandler->OnModifyDocumentRequest(this); + } + + return NS_OK; +} + +} // namespace net +} // namespace mozilla + +#undef LOG diff --git a/netwerk/ipc/ParentProcessDocumentChannel.h b/netwerk/ipc/ParentProcessDocumentChannel.h new file mode 100644 index 0000000000..f9b4ad616d --- /dev/null +++ b/netwerk/ipc/ParentProcessDocumentChannel.h @@ -0,0 +1,62 @@ +/* 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_ParentProcessDocumentChannel_h +#define mozilla_net_ParentProcessDocumentChannel_h + +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/net/DocumentChannel.h" +#include "mozilla/net/DocumentLoadListener.h" +#include "nsIObserver.h" +#include "nsIAsyncVerifyRedirectCallback.h" + +namespace mozilla { +namespace net { + +class EarlyHintConnectArgs; + +class ParentProcessDocumentChannel : public DocumentChannel, + public nsIAsyncVerifyRedirectCallback, + public nsIObserver { + public: + ParentProcessDocumentChannel(nsDocShellLoadState* aLoadState, + class LoadInfo* aLoadInfo, + nsLoadFlags aLoadFlags, uint32_t aCacheKey, + bool aUriModified, + bool aIsEmbeddingBlockedError); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK + NS_DECL_NSIOBSERVER + + NS_IMETHOD AsyncOpen(nsIStreamListener* aListener) override; + NS_IMETHOD Cancel(nsresult aStatusCode) override; + NS_IMETHOD CancelWithReason(nsresult aStatusCode, + const nsACString& aReason) override; + + RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise> + RedirectToRealChannel( + nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>>&& + aStreamFilterEndpoints, + uint32_t aRedirectFlags, uint32_t aLoadFlags, + const nsTArray<EarlyHintConnectArgs>& aEarlyHints); + + private: + virtual ~ParentProcessDocumentChannel(); + void RemoveObserver(); + + RefPtr<DocumentLoadListener> mDocumentLoadListener; + nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>> + mStreamFilterEndpoints; + MozPromiseHolder<PDocumentChannelParent::RedirectToRealChannelPromise> + mPromise; + bool mRequestObserversCalled = false; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_ParentProcessDocumentChannel_h diff --git a/netwerk/ipc/ProxyAutoConfigChild.cpp b/netwerk/ipc/ProxyAutoConfigChild.cpp new file mode 100644 index 0000000000..1ab78c65e3 --- /dev/null +++ b/netwerk/ipc/ProxyAutoConfigChild.cpp @@ -0,0 +1,225 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#include "ProxyAutoConfigChild.h" + +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/net/SocketProcessChild.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsThreadUtils.h" +#include "ProxyAutoConfig.h" + +namespace mozilla::net { + +static bool sThreadLocalSetup = false; +static uint32_t sThreadLocalIndex = 0xdeadbeef; +StaticRefPtr<nsIThread> ProxyAutoConfigChild::sPACThread; +bool ProxyAutoConfigChild::sShutdownObserverRegistered = false; +static StaticRefPtr<ProxyAutoConfigChild> sActor; + +namespace { + +class ShutdownObserver final : public nsIObserver { + public: + ShutdownObserver() = default; + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + private: + ~ShutdownObserver() = default; +}; + +NS_IMPL_ISUPPORTS(ShutdownObserver, nsIObserver) + +NS_IMETHODIMP +ShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + ProxyAutoConfigChild::ShutdownPACThread(); + return NS_OK; +} + +} // namespace + +// static +void ProxyAutoConfigChild::BindProxyAutoConfigChild( + RefPtr<ProxyAutoConfigChild>&& aActor, + Endpoint<PProxyAutoConfigChild>&& aEndpoint) { + // We only allow one ProxyAutoConfigChild at a time, so we need to + // wait until the old one to be destroyed. + if (sActor) { + NS_DispatchToCurrentThread(NS_NewRunnableFunction( + "BindProxyAutoConfigChild", + [actor = std::move(aActor), endpoint = std::move(aEndpoint)]() mutable { + ProxyAutoConfigChild::BindProxyAutoConfigChild(std::move(actor), + std::move(endpoint)); + })); + return; + } + + if (aEndpoint.Bind(aActor)) { + sActor = aActor; + } +} + +// static +bool ProxyAutoConfigChild::Create(Endpoint<PProxyAutoConfigChild>&& aEndpoint) { + if (!sPACThread && !CreatePACThread()) { + NS_WARNING("Failed to create pac thread!"); + return false; + } + + if (!sShutdownObserverRegistered) { + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (NS_WARN_IF(!obs)) { + return false; + } + nsCOMPtr<nsIObserver> observer = new ShutdownObserver(); + nsresult rv = obs->AddObserver(observer, "xpcom-shutdown-threads", false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + sShutdownObserverRegistered = true; + } + + RefPtr<ProxyAutoConfigChild> actor = new ProxyAutoConfigChild(); + if (NS_FAILED(sPACThread->Dispatch(NS_NewRunnableFunction( + "ProxyAutoConfigChild::ProxyAutoConfigChild", + [actor = std::move(actor), + endpoint = std::move(aEndpoint)]() mutable { + MOZ_ASSERT(endpoint.IsValid()); + ProxyAutoConfigChild::BindProxyAutoConfigChild(std::move(actor), + std::move(endpoint)); + })))) { + NS_WARNING("Failed to dispatch runnable!"); + return false; + } + + return true; +} + +// static +bool ProxyAutoConfigChild::CreatePACThread() { + MOZ_ASSERT(NS_IsMainThread()); + + if (SocketProcessChild::GetSingleton()->IsShuttingDown()) { + NS_WARNING("Trying to create pac thread after shutdown has already begun!"); + return false; + } + + nsCOMPtr<nsIThread> thread; + if (NS_FAILED(NS_NewNamedThread("ProxyResolution", getter_AddRefs(thread)))) { + NS_WARNING("NS_NewNamedThread failed!"); + return false; + } + + sPACThread = thread.forget(); + return true; +} + +// static +void ProxyAutoConfigChild::ShutdownPACThread() { + MOZ_ASSERT(NS_IsMainThread()); + + if (sPACThread) { + // Wait until all actos are released. + SpinEventLoopUntil("ProxyAutoConfigChild::ShutdownPACThread"_ns, + [&]() { return !sActor; }); + + nsCOMPtr<nsIThread> thread = sPACThread.get(); + sPACThread = nullptr; + MOZ_ALWAYS_SUCCEEDS(thread->Shutdown()); + } +} + +ProxyAutoConfigChild::ProxyAutoConfigChild() + : mPAC(MakeUnique<ProxyAutoConfig>()) { + if (!sThreadLocalSetup) { + sThreadLocalSetup = true; + PR_NewThreadPrivateIndex(&sThreadLocalIndex, nullptr); + } + + mPAC->SetThreadLocalIndex(sThreadLocalIndex); +} + +ProxyAutoConfigChild::~ProxyAutoConfigChild() = default; + +mozilla::ipc::IPCResult ProxyAutoConfigChild::RecvConfigurePAC( + const nsACString& aPACURI, const nsACString& aPACScriptData, + const bool& aIncludePath, const uint32_t& aExtraHeapSize) { + mPAC->ConfigurePAC(aPACURI, aPACScriptData, aIncludePath, aExtraHeapSize, + GetMainThreadSerialEventTarget()); + mPACLoaded = true; + NS_DispatchToCurrentThread( + NewRunnableMethod("ProxyAutoConfigChild::ProcessPendingQ", this, + &ProxyAutoConfigChild::ProcessPendingQ)); + return IPC_OK(); +} + +void ProxyAutoConfigChild::PendingQuery::Resolve(nsresult aStatus, + const nsACString& aResult) { + mResolver(std::tuple<const nsresult&, const nsACString&>(aStatus, aResult)); +} + +mozilla::ipc::IPCResult ProxyAutoConfigChild::RecvGetProxyForURI( + const nsACString& aTestURI, const nsACString& aTestHost, + GetProxyForURIResolver&& aResolver) { + mPendingQ.insertBack( + new PendingQuery(aTestURI, aTestHost, std::move(aResolver))); + ProcessPendingQ(); + return IPC_OK(); +} + +void ProxyAutoConfigChild::ProcessPendingQ() { + while (ProcessPending()) { + ; + } + + if (mShutdown) { + mPAC->Shutdown(); + } else { + // do GC while the thread has nothing pending + mPAC->GC(); + } +} + +bool ProxyAutoConfigChild::ProcessPending() { + if (mPendingQ.isEmpty()) { + return false; + } + + if (mInProgress || !mPACLoaded) { + return false; + } + + if (mShutdown) { + return true; + } + + mInProgress = true; + RefPtr<PendingQuery> query = mPendingQ.popFirst(); + nsCString result; + nsresult rv = mPAC->GetProxyForURI(query->URI(), query->Host(), result); + query->Resolve(rv, result); + mInProgress = false; + return true; +} + +void ProxyAutoConfigChild::ActorDestroy(ActorDestroyReason aWhy) { + mPendingQ.clear(); + mShutdown = true; + mPAC->Shutdown(); + + // To avoid racing with the main thread, we need to dispatch + // ProxyAutoConfigChild::Destroy again. + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(NewNonOwningRunnableMethod( + "ProxyAutoConfigChild::Destroy", this, &ProxyAutoConfigChild::Destroy))); +} + +void ProxyAutoConfigChild::Destroy() { sActor = nullptr; } + +} // namespace mozilla::net diff --git a/netwerk/ipc/ProxyAutoConfigChild.h b/netwerk/ipc/ProxyAutoConfigChild.h new file mode 100644 index 0000000000..19d757d646 --- /dev/null +++ b/netwerk/ipc/ProxyAutoConfigChild.h @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 ProxyAutoConfigChild_h__ +#define ProxyAutoConfigChild_h__ + +#include "mozilla/LinkedList.h" +#include "mozilla/net/PProxyAutoConfigChild.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { +namespace net { + +class ProxyAutoConfig; + +class ProxyAutoConfigChild final : public PProxyAutoConfigChild { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ProxyAutoConfigChild, final) + + static bool Create(Endpoint<PProxyAutoConfigChild>&& aEndpoint); + static bool CreatePACThread(); + static void ShutdownPACThread(); + + ProxyAutoConfigChild(); + + void ActorDestroy(ActorDestroyReason aWhy) override; + mozilla::ipc::IPCResult RecvConfigurePAC(const nsACString& aPACURI, + const nsACString& aPACScriptData, + const bool& aIncludePath, + const uint32_t& aExtraHeapSize); + mozilla::ipc::IPCResult RecvGetProxyForURI( + const nsACString& aTestURI, const nsACString& aTestHost, + GetProxyForURIResolver&& aResolver); + + void Destroy(); + + private: + virtual ~ProxyAutoConfigChild(); + void ProcessPendingQ(); + bool ProcessPending(); + static void BindProxyAutoConfigChild( + RefPtr<ProxyAutoConfigChild>&& aActor, + Endpoint<PProxyAutoConfigChild>&& aEndpoint); + + UniquePtr<ProxyAutoConfig> mPAC; + bool mInProgress{false}; + bool mPACLoaded{false}; + bool mShutdown{false}; + + class PendingQuery final : public LinkedListElement<RefPtr<PendingQuery>> { + public: + NS_INLINE_DECL_REFCOUNTING(PendingQuery) + + explicit PendingQuery(const nsACString& aTestURI, + const nsACString& aTestHost, + GetProxyForURIResolver&& aResolver) + : mURI(aTestURI), mHost(aTestHost), mResolver(std::move(aResolver)) {} + + void Resolve(nsresult aStatus, const nsACString& aResult); + const nsCString& URI() const { return mURI; } + const nsCString& Host() const { return mHost; } + + private: + ~PendingQuery() = default; + + nsCString mURI; + nsCString mHost; + GetProxyForURIResolver mResolver; + }; + + LinkedList<RefPtr<PendingQuery>> mPendingQ; + + static StaticRefPtr<nsIThread> sPACThread; + static bool sShutdownObserverRegistered; + static Atomic<uint32_t> sLiveActorCount; +}; + +} // namespace net +} // namespace mozilla + +#endif // ProxyAutoConfigChild_h__ diff --git a/netwerk/ipc/ProxyAutoConfigParent.cpp b/netwerk/ipc/ProxyAutoConfigParent.cpp new file mode 100644 index 0000000000..d0a8727ef1 --- /dev/null +++ b/netwerk/ipc/ProxyAutoConfigParent.cpp @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#include "ProxyAutoConfigParent.h" + +#include "mozilla/ipc/Endpoint.h" + +namespace mozilla::net { + +ProxyAutoConfigParent::ProxyAutoConfigParent() = default; + +ProxyAutoConfigParent::~ProxyAutoConfigParent() = default; + +void ProxyAutoConfigParent::Init(Endpoint<PProxyAutoConfigParent>&& aEndpoint) { + DebugOnly<bool> ok = aEndpoint.Bind(this); + MOZ_ASSERT(ok); +} + +} // namespace mozilla::net diff --git a/netwerk/ipc/ProxyAutoConfigParent.h b/netwerk/ipc/ProxyAutoConfigParent.h new file mode 100644 index 0000000000..f65a0714f2 --- /dev/null +++ b/netwerk/ipc/ProxyAutoConfigParent.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 ProxyAutoConfigParent_h__ +#define ProxyAutoConfigParent_h__ + +#include "mozilla/net/PProxyAutoConfigParent.h" + +namespace mozilla { +namespace net { + +class ProxyAutoConfigParent final : public PProxyAutoConfigParent { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ProxyAutoConfigParent, final) + + ProxyAutoConfigParent(); + void Init(Endpoint<PProxyAutoConfigParent>&& aEndpoint); + + private: + virtual ~ProxyAutoConfigParent(); +}; + +} // namespace net +} // namespace mozilla + +#endif // ProxyAutoConfigParent_h__ diff --git a/netwerk/ipc/ProxyConfigLookup.cpp b/netwerk/ipc/ProxyConfigLookup.cpp new file mode 100644 index 0000000000..29ce74091d --- /dev/null +++ b/netwerk/ipc/ProxyConfigLookup.cpp @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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/. */ + +#include "ProxyConfigLookup.h" +#include "ProxyConfigLookupChild.h" +#include "mozilla/Unused.h" +#include "nsContentUtils.h" +#include "nsICancelable.h" +#include "nsIProtocolProxyService.h" +#include "nsIProtocolProxyService2.h" +#include "nsNetUtil.h" +#include "nsThreadUtils.h" +#include "nsIChannel.h" + +namespace mozilla { +namespace net { + +// static +nsresult ProxyConfigLookup::Create( + std::function<void(nsIProxyInfo*, nsresult)>&& aCallback, nsIURI* aURI, + uint32_t aProxyResolveFlags, nsICancelable** aLookupCancellable) { + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr<ProxyConfigLookup> lookUp = + new ProxyConfigLookup(std::move(aCallback), aURI, aProxyResolveFlags); + return lookUp->DoProxyResolve(aLookupCancellable); +} + +ProxyConfigLookup::ProxyConfigLookup( + std::function<void(nsIProxyInfo*, nsresult)>&& aCallback, nsIURI* aURI, + uint32_t aProxyResolveFlags) + : mCallback(std::move(aCallback)), + mURI(aURI), + mProxyResolveFlags(aProxyResolveFlags) {} + +ProxyConfigLookup::~ProxyConfigLookup() = default; + +nsresult ProxyConfigLookup::DoProxyResolve(nsICancelable** aLookupCancellable) { + if (!XRE_IsParentProcess()) { + RefPtr<ProxyConfigLookup> self = this; + bool result = ProxyConfigLookupChild::Create( + mURI, mProxyResolveFlags, + [self](nsIProxyInfo* aProxyinfo, nsresult aResult) { + self->OnProxyAvailable(nullptr, nullptr, aProxyinfo, aResult); + }); + return result ? NS_OK : NS_ERROR_FAILURE; + } + + nsresult rv; + nsCOMPtr<nsIChannel> channel; + rv = NS_NewChannel(getter_AddRefs(channel), mURI, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsIProtocolProxyService> pps = + do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return rv; + } + + // using the nsIProtocolProxyService2 allows a minor performance + // optimization, but if an add-on has only provided the original interface + // then it is ok to use that version. + nsCOMPtr<nsICancelable> proxyRequest; + nsCOMPtr<nsIProtocolProxyService2> pps2 = do_QueryInterface(pps); + if (pps2) { + rv = pps2->AsyncResolve2(channel, mProxyResolveFlags, this, nullptr, + getter_AddRefs(proxyRequest)); + } else { + rv = pps->AsyncResolve(channel, mProxyResolveFlags, this, nullptr, + getter_AddRefs(proxyRequest)); + } + + if (aLookupCancellable) { + proxyRequest.forget(aLookupCancellable); + } + + return rv; +} + +NS_IMETHODIMP ProxyConfigLookup::OnProxyAvailable(nsICancelable* aRequest, + nsIChannel* aChannel, + nsIProxyInfo* aProxyinfo, + nsresult aResult) { + mCallback(aProxyinfo, aResult); + return NS_OK; +} + +NS_IMPL_ISUPPORTS(ProxyConfigLookup, nsIProtocolProxyCallback) + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/ProxyConfigLookup.h b/netwerk/ipc/ProxyConfigLookup.h new file mode 100644 index 0000000000..edef151964 --- /dev/null +++ b/netwerk/ipc/ProxyConfigLookup.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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_ProxyConfigLookup_h +#define mozilla_net_ProxyConfigLookup_h + +#include <functional> +#include "nsIProtocolProxyCallback.h" +#include "nsCOMPtr.h" + +class nsIURI; + +namespace mozilla { +namespace net { + +class ProxyConfigLookup final : public nsIProtocolProxyCallback { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPROTOCOLPROXYCALLBACK + + static nsresult Create( + std::function<void(nsIProxyInfo*, nsresult)>&& aCallback, nsIURI* aURI, + uint32_t aProxyResolveFlags, + nsICancelable** aLookupCancellable = nullptr); + + private: + explicit ProxyConfigLookup( + std::function<void(nsIProxyInfo*, nsresult)>&& aCallback, nsIURI* aURI, + uint32_t aProxyResolveFlags); + virtual ~ProxyConfigLookup(); + nsresult DoProxyResolve(nsICancelable** aLookupCancellable); + + std::function<void(nsIProxyInfo*, nsresult)> mCallback; + nsCOMPtr<nsIURI> mURI; + uint32_t mProxyResolveFlags; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_ProxyConfigLookup_h diff --git a/netwerk/ipc/ProxyConfigLookupChild.cpp b/netwerk/ipc/ProxyConfigLookupChild.cpp new file mode 100644 index 0000000000..913b8460c2 --- /dev/null +++ b/netwerk/ipc/ProxyConfigLookupChild.cpp @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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/. */ + +#include "ProxyConfigLookupChild.h" + +#include "mozilla/net/SocketProcessChild.h" +#include "nsProxyInfo.h" + +namespace mozilla { +namespace net { + +// static +bool ProxyConfigLookupChild::Create( + nsIURI* aURI, uint32_t aProxyResolveFlags, + std::function<void(nsIProxyInfo*, nsresult)>&& aCallback) { + SocketProcessChild* socketChild = SocketProcessChild::GetSingleton(); + if (!socketChild) { + return false; + } + + RefPtr<ProxyConfigLookupChild> child = + new ProxyConfigLookupChild(std::move(aCallback)); + return socketChild->SendPProxyConfigLookupConstructor(child, aURI, + aProxyResolveFlags); +} + +ProxyConfigLookupChild::ProxyConfigLookupChild( + std::function<void(nsIProxyInfo*, nsresult)>&& aCallback) + : mCallback(std::move(aCallback)) {} + +mozilla::ipc::IPCResult ProxyConfigLookupChild::Recv__delete__( + nsTArray<ProxyInfoCloneArgs>&& aProxyInfo, const nsresult& aResult) { + nsCOMPtr<nsIProxyInfo> proxyInfo = + nsProxyInfo::DeserializeProxyInfo(aProxyInfo); + mCallback(proxyInfo, aResult); + return IPC_OK(); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/ProxyConfigLookupChild.h b/netwerk/ipc/ProxyConfigLookupChild.h new file mode 100644 index 0000000000..0c64f2835e --- /dev/null +++ b/netwerk/ipc/ProxyConfigLookupChild.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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_ProxyConfigLookupChild_h +#define mozilla_net_ProxyConfigLookupChild_h + +#include "mozilla/net/PProxyConfigLookupChild.h" +#include <functional> + +class nsIProxyInfo; + +namespace mozilla { +namespace net { + +class ProxyConfigLookupChild final : public PProxyConfigLookupChild { + public: + NS_INLINE_DECL_REFCOUNTING(ProxyConfigLookupChild, override) + + static bool Create(nsIURI* aURI, uint32_t aProxyResolveFlags, + std::function<void(nsIProxyInfo*, nsresult)>&& aCallback); + + mozilla::ipc::IPCResult Recv__delete__( + nsTArray<ProxyInfoCloneArgs>&& aProxyInfo, const nsresult& aResult); + + private: + explicit ProxyConfigLookupChild( + std::function<void(nsIProxyInfo*, nsresult)>&& aCallback); + virtual ~ProxyConfigLookupChild() = default; + + std::function<void(nsIProxyInfo*, nsresult)> mCallback; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_ProxyConfigLookupChild_h diff --git a/netwerk/ipc/ProxyConfigLookupParent.cpp b/netwerk/ipc/ProxyConfigLookupParent.cpp new file mode 100644 index 0000000000..597f43eef5 --- /dev/null +++ b/netwerk/ipc/ProxyConfigLookupParent.cpp @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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/. */ + +#include "ProxyConfigLookupParent.h" +#include "ProxyConfigLookup.h" +#include "mozilla/Unused.h" +#include "nsProxyInfo.h" + +namespace mozilla { +namespace net { + +ProxyConfigLookupParent::ProxyConfigLookupParent(nsIURI* aURI, + uint32_t aProxyResolveFlags) + : mURI(aURI), mProxyResolveFlags(aProxyResolveFlags) {} + +ProxyConfigLookupParent::~ProxyConfigLookupParent() = default; + +void ProxyConfigLookupParent::DoProxyLookup() { + RefPtr<ProxyConfigLookupParent> self = this; + nsresult rv = ProxyConfigLookup::Create( + [self](nsIProxyInfo* aProxyInfo, nsresult aStatus) { + if (self->CanSend()) { + nsTArray<ProxyInfoCloneArgs> proxyInfoArray; + if (aProxyInfo && NS_SUCCEEDED(aStatus)) { + nsProxyInfo::SerializeProxyInfo( + static_cast<nsProxyInfo*>(aProxyInfo), proxyInfoArray); + } + Unused << Send__delete__(self, proxyInfoArray, aStatus); + } + }, + mURI, mProxyResolveFlags); + + if (NS_WARN_IF(NS_FAILED(rv))) { + nsTArray<ProxyInfoCloneArgs> emptyArray; + Unused << Send__delete__(self, emptyArray, rv); + } +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/ProxyConfigLookupParent.h b/netwerk/ipc/ProxyConfigLookupParent.h new file mode 100644 index 0000000000..6ce23866c2 --- /dev/null +++ b/netwerk/ipc/ProxyConfigLookupParent.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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_ProxyConfigLookupParent_h +#define mozilla_net_ProxyConfigLookupParent_h + +#include "mozilla/net/PProxyConfigLookupParent.h" + +class nsIURI; + +namespace mozilla { +namespace net { + +class ProxyConfigLookupParent final : public PProxyConfigLookupParent { + public: + NS_INLINE_DECL_REFCOUNTING(ProxyConfigLookupParent, override) + + explicit ProxyConfigLookupParent(nsIURI* aURI, uint32_t aProxyResolveFlags); + + void DoProxyLookup(); + + private: + virtual ~ProxyConfigLookupParent(); + + nsCOMPtr<nsIURI> mURI; + uint32_t mProxyResolveFlags; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_ProxyConfigLookupParent_h diff --git a/netwerk/ipc/SocketProcessBackgroundChild.cpp b/netwerk/ipc/SocketProcessBackgroundChild.cpp new file mode 100644 index 0000000000..d3835ad0f5 --- /dev/null +++ b/netwerk/ipc/SocketProcessBackgroundChild.cpp @@ -0,0 +1,113 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "SocketProcessBackgroundChild.h" +#include "SocketProcessLogging.h" + +#include "mozilla/ipc/Endpoint.h" +#include "nsThreadUtils.h" + +namespace mozilla::net { + +StaticMutex SocketProcessBackgroundChild::sMutex; +StaticRefPtr<SocketProcessBackgroundChild> + SocketProcessBackgroundChild::sInstance; +StaticRefPtr<nsISerialEventTarget> SocketProcessBackgroundChild::sTaskQueue; + +// static +RefPtr<SocketProcessBackgroundChild> +SocketProcessBackgroundChild::GetSingleton() { + StaticMutexAutoLock lock(sMutex); + return sInstance; +} + +// static +void SocketProcessBackgroundChild::Create( + ipc::Endpoint<PSocketProcessBackgroundChild>&& aEndpoint) { + if (NS_WARN_IF(!aEndpoint.IsValid())) { + MOZ_ASSERT_UNREACHABLE( + "Can't create SocketProcessBackgroundChild with invalid endpoint"); + return; + } + + nsCOMPtr<nsISerialEventTarget> transportQueue; + if (NS_WARN_IF(NS_FAILED(NS_CreateBackgroundTaskQueue( + "SocketBackgroundChildQueue", getter_AddRefs(transportQueue))))) { + return; + } + + RefPtr<SocketProcessBackgroundChild> actor = + new SocketProcessBackgroundChild(); + + transportQueue->Dispatch(NS_NewRunnableFunction( + "BindSocketBackgroundChild", + [endpoint = std::move(aEndpoint), actor]() mutable { + // We checked endpoint validity before the dispatch, so this cannot + // fail. + MOZ_ALWAYS_TRUE(endpoint.Bind(actor)); + })); + + // Immediately store the actor and queue into the global. + // Any messages dispatched to the queue will arrive after it has been bound. + LOG(("SocketProcessBackgroundChild::Create")); + StaticMutexAutoLock lock(sMutex); + MOZ_ASSERT(!sInstance && !sTaskQueue, + "Cannot initialize SocketProcessBackgroundChild twice!"); + sInstance = actor; + sTaskQueue = transportQueue; +} + +// static +void SocketProcessBackgroundChild::Shutdown() { + nsCOMPtr<nsISerialEventTarget> taskQueue = TaskQueue(); + if (!taskQueue) { + return; + } + + taskQueue->Dispatch( + NS_NewRunnableFunction("SocketProcessBackgroundChild::Shutdown", []() { + LOG(("SocketProcessBackgroundChild::Shutdown")); + StaticMutexAutoLock lock(sMutex); + sInstance->Close(); + sInstance = nullptr; + sTaskQueue = nullptr; + })); +} + +// static +already_AddRefed<nsISerialEventTarget> +SocketProcessBackgroundChild::TaskQueue() { + StaticMutexAutoLock lock(sMutex); + return do_AddRef(sTaskQueue); +} + +// static +nsresult SocketProcessBackgroundChild::WithActor( + const char* aName, + MoveOnlyFunction<void(SocketProcessBackgroundChild*)> aCallback) { + nsCOMPtr<nsISerialEventTarget> taskQueue = TaskQueue(); + if (!taskQueue) { + return NS_ERROR_NOT_AVAILABLE; + } + + return taskQueue->Dispatch(NS_NewRunnableFunction( + aName, [callback = std::move(aCallback)]() mutable { + RefPtr<SocketProcessBackgroundChild> actor = + SocketProcessBackgroundChild::GetSingleton(); + if (actor) { + callback(actor); + } + })); +} + +SocketProcessBackgroundChild::SocketProcessBackgroundChild() { + LOG(("SocketProcessBackgroundChild ctor")); +} + +SocketProcessBackgroundChild::~SocketProcessBackgroundChild() { + LOG(("SocketProcessBackgroundChild dtor")); +} + +} // namespace mozilla::net diff --git a/netwerk/ipc/SocketProcessBackgroundChild.h b/netwerk/ipc/SocketProcessBackgroundChild.h new file mode 100644 index 0000000000..b83c8dbcc1 --- /dev/null +++ b/netwerk/ipc/SocketProcessBackgroundChild.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_SocketProcessBackgroundChild_h +#define mozilla_net_SocketProcessBackgroundChild_h + +#include "mozilla/MoveOnlyFunction.h" +#include "mozilla/net/PSocketProcessBackgroundChild.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPtr.h" + +namespace mozilla { +namespace net { + +class SocketProcessBackgroundChild final + : public PSocketProcessBackgroundChild { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SocketProcessBackgroundChild, final) + + static void Create(ipc::Endpoint<PSocketProcessBackgroundChild>&& aEndpoint); + static void Shutdown(); + // |aCallback| will be called with the actor asynchronously on |sTaskQueue|. + static nsresult WithActor( + const char* aName, + MoveOnlyFunction<void(SocketProcessBackgroundChild*)> aCallback); + + private: + SocketProcessBackgroundChild(); + virtual ~SocketProcessBackgroundChild(); + + static RefPtr<SocketProcessBackgroundChild> GetSingleton(); + static already_AddRefed<nsISerialEventTarget> TaskQueue(); + + static StaticMutex sMutex; + static StaticRefPtr<SocketProcessBackgroundChild> sInstance + MOZ_GUARDED_BY(sMutex); + static StaticRefPtr<nsISerialEventTarget> sTaskQueue MOZ_GUARDED_BY(sMutex); +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_SocketProcessBackgroundChild_h diff --git a/netwerk/ipc/SocketProcessBackgroundParent.cpp b/netwerk/ipc/SocketProcessBackgroundParent.cpp new file mode 100644 index 0000000000..c17e1467b6 --- /dev/null +++ b/netwerk/ipc/SocketProcessBackgroundParent.cpp @@ -0,0 +1,169 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "SocketProcessBackgroundParent.h" +#include "SocketProcessLogging.h" + +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/net/HttpConnectionMgrParent.h" +#include "mozilla/net/WebSocketConnectionParent.h" +#include "mozilla/psm/IPCClientCertsParent.h" +#include "mozilla/psm/VerifySSLServerCertParent.h" +#include "mozilla/psm/SelectTLSClientAuthCertParent.h" +#include "nsIHttpChannelInternal.h" + +namespace mozilla::net { + +SocketProcessBackgroundParent::SocketProcessBackgroundParent() { + LOG(("SocketProcessBackgroundParent ctor")); +} + +SocketProcessBackgroundParent::~SocketProcessBackgroundParent() { + LOG(("SocketProcessBackgroundParent dtor")); +} + +mozilla::ipc::IPCResult +SocketProcessBackgroundParent::RecvInitVerifySSLServerCert( + Endpoint<PVerifySSLServerCertParent>&& aEndpoint, + nsTArray<ByteArray>&& aPeerCertChain, const nsACString& aHostName, + const int32_t& aPort, const OriginAttributes& aOriginAttributes, + const Maybe<ByteArray>& aStapledOCSPResponse, + const Maybe<ByteArray>& aSctsFromTLSExtension, + const Maybe<DelegatedCredentialInfoArg>& aDcInfo, + const uint32_t& aProviderFlags, const uint32_t& aCertVerifierFlags) { + LOG(("SocketProcessBackgroundParent::RecvInitVerifySSLServerCert\n")); + if (!aEndpoint.IsValid()) { + return IPC_FAIL(this, "Invalid endpoint"); + } + + nsCOMPtr<nsISerialEventTarget> transportQueue; + if (NS_FAILED(NS_CreateBackgroundTaskQueue("VerifySSLServerCert", + getter_AddRefs(transportQueue)))) { + return IPC_FAIL(this, "NS_CreateBackgroundTaskQueue failed"); + } + + transportQueue->Dispatch(NS_NewRunnableFunction( + "InitVerifySSLServerCert", + [endpoint = std::move(aEndpoint), + peerCertChain = std::move(aPeerCertChain), + hostName = nsCString(aHostName), port(aPort), + originAttributes(aOriginAttributes), + stapledOCSPResponse = std::move(aStapledOCSPResponse), + sctsFromTLSExtension = std::move(aSctsFromTLSExtension), + dcInfo = std::move(aDcInfo), providerFlags(aProviderFlags), + certVerifierFlags(aCertVerifierFlags)]() mutable { + RefPtr<psm::VerifySSLServerCertParent> parent = + new psm::VerifySSLServerCertParent(); + if (!endpoint.Bind(parent)) { + return; + } + + parent->Dispatch(std::move(peerCertChain), hostName, port, + originAttributes, stapledOCSPResponse, + sctsFromTLSExtension, dcInfo, providerFlags, + certVerifierFlags); + })); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult +SocketProcessBackgroundParent::RecvInitSelectTLSClientAuthCert( + Endpoint<PSelectTLSClientAuthCertParent>&& aEndpoint, + const nsACString& aHostName, const OriginAttributes& aOriginAttributes, + const int32_t& aPort, const uint32_t& aProviderFlags, + const uint32_t& aProviderTlsFlags, const ByteArray& aServerCertBytes, + nsTArray<ByteArray>&& aCANames, const uint64_t& aBrowserId) { + LOG(("SocketProcessBackgroundParent::RecvInitSelectTLSClientAuthCert\n")); + if (!aEndpoint.IsValid()) { + return IPC_FAIL(this, "Invalid endpoint"); + } + + nsCOMPtr<nsISerialEventTarget> transportQueue; + if (NS_FAILED(NS_CreateBackgroundTaskQueue("SelectTLSClientAuthCert", + getter_AddRefs(transportQueue)))) { + return IPC_FAIL(this, "NS_CreateBackgroundTaskQueue failed"); + } + + transportQueue->Dispatch(NS_NewRunnableFunction( + "InitSelectTLSClientAuthCert", + [endpoint = std::move(aEndpoint), hostName = nsCString(aHostName), + originAttributes(aOriginAttributes), port(aPort), + providerFlags(aProviderFlags), providerTlsFlags(aProviderTlsFlags), + serverCertBytes(aServerCertBytes), CANAMEs(std::move(aCANames)), + browserId(aBrowserId)]() mutable { + RefPtr<psm::SelectTLSClientAuthCertParent> parent = + new psm::SelectTLSClientAuthCertParent(); + if (!endpoint.Bind(parent)) { + return; + } + + parent->Dispatch(hostName, originAttributes, port, providerFlags, + providerTlsFlags, serverCertBytes, std::move(CANAMEs), + browserId); + })); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessBackgroundParent::RecvInitIPCClientCerts( + Endpoint<PIPCClientCertsParent>&& aEndpoint) { + LOG(("SocketProcessBackgroundParent::RecvInitIPCClientCerts\n")); + if (!aEndpoint.IsValid()) { + return IPC_FAIL(this, "Invalid endpoint"); + } + + nsCOMPtr<nsISerialEventTarget> transportQueue; + if (NS_FAILED(NS_CreateBackgroundTaskQueue("IPCClientCerts", + getter_AddRefs(transportQueue)))) { + return IPC_FAIL(this, "NS_CreateBackgroundTaskQueue failed"); + } + + transportQueue->Dispatch(NS_NewRunnableFunction( + "InitIPCClientCerts", [endpoint = std::move(aEndpoint)]() mutable { + RefPtr<psm::IPCClientCertsParent> parent = + new psm::IPCClientCertsParent(); + endpoint.Bind(parent); + })); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +SocketProcessBackgroundParent::RecvInitWebSocketConnection( + Endpoint<PWebSocketConnectionParent>&& aEndpoint, + const uint32_t& aListenerId) { + LOG(("SocketProcessBackgroundParent::RecvInitWebSocketConnection\n")); + if (!aEndpoint.IsValid()) { + return IPC_FAIL(this, "Invalid endpoint"); + } + + nsCOMPtr<nsISerialEventTarget> transportQueue; + if (NS_FAILED(NS_CreateBackgroundTaskQueue("WebSocketConnection", + getter_AddRefs(transportQueue)))) { + return IPC_FAIL(this, "NS_CreateBackgroundTaskQueue failed"); + } + + transportQueue->Dispatch(NS_NewRunnableFunction( + "InitWebSocketConnection", + [endpoint = std::move(aEndpoint), aListenerId]() mutable { + Maybe<nsCOMPtr<nsIHttpUpgradeListener>> listener = + net::HttpConnectionMgrParent::GetAndRemoveHttpUpgradeListener( + aListenerId); + if (!listener) { + return; + } + + RefPtr<WebSocketConnectionParent> actor = + new WebSocketConnectionParent(*listener); + endpoint.Bind(actor); + })); + return IPC_OK(); +} + +void SocketProcessBackgroundParent::ActorDestroy(ActorDestroyReason aReason) { + LOG(("SocketProcessBackgroundParent::ActorDestroy")); +} + +} // namespace mozilla::net diff --git a/netwerk/ipc/SocketProcessBackgroundParent.h b/netwerk/ipc/SocketProcessBackgroundParent.h new file mode 100644 index 0000000000..0714f8dd6e --- /dev/null +++ b/netwerk/ipc/SocketProcessBackgroundParent.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_SocketProcessBackgroundParent_h +#define mozilla_net_SocketProcessBackgroundParent_h + +#include "mozilla/net/PSocketProcessBackgroundParent.h" + +namespace mozilla { +namespace net { + +class SocketProcessBackgroundParent final + : public PSocketProcessBackgroundParent { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SocketProcessBackgroundParent, final) + + SocketProcessBackgroundParent(); + + mozilla::ipc::IPCResult RecvInitVerifySSLServerCert( + Endpoint<PVerifySSLServerCertParent>&& aEndpoint, + nsTArray<ByteArray>&& aPeerCertChain, const nsACString& aHostName, + const int32_t& aPort, const OriginAttributes& aOriginAttributes, + const Maybe<ByteArray>& aStapledOCSPResponse, + const Maybe<ByteArray>& aSctsFromTLSExtension, + const Maybe<DelegatedCredentialInfoArg>& aDcInfo, + const uint32_t& aProviderFlags, const uint32_t& aCertVerifierFlags); + + mozilla::ipc::IPCResult RecvInitIPCClientCerts( + Endpoint<PIPCClientCertsParent>&& aEndpoint); + + mozilla::ipc::IPCResult RecvInitSelectTLSClientAuthCert( + Endpoint<PSelectTLSClientAuthCertParent>&& aEndpoint, + const nsACString& aHostName, const OriginAttributes& aOriginAttributes, + const int32_t& aPort, const uint32_t& aProviderFlags, + const uint32_t& aProviderTlsFlags, const ByteArray& aServerCertBytes, + nsTArray<ByteArray>&& aCANames, const uint64_t& aBrowserId); + + mozilla::ipc::IPCResult RecvInitWebSocketConnection( + Endpoint<PWebSocketConnectionParent>&& aEndpoint, + const uint32_t& aListenerId); + + void ActorDestroy(ActorDestroyReason aReason) override; + + private: + ~SocketProcessBackgroundParent(); +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_SocketProcessBackgroundParent_h diff --git a/netwerk/ipc/SocketProcessBridgeChild.cpp b/netwerk/ipc/SocketProcessBridgeChild.cpp new file mode 100644 index 0000000000..798ce37bf1 --- /dev/null +++ b/netwerk/ipc/SocketProcessBridgeChild.cpp @@ -0,0 +1,186 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "SocketProcessBridgeChild.h" +#include "SocketProcessLogging.h" + +#include "mozilla/AppShutdown.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/net/NeckoChild.h" +#include "nsIObserverService.h" +#include "nsThreadUtils.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_network.h" + +namespace mozilla { + +using dom::ContentChild; + +namespace net { + +StaticRefPtr<SocketProcessBridgeChild> + SocketProcessBridgeChild::sSocketProcessBridgeChild; + +NS_IMPL_ISUPPORTS(SocketProcessBridgeChild, nsIObserver) + +// static +bool SocketProcessBridgeChild::Create( + Endpoint<PSocketProcessBridgeChild>&& aEndpoint) { + MOZ_ASSERT(NS_IsMainThread()); + + sSocketProcessBridgeChild = new SocketProcessBridgeChild(); + + if (!aEndpoint.Bind(sSocketProcessBridgeChild)) { + MOZ_ASSERT(false, "Bind failed!"); + sSocketProcessBridgeChild = nullptr; + return false; + } + + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + os->AddObserver(sSocketProcessBridgeChild, "content-child-shutdown", false); + } + + sSocketProcessBridgeChild->mSocketProcessPid = aEndpoint.OtherPid(); + return true; +} + +// static +already_AddRefed<SocketProcessBridgeChild> +SocketProcessBridgeChild::GetSingleton() { + RefPtr<SocketProcessBridgeChild> child = sSocketProcessBridgeChild; + return child.forget(); +} + +// static +RefPtr<SocketProcessBridgeChild::GetPromise> +SocketProcessBridgeChild::GetSocketProcessBridge() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!StaticPrefs::network_process_enabled()) { + return GetPromise::CreateAndReject(nsCString("Socket process disabled!"), + __func__); + } + + if (!gNeckoChild) { + return GetPromise::CreateAndReject(nsCString("No NeckoChild!"), __func__); + } + + // ContentChild is shutting down, we should not try to create + // SocketProcessBridgeChild. + ContentChild* content = ContentChild::GetSingleton(); + if (!content || content->IsShuttingDown()) { + return GetPromise::CreateAndReject( + nsCString("ContentChild is shutting down."), __func__); + } + + if (sSocketProcessBridgeChild) { + return GetPromise::CreateAndResolve(sSocketProcessBridgeChild, __func__); + } + + return gNeckoChild->SendInitSocketProcessBridge()->Then( + GetMainThreadSerialEventTarget(), __func__, + [](NeckoChild::InitSocketProcessBridgePromise::ResolveOrRejectValue&& + aResult) { + ContentChild* content = ContentChild::GetSingleton(); + if (!content || content->IsShuttingDown()) { + return GetPromise::CreateAndReject( + nsCString("ContentChild is shutting down."), __func__); + } + if (!sSocketProcessBridgeChild) { + if (aResult.IsReject()) { + return GetPromise::CreateAndReject( + nsCString("SendInitSocketProcessBridge failed"), __func__); + } + + if (!aResult.ResolveValue().IsValid()) { + return GetPromise::CreateAndReject( + nsCString( + "SendInitSocketProcessBridge resolved with an invalid " + "endpoint!"), + __func__); + } + + if (!SocketProcessBridgeChild::Create( + std::move(aResult.ResolveValue()))) { + return GetPromise::CreateAndReject( + nsCString("SendInitSocketProcessBridge resolved with a valid " + "endpoint, " + "but SocketProcessBridgeChild::Create failed!"), + __func__); + } + } + + return GetPromise::CreateAndResolve(sSocketProcessBridgeChild, + __func__); + }); +} + +SocketProcessBridgeChild::SocketProcessBridgeChild() : mShuttingDown(false) { + LOG(("CONSTRUCT SocketProcessBridgeChild::SocketProcessBridgeChild\n")); +} + +SocketProcessBridgeChild::~SocketProcessBridgeChild() { + LOG(("DESTRUCT SocketProcessBridgeChild::SocketProcessBridgeChild\n")); +} + +mozilla::ipc::IPCResult SocketProcessBridgeChild::RecvTest() { + LOG(("SocketProcessBridgeChild::RecvTest\n")); + return IPC_OK(); +} + +void SocketProcessBridgeChild::ActorDestroy(ActorDestroyReason aWhy) { + LOG(("SocketProcessBridgeChild::ActorDestroy\n")); + if (AbnormalShutdown == aWhy) { + if (gNeckoChild && + !AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + // Let NeckoParent know that the socket process connections must be + // rebuilt. + gNeckoChild->SendResetSocketProcessBridge(); + } + + nsresult res; + nsCOMPtr<nsISerialEventTarget> mSTSThread = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &res); + if (NS_SUCCEEDED(res) && mSTSThread) { + // This must be called off the main thread. If we don't make this call + // ipc::BackgroundChild::GetOrCreateSocketActorForCurrentThread() will + // return the previous actor that is no longer able to send. This causes + // rebuilding the socket process connections to fail. + MOZ_ALWAYS_SUCCEEDS(mSTSThread->Dispatch(NS_NewRunnableFunction( + "net::SocketProcessBridgeChild::ActorDestroy", + []() { ipc::BackgroundChild::CloseForCurrentThread(); }))); + } + } + + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + os->RemoveObserver(this, "content-child-shutdown"); + } + GetCurrentSerialEventTarget()->Dispatch( + NewRunnableMethod("net::SocketProcessBridgeChild::DeferredDestroy", this, + &SocketProcessBridgeChild::DeferredDestroy)); + mShuttingDown = true; +} + +NS_IMETHODIMP +SocketProcessBridgeChild::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (!strcmp(aTopic, "content-child-shutdown")) { + PSocketProcessBridgeChild::Close(); + } + return NS_OK; +} + +void SocketProcessBridgeChild::DeferredDestroy() { + MOZ_ASSERT(NS_IsMainThread()); + + sSocketProcessBridgeChild = nullptr; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/SocketProcessBridgeChild.h b/netwerk/ipc/SocketProcessBridgeChild.h new file mode 100644 index 0000000000..8b1c1ad6cf --- /dev/null +++ b/netwerk/ipc/SocketProcessBridgeChild.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_SocketProcessBridgeChild_h +#define mozilla_net_SocketProcessBridgeChild_h + +#include <functional> +#include "mozilla/net/PSocketProcessBridgeChild.h" +#include "nsIObserver.h" + +namespace mozilla { +namespace net { + +// The IPC actor implements PSocketProcessBridgeChild in content process. +// This is allocated and kept alive by NeckoChild. When "content-child-shutdown" +// topic is observed, this actor will be destroyed. +class SocketProcessBridgeChild final : public PSocketProcessBridgeChild, + public nsIObserver { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOBSERVER + + static already_AddRefed<SocketProcessBridgeChild> GetSingleton(); + using GetPromise = + MozPromise<RefPtr<SocketProcessBridgeChild>, nsCString, false>; + static RefPtr<GetPromise> GetSocketProcessBridge(); + + mozilla::ipc::IPCResult RecvTest(); + void ActorDestroy(ActorDestroyReason aWhy) override; + void DeferredDestroy(); + bool IsShuttingDown() const { return mShuttingDown; }; + ProcessId SocketProcessPid() const { return mSocketProcessPid; }; + + private: + DISALLOW_COPY_AND_ASSIGN(SocketProcessBridgeChild); + static bool Create(Endpoint<PSocketProcessBridgeChild>&& aEndpoint); + explicit SocketProcessBridgeChild(); + virtual ~SocketProcessBridgeChild(); + + static StaticRefPtr<SocketProcessBridgeChild> sSocketProcessBridgeChild; + bool mShuttingDown; + ProcessId mSocketProcessPid; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_SocketProcessBridgeChild_h diff --git a/netwerk/ipc/SocketProcessBridgeParent.cpp b/netwerk/ipc/SocketProcessBridgeParent.cpp new file mode 100644 index 0000000000..b16be05b4d --- /dev/null +++ b/netwerk/ipc/SocketProcessBridgeParent.cpp @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "SocketProcessBridgeParent.h" +#include "SocketProcessLogging.h" + +#ifdef MOZ_WEBRTC +# include "mozilla/dom/MediaTransportParent.h" +#endif +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/ipc/Endpoint.h" +#include "SocketProcessChild.h" +#include "mozilla/net/BackgroundDataBridgeParent.h" +#include "nsThreadUtils.h" + +namespace mozilla { +namespace net { + +SocketProcessBridgeParent::SocketProcessBridgeParent(ProcessId aId) : mId(aId) { + LOG( + ("CONSTRUCT SocketProcessBridgeParent::SocketProcessBridgeParent " + "mId=%" PRIPID "\n", + mId)); +} + +SocketProcessBridgeParent::~SocketProcessBridgeParent() { + LOG(("DESTRUCT SocketProcessBridgeParent::SocketProcessBridgeParent\n")); +} + +mozilla::ipc::IPCResult SocketProcessBridgeParent::RecvInitBackgroundDataBridge( + mozilla::ipc::Endpoint<PBackgroundDataBridgeParent>&& aEndpoint, + uint64_t aChannelID) { + LOG(("SocketProcessBridgeParent::RecvInitBackgroundDataBridge\n")); + + nsCOMPtr<nsISerialEventTarget> transportQueue; + if (NS_FAILED(NS_CreateBackgroundTaskQueue("BackgroundDataBridge", + getter_AddRefs(transportQueue)))) { + return IPC_FAIL(this, "NS_CreateBackgroundTaskQueue failed"); + } + + if (!aEndpoint.IsValid()) { + return IPC_FAIL(this, "Invalid endpoint"); + } + + transportQueue->Dispatch(NS_NewRunnableFunction( + "BackgroundDataBridgeParent::Bind", + [endpoint = std::move(aEndpoint), aChannelID]() mutable { + RefPtr<net::BackgroundDataBridgeParent> actor = + new net::BackgroundDataBridgeParent(aChannelID); + endpoint.Bind(actor); + })); + return IPC_OK(); +} + +#ifdef MOZ_WEBRTC +mozilla::ipc::IPCResult SocketProcessBridgeParent::RecvInitMediaTransport( + mozilla::ipc::Endpoint<mozilla::dom::PMediaTransportParent>&& aEndpoint) { + LOG(("SocketProcessBridgeParent::RecvInitMediaTransport\n")); + + if (!aEndpoint.IsValid()) { + return IPC_FAIL(this, "Invalid endpoint"); + } + + if (!mMediaTransportTaskQueue) { + nsCOMPtr<nsISerialEventTarget> transportQueue; + if (NS_FAILED(NS_CreateBackgroundTaskQueue( + "MediaTransport", getter_AddRefs(transportQueue)))) { + return IPC_FAIL(this, "NS_CreateBackgroundTaskQueue failed"); + } + + mMediaTransportTaskQueue = std::move(transportQueue); + } + + mMediaTransportTaskQueue->Dispatch(NS_NewRunnableFunction( + "BackgroundDataBridgeParent::Bind", + [endpoint = std::move(aEndpoint)]() mutable { + RefPtr<MediaTransportParent> actor = new MediaTransportParent(); + endpoint.Bind(actor); + })); + return IPC_OK(); +} +#endif + +void SocketProcessBridgeParent::ActorDestroy(ActorDestroyReason aReason) { + // See bug 1846478. We might be able to remove this dispatch. + GetCurrentSerialEventTarget()->Dispatch(NS_NewRunnableFunction( + "SocketProcessBridgeParent::ActorDestroy", [id = mId] { + if (SocketProcessChild* child = SocketProcessChild::GetSingleton()) { + child->DestroySocketProcessBridgeParent(id); + } + })); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/SocketProcessBridgeParent.h b/netwerk/ipc/SocketProcessBridgeParent.h new file mode 100644 index 0000000000..e9b6053665 --- /dev/null +++ b/netwerk/ipc/SocketProcessBridgeParent.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_SocketProcessBridgeParent_h +#define mozilla_net_SocketProcessBridgeParent_h + +#include "mozilla/net/PSocketProcessBridgeParent.h" + +namespace mozilla { +namespace net { + +// The IPC actor implements PSocketProcessBridgeParent in socket process. +// This is allocated and kept alive by SocketProcessChild. When |ActorDestroy| +// is called, |SocketProcessChild::DestroySocketProcessBridgeParent| will be +// called to destroy this actor. +class SocketProcessBridgeParent final : public PSocketProcessBridgeParent { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SocketProcessBridgeParent, final) + + explicit SocketProcessBridgeParent(ProcessId aId); + + mozilla::ipc::IPCResult RecvInitBackgroundDataBridge( + Endpoint<PBackgroundDataBridgeParent>&& aEndpoint, uint64_t aChannelID); + +#ifdef MOZ_WEBRTC + mozilla::ipc::IPCResult RecvInitMediaTransport( + Endpoint<PMediaTransportParent>&& aEndpoint); +#endif + + void ActorDestroy(ActorDestroyReason aReason) override; + + private: + ~SocketProcessBridgeParent(); + + nsCOMPtr<nsISerialEventTarget> mMediaTransportTaskQueue; + ProcessId mId; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_SocketProcessBridgeParent_h diff --git a/netwerk/ipc/SocketProcessChild.cpp b/netwerk/ipc/SocketProcessChild.cpp new file mode 100644 index 0000000000..000ebde2fd --- /dev/null +++ b/netwerk/ipc/SocketProcessChild.cpp @@ -0,0 +1,833 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "SocketProcessChild.h" +#include "SocketProcessLogging.h" + +#include "base/task.h" +#include "InputChannelThrottleQueueChild.h" +#include "HttpInfo.h" +#include "HttpTransactionChild.h" +#include "HttpConnectionMgrChild.h" +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/Components.h" +#include "mozilla/dom/MemoryReportRequest.h" +#include "mozilla/FOGIPC.h" +#include "mozilla/glean/GleanMetrics.h" +#include "mozilla/ipc/CrashReporterClient.h" +#include "mozilla/ipc/ProcessChild.h" +#include "mozilla/net/AltSvcTransactionChild.h" +#include "mozilla/net/BackgroundDataBridgeParent.h" +#include "mozilla/net/DNSRequestChild.h" +#include "mozilla/net/DNSRequestParent.h" +#include "mozilla/net/NativeDNSResolverOverrideChild.h" +#include "mozilla/net/ProxyAutoConfigChild.h" +#include "mozilla/net/SocketProcessBackgroundChild.h" +#include "mozilla/net/TRRServiceChild.h" +#include "mozilla/ipc/ProcessUtils.h" +#include "mozilla/Preferences.h" +#include "mozilla/RemoteLazyInputStreamChild.h" +#include "mozilla/StaticPrefs_javascript.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/Telemetry.h" +#include "NetworkConnectivityService.h" +#include "nsDebugImpl.h" +#include "nsHttpConnectionInfo.h" +#include "nsHttpHandler.h" +#include "nsIDNSService.h" +#include "nsIHttpActivityObserver.h" +#include "nsIXULRuntime.h" +#include "nsNetUtil.h" +#include "nsNSSComponent.h" +#include "nsSocketTransportService2.h" +#include "nsThreadManager.h" +#include "SocketProcessBridgeParent.h" +#include "jsapi.h" +#include "js/Initialization.h" +#include "js/Prefs.h" +#include "XPCSelfHostedShmem.h" + +#if defined(XP_WIN) +# include <process.h> + +# include "mozilla/WinDllServices.h" +#else +# include <unistd.h> +#endif + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) +# include "mozilla/Sandbox.h" +#endif + +#include "ChildProfilerController.h" + +#ifdef MOZ_WEBRTC +# include "mozilla/net/WebrtcTCPSocketChild.h" +#endif + +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) +# include "mozilla/SandboxTestingChild.h" +#endif + +namespace mozilla { +namespace net { + +using namespace ipc; + +static bool sInitializedJS = false; + +static Atomic<SocketProcessChild*> sSocketProcessChild; + +SocketProcessChild::SocketProcessChild() { + LOG(("CONSTRUCT SocketProcessChild::SocketProcessChild\n")); + nsDebugImpl::SetMultiprocessMode("Socket"); + + MOZ_COUNT_CTOR(SocketProcessChild); + sSocketProcessChild = this; +} + +SocketProcessChild::~SocketProcessChild() { + LOG(("DESTRUCT SocketProcessChild::SocketProcessChild\n")); + MOZ_COUNT_DTOR(SocketProcessChild); + sSocketProcessChild = nullptr; +} + +/* static */ +SocketProcessChild* SocketProcessChild::GetSingleton() { + return sSocketProcessChild; +} + +#if defined(XP_MACOSX) +extern "C" { +void CGSShutdownServerConnections(); +}; +#endif + +void SocketProcessChild::InitSocketBackground() { + Endpoint<PSocketProcessBackgroundParent> parentEndpoint; + Endpoint<PSocketProcessBackgroundChild> childEndpoint; + if (NS_WARN_IF(NS_FAILED(PSocketProcessBackground::CreateEndpoints( + &parentEndpoint, &childEndpoint)))) { + return; + } + + SocketProcessBackgroundChild::Create(std::move(childEndpoint)); + + Unused << SendInitSocketBackground(std::move(parentEndpoint)); +} + +namespace { + +class NetTeardownObserver final : public nsIObserver { + public: + NetTeardownObserver() = default; + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + private: + ~NetTeardownObserver() = default; +}; + +NS_IMPL_ISUPPORTS(NetTeardownObserver, nsIObserver) + +NS_IMETHODIMP +NetTeardownObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (SocketProcessChild* child = SocketProcessChild::GetSingleton()) { + child->CloseIPCClientCertsActor(); + } + + return NS_OK; +} + +} // namespace + +bool SocketProcessChild::Init(mozilla::ipc::UntypedEndpoint&& aEndpoint, + const char* aParentBuildID) { + if (NS_WARN_IF(NS_FAILED(nsThreadManager::get().Init()))) { + return false; + } + if (NS_WARN_IF(!aEndpoint.Bind(this))) { + return false; + } + // This must be sent before any IPDL message, which may hit sentinel + // errors due to parent and content processes having different + // versions. + MessageChannel* channel = GetIPCChannel(); + if (channel && !channel->SendBuildIDsMatchMessage(aParentBuildID)) { + // We need to quit this process if the buildID doesn't match the parent's. + // This can occur when an update occurred in the background. + ProcessChild::QuickExit(); + } + + // Init crash reporter support. + CrashReporterClient::InitSingleton(this); + + if (NS_FAILED(NS_InitMinimalXPCOM())) { + return false; + } + + InitSocketBackground(); + + SetThisProcessName("Socket Process"); +#if defined(XP_MACOSX) + // Close all current connections to the WindowServer. This ensures that the + // Activity Monitor will not label the socket process as "Not responding" + // because it's not running a native event loop. See bug 1384336. + CGSShutdownServerConnections(); +#endif // XP_MACOSX + + nsresult rv; + nsCOMPtr<nsIIOService> ios = do_GetIOService(&rv); + if (NS_FAILED(rv)) { + return false; + } + + nsCOMPtr<nsIProtocolHandler> handler; + rv = ios->GetProtocolHandler("http", getter_AddRefs(handler)); + if (NS_FAILED(rv)) { + return false; + } + + // Initialize DNS Service here, since it needs to be done in main thread. + nsCOMPtr<nsIDNSService> dns = + do_GetService("@mozilla.org/network/dns-service;1", &rv); + if (NS_FAILED(rv)) { + return false; + } + + if (!EnsureNSSInitializedChromeOrContent()) { + return false; + } + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + nsCOMPtr<nsIObserver> observer = new NetTeardownObserver(); + Unused << obs->AddObserver(observer, "profile-change-net-teardown", false); + } + + mSocketThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID); + if (!mSocketThread) { + return false; + } + + return true; +} + +void SocketProcessChild::ActorDestroy(ActorDestroyReason aWhy) { + LOG(("SocketProcessChild::ActorDestroy\n")); + + { + MutexAutoLock lock(mMutex); + mShuttingDown = true; + } + + if (AbnormalShutdown == aWhy) { + NS_WARNING("Shutting down Socket process early due to a crash!"); + ProcessChild::QuickExit(); + } + + // Send the last bits of Glean data over to the main process. + glean::FlushFOGData( + [](ByteBuf&& aBuf) { glean::SendFOGData(std::move(aBuf)); }); + + if (mProfilerController) { + mProfilerController->Shutdown(); + mProfilerController = nullptr; + } + + CrashReporterClient::DestroySingleton(); + XRE_ShutdownChildProcess(); +} + +void SocketProcessChild::CleanUp() { + LOG(("SocketProcessChild::CleanUp\n")); + + SocketProcessBackgroundChild::Shutdown(); + + for (const auto& parent : mSocketProcessBridgeParentMap.Values()) { + if (parent->CanSend()) { + parent->Close(); + } + } + + { + MutexAutoLock lock(mMutex); + mBackgroundDataBridgeMap.Clear(); + } + + // Normally, the IPC channel should be already closed at this point, but + // sometimes it's not (bug 1788860). When the channel is closed, calling + // Close() again is harmless. + Close(); + + NS_ShutdownXPCOM(nullptr); + + if (sInitializedJS) { + JS_ShutDown(); + } +} + +mozilla::ipc::IPCResult SocketProcessChild::RecvInit( + const SocketPorcessInitAttributes& aAttributes) { + Unused << RecvSetOffline(aAttributes.mOffline()); + Unused << RecvSetConnectivity(aAttributes.mConnectivity()); + if (aAttributes.mInitSandbox()) { + Unused << RecvInitLinuxSandbox(aAttributes.mSandboxBroker()); + } + +#if defined(XP_WIN) + RefPtr<DllServices> dllSvc(DllServices::Get()); + dllSvc->StartUntrustedModulesProcessor( + aAttributes.mIsReadyForBackgroundProcessing()); +#endif // defined(XP_WIN) + + return IPC_OK(); +} + +IPCResult SocketProcessChild::RecvPreferenceUpdate(const Pref& aPref) { + Preferences::SetPreference(aPref); + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessChild::RecvRequestMemoryReport( + const uint32_t& aGeneration, const bool& aAnonymize, + const bool& aMinimizeMemoryUsage, + const Maybe<ipc::FileDescriptor>& aDMDFile, + const RequestMemoryReportResolver& aResolver) { + nsPrintfCString processName("Socket (pid %u)", (unsigned)getpid()); + + mozilla::dom::MemoryReportRequestClient::Start( + aGeneration, aAnonymize, aMinimizeMemoryUsage, aDMDFile, processName, + [&](const MemoryReport& aReport) { + Unused << GetSingleton()->SendAddMemoryReport(aReport); + }, + aResolver); + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessChild::RecvSetOffline( + const bool& aOffline) { + LOG(("SocketProcessChild::RecvSetOffline aOffline=%d\n", aOffline)); + + nsCOMPtr<nsIIOService> io(do_GetIOService()); + NS_ASSERTION(io, "IO Service can not be null"); + + io->SetOffline(aOffline); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessChild::RecvSetConnectivity( + const bool& aConnectivity) { + nsCOMPtr<nsIIOService> io(do_GetIOService()); + nsCOMPtr<nsIIOServiceInternal> ioInternal(do_QueryInterface(io)); + NS_ASSERTION(ioInternal, "IO Service can not be null"); + + ioInternal->SetConnectivity(aConnectivity); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessChild::RecvInitLinuxSandbox( + const Maybe<ipc::FileDescriptor>& aBrokerFd) { +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + int fd = -1; + if (aBrokerFd.isSome()) { + fd = aBrokerFd.value().ClonePlatformHandle().release(); + } + SetSocketProcessSandbox(fd); +#endif // XP_LINUX && MOZ_SANDBOX + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessChild::RecvInitSocketProcessBridgeParent( + const ProcessId& aContentProcessId, + Endpoint<mozilla::net::PSocketProcessBridgeParent>&& aEndpoint) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mSocketProcessBridgeParentMap.Contains(aContentProcessId)); + + if (NS_WARN_IF(!aEndpoint.IsValid())) { + return IPC_FAIL(this, "invalid endpoint"); + } + + auto bridge = MakeRefPtr<SocketProcessBridgeParent>(aContentProcessId); + MOZ_ALWAYS_TRUE(aEndpoint.Bind(bridge)); + + mSocketProcessBridgeParentMap.InsertOrUpdate(aContentProcessId, + std::move(bridge)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessChild::RecvInitProfiler( + Endpoint<PProfilerChild>&& aEndpoint) { + mProfilerController = + mozilla::ChildProfilerController::Create(std::move(aEndpoint)); + return IPC_OK(); +} + +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) +mozilla::ipc::IPCResult SocketProcessChild::RecvInitSandboxTesting( + Endpoint<PSandboxTestingChild>&& aEndpoint) { + if (!SandboxTestingChild::Initialize(std::move(aEndpoint))) { + return IPC_FAIL( + this, "InitSandboxTesting failed to initialise the child process."); + } + return IPC_OK(); +} +#endif + +mozilla::ipc::IPCResult SocketProcessChild::RecvSocketProcessTelemetryPing() { + const uint32_t kExpectedUintValue = 42; + Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_SOCKET_ONLY_UINT, + kExpectedUintValue); + return IPC_OK(); +} + +void SocketProcessChild::DestroySocketProcessBridgeParent(ProcessId aId) { + MOZ_ASSERT(NS_IsMainThread()); + + mSocketProcessBridgeParentMap.Remove(aId); +} + +PWebrtcTCPSocketChild* SocketProcessChild::AllocPWebrtcTCPSocketChild( + const Maybe<TabId>& tabId) { + // We don't allocate here: instead we always use IPDL constructor that takes + // an existing object + MOZ_ASSERT_UNREACHABLE( + "AllocPWebrtcTCPSocketChild should not be called on" + " socket child"); + return nullptr; +} + +bool SocketProcessChild::DeallocPWebrtcTCPSocketChild( + PWebrtcTCPSocketChild* aActor) { +#ifdef MOZ_WEBRTC + WebrtcTCPSocketChild* child = static_cast<WebrtcTCPSocketChild*>(aActor); + child->ReleaseIPDLReference(); +#endif + return true; +} + +already_AddRefed<PHttpTransactionChild> +SocketProcessChild::AllocPHttpTransactionChild() { + RefPtr<HttpTransactionChild> actor = new HttpTransactionChild(); + return actor.forget(); +} + +already_AddRefed<PHttpConnectionMgrChild> +SocketProcessChild::AllocPHttpConnectionMgrChild( + const HttpHandlerInitArgs& aArgs) { + LOG(("SocketProcessChild::AllocPHttpConnectionMgrChild \n")); + MOZ_ASSERT(gHttpHandler); + gHttpHandler->SetHttpHandlerInitArgs(aArgs); + + RefPtr<HttpConnectionMgrChild> actor = new HttpConnectionMgrChild(); + return actor.forget(); +} + +mozilla::ipc::IPCResult SocketProcessChild::RecvUpdateDeviceModelId( + const nsACString& aModelId) { + MOZ_ASSERT(gHttpHandler); + gHttpHandler->SetDeviceModelId(aModelId); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +SocketProcessChild::RecvOnHttpActivityDistributorActivated( + const bool& aIsActivated) { + if (nsCOMPtr<nsIHttpActivityObserver> distributor = + components::HttpActivityDistributor::Service()) { + distributor->SetIsActive(aIsActivated); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult +SocketProcessChild::RecvOnHttpActivityDistributorObserveProxyResponse( + const bool& aIsEnabled) { + nsCOMPtr<nsIHttpActivityDistributor> distributor = + do_GetService("@mozilla.org/network/http-activity-distributor;1"); + if (distributor) { + Unused << distributor->SetObserveProxyResponse(aIsEnabled); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult +SocketProcessChild::RecvOnHttpActivityDistributorObserveConnection( + const bool& aIsEnabled) { + nsCOMPtr<nsIHttpActivityDistributor> distributor = + do_GetService("@mozilla.org/network/http-activity-distributor;1"); + if (distributor) { + Unused << distributor->SetObserveConnection(aIsEnabled); + } + return IPC_OK(); +} + +already_AddRefed<PInputChannelThrottleQueueChild> +SocketProcessChild::AllocPInputChannelThrottleQueueChild( + const uint32_t& aMeanBytesPerSecond, const uint32_t& aMaxBytesPerSecond) { + RefPtr<InputChannelThrottleQueueChild> p = + new InputChannelThrottleQueueChild(); + p->Init(aMeanBytesPerSecond, aMaxBytesPerSecond); + return p.forget(); +} + +already_AddRefed<PAltSvcTransactionChild> +SocketProcessChild::AllocPAltSvcTransactionChild( + const HttpConnectionInfoCloneArgs& aConnInfo, const uint32_t& aCaps) { + RefPtr<nsHttpConnectionInfo> cinfo = + nsHttpConnectionInfo::DeserializeHttpConnectionInfoCloneArgs(aConnInfo); + RefPtr<AltSvcTransactionChild> child = + new AltSvcTransactionChild(cinfo, aCaps); + return child.forget(); +} + +already_AddRefed<PDNSRequestChild> SocketProcessChild::AllocPDNSRequestChild( + const nsACString& aHost, const nsACString& aTrrServer, const int32_t& aPort, + const uint16_t& aType, const OriginAttributes& aOriginAttributes, + const nsIDNSService::DNSFlags& aFlags) { + RefPtr<DNSRequestHandler> handler = new DNSRequestHandler(); + RefPtr<DNSRequestChild> actor = new DNSRequestChild(handler); + return actor.forget(); +} + +mozilla::ipc::IPCResult SocketProcessChild::RecvPDNSRequestConstructor( + PDNSRequestChild* aActor, const nsACString& aHost, + const nsACString& aTrrServer, const int32_t& aPort, const uint16_t& aType, + const OriginAttributes& aOriginAttributes, + const nsIDNSService::DNSFlags& aFlags) { + RefPtr<DNSRequestChild> actor = static_cast<DNSRequestChild*>(aActor); + RefPtr<DNSRequestHandler> handler = + actor->GetDNSRequest()->AsDNSRequestHandler(); + handler->DoAsyncResolve(aHost, aTrrServer, aPort, aType, aOriginAttributes, + aFlags); + return IPC_OK(); +} + +void SocketProcessChild::AddDataBridgeToMap( + uint64_t aChannelId, BackgroundDataBridgeParent* aActor) { + MutexAutoLock lock(mMutex); + mBackgroundDataBridgeMap.InsertOrUpdate(aChannelId, RefPtr{aActor}); +} + +void SocketProcessChild::RemoveDataBridgeFromMap(uint64_t aChannelId) { + MutexAutoLock lock(mMutex); + mBackgroundDataBridgeMap.Remove(aChannelId); +} + +Maybe<RefPtr<BackgroundDataBridgeParent>> +SocketProcessChild::GetAndRemoveDataBridge(uint64_t aChannelId) { + MutexAutoLock lock(mMutex); + return mBackgroundDataBridgeMap.Extract(aChannelId); +} + +mozilla::ipc::IPCResult SocketProcessChild::RecvClearSessionCache( + ClearSessionCacheResolver&& aResolve) { + nsNSSComponent::DoClearSSLExternalAndInternalSessionCache(); + aResolve(void_t{}); + return IPC_OK(); +} + +already_AddRefed<PTRRServiceChild> SocketProcessChild::AllocPTRRServiceChild( + const bool& aCaptiveIsPassed, const bool& aParentalControlEnabled, + const nsTArray<nsCString>& aDNSSuffixList) { + RefPtr<TRRServiceChild> actor = new TRRServiceChild(); + return actor.forget(); +} + +mozilla::ipc::IPCResult SocketProcessChild::RecvPTRRServiceConstructor( + PTRRServiceChild* aActor, const bool& aCaptiveIsPassed, + const bool& aParentalControlEnabled, nsTArray<nsCString>&& aDNSSuffixList) { + static_cast<TRRServiceChild*>(aActor)->Init( + aCaptiveIsPassed, aParentalControlEnabled, std::move(aDNSSuffixList)); + return IPC_OK(); +} + +already_AddRefed<PNativeDNSResolverOverrideChild> +SocketProcessChild::AllocPNativeDNSResolverOverrideChild() { + RefPtr<NativeDNSResolverOverrideChild> actor = + new NativeDNSResolverOverrideChild(); + return actor.forget(); +} + +mozilla::ipc::IPCResult +SocketProcessChild::RecvPNativeDNSResolverOverrideConstructor( + PNativeDNSResolverOverrideChild* aActor) { + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessChild::RecvNotifyObserver( + const nsACString& aTopic, const nsAString& aData) { + if (nsCOMPtr<nsIObserverService> obs = + mozilla::services::GetObserverService()) { + obs->NotifyObservers(nullptr, PromiseFlatCString(aTopic).get(), + PromiseFlatString(aData).get()); + } + return IPC_OK(); +} + +namespace { + +class DataResolverBase { + public: + // This type is threadsafe-refcounted, as it's referenced on the socket + // thread, but must be destroyed on the main thread. + NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_MAIN_THREAD( + DataResolverBase) + + DataResolverBase() = default; + + protected: + virtual ~DataResolverBase() = default; +}; + +template <typename DataType, typename ResolverType> +class DataResolver final : public DataResolverBase { + public: + explicit DataResolver(ResolverType&& aResolve) + : mResolve(std::move(aResolve)) {} + + void OnResolve(DataType&& aData) { + MOZ_ASSERT(OnSocketThread()); + + RefPtr<DataResolver<DataType, ResolverType>> self = this; + mData = std::move(aData); + NS_DispatchToMainThread(NS_NewRunnableFunction( + "net::DataResolver::OnResolve", + [self{std::move(self)}]() { self->mResolve(std::move(self->mData)); })); + } + + private: + virtual ~DataResolver() = default; + + ResolverType mResolve; + DataType mData; +}; + +} // anonymous namespace + +mozilla::ipc::IPCResult SocketProcessChild::RecvGetSocketData( + GetSocketDataResolver&& aResolve) { + if (!gSocketTransportService) { + aResolve(SocketDataArgs()); + return IPC_OK(); + } + + RefPtr< + DataResolver<SocketDataArgs, SocketProcessChild::GetSocketDataResolver>> + resolver = new DataResolver<SocketDataArgs, + SocketProcessChild::GetSocketDataResolver>( + std::move(aResolve)); + gSocketTransportService->Dispatch( + NS_NewRunnableFunction( + "net::SocketProcessChild::RecvGetSocketData", + [resolver{std::move(resolver)}]() { + SocketDataArgs args; + gSocketTransportService->GetSocketConnections(&args.info()); + args.totalSent() = gSocketTransportService->GetSentBytes(); + args.totalRecv() = gSocketTransportService->GetReceivedBytes(); + resolver->OnResolve(std::move(args)); + }), + NS_DISPATCH_NORMAL); + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessChild::RecvGetDNSCacheEntries( + GetDNSCacheEntriesResolver&& aResolve) { + nsresult rv = NS_OK; + nsCOMPtr<nsIDNSService> dns = + do_GetService("@mozilla.org/network/dns-service;1", &rv); + if (NS_FAILED(rv)) { + aResolve(nsTArray<DNSCacheEntries>()); + return IPC_OK(); + } + + RefPtr<DataResolver<nsTArray<DNSCacheEntries>, + SocketProcessChild::GetDNSCacheEntriesResolver>> + resolver = + new DataResolver<nsTArray<DNSCacheEntries>, + SocketProcessChild::GetDNSCacheEntriesResolver>( + std::move(aResolve)); + gSocketTransportService->Dispatch( + NS_NewRunnableFunction( + "net::SocketProcessChild::RecvGetDNSCacheEntries", + [resolver{std::move(resolver)}, dns{std::move(dns)}]() { + nsTArray<DNSCacheEntries> entries; + dns->GetDNSCacheEntries(&entries); + resolver->OnResolve(std::move(entries)); + }), + NS_DISPATCH_NORMAL); + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessChild::RecvGetHttpConnectionData( + GetHttpConnectionDataResolver&& aResolve) { + if (!gSocketTransportService) { + aResolve(nsTArray<HttpRetParams>()); + return IPC_OK(); + } + + RefPtr<DataResolver<nsTArray<HttpRetParams>, + SocketProcessChild::GetHttpConnectionDataResolver>> + resolver = + new DataResolver<nsTArray<HttpRetParams>, + SocketProcessChild::GetHttpConnectionDataResolver>( + std::move(aResolve)); + gSocketTransportService->Dispatch( + NS_NewRunnableFunction( + "net::SocketProcessChild::RecvGetHttpConnectionData", + [resolver{std::move(resolver)}]() { + nsTArray<HttpRetParams> data; + HttpInfo::GetHttpConnectionData(&data); + resolver->OnResolve(std::move(data)); + }), + NS_DISPATCH_NORMAL); + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessChild::RecvInitProxyAutoConfigChild( + Endpoint<PProxyAutoConfigChild>&& aEndpoint) { + // For parsing PAC. + if (!sInitializedJS) { + JS::DisableJitBackend(); + + // Set all JS::Prefs. + SET_JS_PREFS_FROM_BROWSER_PREFS; + + const char* jsInitFailureReason = JS_InitWithFailureDiagnostic(); + if (jsInitFailureReason) { + MOZ_CRASH_UNSAFE(jsInitFailureReason); + } + sInitializedJS = true; + + xpc::SelfHostedShmem::GetSingleton(); + } + + Unused << ProxyAutoConfigChild::Create(std::move(aEndpoint)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessChild::RecvRecheckIPConnectivity() { + RefPtr<NetworkConnectivityService> ncs = + NetworkConnectivityService::GetSingleton(); + if (ncs) { + ncs->RecheckIPConnectivity(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessChild::RecvRecheckDNS() { + RefPtr<NetworkConnectivityService> ncs = + NetworkConnectivityService::GetSingleton(); + if (ncs) { + ncs->RecheckDNS(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessChild::RecvFlushFOGData( + FlushFOGDataResolver&& aResolver) { + glean::FlushFOGData(std::move(aResolver)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessChild::RecvTestTriggerMetrics( + TestTriggerMetricsResolver&& aResolve) { + mozilla::glean::test_only_ipc::a_counter.Add( + nsIXULRuntime::PROCESS_TYPE_SOCKET); + aResolve(true); + return IPC_OK(); +} + +#if defined(XP_WIN) +mozilla::ipc::IPCResult SocketProcessChild::RecvGetUntrustedModulesData( + GetUntrustedModulesDataResolver&& aResolver) { + RefPtr<DllServices> dllSvc(DllServices::Get()); + dllSvc->GetUntrustedModulesData()->Then( + GetMainThreadSerialEventTarget(), __func__, + [aResolver](Maybe<UntrustedModulesData>&& aData) { + aResolver(std::move(aData)); + }, + [aResolver](nsresult aReason) { aResolver(Nothing()); }); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +SocketProcessChild::RecvUnblockUntrustedModulesThread() { + if (nsCOMPtr<nsIObserverService> obs = + mozilla::services::GetObserverService()) { + obs->NotifyObservers(nullptr, "unblock-untrusted-modules-thread", nullptr); + } + return IPC_OK(); +} +#endif // defined(XP_WIN) + +bool SocketProcessChild::IsShuttingDown() { + MutexAutoLock lock(mMutex); + return mShuttingDown; +} + +void SocketProcessChild::CloseIPCClientCertsActor() { + LOG(("SocketProcessChild::CloseIPCClientCertsActor")); + MOZ_ASSERT(NS_IsMainThread()); + + mSocketThread->Dispatch(NS_NewRunnableFunction( + "CloseIPCClientCertsActor", [self = RefPtr{this}]() { + LOG(("CloseIPCClientCertsActor")); + if (self->mIPCClientCertsChild) { + self->mIPCClientCertsChild->Close(); + self->mIPCClientCertsChild = nullptr; + } + })); +} + +already_AddRefed<psm::IPCClientCertsChild> +SocketProcessChild::GetIPCClientCertsActor() { + LOG(("SocketProcessChild::GetIPCClientCertsActor")); + // Only socket thread can access the mIPCClientCertsChild. + if (!OnSocketThread()) { + return nullptr; + } + + { + MutexAutoLock lock(mMutex); + if (mShuttingDown) { + return nullptr; + } + } + + if (mIPCClientCertsChild) { + RefPtr<psm::IPCClientCertsChild> actorChild = mIPCClientCertsChild; + return actorChild.forget(); + } + + ipc::Endpoint<psm::PIPCClientCertsParent> parentEndpoint; + ipc::Endpoint<psm::PIPCClientCertsChild> childEndpoint; + psm::PIPCClientCerts::CreateEndpoints(&parentEndpoint, &childEndpoint); + + if (NS_FAILED(SocketProcessBackgroundChild::WithActor( + "SendInitIPCClientCerts", + [endpoint = std::move(parentEndpoint)]( + SocketProcessBackgroundChild* aActor) mutable { + Unused << aActor->SendInitIPCClientCerts(std::move(endpoint)); + }))) { + return nullptr; + } + + RefPtr<psm::IPCClientCertsChild> actor = new psm::IPCClientCertsChild(); + if (!childEndpoint.Bind(actor)) { + return nullptr; + } + + mIPCClientCertsChild = actor; + return actor.forget(); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/SocketProcessChild.h b/netwerk/ipc/SocketProcessChild.h new file mode 100644 index 0000000000..fbcac7462d --- /dev/null +++ b/netwerk/ipc/SocketProcessChild.h @@ -0,0 +1,188 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_SocketProcessChild_h +#define mozilla_net_SocketProcessChild_h + +#include "mozilla/net/PSocketProcessChild.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "mozilla/psm/IPCClientCertsChild.h" +#include "mozilla/Mutex.h" +#include "nsRefPtrHashtable.h" +#include "nsTHashMap.h" + +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) +# include "mozilla/PSandboxTestingChild.h" +#endif + +namespace mozilla { +class ChildProfilerController; +} + +namespace mozilla { +namespace net { + +class ProxyAutoConfigChild; +class SocketProcessBackgroundChild; +class SocketProcessBridgeParent; +class BackgroundDataBridgeParent; + +// The IPC actor implements PSocketProcessChild in child process. +// This is allocated and kept alive by SocketProcessImpl. +class SocketProcessChild final : public PSocketProcessChild { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SocketProcessChild, final) + + SocketProcessChild(); + + static SocketProcessChild* GetSingleton(); + + bool Init(mozilla::ipc::UntypedEndpoint&& aEndpoint, + const char* aParentBuildID); + + void ActorDestroy(ActorDestroyReason aWhy) override; + + mozilla::ipc::IPCResult RecvInit( + const SocketPorcessInitAttributes& aAttributes); + mozilla::ipc::IPCResult RecvPreferenceUpdate(const Pref& aPref); + mozilla::ipc::IPCResult RecvRequestMemoryReport( + const uint32_t& generation, const bool& anonymize, + const bool& minimizeMemoryUsage, + const Maybe<mozilla::ipc::FileDescriptor>& DMDFile, + const RequestMemoryReportResolver& aResolver); + mozilla::ipc::IPCResult RecvSetOffline(const bool& aOffline); + mozilla::ipc::IPCResult RecvSetConnectivity(const bool& aConnectivity); + mozilla::ipc::IPCResult RecvInitLinuxSandbox( + const Maybe<ipc::FileDescriptor>& aBrokerFd); + mozilla::ipc::IPCResult RecvInitSocketProcessBridgeParent( + const ProcessId& aContentProcessId, + Endpoint<mozilla::net::PSocketProcessBridgeParent>&& aEndpoint); + mozilla::ipc::IPCResult RecvInitProfiler( + Endpoint<mozilla::PProfilerChild>&& aEndpoint); +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) + mozilla::ipc::IPCResult RecvInitSandboxTesting( + Endpoint<PSandboxTestingChild>&& aEndpoint); +#endif + mozilla::ipc::IPCResult RecvSocketProcessTelemetryPing(); + + PWebrtcTCPSocketChild* AllocPWebrtcTCPSocketChild(const Maybe<TabId>& tabId); + bool DeallocPWebrtcTCPSocketChild(PWebrtcTCPSocketChild* aActor); + + already_AddRefed<PHttpTransactionChild> AllocPHttpTransactionChild(); + + void CleanUp(); + void DestroySocketProcessBridgeParent(ProcessId aId); + + already_AddRefed<PHttpConnectionMgrChild> AllocPHttpConnectionMgrChild( + const HttpHandlerInitArgs& aArgs); + mozilla::ipc::IPCResult RecvUpdateDeviceModelId(const nsACString& aModelId); + mozilla::ipc::IPCResult RecvOnHttpActivityDistributorActivated( + const bool& aIsActivated); + mozilla::ipc::IPCResult RecvOnHttpActivityDistributorObserveProxyResponse( + const bool& aIsEnabled); + mozilla::ipc::IPCResult RecvOnHttpActivityDistributorObserveConnection( + const bool& aIsEnabled); + + already_AddRefed<PInputChannelThrottleQueueChild> + AllocPInputChannelThrottleQueueChild(const uint32_t& aMeanBytesPerSecond, + const uint32_t& aMaxBytesPerSecond); + + already_AddRefed<PAltSvcTransactionChild> AllocPAltSvcTransactionChild( + const HttpConnectionInfoCloneArgs& aConnInfo, const uint32_t& aCaps); + + bool IsShuttingDown(); + + already_AddRefed<PDNSRequestChild> AllocPDNSRequestChild( + const nsACString& aHost, const nsACString& aTrrServer, + const int32_t& aPort, const uint16_t& aType, + const OriginAttributes& aOriginAttributes, + const nsIDNSService::DNSFlags& aFlags); + mozilla::ipc::IPCResult RecvPDNSRequestConstructor( + PDNSRequestChild* aActor, const nsACString& aHost, + const nsACString& aTrrServer, const int32_t& aPort, const uint16_t& aType, + const OriginAttributes& aOriginAttributes, + const nsIDNSService::DNSFlags& aFlags) override; + + void AddDataBridgeToMap(uint64_t aChannelId, + BackgroundDataBridgeParent* aActor); + void RemoveDataBridgeFromMap(uint64_t aChannelId); + Maybe<RefPtr<BackgroundDataBridgeParent>> GetAndRemoveDataBridge( + uint64_t aChannelId); + + mozilla::ipc::IPCResult RecvClearSessionCache( + ClearSessionCacheResolver&& aResolve); + + already_AddRefed<PTRRServiceChild> AllocPTRRServiceChild( + const bool& aCaptiveIsPassed, const bool& aParentalControlEnabled, + const nsTArray<nsCString>& aDNSSuffixList); + mozilla::ipc::IPCResult RecvPTRRServiceConstructor( + PTRRServiceChild* aActor, const bool& aCaptiveIsPassed, + const bool& aParentalControlEnabled, + nsTArray<nsCString>&& aDNSSuffixList) override; + + already_AddRefed<PNativeDNSResolverOverrideChild> + AllocPNativeDNSResolverOverrideChild(); + mozilla::ipc::IPCResult RecvPNativeDNSResolverOverrideConstructor( + PNativeDNSResolverOverrideChild* aActor) override; + + mozilla::ipc::IPCResult RecvNotifyObserver(const nsACString& aTopic, + const nsAString& aData); + + mozilla::ipc::IPCResult RecvGetSocketData(GetSocketDataResolver&& aResolve); + mozilla::ipc::IPCResult RecvGetDNSCacheEntries( + GetDNSCacheEntriesResolver&& aResolve); + mozilla::ipc::IPCResult RecvGetHttpConnectionData( + GetHttpConnectionDataResolver&& aResolve); + + mozilla::ipc::IPCResult RecvInitProxyAutoConfigChild( + Endpoint<PProxyAutoConfigChild>&& aEndpoint); + + mozilla::ipc::IPCResult RecvRecheckIPConnectivity(); + mozilla::ipc::IPCResult RecvRecheckDNS(); + + mozilla::ipc::IPCResult RecvFlushFOGData(FlushFOGDataResolver&& aResolver); + + mozilla::ipc::IPCResult RecvTestTriggerMetrics( + TestTriggerMetricsResolver&& aResolve); + +#if defined(XP_WIN) + mozilla::ipc::IPCResult RecvGetUntrustedModulesData( + GetUntrustedModulesDataResolver&& aResolver); + mozilla::ipc::IPCResult RecvUnblockUntrustedModulesThread(); +#endif // defined(XP_WIN) + + already_AddRefed<psm::IPCClientCertsChild> GetIPCClientCertsActor(); + void CloseIPCClientCertsActor(); + + protected: + friend class SocketProcessImpl; + ~SocketProcessChild(); + + void InitSocketBackground(); + + private: + // Mapping of content process id and the SocketProcessBridgeParent. + // This table keeps SocketProcessBridgeParent alive in socket process. + nsRefPtrHashtable<nsUint32HashKey, SocketProcessBridgeParent> + mSocketProcessBridgeParentMap; + + RefPtr<ChildProfilerController> mProfilerController; + + // Protect the table below. + Mutex mMutex MOZ_UNANNOTATED{"SocketProcessChild::mMutex"}; + nsTHashMap<uint64_t, RefPtr<BackgroundDataBridgeParent>> + mBackgroundDataBridgeMap; + + bool mShuttingDown MOZ_GUARDED_BY(mMutex) = false; + + nsCOMPtr<nsIEventTarget> mSocketThread; + // mIPCClientCertsChild is only accessed on the socket thread. + RefPtr<psm::IPCClientCertsChild> mIPCClientCertsChild; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_SocketProcessChild_h diff --git a/netwerk/ipc/SocketProcessHost.cpp b/netwerk/ipc/SocketProcessHost.cpp new file mode 100644 index 0000000000..5a09f5200c --- /dev/null +++ b/netwerk/ipc/SocketProcessHost.cpp @@ -0,0 +1,292 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "SocketProcessHost.h" + +#include "SocketProcessParent.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/ipc/FileDescriptor.h" +#include "mozilla/ipc/ProcessUtils.h" +#include "nsAppRunner.h" +#include "nsIOService.h" +#include "nsIObserverService.h" +#include "ProfilerParent.h" +#include "nsNetUtil.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/ProcessChild.h" + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) +# include "mozilla/SandboxBroker.h" +# include "mozilla/SandboxBrokerPolicyFactory.h" +# include "mozilla/SandboxSettings.h" +#endif + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +# include "mozilla/Sandbox.h" +#endif + +#if defined(XP_WIN) +# include "mozilla/WinDllServices.h" +#endif + +using namespace mozilla::ipc; + +namespace mozilla { +namespace net { + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +bool SocketProcessHost::sLaunchWithMacSandbox = false; +#endif + +SocketProcessHost::SocketProcessHost(Listener* aListener) + : GeckoChildProcessHost(GeckoProcessType_Socket), + mListener(aListener), + mTaskFactory(Some(this)), + mLaunchPhase(LaunchPhase::Unlaunched), + mShutdownRequested(false), + mChannelClosed(false) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_COUNT_CTOR(SocketProcessHost); +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + if (!sLaunchWithMacSandbox) { + sLaunchWithMacSandbox = + (PR_GetEnv("MOZ_DISABLE_SOCKET_PROCESS_SANDBOX") == nullptr); + } + mDisableOSActivityMode = sLaunchWithMacSandbox; +#endif +} + +SocketProcessHost::~SocketProcessHost() { MOZ_COUNT_DTOR(SocketProcessHost); } + +bool SocketProcessHost::Launch() { + MOZ_ASSERT(mLaunchPhase == LaunchPhase::Unlaunched); + MOZ_ASSERT(!mSocketProcessParent); + MOZ_ASSERT(NS_IsMainThread()); + + std::vector<std::string> extraArgs; + ProcessChild::AddPlatformBuildID(extraArgs); + + SharedPreferenceSerializer prefSerializer; + if (!prefSerializer.SerializeToSharedMemory(GeckoProcessType_VR, + /* remoteType */ ""_ns)) { + return false; + } + prefSerializer.AddSharedPrefCmdLineArgs(*this, extraArgs); + + mLaunchPhase = LaunchPhase::Waiting; + if (!GeckoChildProcessHost::LaunchAndWaitForProcessHandle(extraArgs)) { + mLaunchPhase = LaunchPhase::Complete; + return false; + } + + return true; +} + +static void HandleErrorAfterDestroy( + RefPtr<SocketProcessHost::Listener>&& aListener) { + if (!aListener) { + return; + } + + NS_DispatchToMainThread(NS_NewRunnableFunction( + "HandleErrorAfterDestroy", [listener = std::move(aListener)]() { + listener->OnProcessLaunchComplete(nullptr, false); + })); +} + +void SocketProcessHost::OnChannelConnected(base::ProcessId peer_pid) { + MOZ_ASSERT(!NS_IsMainThread()); + + GeckoChildProcessHost::OnChannelConnected(peer_pid); + + // Post a task to the main thread. Take the lock because mTaskFactory is not + // thread-safe. + RefPtr<Runnable> runnable; + { + MonitorAutoLock lock(mMonitor); + if (!mTaskFactory) { + HandleErrorAfterDestroy(std::move(mListener)); + return; + } + runnable = + (*mTaskFactory) + .NewRunnableMethod(&SocketProcessHost::OnChannelConnectedTask); + } + NS_DispatchToMainThread(runnable); +} + +void SocketProcessHost::OnChannelConnectedTask() { + MOZ_ASSERT(NS_IsMainThread()); + + if (mLaunchPhase == LaunchPhase::Waiting) { + InitAfterConnect(true); + } +} + +void SocketProcessHost::InitAfterConnect(bool aSucceeded) { + MOZ_ASSERT(mLaunchPhase == LaunchPhase::Waiting); + MOZ_ASSERT(!mSocketProcessParent); + MOZ_ASSERT(NS_IsMainThread()); + + mLaunchPhase = LaunchPhase::Complete; + if (!aSucceeded) { + if (mListener) { + mListener->OnProcessLaunchComplete(this, false); + } + return; + } + + mSocketProcessParent = MakeRefPtr<SocketProcessParent>(this); + DebugOnly<bool> rv = TakeInitialEndpoint().Bind(mSocketProcessParent.get()); + MOZ_ASSERT(rv); + + SocketPorcessInitAttributes attributes; + nsCOMPtr<nsIIOService> ioService(do_GetIOService()); + MOZ_ASSERT(ioService, "No IO service?"); + DebugOnly<nsresult> result = ioService->GetOffline(&attributes.mOffline()); + MOZ_ASSERT(NS_SUCCEEDED(result), "Failed getting offline?"); + result = ioService->GetConnectivity(&attributes.mConnectivity()); + MOZ_ASSERT(NS_SUCCEEDED(result), "Failed getting connectivity?"); + + attributes.mInitSandbox() = false; + +#if defined(XP_WIN) + RefPtr<DllServices> dllSvc(DllServices::Get()); + attributes.mIsReadyForBackgroundProcessing() = + dllSvc->IsReadyForBackgroundProcessing(); +#endif + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + if (GetEffectiveSocketProcessSandboxLevel() > 0) { + auto policy = SandboxBrokerPolicyFactory::GetSocketProcessPolicy( + GetActor()->OtherPid()); + if (policy != nullptr) { + attributes.mSandboxBroker() = Some(FileDescriptor()); + mSandboxBroker = + SandboxBroker::Create(std::move(policy), GetActor()->OtherPid(), + attributes.mSandboxBroker().ref()); + // This is unlikely to fail and probably indicates OS resource + // exhaustion. + Unused << NS_WARN_IF(mSandboxBroker == nullptr); + MOZ_ASSERT(attributes.mSandboxBroker().ref().IsValid()); + } + attributes.mInitSandbox() = true; + } +#endif // XP_LINUX && MOZ_SANDBOX + + Unused << GetActor()->SendInit(attributes); + + Unused << GetActor()->SendInitProfiler( + ProfilerParent::CreateForProcess(GetActor()->OtherPid())); + + if (mListener) { + mListener->OnProcessLaunchComplete(this, true); + } +} + +void SocketProcessHost::Shutdown() { + MOZ_ASSERT(!mShutdownRequested); + MOZ_ASSERT(NS_IsMainThread()); + + mListener = nullptr; + + if (mSocketProcessParent) { + // OnChannelClosed uses this to check if the shutdown was expected or + // unexpected. + mShutdownRequested = true; + + // The channel might already be closed if we got here unexpectedly. + if (!mChannelClosed) { + mSocketProcessParent->Close(); + } + + return; + } + + DestroyProcess(); +} + +void SocketProcessHost::OnChannelClosed() { + MOZ_ASSERT(NS_IsMainThread()); + + mChannelClosed = true; + + if (!mShutdownRequested && mListener) { + // This is an unclean shutdown. Notify our listener that we're going away. + mListener->OnProcessUnexpectedShutdown(this); + } else { + DestroyProcess(); + } + + // Release the actor. + SocketProcessParent::Destroy(std::move(mSocketProcessParent)); + MOZ_ASSERT(!mSocketProcessParent); +} + +void SocketProcessHost::DestroyProcess() { + { + MonitorAutoLock lock(mMonitor); + mTaskFactory.reset(); + } + + GetCurrentSerialEventTarget()->Dispatch(NS_NewRunnableFunction( + "DestroySocketProcessRunnable", [this] { Destroy(); })); +} + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +bool SocketProcessHost::FillMacSandboxInfo(MacSandboxInfo& aInfo) { + GeckoChildProcessHost::FillMacSandboxInfo(aInfo); + if (!aInfo.shouldLog && PR_GetEnv("MOZ_SANDBOX_SOCKET_PROCESS_LOGGING")) { + aInfo.shouldLog = true; + } + return true; +} + +/* static */ +MacSandboxType SocketProcessHost::GetMacSandboxType() { + return MacSandboxType_Socket; +} +#endif + +//----------------------------------------------------------------------------- +// SocketProcessMemoryReporter +//----------------------------------------------------------------------------- + +bool SocketProcessMemoryReporter::IsAlive() const { + MOZ_ASSERT(gIOService); + + if (!gIOService->mSocketProcess) { + return false; + } + + return gIOService->mSocketProcess->IsConnected(); +} + +bool SocketProcessMemoryReporter::SendRequestMemoryReport( + const uint32_t& aGeneration, const bool& aAnonymize, + const bool& aMinimizeMemoryUsage, + const Maybe<ipc::FileDescriptor>& aDMDFile) { + MOZ_ASSERT(gIOService); + + if (!gIOService->mSocketProcess) { + return false; + } + + SocketProcessParent* actor = gIOService->mSocketProcess->GetActor(); + if (!actor) { + return false; + } + + return actor->SendRequestMemoryReport(aGeneration, aAnonymize, + aMinimizeMemoryUsage, aDMDFile); +} + +int32_t SocketProcessMemoryReporter::Pid() const { + MOZ_ASSERT(gIOService); + return gIOService->SocketProcessPid(); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/SocketProcessHost.h b/netwerk/ipc/SocketProcessHost.h new file mode 100644 index 0000000000..44648b7fe7 --- /dev/null +++ b/netwerk/ipc/SocketProcessHost.h @@ -0,0 +1,150 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_SocketProcessHost_h +#define mozilla_net_SocketProcessHost_h + +#include "mozilla/Maybe.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/ipc/GeckoChildProcessHost.h" +#include "mozilla/MemoryReportingProcess.h" +#include "mozilla/ipc/TaskFactory.h" + +namespace mozilla { + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) +class SandboxBroker; +#endif + +namespace net { + +class SocketProcessParent; + +// SocketProcessHost is the "parent process" container for a subprocess handle +// and IPC connection. It owns the parent process IPDL actor, which in this +// case, is a SocketProcessParent. +// SocketProcessHost is allocated and managed by nsIOService in parent process. +class SocketProcessHost final : public mozilla::ipc::GeckoChildProcessHost { + friend class SocketProcessParent; + + public: + class Listener { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Listener) + + // Called when the process of launching the process is complete. + virtual void OnProcessLaunchComplete(SocketProcessHost* aHost, + bool aSucceeded) = 0; + + // Called when the channel is closed but Shutdown() is not invoked. + virtual void OnProcessUnexpectedShutdown(SocketProcessHost* aHost) = 0; + + protected: + virtual ~Listener() = default; + }; + + explicit SocketProcessHost(Listener* listener); + + // Launch the socket process asynchronously. + // The OnProcessLaunchComplete listener callback will be invoked + // either when a connection has been established, or if a connection + // could not be established due to an asynchronous error. + bool Launch(); + + // Inform the socket process that it should clean up its resources and shut + // down. This initiates an asynchronous shutdown sequence. After this method + // returns, it is safe for the caller to forget its pointer to the + // SocketProcessHost. + void Shutdown(); + + // Return the actor for the top-level actor of the process. Return null if + // the process is not connected. + SocketProcessParent* GetActor() const { + MOZ_ASSERT(NS_IsMainThread()); + + return mSocketProcessParent.get(); + } + + bool IsConnected() const { + MOZ_ASSERT(NS_IsMainThread()); + + return !!mSocketProcessParent; + } + + // Called on the IO thread. + void OnChannelConnected(base::ProcessId peer_pid) override; + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + // Return the sandbox type to be used with this process type. + static MacSandboxType GetMacSandboxType(); +#endif + + private: + ~SocketProcessHost(); + + // Called on the main thread. + void OnChannelConnectedTask(); + + // Called on the main thread after a connection has been established. + void InitAfterConnect(bool aSucceeded); + + // Called on the main thread when the mSocketParent actor is shutting down. + void OnChannelClosed(); + + void DestroyProcess(); + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + static bool sLaunchWithMacSandbox; + + // Sandbox the Socket process at launch for all instances + bool IsMacSandboxLaunchEnabled() override { return sLaunchWithMacSandbox; } + + // Override so we can turn on Socket process-specific sandbox logging + bool FillMacSandboxInfo(MacSandboxInfo& aInfo) override; +#endif + + DISALLOW_COPY_AND_ASSIGN(SocketProcessHost); + + RefPtr<Listener> mListener; + mozilla::Maybe<mozilla::ipc::TaskFactory<SocketProcessHost>> mTaskFactory; + + enum class LaunchPhase { Unlaunched, Waiting, Complete }; + LaunchPhase mLaunchPhase; + + RefPtr<SocketProcessParent> mSocketProcessParent; + // mShutdownRequested is set to true only when Shutdown() is called. + // If mShutdownRequested is false and the IPC channel is closed, + // OnProcessUnexpectedShutdown will be invoked. + bool mShutdownRequested; + bool mChannelClosed; + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + UniquePtr<SandboxBroker> mSandboxBroker; +#endif +}; + +class SocketProcessMemoryReporter : public MemoryReportingProcess { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SocketProcessMemoryReporter, override) + + SocketProcessMemoryReporter() = default; + + bool IsAlive() const override; + + bool SendRequestMemoryReport( + const uint32_t& aGeneration, const bool& aAnonymize, + const bool& aMinimizeMemoryUsage, + const Maybe<mozilla::ipc::FileDescriptor>& aDMDFile) override; + + int32_t Pid() const override; + + protected: + virtual ~SocketProcessMemoryReporter() = default; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_SocketProcessHost_h diff --git a/netwerk/ipc/SocketProcessImpl.cpp b/netwerk/ipc/SocketProcessImpl.cpp new file mode 100644 index 0000000000..a863d4a875 --- /dev/null +++ b/netwerk/ipc/SocketProcessImpl.cpp @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "SocketProcessImpl.h" + +#include "base/command_line.h" +#include "base/shared_memory.h" +#include "base/string_util.h" +#include "mozilla/BackgroundHangMonitor.h" +#include "mozilla/Preferences.h" +#include "mozilla/GeckoArgs.h" +#include "mozilla/ipc/ProcessUtils.h" +#include "mozilla/ipc/IOThreadChild.h" + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +# include "mozilla/sandboxTarget.h" +#elif defined(__OpenBSD__) && defined(MOZ_SANDBOX) +# include "mozilla/SandboxSettings.h" +# include "prlink.h" +#endif + +#ifdef XP_UNIX +# include <unistd.h> // For sleep(). +#endif + +using mozilla::ipc::IOThreadChild; + +namespace mozilla { +namespace net { + +LazyLogModule gSocketProcessLog("socketprocess"); + +SocketProcessImpl::~SocketProcessImpl() = default; + +bool SocketProcessImpl::Init(int aArgc, char* aArgv[]) { +#ifdef XP_UNIX + if (PR_GetEnv("MOZ_DEBUG_SOCKET_PROCESS")) { + printf_stderr("\n\nSOCKETPROCESSnSOCKETPROCESS\n debug me @ %d\n\n", + base::GetCurrentProcId()); + sleep(30); + } +#endif +#if defined(MOZ_SANDBOX) && defined(XP_WIN) + LoadLibraryW(L"nss3.dll"); + LoadLibraryW(L"softokn3.dll"); + LoadLibraryW(L"freebl3.dll"); + LoadLibraryW(L"ipcclientcerts.dll"); + LoadLibraryW(L"winmm.dll"); + mozilla::SandboxTarget::Instance()->StartSandbox(); +#elif defined(__OpenBSD__) && defined(MOZ_SANDBOX) + PR_LoadLibrary("libnss3.so"); + PR_LoadLibrary("libsoftokn3.so"); + PR_LoadLibrary("libfreebl3.so"); + PR_LoadLibrary("libipcclientcerts.so"); + StartOpenBSDSandbox(GeckoProcessType_Socket); +#endif + + Maybe<const char*> parentBuildID = + geckoargs::sParentBuildID.Get(aArgc, aArgv); + if (parentBuildID.isNothing()) { + return false; + } + + if (!ProcessChild::InitPrefs(aArgc, aArgv)) { + return false; + } + + return mSocketProcessChild->Init(TakeInitialEndpoint(), *parentBuildID); +} + +void SocketProcessImpl::CleanUp() { mSocketProcessChild->CleanUp(); } + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/SocketProcessImpl.h b/netwerk/ipc/SocketProcessImpl.h new file mode 100644 index 0000000000..c471ca2834 --- /dev/null +++ b/netwerk/ipc/SocketProcessImpl.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_SocketProcessImpl_h +#define mozilla_net_SocketProcessImpl_h + +#include "mozilla/ipc/ProcessChild.h" +#include "SocketProcessChild.h" + +namespace mozilla { +namespace net { + +// This class owns the subprocess instance of socket child process. +// It is instantiated as a singleton in XRE_InitChildProcess. +class SocketProcessImpl final : public mozilla::ipc::ProcessChild { + protected: + using ProcessChild = mozilla::ipc::ProcessChild; + + public: + using ProcessChild::ProcessChild; + ~SocketProcessImpl(); + + bool Init(int aArgc, char* aArgv[]) override; + void CleanUp() override; + + private: + RefPtr<SocketProcessChild> mSocketProcessChild = new SocketProcessChild; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_SocketProcessImpl_h diff --git a/netwerk/ipc/SocketProcessLogging.h b/netwerk/ipc/SocketProcessLogging.h new file mode 100644 index 0000000000..169de4986d --- /dev/null +++ b/netwerk/ipc/SocketProcessLogging.h @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_SocketProcessLogging_h +#define mozilla_SocketProcessLogging_h + +#include "mozilla/Logging.h" + +namespace mozilla { +namespace net { +extern LazyLogModule gSocketProcessLog; +} +} // namespace mozilla + +#define LOG(msg) MOZ_LOG(gSocketProcessLog, mozilla::LogLevel::Debug, msg) +#define LOG_ENABLED() MOZ_LOG_TEST(gSocketProcessLog, mozilla::LogLevel::Debug) + +#endif // mozilla_SocketProcessLogging_h diff --git a/netwerk/ipc/SocketProcessParent.cpp b/netwerk/ipc/SocketProcessParent.cpp new file mode 100644 index 0000000000..6b1ccfb1ad --- /dev/null +++ b/netwerk/ipc/SocketProcessParent.cpp @@ -0,0 +1,364 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "SocketProcessParent.h" +#include "SocketProcessLogging.h" + +#include "AltServiceParent.h" +#include "CachePushChecker.h" +#include "HttpTransactionParent.h" +#include "SocketProcessHost.h" +#include "TLSClientAuthCertSelection.h" +#include "mozilla/Components.h" +#include "mozilla/dom/MemoryReportRequest.h" +#include "mozilla/FOGIPC.h" +#include "mozilla/net/DNSRequestParent.h" +#include "mozilla/net/ProxyConfigLookupParent.h" +#include "mozilla/net/SocketProcessBackgroundParent.h" +#include "mozilla/RemoteLazyInputStreamParent.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TelemetryIPC.h" +#include "nsIConsoleService.h" +#include "nsIHttpActivityObserver.h" +#include "nsIObserverService.h" +#include "nsNSSCertificate.h" +#include "nsNSSComponent.h" +#include "nsIOService.h" +#include "nsHttpHandler.h" +#include "nsHttpConnectionInfo.h" +#include "secerr.h" +#ifdef MOZ_WEBRTC +# include "mozilla/dom/ContentProcessManager.h" +# include "mozilla/dom/BrowserParent.h" +# include "mozilla/net/WebrtcTCPSocketParent.h" +#endif +#if defined(MOZ_WIDGET_ANDROID) +# include "mozilla/java/GeckoProcessManagerWrappers.h" +# include "mozilla/java/GeckoProcessTypeWrappers.h" +#endif // defined(MOZ_WIDGET_ANDROID) +#if defined(XP_WIN) +# include "mozilla/WinDllServices.h" +#endif + +namespace mozilla { +namespace net { + +static SocketProcessParent* sSocketProcessParent; + +SocketProcessParent::SocketProcessParent(SocketProcessHost* aHost) + : mHost(aHost) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mHost); + + MOZ_COUNT_CTOR(SocketProcessParent); + sSocketProcessParent = this; +} + +SocketProcessParent::~SocketProcessParent() { + MOZ_ASSERT(NS_IsMainThread()); + + MOZ_COUNT_DTOR(SocketProcessParent); + sSocketProcessParent = nullptr; +} + +/* static */ +SocketProcessParent* SocketProcessParent::GetSingleton() { + MOZ_ASSERT(NS_IsMainThread()); + + return sSocketProcessParent; +} + +void SocketProcessParent::ActorDestroy(ActorDestroyReason aWhy) { +#if defined(MOZ_WIDGET_ANDROID) + nsCOMPtr<nsIEventTarget> launcherThread(ipc::GetIPCLauncher()); + MOZ_ASSERT(launcherThread); + + auto procType = java::GeckoProcessType::SOCKET(); + auto selector = + java::GeckoProcessManager::Selector::New(procType, OtherPid()); + + launcherThread->Dispatch(NS_NewRunnableFunction( + "SocketProcessParent::ActorDestroy", + [selector = java::GeckoProcessManager::Selector::GlobalRef(selector)]() { + java::GeckoProcessManager::ShutdownProcess(selector); + })); +#endif // defined(MOZ_WIDGET_ANDROID) + + if (aWhy == AbnormalShutdown) { + GenerateCrashReport(OtherPid()); + MaybeTerminateProcess(); + } + + if (mHost) { + mHost->OnChannelClosed(); + } +} + +bool SocketProcessParent::SendRequestMemoryReport( + const uint32_t& aGeneration, const bool& aAnonymize, + const bool& aMinimizeMemoryUsage, + const Maybe<ipc::FileDescriptor>& aDMDFile) { + mMemoryReportRequest = MakeUnique<dom::MemoryReportRequestHost>(aGeneration); + + PSocketProcessParent::SendRequestMemoryReport( + aGeneration, aAnonymize, aMinimizeMemoryUsage, aDMDFile, + [&](const uint32_t& aGeneration2) { + MOZ_ASSERT(gIOService); + if (!gIOService->SocketProcess()) { + return; + } + SocketProcessParent* actor = gIOService->SocketProcess()->GetActor(); + if (!actor) { + return; + } + if (actor->mMemoryReportRequest) { + actor->mMemoryReportRequest->Finish(aGeneration2); + actor->mMemoryReportRequest = nullptr; + } + }, + [&](mozilla::ipc::ResponseRejectReason) { + MOZ_ASSERT(gIOService); + if (!gIOService->SocketProcess()) { + return; + } + SocketProcessParent* actor = gIOService->SocketProcess()->GetActor(); + if (!actor) { + return; + } + actor->mMemoryReportRequest = nullptr; + }); + + return true; +} + +mozilla::ipc::IPCResult SocketProcessParent::RecvAddMemoryReport( + const MemoryReport& aReport) { + if (mMemoryReportRequest) { + mMemoryReportRequest->RecvReport(aReport); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessParent::RecvAccumulateChildHistograms( + nsTArray<HistogramAccumulation>&& aAccumulations) { + TelemetryIPC::AccumulateChildHistograms(Telemetry::ProcessID::Socket, + aAccumulations); + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessParent::RecvAccumulateChildKeyedHistograms( + nsTArray<KeyedHistogramAccumulation>&& aAccumulations) { + TelemetryIPC::AccumulateChildKeyedHistograms(Telemetry::ProcessID::Socket, + aAccumulations); + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessParent::RecvUpdateChildScalars( + nsTArray<ScalarAction>&& aScalarActions) { + TelemetryIPC::UpdateChildScalars(Telemetry::ProcessID::Socket, + aScalarActions); + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessParent::RecvUpdateChildKeyedScalars( + nsTArray<KeyedScalarAction>&& aScalarActions) { + TelemetryIPC::UpdateChildKeyedScalars(Telemetry::ProcessID::Socket, + aScalarActions); + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessParent::RecvRecordChildEvents( + nsTArray<mozilla::Telemetry::ChildEventData>&& aEvents) { + TelemetryIPC::RecordChildEvents(Telemetry::ProcessID::Socket, aEvents); + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessParent::RecvRecordDiscardedData( + const mozilla::Telemetry::DiscardedData& aDiscardedData) { + TelemetryIPC::RecordDiscardedData(Telemetry::ProcessID::Socket, + aDiscardedData); + return IPC_OK(); +} + +PWebrtcTCPSocketParent* SocketProcessParent::AllocPWebrtcTCPSocketParent( + const Maybe<TabId>& aTabId) { +#ifdef MOZ_WEBRTC + WebrtcTCPSocketParent* parent = new WebrtcTCPSocketParent(aTabId); + parent->AddRef(); + return parent; +#else + return nullptr; +#endif +} + +bool SocketProcessParent::DeallocPWebrtcTCPSocketParent( + PWebrtcTCPSocketParent* aActor) { +#ifdef MOZ_WEBRTC + WebrtcTCPSocketParent* parent = static_cast<WebrtcTCPSocketParent*>(aActor); + parent->Release(); +#endif + return true; +} + +already_AddRefed<PDNSRequestParent> SocketProcessParent::AllocPDNSRequestParent( + const nsACString& aHost, const nsACString& aTrrServer, const int32_t& port, + const uint16_t& aType, const OriginAttributes& aOriginAttributes, + const nsIDNSService::DNSFlags& aFlags) { + RefPtr<DNSRequestHandler> handler = new DNSRequestHandler(); + RefPtr<DNSRequestParent> actor = new DNSRequestParent(handler); + return actor.forget(); +} + +mozilla::ipc::IPCResult SocketProcessParent::RecvPDNSRequestConstructor( + PDNSRequestParent* aActor, const nsACString& aHost, + const nsACString& aTrrServer, const int32_t& port, const uint16_t& aType, + const OriginAttributes& aOriginAttributes, + const nsIDNSService::DNSFlags& aFlags) { + RefPtr<DNSRequestParent> actor = static_cast<DNSRequestParent*>(aActor); + RefPtr<DNSRequestHandler> handler = + actor->GetDNSRequest()->AsDNSRequestHandler(); + handler->DoAsyncResolve(aHost, aTrrServer, port, aType, aOriginAttributes, + aFlags); + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessParent::RecvObserveHttpActivity( + const HttpActivityArgs& aArgs, const uint32_t& aActivityType, + const uint32_t& aActivitySubtype, const PRTime& aTimestamp, + const uint64_t& aExtraSizeData, const nsACString& aExtraStringData) { + nsCOMPtr<nsIHttpActivityDistributor> activityDistributor = + components::HttpActivityDistributor::Service(); + MOZ_ASSERT(activityDistributor); + + Unused << activityDistributor->ObserveActivityWithArgs( + aArgs, aActivityType, aActivitySubtype, aTimestamp, aExtraSizeData, + aExtraStringData); + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessParent::RecvInitSocketBackground( + Endpoint<PSocketProcessBackgroundParent>&& aEndpoint) { + if (!aEndpoint.IsValid()) { + return IPC_FAIL(this, "Invalid endpoint"); + } + + nsCOMPtr<nsISerialEventTarget> transportQueue; + if (NS_FAILED(NS_CreateBackgroundTaskQueue("SocketBackgroundParentQueue", + getter_AddRefs(transportQueue)))) { + return IPC_FAIL(this, "NS_CreateBackgroundTaskQueue failed"); + } + + transportQueue->Dispatch( + NS_NewRunnableFunction("BindSocketBackgroundParent", + [endpoint = std::move(aEndpoint)]() mutable { + RefPtr<SocketProcessBackgroundParent> parent = + new SocketProcessBackgroundParent(); + endpoint.Bind(parent); + })); + return IPC_OK(); +} + +already_AddRefed<PAltServiceParent> +SocketProcessParent::AllocPAltServiceParent() { + RefPtr<AltServiceParent> actor = new AltServiceParent(); + return actor.forget(); +} + +already_AddRefed<PProxyConfigLookupParent> +SocketProcessParent::AllocPProxyConfigLookupParent( + nsIURI* aURI, const uint32_t& aProxyResolveFlags) { + RefPtr<ProxyConfigLookupParent> actor = + new ProxyConfigLookupParent(aURI, aProxyResolveFlags); + return actor.forget(); +} + +mozilla::ipc::IPCResult SocketProcessParent::RecvPProxyConfigLookupConstructor( + PProxyConfigLookupParent* aActor, nsIURI* aURI, + const uint32_t& aProxyResolveFlags) { + static_cast<ProxyConfigLookupParent*>(aActor)->DoProxyLookup(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessParent::RecvCachePushCheck( + nsIURI* aPushedURL, OriginAttributes&& aOriginAttributes, + nsCString&& aRequestString, CachePushCheckResolver&& aResolver) { + RefPtr<CachePushChecker> checker = new CachePushChecker( + aPushedURL, aOriginAttributes, aRequestString, aResolver); + if (NS_FAILED(checker->DoCheck())) { + aResolver(false); + } + return IPC_OK(); +} + +// To ensure that IPDL is finished before SocketParent gets deleted. +class DeferredDeleteSocketProcessParent : public Runnable { + public: + explicit DeferredDeleteSocketProcessParent( + RefPtr<SocketProcessParent>&& aParent) + : Runnable("net::DeferredDeleteSocketProcessParent"), + mParent(std::move(aParent)) {} + + NS_IMETHODIMP Run() override { return NS_OK; } + + private: + RefPtr<SocketProcessParent> mParent; +}; + +/* static */ +void SocketProcessParent::Destroy(RefPtr<SocketProcessParent>&& aParent) { + NS_DispatchToMainThread( + new DeferredDeleteSocketProcessParent(std::move(aParent))); +} + +mozilla::ipc::IPCResult SocketProcessParent::RecvExcludeHttp2OrHttp3( + const HttpConnectionInfoCloneArgs& aArgs) { + RefPtr<nsHttpConnectionInfo> cinfo = + nsHttpConnectionInfo::DeserializeHttpConnectionInfoCloneArgs(aArgs); + if (!cinfo) { + MOZ_ASSERT(false, "failed to deserizlize http connection info"); + return IPC_OK(); + } + + if (cinfo->IsHttp3()) { + gHttpHandler->ExcludeHttp3(cinfo); + } else { + gHttpHandler->ExcludeHttp2(cinfo); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessParent::RecvOnConsoleMessage( + const nsString& aMessage) { + nsCOMPtr<nsIConsoleService> consoleService = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (consoleService) { + consoleService->LogStringMessage(aMessage.get()); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessParent::RecvFOGData(ByteBuf&& aBuf) { + glean::FOGData(std::move(aBuf)); + return IPC_OK(); +} + +#if defined(XP_WIN) +mozilla::ipc::IPCResult SocketProcessParent::RecvGetModulesTrust( + ModulePaths&& aModPaths, bool aRunAtNormalPriority, + GetModulesTrustResolver&& aResolver) { + RefPtr<DllServices> dllSvc(DllServices::Get()); + dllSvc->GetModulesTrust(std::move(aModPaths), aRunAtNormalPriority) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [aResolver](ModulesMapResult&& aResult) { + aResolver(Some(ModulesMapResult(std::move(aResult)))); + }, + [aResolver](nsresult aRv) { aResolver(Nothing()); }); + return IPC_OK(); +} +#endif // defined(XP_WIN) + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/SocketProcessParent.h b/netwerk/ipc/SocketProcessParent.h new file mode 100644 index 0000000000..732e431e35 --- /dev/null +++ b/netwerk/ipc/SocketProcessParent.h @@ -0,0 +1,117 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_SocketProcessParent_h +#define mozilla_net_SocketProcessParent_h + +#include "mozilla/UniquePtr.h" +#include "mozilla/ipc/CrashReporterHelper.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "mozilla/net/PSocketProcessParent.h" + +namespace mozilla { + +namespace dom { +class MemoryReport; +class MemoryReportRequestHost; +} // namespace dom + +namespace net { + +class SocketProcessHost; + +// IPC actor of socket process in parent process. This is allocated and managed +// by SocketProcessHost. +class SocketProcessParent final + : public PSocketProcessParent, + public ipc::CrashReporterHelper<GeckoProcessType_Socket> { + public: + friend class SocketProcessHost; + + NS_INLINE_DECL_REFCOUNTING(SocketProcessParent, final) + + explicit SocketProcessParent(SocketProcessHost* aHost); + + static SocketProcessParent* GetSingleton(); + + mozilla::ipc::IPCResult RecvAddMemoryReport(const MemoryReport& aReport); + mozilla::ipc::IPCResult RecvAccumulateChildHistograms( + nsTArray<HistogramAccumulation>&& aAccumulations); + mozilla::ipc::IPCResult RecvAccumulateChildKeyedHistograms( + nsTArray<KeyedHistogramAccumulation>&& aAccumulations); + mozilla::ipc::IPCResult RecvUpdateChildScalars( + nsTArray<ScalarAction>&& aScalarActions); + mozilla::ipc::IPCResult RecvUpdateChildKeyedScalars( + nsTArray<KeyedScalarAction>&& aScalarActions); + mozilla::ipc::IPCResult RecvRecordChildEvents( + nsTArray<ChildEventData>&& events); + mozilla::ipc::IPCResult RecvRecordDiscardedData( + const DiscardedData& aDiscardedData); + + PWebrtcTCPSocketParent* AllocPWebrtcTCPSocketParent( + const Maybe<TabId>& aTabId); + bool DeallocPWebrtcTCPSocketParent(PWebrtcTCPSocketParent* aActor); + already_AddRefed<PDNSRequestParent> AllocPDNSRequestParent( + const nsACString& aHost, const nsACString& aTrrServer, + const int32_t& port, const uint16_t& aType, + const OriginAttributes& aOriginAttributes, + const nsIDNSService::DNSFlags& aFlags); + virtual mozilla::ipc::IPCResult RecvPDNSRequestConstructor( + PDNSRequestParent* actor, const nsACString& aHost, + const nsACString& trrServer, const int32_t& port, const uint16_t& type, + const OriginAttributes& aOriginAttributes, + const nsIDNSService::DNSFlags& flags) override; + + void ActorDestroy(ActorDestroyReason aWhy) override; + bool SendRequestMemoryReport(const uint32_t& aGeneration, + const bool& aAnonymize, + const bool& aMinimizeMemoryUsage, + const Maybe<ipc::FileDescriptor>& aDMDFile); + + mozilla::ipc::IPCResult RecvObserveHttpActivity( + const HttpActivityArgs& aArgs, const uint32_t& aActivityType, + const uint32_t& aActivitySubtype, const PRTime& aTimestamp, + const uint64_t& aExtraSizeData, const nsACString& aExtraStringData); + + mozilla::ipc::IPCResult RecvInitSocketBackground( + Endpoint<PSocketProcessBackgroundParent>&& aEndpoint); + + already_AddRefed<PAltServiceParent> AllocPAltServiceParent(); + + already_AddRefed<PProxyConfigLookupParent> AllocPProxyConfigLookupParent( + nsIURI* aURI, const uint32_t& aProxyResolveFlags); + mozilla::ipc::IPCResult RecvPProxyConfigLookupConstructor( + PProxyConfigLookupParent* aActor, nsIURI* aURI, + const uint32_t& aProxyResolveFlags) override; + + mozilla::ipc::IPCResult RecvCachePushCheck( + nsIURI* aPushedURL, OriginAttributes&& aOriginAttributes, + nsCString&& aRequestString, CachePushCheckResolver&& aResolver); + + mozilla::ipc::IPCResult RecvExcludeHttp2OrHttp3( + const HttpConnectionInfoCloneArgs& aArgs); + mozilla::ipc::IPCResult RecvOnConsoleMessage(const nsString& aMessage); + + mozilla::ipc::IPCResult RecvFOGData(ByteBuf&& aBuf); + +#if defined(XP_WIN) + mozilla::ipc::IPCResult RecvGetModulesTrust( + ModulePaths&& aModPaths, bool aRunAtNormalPriority, + GetModulesTrustResolver&& aResolver); +#endif // defined(XP_WIN) + + private: + ~SocketProcessParent(); + + SocketProcessHost* mHost; + UniquePtr<dom::MemoryReportRequestHost> mMemoryReportRequest; + + static void Destroy(RefPtr<SocketProcessParent>&& aParent); +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_SocketProcessParent_h diff --git a/netwerk/ipc/moz.build b/netwerk/ipc/moz.build new file mode 100644 index 0000000000..ee87862785 --- /dev/null +++ b/netwerk/ipc/moz.build @@ -0,0 +1,117 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +EXPORTS.mozilla.net += [ + "ChannelEventQueue.h", + "DocumentChannel.h", + "DocumentChannelChild.h", + "DocumentChannelParent.h", + "DocumentLoadListener.h", + "InputChannelThrottleQueueChild.h", + "InputChannelThrottleQueueParent.h", + "NeckoChild.h", + "NeckoCommon.h", + "NeckoMessageUtils.h", + "NeckoParent.h", + "NeckoTargetHolder.h", + "ParentChannelWrapper.h", + "ParentProcessDocumentChannel.h", + "ProxyAutoConfigChild.h", + "ProxyAutoConfigParent.h", + "ProxyConfigLookup.h", + "ProxyConfigLookupChild.h", + "ProxyConfigLookupParent.h", + "SocketProcessBackgroundChild.h", + "SocketProcessBackgroundParent.h", + "SocketProcessBridgeChild.h", + "SocketProcessBridgeParent.h", + "SocketProcessChild.h", + "SocketProcessHost.h", + "SocketProcessImpl.h", + "SocketProcessParent.h", +] + +UNIFIED_SOURCES += [ + "ChannelEventQueue.cpp", + "DocumentChannel.cpp", + "DocumentChannelChild.cpp", + "DocumentChannelParent.cpp", + "DocumentLoadListener.cpp", + "InputChannelThrottleQueueChild.cpp", + "InputChannelThrottleQueueParent.cpp", + "NeckoChild.cpp", + "NeckoCommon.cpp", + "NeckoParent.cpp", + "NeckoTargetHolder.cpp", + "ParentChannelWrapper.cpp", + "ParentProcessDocumentChannel.cpp", + "ProxyConfigLookup.cpp", + "ProxyConfigLookupChild.cpp", + "ProxyConfigLookupParent.cpp", + "SocketProcessBackgroundChild.cpp", + "SocketProcessBackgroundParent.cpp", + "SocketProcessBridgeChild.cpp", + "SocketProcessBridgeParent.cpp", + "SocketProcessChild.cpp", + "SocketProcessHost.cpp", + "SocketProcessImpl.cpp", + "SocketProcessParent.cpp", +] + +SOURCES += [ + "ProxyAutoConfigChild.cpp", + "ProxyAutoConfigParent.cpp", +] + + +PREPROCESSED_IPDL_SOURCES += [ + "PNecko.ipdl", + "PSocketProcess.ipdl", + "PSocketProcessBridge.ipdl", +] + +IPDL_SOURCES = [ + "NeckoChannelParams.ipdlh", + "PDataChannel.ipdl", + "PDocumentChannel.ipdl", + "PFileChannel.ipdl", + "PInputChannelThrottleQueue.ipdl", + "PProxyAutoConfig.ipdl", + "PProxyConfigLookup.ipdl", + "PSimpleChannel.ipdl", + "PSocketProcessBackground.ipdl", +] + +# needed so --disable-webrtc builds work (yes, a bit messy) +if not CONFIG["MOZ_WEBRTC"]: + IPDL_SOURCES += [ + "../../dom/media/webrtc/transport/ipc/PStunAddrsRequest.ipdl", + "../../dom/media/webrtc/transport/ipc/PWebrtcTCPSocket.ipdl", + "../../dom/media/webrtc/transport/ipc/WebrtcProxyConfig.ipdlh", + ] + EXPORTS.mozilla.net += [ + "../../dom/media/webrtc/transport/ipc/NrIceStunAddrMessageUtils.h", + "../../dom/media/webrtc/transport/ipc/PStunAddrsParams.h", + ] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +LOCAL_INCLUDES += [ + "/caps", + "/dom/base", + "/dom/media/webrtc/transport", + "/media/webrtc", + "/modules/libjar", + "/netwerk/base", + "/netwerk/protocol/http", + "/security/manager/ssl", + "/xpcom/threads", +] + +# Add libFuzzer configuration directives +include("/tools/fuzzing/libfuzzer-config.mozbuild") |