diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /netwerk/ipc | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
56 files changed, 11528 insertions, 0 deletions
diff --git a/netwerk/ipc/ChannelEventQueue.cpp b/netwerk/ipc/ChannelEventQueue.cpp new file mode 100644 index 0000000000..0aa4cf71f4 --- /dev/null +++ b/netwerk/ipc/ChannelEventQueue.cpp @@ -0,0 +1,218 @@ +/* -*- 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(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; + }; + + // 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; + } + + 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..75df2d15a0 --- /dev/null +++ b/netwerk/ipc/ChannelEventQueue.h @@ -0,0 +1,354 @@ +/* -*- 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(GetMainThreadEventTarget()); + } +}; + +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. + +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(); + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + bool IsEmpty() const { return mEventQueue.IsEmpty(); } +#endif + + private: + // Private destructor, to discourage deletion outside of Release(): + ~ChannelEventQueue() = default; + + void SuspendInternal(); + void ResumeInternal(); + + bool MaybeSuspendIfEventsAreSuppressed(); + + inline void MaybeFlushQueue(); + void FlushQueue(); + inline void CompleteResume(); + + ChannelEvent* TakeEvent(); + + nsTArray<UniquePtr<ChannelEvent>> mEventQueue; + + uint32_t mSuspendCount; + bool mSuspended; + uint32_t mForcedCount; // Support ForcedQueueing on multiple thread. + bool mFlushing; + + // Whether the queue is associated with an XHR. This is lazily instantiated + // the first time it is needed. + bool mHasCheckedForXMLHttpRequest; + bool mForXMLHttpRequest; + + // Keep ptr to avoid refcount cycle: only grab ref during flushing. + nsISupports* mOwner; + + // For atomic mEventQueue operation and state update + Mutex mMutex; + + // To guarantee event execution order among threads + RecursiveMutex mRunningMutex; + + 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(mOwner); + Unused << kungFuDeathGrip; // Not used in this function + + // 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); + + bool enqueue = !!mForcedCount || mSuspended || mFlushing || + !mEventQueue.IsEmpty() || + MaybeSuspendIfEventsAreSuppressed(); + + 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. + SuspendInternal(); + mEventQueue.AppendElement(std::move(event)); + ResumeInternal(); + return; + } + } + + MOZ_RELEASE_ASSERT(!aAssertionWhenNotQueued); + 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), 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..4dfa0eddf8 --- /dev/null +++ b/netwerk/ipc/DocumentChannel.cpp @@ -0,0 +1,468 @@ +/* -*- 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 aIsXFOError) + : mAsyncOpenTime(TimeStamp::Now()), + mLoadState(aLoadState), + mCacheKey(aCacheKey), + mLoadFlags(aLoadFlags), + mURI(aLoadState->URI()), + mLoadInfo(aLoadInfo), + mUriModified(aUriModified), + mIsXFOError(aIsXFOError) { + 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); +} + +// Changes here should also be made in +// E10SUtils.documentChannelPermittedForURI(). +static bool URIUsesDocChannel(nsIURI* aURI) { + if (SchemeIsJavascript(aURI)) { + return false; + } + + nsCString spec = aURI->GetSpecOrDefault(); + return !spec.EqualsLiteral("about:printpreview") && + !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 aIsXFOError) { + RefPtr<DocumentChannel> channel; + if (XRE_IsContentProcess()) { + channel = new DocumentChannelChild(aLoadState, aLoadInfo, aLoadFlags, + aCacheKey, aUriModified, aIsXFOError); + } else { + channel = + new ParentProcessDocumentChannel(aLoadState, aLoadInfo, aLoadFlags, + aCacheKey, aUriModified, aIsXFOError); + } + 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::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("DocumentChannel::SetLoadFlags: Don't set flags after creation"); +} + +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(nsISupports** 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..03a84c7237 --- /dev/null +++ b/netwerk/ipc/DocumentChannel.h @@ -0,0 +1,119 @@ +/* -*- 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; + } + + /** + * 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 aIsXFOError); + 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 aIsXFOError); + + void ShutdownListeners(nsresult aStatusCode); + void DisconnectChildListeners(const nsresult& aStatus, + const nsresult& aLoadGroupStatus); + virtual void DeleteIPDL() {} + + nsDocShell* GetDocShell(); + + virtual ~DocumentChannel() = default; + + const TimeStamp mAsyncOpenTime; + 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; + // mIsXFOError is true if we're handling a load error and the status of the + // failed channel is NS_ERROR_XFO_VIOLATION. + bool mIsXFOError = 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..527e27987a --- /dev/null +++ b/netwerk/ipc/DocumentChannelChild.cpp @@ -0,0 +1,427 @@ +/* -*- 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/extensions/StreamFilterParent.h" +#include "mozilla/net/HttpBaseChannel.h" +#include "mozilla/net/NeckoChild.h" +#include "mozilla/ScopeExit.h" +#include "nsHashPropertyBag.h" +#include "nsIHttpChannelInternal.h" +#include "nsIObjectLoadingContent.h" +#include "nsIWritablePropertyBag.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 aIsXFOError) + : DocumentChannel(aLoadState, aLoadInfo, aLoadFlags, aCacheKey, + aUriModified, aIsXFOError) { + 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; + } + + DocumentChannelCreationArgs args; + + args.loadState() = mLoadState->Serialize(); + args.cacheKey() = mCacheKey; + args.channelId() = mChannelId; + args.asyncOpenTime() = mAsyncOpenTime; + + Maybe<IPCClientInfo> ipcClientInfo; + if (mInitialClientInfo.isSome()) { + ipcClientInfo.emplace(mInitialClientInfo.ref().ToIPC()); + } + args.initialClientInfo() = ipcClientInfo; + + if (mTiming) { + args.timing() = Some(mTiming); + } + + switch (mLoadInfo->GetExternalContentPolicyType()) { + case ExtContentPolicy::TYPE_DOCUMENT: + case ExtContentPolicy::TYPE_SUBDOCUMENT: { + DocumentCreationArgs docArgs; + docArgs.uriModified() = mUriModified; + docArgs.isXFOError() = mIsXFOError; + + args.elementCreationArgs() = docArgs; + break; + } + + case ExtContentPolicy::TYPE_OBJECT: { + ObjectCreationArgs objectArgs; + objectArgs.embedderInnerWindowId() = InnerWindowIDForExtantDoc(docShell); + objectArgs.loadFlags() = mLoadFlags; + objectArgs.contentPolicyType() = mLoadInfo->InternalContentPolicyType(); + objectArgs.isUrgentStart() = UserActivation::IsHandlingUserInput(); + + args.elementCreationArgs() = 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; + } + + gNeckoChild->SendPDocumentChannelConstructor(this, loadingContext, args); + + mIsPending = true; + mWasOpened = true; + mListener = listener; + + return NS_OK; +} + +IPCResult DocumentChannelChild::RecvFailedAsyncOpen( + const nsresult& aStatusCode) { + ShutdownListeners(aStatusCode); + return IPC_OK(); +} + +IPCResult DocumentChannelChild::RecvDisconnectChildListeners( + const nsresult& aStatus, const nsresult& aLoadGroupStatus, + bool aSwitchedProcess) { + // 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 (!aSwitchedProcess) { + DisconnectChildListeners(aStatus, aLoadGroupStatus); + } + 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(), cspToInheritLoadingDocument, getter_AddRefs(loadInfo))); + + mRedirectResolver = std::move(aResolve); + + nsCOMPtr<nsIChannel> newChannel; + MOZ_ASSERT((aArgs.loadStateLoadFlags() & + 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); + } + + // 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(), GetMainThreadEventTarget()); + + 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) { + if (mCanceled) { + return NS_OK; + } + + mCanceled = true; + if (CanSend()) { + SendCancel(aStatusCode); + } + + 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..b7a962b2ee --- /dev/null +++ b/netwerk/ipc/DocumentChannelChild.h @@ -0,0 +1,72 @@ +/* -*- 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 aIsXFOError); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK + + NS_IMETHOD AsyncOpen(nsIStreamListener* aListener) override; + NS_IMETHOD Cancel(nsresult aStatusCode) 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; +}; + +} // 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..9eec9a1515 --- /dev/null +++ b/netwerk/ipc/DocumentChannelParent.cpp @@ -0,0 +1,145 @@ +/* -*- 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 "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 = + new nsDocShellLoadState(aArgs.loadState()); + LOG(("DocumentChannelParent Init [this=%p, uri=%s]", this, + loadState->URI()->GetSpecOrDefault().get())); + + RefPtr<DocumentLoadListener::OpenPromise> promise; + if (loadState->GetChannelInitialized()) { + promise = DocumentLoadListener::ClaimParentLoad( + getter_AddRefs(mDocumentLoadListener), loadState->GetLoadIdentifier()); + } + 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().refOr(nullptr), + std::move(clientInfo), Some(docArgs.uriModified()), + Some(docArgs.isXFOError()), IProtocol::OtherPid(), &rv); + } else { + const ObjectCreationArgs& objectArgs = aArgs.elementCreationArgs(); + + promise = mDocumentLoadListener->OpenObject( + loadState, aArgs.cacheKey(), Some(aArgs.channelId()), + aArgs.asyncOpenTime(), aArgs.timing().refOr(nullptr), + std::move(clientInfo), objectArgs.embedderInnerWindowId(), + objectArgs.loadFlags(), objectArgs.contentPolicyType(), + objectArgs.isUrgentStart(), IProtocol::OtherPid(), + 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); + // 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.mSwitchedProcess); + } + 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) { + if (!CanSend()) { + return PDocumentChannelParent::RedirectToRealChannelPromise:: + CreateAndReject(ResponseRejectReason::ChannelClosed, __func__); + } + RedirectToRealChannelArgs args; + mDocumentLoadListener->SerializeRedirectData( + args, false, aRedirectFlags, aLoadFlags, + static_cast<ContentParent*>(Manager()->Manager())); + 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..5bbdcca039 --- /dev/null +++ b/netwerk/ipc/DocumentChannelParent.h @@ -0,0 +1,64 @@ +/* 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 { + +/** + * 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 + bool RecvCancel(const nsresult& aStatus) { + if (mDocumentLoadListener) { + mDocumentLoadListener->Cancel(aStatus); + } + return true; + } + void ActorDestroy(ActorDestroyReason aWhy) override { + if (mDocumentLoadListener) { + mDocumentLoadListener->Cancel(NS_BINDING_ABORTED); + } + } + + private: + RefPtr<ObjectUpgradePromise> UpgradeObjectLoad() override; + + RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise> + RedirectToRealChannel( + nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>>&& + aStreamFilterEndpoints, + uint32_t aRedirectFlags, uint32_t aLoadFlags); + + 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..3d7611ba7a --- /dev/null +++ b/netwerk/ipc/DocumentLoadListener.cpp @@ -0,0 +1,2544 @@ +/* -*- 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 "mozilla/AntiTrackingUtils.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/LoadInfo.h" +#include "mozilla/MozPromiseInlines.h" // For MozPromise::FromDomPromise +#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/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 "nsDocShell.h" +#include "nsDocShellLoadState.h" +#include "nsDocShellLoadTypes.h" +#include "nsDOMNavigationTiming.h" +#include "nsExternalHelperAppService.h" +#include "nsHttpChannel.h" +#include "nsIBrowser.h" +#include "nsIE10SUtils.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 "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/RemoteWebProgress.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) + +using namespace mozilla::dom; + +namespace mozilla { +namespace net { + +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->HasLoadFlags(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(), + securityFlags, sandboxFlags); + } else { + OriginAttributes attrs; + aBrowsingContext->GetOriginAttributes(attrs); + loadInfo = LoadInfo::CreateForDocument(aBrowsingContext, + aLoadState->TriggeringPrincipal(), + attrs, securityFlags, sandboxFlags); + } + + loadInfo->SetTriggeringSandboxFlags(aLoadState->TriggeringSandboxFlags()); + loadInfo->SetHasValidUserGestureActivation( + aLoadState->HasValidUserGestureActivation()); + + 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()); + + return loadInfo.forget(); +} + +static auto WebProgressForBrowsingContext( + CanonicalBrowsingContext* aBrowsingContext) + -> already_AddRefed<BrowsingContextWebProgress> { + return RefPtr<BrowsingContextWebProgress>(aBrowsingContext->GetWebProgress()) + .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, mBrowsingContext->GetAllowPlugins()); + 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)) { + 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_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, + aLoadingBrowsingContext->UsePrivateBrowsing()); +} + +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(); + + nsDocShell::InternalAddURIVisit(uri, previousURI, previousFlags, + responseStatus, browsingContext, widget, + mLoadStateLoadType); +} + +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; +} + +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, + base::ProcessId aPid, nsresult* aRv) + -> RefPtr<OpenPromise> { + auto* loadingContext = GetLoadingBrowsingContext(); + + MOZ_DIAGNOSTIC_ASSERT_IF(loadingContext->GetParent(), + loadingContext->GetParentWindowContext()); + + OriginAttributes attrs; + loadingContext->GetOriginAttributes(attrs); + + mLoadIdentifier = aLoadState->GetLoadIdentifier(); + + 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; + } + + auto* documentContext = GetDocumentBrowsingContext(); + if (documentContext && 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, + mChannel); + if (!mLoadingSessionHistoryInfo) { + *aRv = NS_BINDING_ABORTED; + mParentChannelListener = nullptr; + mChannel = nullptr; + return nullptr; + } + } + + 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); + } + + RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel); + if (httpChannelImpl) { + httpChannelImpl->SetWarningReporter(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->HasLoadFlags( + 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->LoadFlags(), 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); + + mOtherPid = aPid; + mChannelCreationURI = aLoadState->URI(); + mLoadStateLoadFlags = aLoadState->LoadFlags(); + 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?"); + } + + *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> aIsXFOError, base::ProcessId aPid, + 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(aIsXFOError)); + + return Open(aLoadState, loadInfo, loadFlags, aCacheKey, aChannelId, + aAsyncOpenTime, aTiming, std::move(aInfo), false, aPid, 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, + base::ProcessId aPid, ObjectUpgradeHandler* aObjectUpgradeHandler, + nsresult* aRv) -> RefPtr<OpenPromise> { + LOG(("DocumentLoadListener [%p] OpenObject [uri=%s]", this, + aLoadState->URI()->GetSpecOrDefault().get())); + + MOZ_ASSERT(!mIsDocumentLoad); + + auto sandboxFlags = GetLoadingBrowsingContext()->GetSandboxFlags(); + + RefPtr<LoadInfo> loadInfo = CreateObjectLoadInfo( + aLoadState, aInnerWindowId, aContentPolicyType, sandboxFlags); + + mObjectUpgradeHandler = aObjectUpgradeHandler; + + return Open(aLoadState, loadInfo, aLoadFlags, aCacheKey, aChannelId, + aAsyncOpenTime, aTiming, std::move(aInfo), aUrgentStart, aPid, + 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; + } + + if (nsCOMPtr<nsIContentSecurityPolicy> csp = aLoadState->Csp()) { + // Check CSP navigate-to + bool allowsNavigateTo = false; + nsresult rv = csp->GetAllowsNavigateTo(aLoadState->URI(), + aLoadState->IsFormSubmission(), + false, /* aWasRedirected */ + false, /* aEnforceWhitelist */ + &allowsNavigateTo); + if (NS_FAILED(rv) || !allowsNavigateTo) { + 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()->OtherPid(), &rv); +} + +void DocumentLoadListener::FireStateChange(uint32_t aStateFlags, + nsresult aStatus) { + nsCOMPtr<nsIChannel> request = GetChannel(); + nsCOMPtr<nsIWebProgress> webProgress = + new RemoteWebProgress(GetLoadType(), true, true); + + RefPtr<BrowsingContextWebProgress> loadingWebProgress = + WebProgressForBrowsingContext(GetLoadingBrowsingContext()); + + if (loadingWebProgress) { + NS_DispatchToMainThread( + NS_NewRunnableFunction("DocumentLoadListener::FireStateChange", [=]() { + loadingWebProgress->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.mSwitchedProcess) { + // 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) + -> 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; + } + + 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); +} + +void DocumentLoadListener::Disconnect() { + LOG(("DocumentLoadListener Disconnect [this=%p]", this)); + // 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); + } + + if (auto* ctx = GetDocumentBrowsingContext()) { + ctx->EndDocumentLoad(mDoingProcessSwitch); + } +} + +void DocumentLoadListener::Cancel(const nsresult& aStatusCode) { + LOG( + ("DocumentLoadListener Cancel [this=%p, " + "aStatusCode=%" PRIx32 " ]", + this, static_cast<uint32_t>(aStatusCode))); + if (mOpenPromiseResolved) { + return; + } + if (mChannel) { + mChannel->Cancel(aStatusCode); + } + + DisconnectListeners(aStatusCode, aStatusCode); +} + +void DocumentLoadListener::DisconnectListeners(nsresult aStatus, + nsresult aLoadGroupStatus, + bool aSwitchedProcess) { + LOG( + ("DocumentLoadListener DisconnectListener [this=%p, " + "aStatus=%" PRIx32 " aLoadGroupStatus=%" PRIx32 " ]", + this, static_cast<uint32_t>(aStatus), + static_cast<uint32_t>(aLoadGroupStatus))); + + RejectOpenPromise(aStatus, aLoadGroupStatus, aSwitchedProcess, __func__); + + Disconnect(); + + if (!aSwitchedProcess) { + // If we're not going to send anything else to the content process, and + // we haven't yet consumed a stream filter promise, then we're never going + // to. If we're disconnecting the old content process due to a proces + // switch, then we can rely on FinishReplacementChannelSetup being called + // (even if the switch failed), so we clear at that point instead. + // TODO: This might be because we retargeted the stream to the download + // handler or similar. Do we need to attach a stream filter to that? + 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 nsIHttpChannel::FlashPluginState& aState) { + parentChannel->NotifyFlashPluginStateChanged(aState); + }, + [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); + }, + [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(); + } + nsresult rv = NS_OK; + for (auto& variant : streamListenerFunctions) { + variant.match( + [&](const OnStartRequestParams& aParams) { + rv = aListener->OnStartRequest(aParams.request); + if (NS_FAILED(rv)) { + aParams.request->Cancel(rv); + } + }, + [&](const OnDataAvailableParams& aParams) { + // Don't deliver OnDataAvailable if we've + // already failed. + if (NS_FAILED(rv)) { + return; + } + nsCOMPtr<nsIInputStream> stringStream; + rv = NS_NewByteInputStream( + getter_AddRefs(stringStream), + Span<const char>(aParams.data.get(), aParams.count), + NS_ASSIGNMENT_DEPEND); + if (NS_SUCCEEDED(rv)) { + rv = aListener->OnDataAvailable(aParams.request, stringStream, + aParams.offset, aParams.count); + } + if (NS_FAILED(rv)) { + aParams.request->Cancel(rv); + } + }, + [&](const OnStopRequestParams& aParams) { + if (NS_SUCCEEDED(rv)) { + aListener->OnStopRequest(aParams.request, aParams.status); + } else { + aListener->OnStopRequest(aParams.request, rv); + } + rv = NS_OK; + }, + [&](const OnAfterLastPartParams& aParams) { + nsCOMPtr<nsIMultiPartChannelListener> multiListener = + do_QueryInterface(aListener); + if (multiListener) { + if (NS_SUCCEEDED(rv)) { + multiListener->OnAfterLastPart(aParams.status); + } else { + multiListener->OnAfterLastPart(rv); + } + } + }); + } + // 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(); + + if (auto* ctx = GetDocumentBrowsingContext()) { + ctx->EndDocumentLoad(mDoingProcessSwitch); + } + + return !mIsFinished; +} + +void DocumentLoadListener::SerializeRedirectData( + RedirectToRealChannelArgs& aArgs, bool aIsCrossProcess, + uint32_t aRedirectFlags, uint32_t aLoadFlags, + ContentParent* aParent) const { + // Use the original URI of the current channel, as this is what + // we'll use to construct the channel in the content process. + aArgs.uri() = mChannelCreationURI; + if (!aArgs.uri()) { + mChannel->GetOriginalURI(getter_AddRefs(aArgs.uri())); + } + + aArgs.loadIdentifier() = mLoadIdentifier; + + // 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(); + + nsCOMPtr<nsIPrincipal> uriPrincipal; + nsIScriptSecurityManager* sm = nsContentUtils::GetSecurityManager(); + sm->GetChannelURIPrincipal(mChannel, getter_AddRefs(uriPrincipal)); + + nsCOMPtr<nsIRedirectHistoryEntry> entry = + new nsRedirectHistoryEntry(uriPrincipal, nullptr, ""_ns); + + redirectLoadInfo->AppendRedirectHistoryEntry(entry, true); + } + + const Maybe<ClientInfo>& reservedClientInfo = + channelLoadInfo->GetReservedClientInfo(); + if (reservedClientInfo) { + redirectLoadInfo->SetReservedClientInfo(*reservedClientInfo); + } + + aArgs.registrarId() = mRedirectChannelId; + + 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(aParent)); + } + + 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.loadStateLoadFlags() = mLoadStateLoadFlags; + aArgs.loadStateLoadType() = mLoadStateLoadType; + aArgs.originalUriString() = mOriginalUriString; + if (mLoadingSessionHistoryInfo) { + aArgs.loadingSessionHistoryInfo().emplace(*mLoadingSessionHistoryInfo); + } +} + +static bool IsLargeAllocationLoad(CanonicalBrowsingContext* aBrowsingContext, + nsIChannel* aChannel) { + if (!StaticPrefs::dom_largeAllocationHeader_enabled() || + aBrowsingContext->UseRemoteSubframes()) { + return false; + } + + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel); + if (!httpChannel) { + return false; + } + + nsAutoCString ignoredHeaderValue; + nsresult rv = + httpChannel->GetResponseHeader("Large-Allocation"_ns, ignoredHeaderValue); + if (NS_FAILED(rv)) { + return false; + } + + // On all platforms other than win32, LargeAllocation is disabled by default, + // and has to be force-enabled using `dom.largeAllocation.forceEnable`. +#if defined(XP_WIN) && defined(_X86_) + return true; +#else + return StaticPrefs::dom_largeAllocation_forceEnable(); +#endif +} + +static bool ContextCanProcessSwitch(CanonicalBrowsingContext* aBrowsingContext, + WindowGlobalParent* aParentWindow) { + if (NS_WARN_IF(!aBrowsingContext)) { + LOG(("Process Switch Abort: no browsing context")); + return false; + } + if (!aBrowsingContext->IsContent()) { + LOG(("Process Switch Abort: non-content browsing context")); + return false; + } + + if (aParentWindow && !aBrowsingContext->UseRemoteSubframes()) { + LOG(("Process Switch Abort: remote subframes disabled")); + return false; + } + + if (aParentWindow && aParentWindow->IsInProcess()) { + LOG(("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) { + LOG(("Process Switch Abort: cannot get embedder element")); + return false; + } + nsCOMPtr<nsIBrowser> browser = browserElement->AsBrowser(); + if (!browser) { + LOG(("Process Switch Abort: not loaded within nsIBrowser")); + return false; + } + + nsIBrowser::ProcessBehavior processBehavior = + nsIBrowser::PROCESS_BEHAVIOR_DISABLED; + nsresult rv = browser->GetProcessSwitchBehavior(&processBehavior); + if (NS_FAILED(rv)) { + MOZ_ASSERT_UNREACHABLE( + "nsIBrowser::GetProcessSwitchBehavior shouldn't fail"); + LOG(("Process Switch Abort: failed to get process switch behavior")); + return false; + } + + // Check if the process switch we're considering is disabled by the + // <browser>'s process behavior. + if (processBehavior == nsIBrowser::PROCESS_BEHAVIOR_DISABLED) { + LOG(("Process Switch Abort: switch disabled by <browser>")); + return false; + } + if (!aParentWindow && + processBehavior == nsIBrowser::PROCESS_BEHAVIOR_SUBFRAME_ONLY) { + LOG(("Process Switch Abort: toplevel switch disabled by <browser>")); + return false; + } + + return true; +} + +bool DocumentLoadListener::MaybeTriggerProcessSwitch( + bool* aWillSwitchToRemote) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_DIAGNOSTIC_ASSERT(!mDoingProcessSwitch, + "Already in the middle of switching?"); + MOZ_DIAGNOSTIC_ASSERT(mChannel); + MOZ_DIAGNOSTIC_ASSERT(mParentChannelListener); + MOZ_DIAGNOSTIC_ASSERT(aWillSwitchToRemote); + + LOG(("DocumentLoadListener MaybeTriggerProcessSwitch [this=%p]", this)); + + // 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 && !mChannel->IsDocument()) { + LOG(("Process Switch Abort: non-document load")); + return false; + } + + // Get the loading BrowsingContext. This may not be the context which will be + // switching processes 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(); + RefPtr<WindowGlobalParent> parentWindow = GetParentWindowContext(); + if (!ContextCanProcessSwitch(browsingContext, parentWindow)) { + return false; + } + + // Get the final principal, used to select which process to load into. + nsCOMPtr<nsIPrincipal> resultPrincipal; + nsresult rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal( + mChannel, getter_AddRefs(resultPrincipal)); + if (NS_FAILED(rv)) { + LOG(("Process Switch Abort: failed to get channel result principal")); + return false; + } + + nsAutoCString currentRemoteType(NOT_REMOTE_TYPE); + if (RefPtr<ContentParent> contentParent = + browsingContext->GetContentParent()) { + currentRemoteType = contentParent->GetRemoteType(); + } + MOZ_ASSERT_IF(currentRemoteType.IsEmpty(), !OtherPid()); + + // Determine what type of content process this load should finish in. + nsAutoCString preferredRemoteType(currentRemoteType); + bool replaceBrowsingContext = false; + uint64_t specificGroupId = 0; + + // If we're in a preloaded browser, force browsing context replacement to + // ensure the current process is re-selected. + { + Element* browserElement = browsingContext->Top()->GetEmbedderElement(); + + nsAutoString isPreloadBrowserStr; + if (browserElement->GetAttr(kNameSpaceID_None, nsGkAtoms::preloadedState, + isPreloadBrowserStr) && + isPreloadBrowserStr.EqualsLiteral("consumed")) { + nsCOMPtr<nsIURI> originalURI; + if (NS_SUCCEEDED(mChannel->GetOriginalURI(getter_AddRefs(originalURI))) && + !originalURI->GetSpecOrDefault().EqualsLiteral("about:newtab")) { + LOG(("Process Switch: leaving preloaded browser")); + replaceBrowsingContext = true; + browserElement->UnsetAttr(kNameSpaceID_None, nsGkAtoms::preloadedState, + true); + } + } + } + + // Update the preferred final process for our load based on the + // Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers. + { + bool isCOOPSwitch = HasCrossOriginOpenerPolicyMismatch(); + replaceBrowsingContext |= isCOOPSwitch; + + // Determine our COOP status, which will be used to determine our preferred + // remote type. + nsILoadInfo::CrossOriginOpenerPolicy coop = + nsILoadInfo::OPENER_POLICY_UNSAFE_NONE; + if (parentWindow) { + coop = browsingContext->Top()->GetOpenerPolicy(); + } else if (nsCOMPtr<nsIHttpChannelInternal> httpChannel = + do_QueryInterface(mChannel)) { + MOZ_ALWAYS_SUCCEEDS(httpChannel->GetCrossOriginOpenerPolicy(&coop)); + } + + if (coop == + nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP) { + // We want documents with SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP COOP + // policy to be loaded in a separate process in which we can enable + // high-resolution timers. + nsAutoCString siteOrigin; + resultPrincipal->GetSiteOrigin(siteOrigin); + preferredRemoteType = WITH_COOP_COEP_REMOTE_TYPE_PREFIX; + preferredRemoteType.Append(siteOrigin); + } else if (isCOOPSwitch) { + // If we're doing a COOP switch, we do not need any affinity to the + // current remote type. Clear it back to the default value. + preferredRemoteType = DEFAULT_REMOTE_TYPE; + } + } + + // If we're performing a large allocation load, override the remote type + // with `LARGE_ALLOCATION_REMOTE_TYPE` to move it into an exclusive content + // process. If we're already in one, and don't otherwise we force ourselves + // out of that content process. + if (!parentWindow && browsingContext->Group()->Toplevels().Length() == 1) { + if (IsLargeAllocationLoad(browsingContext, mChannel)) { + preferredRemoteType = LARGE_ALLOCATION_REMOTE_TYPE; + replaceBrowsingContext = true; + } else if (preferredRemoteType == LARGE_ALLOCATION_REMOTE_TYPE) { + preferredRemoteType = DEFAULT_REMOTE_TYPE; + replaceBrowsingContext = true; + } + } + + // Put toplevel BrowsingContexts which load within the extension process into + // a specific BrowsingContextGroup. + if (auto* addonPolicy = BasePrincipal::Cast(resultPrincipal)->AddonPolicy()) { + if (!parentWindow) { + // Toplevel extension BrowsingContexts must be loaded in the extension + // browsing context group, within the extension content process. + if (ExtensionPolicyService::GetSingleton().UseRemoteExtensions()) { + preferredRemoteType = EXTENSION_REMOTE_TYPE; + } else { + preferredRemoteType = NOT_REMOTE_TYPE; + } + + if (browsingContext->Group()->Id() != + addonPolicy->GetBrowsingContextGroupId()) { + replaceBrowsingContext = true; + specificGroupId = addonPolicy->GetBrowsingContextGroupId(); + } + } else { + // As a temporary measure, extension iframes must be loaded within the + // same process as their parent document. + preferredRemoteType = parentWindow->GetRemoteType(); + } + } + + LOG( + ("DocumentLoadListener GetRemoteTypeForPrincipal " + "[this=%p, contentParent=%s, preferredRemoteType=%s]", + this, currentRemoteType.get(), preferredRemoteType.get())); + + nsCOMPtr<nsIE10SUtils> e10sUtils = + do_ImportModule("resource://gre/modules/E10SUtils.jsm", "E10SUtils"); + if (!e10sUtils) { + LOG(("Process Switch Abort: Could not import E10SUtils")); + return false; + } + + // Get information about the current document loaded in our BrowsingContext. + nsCOMPtr<nsIPrincipal> currentPrincipal; + RefPtr<WindowGlobalParent> wgp = browsingContext->GetCurrentWindowGlobal(); + if (wgp) { + currentPrincipal = wgp->DocumentPrincipal(); + } + + nsAutoCString remoteType; + rv = e10sUtils->GetRemoteTypeForPrincipal( + resultPrincipal, mChannelCreationURI, browsingContext->UseRemoteTabs(), + browsingContext->UseRemoteSubframes(), preferredRemoteType, + currentPrincipal, parentWindow, remoteType); + if (NS_WARN_IF(NS_FAILED(rv))) { + LOG(("Process Switch Abort: getRemoteTypeForPrincipal threw an exception")); + return false; + } + + // If the final decision is to switch from an 'extension' remote type to any + // other remote type, ensure the browsing context is replaced so that we leave + // the extension-specific BrowsingContextGroup. + if (!parentWindow && currentRemoteType != remoteType && + currentRemoteType == EXTENSION_REMOTE_TYPE) { + replaceBrowsingContext = true; + } + + LOG(("GetRemoteTypeForPrincipal -> current:%s remoteType:%s", + currentRemoteType.get(), remoteType.get())); + + // Check if a process switch is needed. + if (currentRemoteType == remoteType && !replaceBrowsingContext) { + LOG(("Process Switch Abort: type (%s) is compatible", remoteType.get())); + return false; + } + + if (NS_WARN_IF(parentWindow && remoteType.IsEmpty())) { + LOG(("Process Switch Abort: non-remote target process for subframe")); + return false; + } + + *aWillSwitchToRemote = !remoteType.IsEmpty(); + + // If we're doing a document load, we can immediately perform a process + // switch. + if (mIsDocumentLoad) { + TriggerProcessSwitch(browsingContext, remoteType, replaceBrowsingContext, + specificGroupId); + 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) { + LOG(("Process Switch Abort: no object upgrade handler")); + return false; + } + + if (!StaticPrefs::fission_remoteObjectEmbed()) { + LOG(("Process Switch Abort: remote <object>/<embed> disabled")); + return false; + } + + mObjectUpgradeHandler->UpgradeObjectLoad()->Then( + GetMainThreadSerialEventTarget(), __func__, + [self = RefPtr{this}, remoteType, replaceBrowsingContext, specificGroupId, + wgp](const RefPtr<CanonicalBrowsingContext>& aBrowsingContext) { + if (aBrowsingContext->IsDiscarded() || + wgp != aBrowsingContext->GetParentWindowContext()) { + LOG( + ("Process Switch: Got invalid BrowsingContext from object " + "upgrade!")); + self->RedirectToRealChannelFinished(NS_ERROR_FAILURE); + return; + } + + LOG(("Process Switch: Upgraded Object to Document Load")); + self->TriggerProcessSwitch(aBrowsingContext, remoteType, + replaceBrowsingContext, specificGroupId); + }, + [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 nsCString& aRemoteType, + bool aReplaceBrowsingContext, uint64_t aSpecificGroupId) { + nsAutoCString currentRemoteType(NOT_REMOTE_TYPE); + if (RefPtr<ContentParent> contentParent = aContext->GetContentParent()) { + currentRemoteType = contentParent->GetRemoteType(); + } + MOZ_ASSERT_IF(currentRemoteType.IsEmpty(), !OtherPid()); + + LOG(("Process Switch: Changing Remoteness from '%s' to '%s'", + currentRemoteType.get(), aRemoteType.get())); + + // We're now committing to a process switch, so we can disconnect from + // the listeners in the old process. + mDoingProcessSwitch = true; + + RefPtr<WindowGlobalParent> wgp = aContext->GetCurrentWindowGlobal(); + if (wgp && wgp->IsProcessRoot()) { + if (RefPtr<BrowserParent> browserParent = wgp->GetBrowserParent()) { + // This load has already started, so we want to filter out any 'stop' + // progress events coming from the old process as a result of us + // disconnecting from it. + browserParent->SuspendProgressEvents(); + } + } + DisconnectListeners(NS_BINDING_ABORTED, NS_BINDING_ABORTED, true); + + LOG(("Process Switch: Calling ChangeRemoteness")); + aContext + ->ChangeRemoteness(aRemoteType, mLoadIdentifier, aReplaceBrowsingContext, + aSpecificGroupId) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [self = RefPtr{this}](BrowserParent* aBrowserParent) { + MOZ_ASSERT(self->mChannel, + "Something went wrong, channel got cancelled"); + self->TriggerRedirectToRealChannel(Some( + aBrowserParent ? aBrowserParent->Manager()->ChildID() : 0)); + }, + [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(mLoadStateLoadFlags); + 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<uint64_t>& 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) { + if (!*aDestinationProcess) { + MOZ_ASSERT(aStreamFilterEndpoints.IsEmpty()); + return RedirectToParentProcess(aRedirectFlags, aLoadFlags); + } + dom::ContentParent* cp = + dom::ContentProcessManager::GetSingleton()->GetContentProcessById( + ContentParentId{*aDestinationProcess}); + if (!cp) { + return PDocumentChannelParent::RedirectToRealChannelPromise:: + CreateAndReject(ipc::ResponseRejectReason::SendError, __func__); + } + + RedirectToRealChannelArgs args; + SerializeRedirectData(args, !!aDestinationProcess, aRedirectFlags, + aLoadFlags, cp); + if (mTiming) { + mTiming->Anonymize(args.uri()); + args.timing() = Some(std::move(mTiming)); + } + + auto loadInfo = args.loadInfo(); + + if (loadInfo.isNothing()) { + return PDocumentChannelParent::RedirectToRealChannelPromise:: + CreateAndReject(ipc::ResponseRejectReason::SendError, __func__); + } + + auto triggeringPrincipalOrErr = + PrincipalInfoToPrincipal(loadInfo.ref().triggeringPrincipalInfo()); + + if (triggeringPrincipalOrErr.isOk()) { + nsCOMPtr<nsIPrincipal> triggeringPrincipal = + triggeringPrincipalOrErr.unwrap(); + cp->TransmitBlobDataIfBlobURL(args.uri(), triggeringPrincipal); + } + + 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, 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<uint64_t>& aDestinationProcess) { + 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(mStreamFilterRequests.Length()); + if (!mStreamFilterRequests.IsEmpty()) { + base::ProcessId pid = OtherPid(); + if (aDestinationProcess) { + if (*aDestinationProcess) { + dom::ContentParent* cp = + dom::ContentProcessManager::GetSingleton()->GetContentProcessById( + ContentParentId(*aDestinationProcess)); + if (cp) { + pid = cp->OtherPid(); + } + } else { + pid = 0; + } + } + + for (StreamFilterRequest& request : mStreamFilterRequests) { + if (!pid) { + request.mPromise->Reject(false, __func__); + request.mPromise = nullptr; + continue; + } + ParentEndpoint parent; + nsresult rv = extensions::PStreamFilter::CreateEndpoints( + pid, request.mChildProcessId, &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(mStreamFilterRequests)]( + 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 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) { + auto* bc = GetDocumentBrowsingContext(); + if (!bc) { + return false; + } + + nsCOMPtr<nsIInputStream> newPostData; + nsCOMPtr<nsIURI> newURI = nsDocShell::AttemptURIFixup( + mChannel, aStatus, mOriginalUriString, mLoadStateLoadType, bc->IsTop(), + mLoadStateLoadFlags & + nsDocShell::INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP, + bc->UsePrivateBrowsing(), true, getter_AddRefs(newPostData)); + 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); + + 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); + + 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); + } + } + + auto* loadingContext = GetLoadingBrowsingContext(); + if (!loadingContext || loadingContext->IsDiscarded()) { + DisconnectListeners(NS_ERROR_UNEXPECTED, NS_ERROR_UNEXPECTED); + return NS_ERROR_UNEXPECTED; + } + + // 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; + } + + // 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; + } + + 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)) { + if (!mSupportsRedirectToRealChannel) { + // 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. + mDoingProcessSwitch = true; + + // If we're not going to process switch, then we must have an existing + // window global, right? + MOZ_ASSERT(loadingContext->GetCurrentWindowGlobal()); + + RefPtr<BrowserParent> browserParent = + loadingContext->GetCurrentWindowGlobal()->GetBrowserParent(); + + // XXX(anny) This is currently a dead code path because parent-controlled + // DC pref is off. When we enable the pref, we might get extra STATE_START + // progress events + + // 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(loadingContext->OwnerProcessId())); + } else { + TriggerRedirectToRealChannel(Nothing()); + } + + // If we're not switching, then check if we're currently remote. + if (loadingContext->GetContentParent()) { + willBeRemote = true; + } + } + + // 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, 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; +} + +// Rather than forwarding all these nsIParentChannel functions to the child, +// we cache a list of them, and then ask the 'real' channel to forward them +// for us after it's created. +NS_IMETHODIMP +DocumentLoadListener::NotifyFlashPluginStateChanged( + nsIHttpChannel::FlashPluginState aState) { + mIParentChannelFunctions.AppendElement( + IParentChannelFunction{VariantIndex<0>{}, aState}); + 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<1>{}, 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<2>{}, std::move(params)}); + return NS_OK; +} + +NS_IMETHODIMP +DocumentLoadListener::NotifyClassificationFlags(uint32_t aClassificationFlags, + bool aIsThirdParty) { + mIParentChannelFunctions.AppendElement(IParentChannelFunction{ + VariantIndex<3>{}, + 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; + + // 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; + } + + if (GetDocumentBrowsingContext()) { + if (mLoadingSessionHistoryInfo) { + mLoadingSessionHistoryInfo = + GetDocumentBrowsingContext() + ->ReplaceLoadingSessionHistoryEntryForLoad( + mLoadingSessionHistoryInfo.get(), aNewChannel); + } + if (!net::ChannelIsPost(aOldChannel)) { + AddURIVisit(aOldChannel, 0); + + nsCOMPtr<nsIURI> oldURI; + aOldChannel->GetURI(getter_AddRefs(oldURI)); + nsDocShell::SaveLastVisit(aNewChannel, oldURI, aFlags); + } + } + mHaveVisibleRedirect |= true; + + LOG( + ("DocumentLoadListener AsyncOnChannelRedirect [this=%p] " + "mHaveVisibleRedirect=%c", + this, mHaveVisibleRedirect ? 'T' : 'F')); + + // If this is a cross-origin redirect, then we should no longer allow + // mixed content. The destination docshell checks this in its redirect + // handling, but if we deliver to a new docshell (with a process switch) + // then this doesn't happen. + // Manually remove the allow mixed content flags. + nsresult rv = nsContentUtils::CheckSameOrigin(aOldChannel, aNewChannel); + if (NS_FAILED(rv)) { + if (mLoadStateLoadType == LOAD_NORMAL_ALLOW_MIXED_CONTENT) { + mLoadStateLoadType = LOAD_NORMAL; + } else if (mLoadStateLoadType == LOAD_RELOAD_ALLOW_MIXED_CONTENT) { + mLoadStateLoadType = LOAD_RELOAD_NORMAL; + } + MOZ_ASSERT(!LOAD_TYPE_HAS_FLAGS( + mLoadStateLoadType, nsIWebNavigation::LOAD_FLAGS_ALLOW_MIXED_CONTENT)); + } + + // 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. + aNewChannel->GetOriginalURI(getter_AddRefs(mChannelCreationURI)); + + // 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(); + +#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; +} + +// 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(base::ProcessId aChildProcessId) + -> RefPtr<ChildEndpointPromise> { + LOG(("DocumentLoadListener AttachStreamFilter [this=%p]", this)); + + StreamFilterRequest* request = mStreamFilterRequests.AppendElement(); + request->mPromise = new ChildEndpointPromise::Private(__func__); + request->mChildProcessId = aChildProcessId; + 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; + nsCOMPtr<nsIWebProgress> webProgress = + new RemoteWebProgress(mLoadStateLoadType, true, true); + + RefPtr<BrowsingContextWebProgress> topWebProgress = + WebProgressForBrowsingContext(GetTopBrowsingContext()); + const nsString message(aStatusArg); + + if (topWebProgress) { + NS_DispatchToMainThread( + NS_NewRunnableFunction("DocumentLoadListener::OnStatus", [=]() { + topWebProgress->OnStatusChange(webProgress, channel, aStatus, + message.get()); + })); + } + 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..a77bfa9285 --- /dev/null +++ b/netwerk/ipc/DocumentLoadListener.h @@ -0,0 +1,575 @@ +/* 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 "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 "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; +} +namespace net { +using ChildEndpointPromise = + MozPromise<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; + base::ProcessId mChildProcessId; + 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: + // See the comment on GetLoadingBrowsingContext for explanation of + // aLoadingBrowsingContext. + DocumentLoadListener(dom::CanonicalBrowsingContext* aLoadingBrowsingContext, + bool aIsDocumentLoad); + + struct OpenPromiseSucceededType { + nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>> + mStreamFilterEndpoints; + uint32_t mRedirectFlags; + uint32_t mLoadFlags; + RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise::Private> + mPromise; + }; + struct OpenPromiseFailedType { + nsresult mStatus; + nsresult mLoadGroupStatus; + // This is set to true if we're rejecting the promise because we + // switched to load away to a new process. + bool mSwitchedProcess = false; + }; + + typedef MozPromise<OpenPromiseSucceededType, OpenPromiseFailedType, + true /* isExclusive */> + OpenPromise; + + // 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, + base::ProcessId aPid, 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> aIsXFOError, base::ProcessId aPid, + 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, + base::ProcessId aPid, ObjectUpgradeHandler* aUpgradeHandler, + 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); + + // 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 + + // 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); + + 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) override { + LogBlockedCORSRequestParams params; + params.mMessage = aMessage; + params.mCategory = aCategory; + 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; + } + + base::ProcessId OtherPid() const { return mOtherPid; } + + [[nodiscard]] RefPtr<ChildEndpointPromise> AttachStreamFilter( + base::ProcessId aChildProcessId); + + // Serializes all data needed to setup the new replacement channel + // in the content process into the RedirectToRealChannelArgs struct. + void SerializeRedirectData(RedirectToRealChannelArgs& aArgs, + bool aIsCrossProcess, uint32_t aRedirectFlags, + uint32_t aLoadFlags, + dom::ContentParent* aParent) const; + + uint64_t GetLoadIdentifier() const { return mLoadIdentifier; } + uint32_t GetLoadType() const { return mLoadStateLoadType; } + + mozilla::dom::LoadingSessionHistoryInfo* GetLoadingSessionHistoryInfo() { + return mLoadingSessionHistoryInfo.get(); + } + + bool IsDocumentLoad() const { return mIsDocumentLoad; } + + 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. + void DisconnectListeners(nsresult aStatus, nsresult aLoadGroupStatus, + bool aSwitchedProcess = 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<uint64_t>& aDestinationProcess); + + // 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); + void TriggerProcessSwitch(dom::CanonicalBrowsingContext* aContext, + const nsCString& aRemoteType, + bool aReplaceBrowsingContext, + uint64_t aSpecificGroupId); + + // 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 = ipc::Endpoint<extensions::PStreamFilterParent>; + RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise> + RedirectToRealChannel(uint32_t aRedirectFlags, uint32_t aLoadFlags, + const Maybe<uint64_t>& 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(); + + 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 + // + // NotifyFlashPluginStateChanged(nsIHttpChannel::FlashPluginState aState) = 0; + // 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; + }; + + typedef mozilla::Variant< + nsIHttpChannel::FlashPluginState, ClassifierMatchedInfoParams, + ClassifierMatchedTrackingInfoParams, ClassificationFlagsParams> + IParentChannelFunction; + + // 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; + }; + + struct LogMimeTypeMismatchParams { + nsCString mMessageName; + bool mWarning; + nsString mURL; + nsString mContentType; + }; + + typedef mozilla::Variant<ReportSecurityMessageParams, + LogBlockedCORSRequestParams, + LogMimeTypeMismatchParams> + SecurityWarningFunction; + nsTArray<SecurityWarningFunction> mSecurityWarningFunctions; + + 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; + }; + typedef mozilla::Variant<OnStartRequestParams, OnDataAvailableParams, + OnStopRequestParams, OnAfterLastPartParams> + StreamListenerFunction; + // 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; + + // 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; + + // The original URI of the current channel. If there are redirects, + // then the value on the channel gets overwritten with the original + // URI of the first channel in the redirect chain, so we cache the + // value we need here. + nsCOMPtr<nsIURI> mChannelCreationURI; + + // 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; + + // 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; + + 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 mLoadStateLoadFlags = 0; + uint32_t mLoadStateLoadType = 0; + + // 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; + // Set to true if we're currently in the middle of replacing this with + // a new channel connected a different process. + bool mDoingProcessSwitch = 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; + + bool mSupportsRedirectToRealChannel = true; + + // The process id of the content process that we are being called from + // or 0 initiated from a parent process load. + base::ProcessId mOtherPid = 0; + + void RejectOpenPromise(nsresult aStatus, nsresult aLoadGroupStatus, + bool aSwitchedProcess, 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, aSwitchedProcess}), + 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..30a406facb --- /dev/null +++ b/netwerk/ipc/InputChannelThrottleQueueParent.cpp @@ -0,0 +1,128 @@ +/* -*- 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 (!mRefCnt.isThreadSafe) { + NS_ASSERT_OWNINGTHREAD(InputChannelThrottleQueueParent); + } + + nsrefcnt count = --mRefCnt; + NS_LOG_RELEASE(this, count, "InputChannelThrottleQueueParent"); + + if (count == 0) { + if (!mRefCnt.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; +} + +InputChannelThrottleQueueParent::InputChannelThrottleQueueParent() + : mBytesProcessed(0), mMeanBytesPerSecond(0), mMaxBytesPerSecond(0) {} + +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..a81424fd30 --- /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(); + mozilla::ipc::IPCResult RecvRecordRead(const uint32_t& aBytesRead); + void ActorDestroy(ActorDestroyReason aWhy) override {} + + private: + virtual ~InputChannelThrottleQueueParent() = default; + + uint64_t mBytesProcessed; + uint32_t mMeanBytesPerSecond; + uint32_t mMaxBytesPerSecond; +}; + +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..b6a51b279a --- /dev/null +++ b/netwerk/ipc/NeckoChannelParams.ipdlh @@ -0,0 +1,546 @@ +/* -*- 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 protocol PFTPChannel; +include protocol PChildToParentStream; +include BlobTypes; +include ClientIPCTypes; +include URIParams; +include IPCServiceWorkerDescriptor; +include IPCStream; +include PBackgroundSharedTypes; +include DOMTypes; + +include "mozilla/dom/PropertyBagUtils.h"; +include "mozilla/dom/ReferrerInfoUtils.h"; +include "mozilla/ipc/URIUtils.h"; + +using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h"; +using 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"; +using refcounted class nsIPropertyBag2 from "nsIPropertyBag2.h"; +using refcounted class nsDOMNavigationTiming from "nsDOMNavigationTiming.h"; +using nsContentPolicyType from "nsIContentPolicy.h"; +using nsILoadInfo::CrossOriginEmbedderPolicy from "nsILoadInfo.h"; +using class mozilla::dom::LoadingSessionHistoryInfo from "mozilla/dom/SessionHistoryEntry.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 isOnContentBlockingAllowList; + CookiePermissionData[] cookiePermissions; + bool isFixed; + nsString partitionKey; +}; + +//----------------------------------------------------------------------------- +// Preferrer alternative data type +//----------------------------------------------------------------------------- + +struct PreferredAlternativeDataTypeParams +{ + nsCString type; + nsCString contentType; + bool deliverAltData; +}; + +//----------------------------------------------------------------------------- +// LoadInfo IPDL structs +//----------------------------------------------------------------------------- + +struct RedirectHistoryEntryInfo +{ + PrincipalInfo principalInfo; + URIParams? referrerUri; + nsCString remoteAddress; +}; + +struct LoadInfoArgs +{ + PrincipalInfo? requestingPrincipalInfo; + PrincipalInfo triggeringPrincipalInfo; + PrincipalInfo? principalToInheritInfo; + PrincipalInfo? sandboxedLoadingPrincipalInfo; + PrincipalInfo? topLevelPrincipalInfo; + PrincipalInfo? topLevelStorageAreaPrincipalInfo; + URIParams? resultPrincipalURI; + uint32_t securityFlags; + uint32_t sandboxFlags; + uint32_t triggeringSandboxFlags; + nsContentPolicyType contentPolicyType; + uint32_t tainting; + bool blockAllMixedContent; + bool upgradeInsecureRequests; + bool browserUpgradeInsecureRequests; + bool browserDidUpgradeInsecureRequests; + bool browserWouldUpgradeInsecureRequests; + bool forceAllowDataURI; + bool allowInsecureRedirectToDataURI; + bool bypassCORSChecks; + 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; + + /** + * 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; + nsString cspNonce; + bool skipContentSniffing; + uint32_t httpsOnlyStatus; + bool hasValidUserGestureActivation; + bool allowDeprecatedSystemRequests; + bool isInDevToolsContext; + bool parserCreatedScript; + bool isFromProcessingFrameAttributes; + CookieJarSettingsArgs cookieJarSettings; + uint32_t requestBlockingReason; + CSPInfo? cspToInheritInfo; + bool hasStoragePermission; + CrossOriginEmbedderPolicy loadingEmbedderPolicy; +}; + +/** + * 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; + + // WebExtextensions WebRequest API allows extentions to redirect a channel + // Before the CORS-preflight was fetched, which can lead into unexpeced CORS blocks. + // We can set this to skip the Cors Checks for the old Channel. + bool bypassCORSChecks; + + // 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; + + // 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; + + // 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; + + bool hasStoragePermission; + + bool isThirdPartyContextToTopWindow; + + bool isInThirdPartyContext; + + // 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 +{ + URIParams 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? + URIParams? original; + URIParams? doc; + nsIReferrerInfo referrerInfo; + URIParams? apiRedirectTo; + URIParams? topWindowURI; + uint32_t loadFlags; + RequestHeaderTuples requestHeaders; + nsCString requestMethod; + IPCStream? uploadStream; + bool uploadStreamHasHeaders; + int16_t priority; + uint32_t classOfService; + uint8_t redirectionLimit; + bool allowSTS; + uint32_t thirdPartyFlags; + bool resumeAt; + uint64_t startPos; + nsCString entityID; + bool chooseApplicationCache; + nsCString appCacheClientID; + bool allowSpdy; + bool allowHttp3; + bool allowAltSvc; + bool beConservative; + uint32_t tlsFlags; + LoadInfoArgs? loadInfo; + uint32_t cacheKey; + uint64_t requestContextID; + CorsPreflightArgs? preflightArgs; + uint32_t initialRwin; + bool blockAuthPrompt; + bool allowStaleCacheContent; + bool preferCacheLoadOverBypass; + nsCString contentTypeHint; + uint32_t corsMode; + uint32_t redirectMode; + uint64_t channelId; + nsString integrityMetadata; + uint64_t contentWindowId; + PreferredAlternativeDataTypeParams[] preferredAlternativeTypes; + uint64_t topLevelOuterContentWindowId; + TimeStamp launchServiceWorkerStart; + TimeStamp launchServiceWorkerEnd; + TimeStamp dispatchFetchEventStart; + TimeStamp dispatchFetchEventEnd; + TimeStamp handleFetchEventStart; + TimeStamp handleFetchEventEnd; + bool forceMainDocumentChannel; + TimeStamp navigationStartTimeStamp; + bool hasNonEmptySandboxingFlag; +}; + +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; + uint32_t tlsFlags; + bool isolated; + bool isTrrServiceChannel; + uint8_t trrMode; + bool isIPv4Disabled; + bool isIPv6Disabled; + nsCString topWindowOrigin; + bool isHttp3; + 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; +}; + +//----------------------------------------------------------------------------- +// FTP IPDL structs +//----------------------------------------------------------------------------- + +struct FTPChannelOpenArgs +{ + URIParams uri; + uint64_t startPos; + nsCString entityID; + IPCStream? uploadStream; + LoadInfoArgs? loadInfo; + uint32_t loadFlags; +}; + +struct FTPChannelConnectArgs +{ + uint32_t channelId; +}; + +union FTPChannelCreationArgs +{ + FTPChannelOpenArgs; // For AsyncOpen: the common case. + FTPChannelConnectArgs; // Used for redirected-to channels +}; + +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; + int32_t sameSite; + int32_t rawSameSite; + uint8_t schemeMap; +}; + +struct DocumentCreationArgs { + bool uriModified; + bool isXFOError; +}; + +struct ObjectCreationArgs { + uint64_t embedderInnerWindowId; + uint32_t loadFlags; + nsContentPolicyType contentPolicyType; + bool isUrgentStart; +}; + +union DocumentChannelElementCreationArgs { + DocumentCreationArgs; + ObjectCreationArgs; +}; + +struct DocumentChannelCreationArgs { + DocShellLoadStateInit loadState; + TimeStamp asyncOpenTime; + uint64_t channelId; + uint32_t cacheKey; + nsDOMNavigationTiming? timing; + IPCClientInfo? initialClientInfo; + DocumentChannelElementCreationArgs elementCreationArgs; +}; + +struct RedirectToRealChannelArgs { + uint32_t registrarId; + nsIURI uri; + uint32_t newLoadFlags; + ReplacementChannelConfigInit? init; + LoadInfoArgs? loadInfo; + uint64_t channelId; + nsIURI originalURI; + uint32_t redirectMode; + uint32_t redirectFlags; + uint32_t? contentDisposition; + nsString? contentDispositionFilename; + nsIPropertyBag2 properties; + uint32_t loadStateLoadFlags; + uint32_t loadStateLoadType; + nsDOMNavigationTiming? timing; + nsString srcdocData; + nsIURI baseUri; + LoadingSessionHistoryInfo? loadingSessionHistoryInfo; + uint64_t loadIdentifier; + nsCString? originalUriString; +}; + +struct TimingStructArgs { + TimeStamp domainLookupStart; + TimeStamp domainLookupEnd; + TimeStamp connectStart; + TimeStamp tcpConnectEnd; + TimeStamp secureConnectionStart; + TimeStamp connectEnd; + TimeStamp requestStart; + TimeStamp responseStart; + TimeStamp responseEnd; +}; + +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; + nsCString protocolVersion; + + // 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; +}; + +struct HttpActivity +{ + nsCString host; + int32_t port; + bool endToEndSSL; +}; + +union HttpActivityArgs +{ + uint64_t; + HttpActivity; +}; + +struct TransactionObserverResult +{ + bool versionOk; + bool authOk; + nsresult closeReason; +}; + +struct SpeculativeConnectionOverriderArgs { + uint32_t parallelSpeculativeConnectLimit; + bool ignoreIdle; + bool isFromPredictor; + bool allow1918; +}; + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/NeckoChild.cpp b/netwerk/ipc/NeckoChild.cpp new file mode 100644 index 0000000000..401af4c719 --- /dev/null +++ b/netwerk/ipc/NeckoChild.cpp @@ -0,0 +1,360 @@ + +/* 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/CookieServiceChild.h" +#include "mozilla/net/FTPChannelChild.h" +#include "mozilla/net/DataChannelChild.h" +#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/ClassifierDummyChannelChild.h" +#include "mozilla/net/SocketProcessBridgeChild.h" +#ifdef MOZ_WEBRTC +# include "mozilla/net/StunAddrsRequestChild.h" +# include "mozilla/net/WebrtcTCPSocketChild.h" +#endif + +#include "SerializedLoadContext.h" +#include "nsGlobalWindow.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; + } + gNeckoChild = cpc->SendPNeckoConstructor(); + NS_ASSERTION(gNeckoChild, "PNecko Protocol init failed!"); + SocketProcessBridgeChild::GetSocketProcessBridge(); + } +} + +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 nsCString& 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; +} + +PFTPChannelChild* NeckoChild::AllocPFTPChannelChild( + PBrowserChild* aBrowser, const SerializedLoadContext& aSerialized, + const FTPChannelCreationArgs& aOpenArgs) { + // We don't allocate here: see FTPChannelChild::AsyncOpen() + MOZ_CRASH("AllocPFTPChannelChild should not be called"); + return nullptr; +} + +bool NeckoChild::DeallocPFTPChannelChild(PFTPChannelChild* channel) { + MOZ_ASSERT(IsNeckoChild(), "DeallocPFTPChannelChild called by non-child!"); + + FTPChannelChild* child = static_cast<FTPChannelChild*>(channel); + child->ReleaseIPDLReference(); + return true; +} + +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) { + nsCOMPtr<nsISerialEventTarget> target; + if (nsGlobalWindowInner* win = + nsGlobalWindowInner::GetInnerWindowWithId(aInnerWindowID)) { + target = win->EventTargetFor(TaskCategory::Other); + } + + RefPtr<WebSocketEventListenerChild> c = + new WebSocketEventListenerChild(aInnerWindowID, target); + + if (target) { + gNeckoChild->SetEventTargetForActor(c, target); + } + + 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 nsString& 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 nsCString& 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; +} + +mozilla::ipc::IPCResult NeckoChild::RecvAsyncAuthPromptForNestedFrame( + const TabId& aNestedFrameId, const nsCString& aUri, const nsString& aRealm, + const uint64_t& aCallbackId) { + RefPtr<dom::BrowserChild> browserChild = + dom::BrowserChild::FindBrowserChild(aNestedFrameId); + if (!browserChild) { + MOZ_CRASH(); + return IPC_FAIL_NO_REASON(this); + } + browserChild->SendAsyncAuthPrompt(aUri, aRealm, aCallbackId); + return IPC_OK(); +} + +/* 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(); +} + +PClassifierDummyChannelChild* NeckoChild::AllocPClassifierDummyChannelChild( + nsIURI* aURI, nsIURI* aTopWindowURI, const nsresult& aTopWindowURIResult, + const Maybe<LoadInfoArgs>& aLoadInfo) { + return new ClassifierDummyChannelChild(); +} + +bool NeckoChild::DeallocPClassifierDummyChannelChild( + PClassifierDummyChannelChild* aActor) { + delete static_cast<ClassifierDummyChannelChild*>(aActor); + return true; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/NeckoChild.h b/netwerk/ipc/NeckoChild.h new file mode 100644 index 0000000000..efc98e95b9 --- /dev/null +++ b/netwerk/ipc/NeckoChild.h @@ -0,0 +1,96 @@ +/* -*- 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: + NeckoChild() = default; + virtual ~NeckoChild(); + + static void InitNeckoChild(); + + protected: + PStunAddrsRequestChild* AllocPStunAddrsRequestChild(); + bool DeallocPStunAddrsRequestChild(PStunAddrsRequestChild* aActor); + + PWebrtcTCPSocketChild* AllocPWebrtcTCPSocketChild(const Maybe<TabId>& tabId); + bool DeallocPWebrtcTCPSocketChild(PWebrtcTCPSocketChild* aActor); + + PAltDataOutputStreamChild* AllocPAltDataOutputStreamChild( + const nsCString& type, const int64_t& predictedSize, + PHttpChannelChild* channel); + bool DeallocPAltDataOutputStreamChild(PAltDataOutputStreamChild* aActor); + + PCookieServiceChild* AllocPCookieServiceChild(); + bool DeallocPCookieServiceChild(PCookieServiceChild*); + PFTPChannelChild* AllocPFTPChannelChild( + PBrowserChild* aBrowser, const SerializedLoadContext& aSerialized, + const FTPChannelCreationArgs& aOpenArgs); + bool DeallocPFTPChannelChild(PFTPChannelChild*); + PWebSocketChild* AllocPWebSocketChild(PBrowserChild*, + const SerializedLoadContext&, + const uint32_t&); + bool DeallocPWebSocketChild(PWebSocketChild*); + PTCPSocketChild* AllocPTCPSocketChild(const nsString& 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 nsCString& aFilter); + bool DeallocPUDPSocketChild(PUDPSocketChild*); + PSimpleChannelChild* AllocPSimpleChannelChild(const uint32_t& channelId); + bool DeallocPSimpleChannelChild(PSimpleChannelChild* child); + PTransportProviderChild* AllocPTransportProviderChild(); + bool DeallocPTransportProviderChild(PTransportProviderChild* aActor); + mozilla::ipc::IPCResult RecvAsyncAuthPromptForNestedFrame( + const TabId& aNestedFrameId, const nsCString& aUri, + const nsString& aRealm, const uint64_t& aCallbackId); + 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); + + PClassifierDummyChannelChild* AllocPClassifierDummyChannelChild( + nsIURI* aURI, nsIURI* aTopWindowURI, const nsresult& aTopWindowURIResult, + const Maybe<LoadInfoArgs>& aLoadInfo); + + bool DeallocPClassifierDummyChannelChild( + PClassifierDummyChannelChild* aChannel); +}; + +/** + * 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.h b/netwerk/ipc/NeckoCommon.h new file mode 100644 index 0000000000..4de3c05fa8 --- /dev/null +++ b/netwerk/ipc/NeckoCommon.h @@ -0,0 +1,116 @@ +/* -*- 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 "nsXULAppAPI.h" +#include "prenv.h" +#include "nsPrintfCString.h" +#include "mozilla/Preferences.h" + +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) = 0; + [[nodiscard]] virtual nsresult LogMimeTypeMismatch( + const nsACString& aMessageName, bool aWarning, const nsAString& aURL, + const nsAString& aContentType) = 0; +}; + +} // 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..81887f8b61 --- /dev/null +++ b/netwerk/ipc/NeckoMessageUtils.h @@ -0,0 +1,149 @@ +/* -*- 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" + +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(Message* aMsg, const Permission& aParam) { + WriteParam(aMsg, aParam.origin); + WriteParam(aMsg, aParam.type); + WriteParam(aMsg, aParam.capability); + WriteParam(aMsg, aParam.expireType); + WriteParam(aMsg, aParam.expireTime); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + Permission* aResult) { + return ReadParam(aMsg, aIter, &aResult->origin) && + ReadParam(aMsg, aIter, &aResult->type) && + ReadParam(aMsg, aIter, &aResult->capability) && + ReadParam(aMsg, aIter, &aResult->expireType) && + ReadParam(aMsg, aIter, &aResult->expireTime); + } + + static void Log(const Permission& p, std::wstring* l) { + l->append(L"("); + LogParam(p.origin, l); + l->append(L", "); + LogParam(p.capability, l); + l->append(L", "); + LogParam(p.expireTime, l); + l->append(L", "); + LogParam(p.expireType, l); + l->append(L")"); + } +}; + +template <> +struct ParamTraits<mozilla::net::NetAddr> { + static void Write(Message* aMsg, const mozilla::net::NetAddr& aParam) { + WriteParam(aMsg, aParam.raw.family); + if (aParam.raw.family == AF_UNSPEC) { + aMsg->WriteBytes(aParam.raw.data, sizeof(aParam.raw.data)); + } else if (aParam.raw.family == AF_INET) { + WriteParam(aMsg, aParam.inet.port); + WriteParam(aMsg, aParam.inet.ip); + } else if (aParam.raw.family == AF_INET6) { + WriteParam(aMsg, aParam.inet6.port); + WriteParam(aMsg, aParam.inet6.flowinfo); + WriteParam(aMsg, aParam.inet6.ip.u64[0]); + WriteParam(aMsg, aParam.inet6.ip.u64[1]); + WriteParam(aMsg, 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"); + aMsg->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(const Message* aMsg, PickleIterator* aIter, + mozilla::net::NetAddr* aResult) { + if (!ReadParam(aMsg, aIter, &aResult->raw.family)) return false; + + if (aResult->raw.family == AF_UNSPEC) { + return aMsg->ReadBytesInto(aIter, &aResult->raw.data, + sizeof(aResult->raw.data)); + } else if (aResult->raw.family == AF_INET) { + return ReadParam(aMsg, aIter, &aResult->inet.port) && + ReadParam(aMsg, aIter, &aResult->inet.ip); + } else if (aResult->raw.family == AF_INET6) { + return ReadParam(aMsg, aIter, &aResult->inet6.port) && + ReadParam(aMsg, aIter, &aResult->inet6.flowinfo) && + ReadParam(aMsg, aIter, &aResult->inet6.ip.u64[0]) && + ReadParam(aMsg, aIter, &aResult->inet6.ip.u64[1]) && + ReadParam(aMsg, aIter, &aResult->inet6.scope_id); +#if defined(XP_UNIX) + } else if (aResult->raw.family == AF_LOCAL) { + return aMsg->ReadBytesInto(aIter, &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<nsIHttpChannel::FlashPluginState> + : public ContiguousEnumSerializerInclusive< + nsIHttpChannel::FlashPluginState, nsIHttpChannel::FlashPluginUnknown, + nsIHttpChannel::FlashPluginLastValue> {}; + +} // 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..f0ee16d855 --- /dev/null +++ b/netwerk/ipc/NeckoParent.cpp @@ -0,0 +1,922 @@ +/* -*- 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/FTPChannelParent.h" +#include "mozilla/net/WebSocketChannelParent.h" +#include "mozilla/net/WebSocketEventListenerParent.h" +#include "mozilla/net/DataChannelParent.h" +#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/ClassifierDummyChannelParent.h" +#include "mozilla/net/IPCTransportProvider.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/ChromeUtils.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/TabContext.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/network/TCPSocketParent.h" +#include "mozilla/dom/network/TCPServerSocketParent.h" +#include "mozilla/dom/network/UDPSocketParent.h" +#include "mozilla/dom/ServiceWorkerManager.h" +#include "mozilla/LoadContext.h" +#include "mozilla/MozPromise.h" +#include "nsPrintfCString.h" +#include "nsHTMLDNSPrefetch.h" +#include "nsEscape.h" +#include "SerializedLoadContext.h" +#include "nsAuthInformationHolder.h" +#include "nsIAuthPromptCallback.h" +#include "nsINetworkPredictor.h" +#include "nsINetworkPredictorVerifier.h" +#include "nsISpeculativeConnect.h" +#include "nsHttpHandler.h" +#include "nsNetUtil.h" + +using IPC::SerializedLoadContext; +using mozilla::OriginAttributes; +using mozilla::dom::BrowserParent; +using mozilla::dom::ChromeUtils; +using mozilla::dom::ContentParent; +using mozilla::dom::ServiceWorkerManager; +using mozilla::dom::TabContext; +using mozilla::dom::TCPServerSocketParent; +using mozilla::dom::TCPSocketParent; +using mozilla::dom::UDPSocketParent; +using mozilla::ipc::LoadInfoArgsToLoadInfo; +using mozilla::ipc::PrincipalInfo; +using mozilla::net::PTCPServerSocketParent; +using mozilla::net::PTCPSocketParent; +using mozilla::net::PUDPSocketParent; + +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 Maybe<LoadInfoArgs>& aOptionalLoadInfoArgs) { + if (aOptionalLoadInfoArgs.isNothing()) { + return nullptr; + } + + const LoadInfoArgs& loadInfoArgs = aOptionalLoadInfoArgs.ref(); + const Maybe<PrincipalInfo>& optionalPrincipalInfo = + loadInfoArgs.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()); +} + +static already_AddRefed<nsIPrincipal> GetRequestingPrincipal( + const FTPChannelCreationArgs& aArgs) { + if (aArgs.type() != FTPChannelCreationArgs::TFTPChannelOpenArgs) { + return nullptr; + } + + const FTPChannelOpenArgs& args = aArgs.get_FTPChannelOpenArgs(); + 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 nsCString& 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; +} + +PFTPChannelParent* NeckoParent::AllocPFTPChannelParent( + PBrowserParent* aBrowser, const SerializedLoadContext& aSerialized, + const FTPChannelCreationArgs& aOpenArgs) { + nsCOMPtr<nsIPrincipal> requestingPrincipal = + GetRequestingPrincipal(aOpenArgs); + + nsCOMPtr<nsILoadContext> loadContext; + const char* error = CreateChannelLoadContext( + aBrowser, Manager(), aSerialized, requestingPrincipal, loadContext); + if (error) { + printf_stderr( + "NeckoParent::AllocPFTPChannelParent: " + "FATAL error: %s: KILLING CHILD PROCESS\n", + error); + return nullptr; + } + PBOverrideStatus overrideStatus = + PBOverrideStatusFromLoadContext(aSerialized); + FTPChannelParent* p = new FTPChannelParent(BrowserParent::GetFrom(aBrowser), + loadContext, overrideStatus); + p->AddRef(); + return p; +} + +bool NeckoParent::DeallocPFTPChannelParent(PFTPChannelParent* channel) { + FTPChannelParent* p = static_cast<FTPChannelParent*>(channel); + p->Release(); + return true; +} + +mozilla::ipc::IPCResult NeckoParent::RecvPFTPChannelConstructor( + PFTPChannelParent* aActor, PBrowserParent* aBrowser, + const SerializedLoadContext& aSerialized, + const FTPChannelCreationArgs& aOpenArgs) { + FTPChannelParent* p = static_cast<FTPChannelParent*>(aActor); + if (!p->Init(aOpenArgs)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +already_AddRefed<PDocumentChannelParent> +NeckoParent::AllocPDocumentChannelParent( + const MaybeDiscarded<BrowsingContext>& aContext, + const DocumentChannelCreationArgs& args) { + RefPtr<DocumentChannelParent> p = new DocumentChannelParent(); + return p.forget(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvPDocumentChannelConstructor( + PDocumentChannelParent* aActor, + const MaybeDiscarded<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(); +} + +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 nsString& /* 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 nsCString& /* unused */) { + RefPtr<UDPSocketParent> p = new UDPSocketParent(this); + + return p.forget().take(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvPUDPSocketConstructor( + PUDPSocketParent* aActor, nsIPrincipal* aPrincipal, + const nsCString& 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 nsCString& aHost, const nsCString& aTrrServer, const uint16_t& aType, + const OriginAttributes& aOriginAttributes, const uint32_t& aFlags) { + RefPtr<DNSRequestHandler> handler = new DNSRequestHandler(); + RefPtr<DNSRequestParent> actor = new DNSRequestParent(handler); + return actor.forget(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvPDNSRequestConstructor( + PDNSRequestParent* aActor, const nsCString& aHost, + const nsCString& aTrrServer, const uint16_t& aType, + const OriginAttributes& aOriginAttributes, const uint32_t& aFlags) { + RefPtr<DNSRequestParent> actor = static_cast<DNSRequestParent*>(aActor); + RefPtr<DNSRequestHandler> handler = + actor->GetDNSRequest()->AsDNSRequestHandler(); + handler->DoAsyncResolve(aHost, aTrrServer, aType, aOriginAttributes, aFlags); + return IPC_OK(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvSpeculativeConnect( + nsIURI* aURI, nsIPrincipal* aPrincipal, 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 (aAnonymous) { + speculator->SpeculativeAnonymousConnect(aURI, principal, nullptr); + } else { + speculator->SpeculativeConnect(aURI, principal, nullptr); + } + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvHTMLDNSPrefetch( + const nsString& hostname, const bool& isHttps, + const OriginAttributes& aOriginAttributes, const uint32_t& flags) { + nsHTMLDNSPrefetch::Prefetch(hostname, isHttps, aOriginAttributes, flags); + return IPC_OK(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvCancelHTMLDNSPrefetch( + const nsString& hostname, const bool& isHttps, + const OriginAttributes& aOriginAttributes, const uint32_t& flags, + const nsresult& reason) { + nsHTMLDNSPrefetch::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; +} + +namespace { +std::map<uint64_t, nsCOMPtr<nsIAuthPromptCallback> >& CallbackMap() { + MOZ_ASSERT(NS_IsMainThread()); + static std::map<uint64_t, nsCOMPtr<nsIAuthPromptCallback> > sCallbackMap; + return sCallbackMap; +} +} // namespace + +NS_IMPL_ISUPPORTS(NeckoParent::NestedFrameAuthPrompt, nsIAuthPrompt2) + +NeckoParent::NestedFrameAuthPrompt::NestedFrameAuthPrompt(PNeckoParent* aParent, + TabId aNestedFrameId) + : mNeckoParent(aParent), mNestedFrameId(aNestedFrameId) {} + +NS_IMETHODIMP +NeckoParent::NestedFrameAuthPrompt::AsyncPromptAuth( + nsIChannel* aChannel, nsIAuthPromptCallback* callback, nsISupports*, + uint32_t, nsIAuthInformation* aInfo, nsICancelable**) { + static uint64_t callbackId = 0; + MOZ_ASSERT(XRE_IsParentProcess()); + nsCOMPtr<nsIURI> uri; + nsresult rv = aChannel->GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString spec; + if (uri) { + rv = uri->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + } + nsString realm; + rv = aInfo->GetRealm(realm); + NS_ENSURE_SUCCESS(rv, rv); + callbackId++; + if (mNeckoParent->SendAsyncAuthPromptForNestedFrame(mNestedFrameId, spec, + realm, callbackId)) { + CallbackMap()[callbackId] = callback; + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +mozilla::ipc::IPCResult NeckoParent::RecvOnAuthAvailable( + const uint64_t& aCallbackId, const nsString& aUser, + const nsString& aPassword, const nsString& aDomain) { + nsCOMPtr<nsIAuthPromptCallback> callback = CallbackMap()[aCallbackId]; + if (!callback) { + return IPC_OK(); + } + CallbackMap().erase(aCallbackId); + + RefPtr<nsAuthInformationHolder> holder = + new nsAuthInformationHolder(0, u""_ns, ""_ns); + holder->SetUsername(aUser); + holder->SetPassword(aPassword); + holder->SetDomain(aDomain); + + callback->OnAuthAvailable(nullptr, holder); + return IPC_OK(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvOnAuthCancelled( + const uint64_t& aCallbackId, const bool& aUserCancel) { + nsCOMPtr<nsIAuthPromptCallback> callback = CallbackMap()[aCallbackId]; + if (!callback) { + return IPC_OK(); + } + CallbackMap().erase(aCallbackId); + callback->OnAuthCancelled(nullptr, aUserCancel); + return IPC_OK(); +} + +/* 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(); +} + +PClassifierDummyChannelParent* NeckoParent::AllocPClassifierDummyChannelParent( + nsIURI* aURI, nsIURI* aTopWindowURI, const nsresult& aTopWindowURIResult, + const Maybe<LoadInfoArgs>& aLoadInfo) { + RefPtr<ClassifierDummyChannelParent> c = new ClassifierDummyChannelParent(); + return c.forget().take(); +} + +mozilla::ipc::IPCResult NeckoParent::RecvPClassifierDummyChannelConstructor( + PClassifierDummyChannelParent* aActor, nsIURI* aURI, nsIURI* aTopWindowURI, + const nsresult& aTopWindowURIResult, const Maybe<LoadInfoArgs>& aLoadInfo) { + ClassifierDummyChannelParent* p = + static_cast<ClassifierDummyChannelParent*>(aActor); + + if (NS_WARN_IF(!aURI)) { + return IPC_FAIL_NO_REASON(this); + } + + nsCOMPtr<nsILoadInfo> loadInfo; + nsresult rv = LoadInfoArgsToLoadInfo(aLoadInfo, getter_AddRefs(loadInfo)); + if (NS_WARN_IF(NS_FAILED(rv)) || !loadInfo) { + return IPC_FAIL_NO_REASON(this); + } + + p->Init(aURI, aTopWindowURI, aTopWindowURIResult, loadInfo); + return IPC_OK(); +} + +bool NeckoParent::DeallocPClassifierDummyChannelParent( + PClassifierDummyChannelParent* aActor) { + RefPtr<ClassifierDummyChannelParent> c = + dont_AddRef(static_cast<ClassifierDummyChannelParent*>(aActor)); + MOZ_ASSERT(c); + return true; +} + +mozilla::ipc::IPCResult NeckoParent::RecvInitSocketProcessBridge( + InitSocketProcessBridgeResolver&& aResolver) { + MOZ_ASSERT(NS_IsMainThread()); + + Endpoint<PSocketProcessBridgeChild> invalidEndpoint; + if (NS_WARN_IF(mSocketProcessBridgeInited)) { + aResolver(std::move(invalidEndpoint)); + return IPC_OK(); + } + + SocketProcessParent* parent = SocketProcessParent::GetSingleton(); + if (NS_WARN_IF(!parent)) { + aResolver(std::move(invalidEndpoint)); + return IPC_OK(); + } + + Endpoint<PSocketProcessBridgeParent> parentEndpoint; + Endpoint<PSocketProcessBridgeChild> childEndpoint; + if (NS_WARN_IF(NS_FAILED(PSocketProcessBridge::CreateEndpoints( + parent->OtherPid(), Manager()->OtherPid(), &parentEndpoint, + &childEndpoint)))) { + aResolver(std::move(invalidEndpoint)); + return IPC_OK(); + } + + if (NS_WARN_IF(!parent->SendInitSocketProcessBridgeParent( + Manager()->OtherPid(), std::move(parentEndpoint)))) { + aResolver(std::move(invalidEndpoint)); + return IPC_OK(); + } + + aResolver(std::move(childEndpoint)); + mSocketProcessBridgeInited = true; + 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, 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 nsCOMPtr<nsIInputStream>& aStream) { + aResolver(aStream); + }, + [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(nullptr); + }); + + return IPC_OK(); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/NeckoParent.h b/netwerk/ipc/NeckoParent.h new file mode 100644 index 0000000000..1137c94b01 --- /dev/null +++ b/netwerk/ipc/NeckoParent.h @@ -0,0 +1,252 @@ +/* -*- 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: + NeckoParent(); + virtual ~NeckoParent() = default; + + [[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); + } + + /* + * This implementation of nsIAuthPrompt2 is used for nested remote iframes + * that want an auth prompt. This class lives in the parent process and + * informs the NeckoChild that we want an auth prompt, which forwards the + * request to the BrowserParent in the remote iframe that contains the nested + * iframe + */ + class NestedFrameAuthPrompt final : public nsIAuthPrompt2 { + ~NestedFrameAuthPrompt() {} + + public: + NS_DECL_ISUPPORTS + + NestedFrameAuthPrompt(PNeckoParent* aParent, TabId aNestedFrameId); + + NS_IMETHOD PromptAuth(nsIChannel*, uint32_t, nsIAuthInformation*, + bool*) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD AsyncPromptAuth(nsIChannel* aChannel, + nsIAuthPromptCallback* callback, nsISupports*, + uint32_t, nsIAuthInformation* aInfo, + nsICancelable**) override; + + protected: + PNeckoParent* mNeckoParent; + TabId mNestedFrameId; + }; + + protected: + 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 nsCString& type, const int64_t& predictedSize, + PHttpChannelParent* channel); + bool DeallocPAltDataOutputStreamParent(PAltDataOutputStreamParent* aActor); + + bool DeallocPCookieServiceParent(PCookieServiceParent*); + PFTPChannelParent* AllocPFTPChannelParent( + PBrowserParent* aBrowser, const SerializedLoadContext& aSerialized, + const FTPChannelCreationArgs& aOpenArgs); + virtual mozilla::ipc::IPCResult RecvPFTPChannelConstructor( + PFTPChannelParent* aActor, PBrowserParent* aBrowser, + const SerializedLoadContext& aSerialized, + const FTPChannelCreationArgs& aOpenArgs) override; + bool DeallocPFTPChannelParent(PFTPChannelParent*); + PWebSocketParent* AllocPWebSocketParent( + PBrowserParent* browser, const SerializedLoadContext& aSerialized, + const uint32_t& aSerial); + bool DeallocPWebSocketParent(PWebSocketParent*); + PTCPSocketParent* AllocPTCPSocketParent(const nsString& 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 nsCString& aFilter); + virtual mozilla::ipc::IPCResult RecvPUDPSocketConstructor( + PUDPSocketParent*, nsIPrincipal* aPrincipal, + const nsCString& aFilter) override; + bool DeallocPUDPSocketParent(PUDPSocketParent*); + already_AddRefed<PDNSRequestParent> AllocPDNSRequestParent( + const nsCString& aHost, const nsCString& aTrrServer, + const uint16_t& aType, const OriginAttributes& aOriginAttributes, + const uint32_t& aFlags); + virtual mozilla::ipc::IPCResult RecvPDNSRequestConstructor( + PDNSRequestParent* actor, const nsCString& hostName, + const nsCString& trrServer, const uint16_t& type, + const OriginAttributes& aOriginAttributes, + const uint32_t& flags) override; + mozilla::ipc::IPCResult RecvSpeculativeConnect(nsIURI* aURI, + nsIPrincipal* aPrincipal, + const bool& aAnonymous); + mozilla::ipc::IPCResult RecvHTMLDNSPrefetch( + const nsString& hostname, const bool& isHttps, + const OriginAttributes& aOriginAttributes, const uint32_t& flags); + mozilla::ipc::IPCResult RecvCancelHTMLDNSPrefetch( + const nsString& hostname, const bool& isHttps, + const OriginAttributes& aOriginAttributes, const uint32_t& 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; + + PSimpleChannelParent* AllocPSimpleChannelParent(const uint32_t& channelId); + bool DeallocPSimpleChannelParent(PSimpleChannelParent* parent); + + 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); + + mozilla::ipc::IPCResult RecvOnAuthAvailable(const uint64_t& aCallbackId, + const nsString& aUser, + const nsString& aPassword, + const nsString& aDomain); + mozilla::ipc::IPCResult RecvOnAuthCancelled(const uint64_t& aCallbackId, + const bool& aUserCancel); + + /* 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, GetPageThumbStreamResolver&& aResolve); + + PClassifierDummyChannelParent* AllocPClassifierDummyChannelParent( + nsIURI* aURI, nsIURI* aTopWindowURI, const nsresult& aTopWindowURIResult, + const Maybe<LoadInfoArgs>& aLoadInfo); + + bool DeallocPClassifierDummyChannelParent( + PClassifierDummyChannelParent* aParent); + + virtual mozilla::ipc::IPCResult RecvPClassifierDummyChannelConstructor( + PClassifierDummyChannelParent* aActor, nsIURI* aURI, + nsIURI* aTopWindowURI, const nsresult& aTopWindowURIResult, + const Maybe<LoadInfoArgs>& aLoadInfo) override; + + mozilla::ipc::IPCResult RecvInitSocketProcessBridge( + InitSocketProcessBridgeResolver&& aResolver); + + 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..2dba003fe4 --- /dev/null +++ b/netwerk/ipc/PDataChannel.ipdl @@ -0,0 +1,24 @@ +/* -*- 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 { + +async refcounted 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..b6fdbb7b20 --- /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 nsHttpHeaderArray from "nsHttpHeaderArray.h"; +using mozilla::net::NetAddr from "mozilla/net/DNS.h"; + +namespace mozilla { +namespace net { + +refcounted protocol PDocumentChannel +{ + manager PNecko; + +parent: + + async Cancel(nsresult status); + + 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 aSwitchedProcess); + + // 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..cc5f456166 --- /dev/null +++ b/netwerk/ipc/PFileChannel.ipdl @@ -0,0 +1,27 @@ +/* -*- 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 + */ +async refcounted 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..5668fed06a --- /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 { + +refcounted 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..624645e2a1 --- /dev/null +++ b/netwerk/ipc/PNecko.ipdl @@ -0,0 +1,190 @@ +/* -*- 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; +include protocol PFTPChannel; +include protocol PWebSocket; +include protocol PWebSocketEventListener; +include protocol PTCPSocket; +include protocol PTCPServerSocket; +include protocol PUDPSocket; +include protocol PDNSRequest; +include protocol PFileDescriptorSet; +include protocol PDataChannel; +include protocol PSimpleChannel; +include protocol PTransportProvider; +include protocol PChildToParentStream; //FIXME: bug #792908 +include protocol PParentToChildStream; //FIXME: bug #792908 +include protocol PStunAddrsRequest; +include protocol PFileChannel; +include protocol PClassifierDummyChannel; +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 refcounted class nsIInputStream from "mozilla/ipc/IPCStreamUtils.h"; +using refcounted class nsIURI from "mozilla/ipc/URIUtils.h"; +using refcounted class nsIPrincipal from "nsIPrincipal.h"; + + +namespace mozilla { +namespace net { + +//------------------------------------------------------------------- +nested(upto inside_cpow) sync protocol PNecko +{ + manager PContent; + manages PHttpChannel; + manages PCookieService; + manages PFTPChannel; + manages PWebSocket; + manages PWebSocketEventListener; + manages PTCPSocket; + manages PTCPServerSocket; + manages PUDPSocket; + manages PDNSRequest; + manages PDataChannel; + manages PSimpleChannel; + manages PFileChannel; + manages PTransportProvider; + manages PAltDataOutputStream; + manages PStunAddrsRequest; + manages PClassifierDummyChannel; + manages PWebrtcTCPSocket; + manages PDocumentChannel; + +parent: + async __delete__(); + + nested(inside_cpow) async PCookieService(); + async PHttpChannel(nullable PBrowser browser, + SerializedLoadContext loadContext, + HttpChannelCreationArgs args); + async PFTPChannel(nullable PBrowser browser, SerializedLoadContext loadContext, + FTPChannelCreationArgs args); + + async PWebSocket(nullable PBrowser browser, SerializedLoadContext loadContext, + uint32_t aSerialID); + async PTCPServerSocket(uint16_t localPort, uint16_t backlog, bool useArrayBuffers); + async PUDPSocket(nsIPrincipal principal, nsCString filter); + + async PDNSRequest(nsCString hostName, nsCString trrServer, uint16_t type, + OriginAttributes originAttributes, uint32_t flags); + + async PDocumentChannel(MaybeDiscardedBrowsingContext browsingContext, + DocumentChannelCreationArgs args); + + async PWebSocketEventListener(uint64_t aInnerWindowID); + + /* Predictor Methods */ + async PredPredict(nsIURI targetURI, nsIURI sourceURI, + uint32_t reason, OriginAttributes originAttributes, + bool hasVerifier); + async PredLearn(nsIURI targetURI, nsIURI sourceURI, + uint32_t reason, OriginAttributes originAttributes); + async PredReset(); + + async SpeculativeConnect(nsIURI uri, nsIPrincipal principal, bool anonymous); + async HTMLDNSPrefetch(nsString hostname, bool isHttps, + OriginAttributes originAttributes, uint32_t flags); + async CancelHTMLDNSPrefetch(nsString hostname, bool isHttps, + OriginAttributes originAttributes, + uint32_t 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); + async PSimpleChannel(uint32_t channelId); + async PFileChannel(uint32_t channelId); + + async PClassifierDummyChannel(nsIURI uri, nsIURI aTopWindowURI, + nsresult aTopWindowURIResult, + LoadInfoArgs? loadInfo); + + /** + * These are called from the child with the results of the auth prompt. + * callbackId is the id that was passed in PBrowser::AsyncAuthPrompt, + * corresponding to an nsIAuthPromptCallback + */ + async OnAuthAvailable(uint64_t callbackId, nsString user, + nsString password, nsString domain); + async OnAuthCancelled(uint64_t callbackId, bool userCancel); + + 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(nsIURI uri) returns (nsIInputStream stream); + async GetExtensionFD(nsIURI uri) returns (FileDescriptor fd); + + async InitSocketProcessBridge() + returns (Endpoint<PSocketProcessBridgeChild> endpoint); + + async EnsureHSTSData() + returns (bool result); + + /** + * Page thumbnails remote resource loading + */ + async GetPageThumbStream(nsIURI uri) returns (nsIInputStream stream); + +child: + /* + * Bring up the http auth prompt for a nested remote mozbrowser. + * NestedFrameId is the id corresponding to the PBrowser. It is the same id + * that was passed to the PBrowserOrId param in to the PHttpChannel constructor + */ + async AsyncAuthPromptForNestedFrame(TabId nestedFrameId, nsCString uri, + nsString realm, uint64_t callbackId); + + /* Predictor Methods */ + async PredOnPredictPrefetch(nsIURI uri, uint32_t httpStatus); + async PredOnPredictPreconnect(nsIURI uri); + async PredOnPredictDNS(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. + prio(mediumhigh) async NetworkChangeNotification(nsCString type); + + async PTransportProvider(); + +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/PProxyConfigLookup.ipdl b/netwerk/ipc/PProxyConfigLookup.ipdl new file mode 100644 index 0000000000..49cb69071a --- /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 refcounted 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..74a113e301 --- /dev/null +++ b/netwerk/ipc/PSimpleChannel.ipdl @@ -0,0 +1,24 @@ +/* -*- 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 { + +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..b885d6a6a7 --- /dev/null +++ b/netwerk/ipc/PSocketProcess.ipdl @@ -0,0 +1,182 @@ +/* -*- 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 PFileDescriptorSet; +include protocol PChildToParentStream; +include protocol PParentToChildStream; +include protocol PInputChannelThrottleQueue; +include protocol PBackground; +include protocol PAltService; +include protocol PAltSvcTransaction; +include protocol PTRRService; +include protocol PProxyConfigLookup; +include protocol PNativeDNSResolverOverride; +include protocol PRemoteLazyInputStream; + +include MemoryReportTypes; +include NeckoChannelParams; +include PrefsTypes; +include PSMIPCTypes; + +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"; +using refcounted 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"; + +namespace mozilla { +namespace net { + +struct HttpHandlerInitArgs { + bool mFastOpenSupported; + 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; + FileDescriptor? mSandboxBroker; +}; + +sync protocol PSocketProcess +{ + manages PDNSRequest; + manages PWebrtcTCPSocket; + manages PFileDescriptorSet; + manages PHttpTransaction; + manages PHttpConnectionMgr; + manages PChildToParentStream; + manages PParentToChildStream; + manages PInputChannelThrottleQueue; + manages PAltService; + manages PAltSvcTransaction; + manages PTRRService; + manages PProxyConfigLookup; + manages PNativeDNSResolverOverride; + manages PRemoteLazyInputStream; + +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 PChildToParentStream(); + async ObserveHttpActivity(HttpActivityArgs aActivityArgs, + uint32_t aActivityType, + uint32_t aActivitySubtype, + PRTime aTimestamp, + uint64_t aExtraSizeData, + nsCString aExtraStringData); + async InitBackground(Endpoint<PBackgroundParent> aEndpoint); + async PAltService(); + sync GetTLSClientCert(nsCString aHostName, + OriginAttributes aOriginAttributes, + int32_t aPort, + uint32_t aProviderFlags, + uint32_t aProviderTlsFlags, + ByteArray aServerCert, + ByteArray? aClientCert, + ByteArray[] aCollectedCANames) + returns (bool aSucceeded, ByteArray aOutCert, ByteArray aOutKey, ByteArray[] aBuiltChain); + async PProxyConfigLookup(nsIURI aUri, uint32_t aFlags); + async CachePushCheck(nsIURI aPushedURL, + OriginAttributes aOriginAttributes, + nsCString aRequestString) + returns (bool aAccepted); + +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); + // test-only + async SocketProcessTelemetryPing(); + + async PHttpTransaction(); + async PParentToChildStream(); + async PHttpConnectionMgr(HttpHandlerInitArgs aArgs); + async UpdateDeviceModelId(nsCString aModelId); + + async OnHttpActivityDistributorActivated(bool aIsActivated); + async PInputChannelThrottleQueue(uint32_t meanBytesPerSecond, + uint32_t maxBytesPerSecond); + async PAltSvcTransaction(HttpConnectionInfoCloneArgs aConnInfo, + uint32_t aCaps); + async ClearSessionCache(); + async PTRRService(bool aCaptiveIsPassed, + bool aParentalControlEnabled, + nsCString[] aDNSSuffixList); + async PNativeDNSResolverOverride(); + async NotifyObserver(nsCString aTopic, nsString aData); + + async PRemoteLazyInputStream(nsID aID, uint64_t aSize); + + async GetSocketData() + returns (SocketDataArgs data); + async GetDNSCacheEntries() + returns (DNSCacheEntries[] entries); + async GetHttpConnectionData() + returns (HttpRetParams[] params); + +both: + async PFileDescriptorSet(FileDescriptor fd); + async PDNSRequest(nsCString hostName, nsCString trrServer, uint16_t type, + OriginAttributes originAttributes, uint32_t flags); +}; + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/PSocketProcessBridge.ipdl b/netwerk/ipc/PSocketProcessBridge.ipdl new file mode 100644 index 0000000000..b57eca181b --- /dev/null +++ b/netwerk/ipc/PSocketProcessBridge.ipdl @@ -0,0 +1,30 @@ +/* -*- 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 PBackground; + +namespace mozilla { +namespace net { + +/** + * PSocketProcessBridge is the IPC protocol between content process and + * socket process. This protocol allows socket process to send data to + * content process bypassing parent process. + * Once created, PSocketProcessBridgeChild is the actor that lives in + * content process and PSocketProcessBridgeParent lives in + * socket process. + */ +nested(upto inside_cpow) sync protocol PSocketProcessBridge +{ + +parent: + async InitBackground(Endpoint<PBackgroundParent> aEndpoint); +both: + async Test(); +}; + +} +} diff --git a/netwerk/ipc/ParentChannelWrapper.cpp b/netwerk/ipc/ParentChannelWrapper.cpp new file mode 100644 index 0000000000..57b9fe37c1 --- /dev/null +++ b/netwerk/ipc/ParentChannelWrapper.cpp @@ -0,0 +1,107 @@ +/* -*- 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/UrlClassifierCommon.h" +#include "nsIRedirectChannelRegistrar.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::NotifyFlashPluginStateChanged( + nsIHttpChannel::FlashPluginState aState) { + // For now, only HttpChannel use this attribute. + RefPtr<HttpBaseChannel> httpChannel = do_QueryObject(mChannel); + if (httpChannel) { + httpChannel->SetFlashPluginState(aState); + } + 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..b9856a2913 --- /dev/null +++ b/netwerk/ipc/ParentProcessDocumentChannel.cpp @@ -0,0 +1,300 @@ +/* -*- 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/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" + +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 aIsXFOError) + : DocumentChannel(aLoadState, aLoadInfo, aLoadFlags, aCacheKey, + aUriModified, aIsXFOError) { + 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) { + 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()) { + 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), mAsyncOpenTime, mTiming, + std::move(initialClientInfo), Some(mUriModified), Some(mIsXFOError), + 0 /* ProcessId */, &rv); + } else { + promise = mDocumentLoadListener->OpenObject( + mLoadState, mCacheKey, Some(mChannelId), mAsyncOpenTime, mTiming, + std::move(initialClientInfo), InnerWindowIDForExtantDoc(docShell), + mLoadFlags, mLoadInfo->InternalContentPolicyType(), + UserActivation::IsHandlingUserInput(), 0 /* ProcessId */, + 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) { + // 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) + ->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.mSwitchedProcess) { + self->DisconnectChildListeners(aRejectValue.mStatus, + aRejectValue.mLoadGroupStatus); + } + self->RemoveObserver(); + }); + return NS_OK; +} + +NS_IMETHODIMP ParentProcessDocumentChannel::Cancel(nsresult aStatus) { + LOG(("ParentProcessDocumentChannel Cancel [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(aStatus); + + 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..5331be8d8b --- /dev/null +++ b/netwerk/ipc/ParentProcessDocumentChannel.h @@ -0,0 +1,55 @@ +/* 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 "ProtocolUtils.h" +#include "mozilla/net/DocumentChannel.h" +#include "mozilla/net/DocumentLoadListener.h" +#include "nsIObserver.h" + +namespace mozilla { +namespace net { + +class ParentProcessDocumentChannel : public DocumentChannel, + public nsIAsyncVerifyRedirectCallback, + public nsIObserver { + public: + ParentProcessDocumentChannel(nsDocShellLoadState* aLoadState, + class LoadInfo* aLoadInfo, + nsLoadFlags aLoadFlags, uint32_t aCacheKey, + bool aUriModified, bool aIsXFOError); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK + NS_DECL_NSIOBSERVER + + NS_IMETHOD AsyncOpen(nsIStreamListener* aListener) override; + NS_IMETHOD Cancel(nsresult aStatusCode) override; + + RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise> + RedirectToRealChannel( + nsTArray<ipc::Endpoint<extensions::PStreamFilterParent>>&& + aStreamFilterEndpoints, + uint32_t aRedirectFlags, uint32_t aLoadFlags); + + 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/ProxyConfigLookup.cpp b/netwerk/ipc/ProxyConfigLookup.cpp new file mode 100644 index 0000000000..58f6088d31 --- /dev/null +++ b/netwerk/ipc/ProxyConfigLookup.cpp @@ -0,0 +1,98 @@ +/* -*- 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" + +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..da935c9028 --- /dev/null +++ b/netwerk/ipc/ProxyConfigLookup.h @@ -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/. */ + +#ifndef mozilla_net_ProxyConfigLookup_h +#define mozilla_net_ProxyConfigLookup_h + +#include <functional> +#include "nsIProtocolProxyCallback.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/SocketProcessBridgeChild.cpp b/netwerk/ipc/SocketProcessBridgeChild.cpp new file mode 100644 index 0000000000..01a5a7ad35 --- /dev/null +++ b/netwerk/ipc/SocketProcessBridgeChild.cpp @@ -0,0 +1,180 @@ +/* -*- 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/dom/ContentChild.h" +#include "mozilla/net/NeckoChild.h" +#include "nsIObserverService.h" +#include "nsThreadUtils.h" +#include "mozilla/Preferences.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(std::move(aEndpoint)); + if (sSocketProcessBridgeChild->Inited()) { + return true; + } + + sSocketProcessBridgeChild = nullptr; + return false; +} + +// static +already_AddRefed<SocketProcessBridgeChild> +SocketProcessBridgeChild::GetSingleton() { + RefPtr<SocketProcessBridgeChild> child = sSocketProcessBridgeChild; + return child.forget(); +} + +static bool SocketProcessEnabled() { + static bool sInited = false; + static bool sSocketProcessEnabled = false; + if (!sInited) { + sSocketProcessEnabled = Preferences::GetBool("network.process.enabled") && + XRE_IsContentProcess(); + sInited = true; + } + + return sSocketProcessEnabled; +} + +// static +RefPtr<SocketProcessBridgeChild::GetPromise> +SocketProcessBridgeChild::GetSocketProcessBridge() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!SocketProcessEnabled()) { + 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( + Endpoint<PSocketProcessBridgeChild>&& aEndpoint) + : mShuttingDown(false) { + LOG(("CONSTRUCT SocketProcessBridgeChild::SocketProcessBridgeChild\n")); + + mInited = aEndpoint.Bind(this); + if (!mInited) { + MOZ_ASSERT(false, "Bind failed!"); + return; + } + + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + os->AddObserver(this, "content-child-shutdown", false); + } + + mSocketProcessPid = aEndpoint.OtherPid(); +} + +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")); + 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..385d8e8ac6 --- /dev/null +++ b/netwerk/ipc/SocketProcessBridgeChild.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_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(); + typedef MozPromise<RefPtr<SocketProcessBridgeChild>, nsCString, false> + GetPromise; + static RefPtr<GetPromise> GetSocketProcessBridge(); + + mozilla::ipc::IPCResult RecvTest(); + void ActorDestroy(ActorDestroyReason aWhy) override; + void DeferredDestroy(); + bool IsShuttingDown() const { return mShuttingDown; }; + bool Inited() const { return mInited; }; + ProcessId SocketProcessPid() const { return mSocketProcessPid; }; + + private: + DISALLOW_COPY_AND_ASSIGN(SocketProcessBridgeChild); + static bool Create(Endpoint<PSocketProcessBridgeChild>&& aEndpoint); + explicit SocketProcessBridgeChild( + Endpoint<PSocketProcessBridgeChild>&& aEndpoint); + virtual ~SocketProcessBridgeChild(); + + static StaticRefPtr<SocketProcessBridgeChild> sSocketProcessBridgeChild; + bool mShuttingDown; + bool mInited = false; + 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..f28fa98925 --- /dev/null +++ b/netwerk/ipc/SocketProcessBridgeParent.cpp @@ -0,0 +1,65 @@ +/* -*- 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" + +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/ipc/Endpoint.h" +#include "SocketProcessChild.h" + +namespace mozilla { +namespace net { + +SocketProcessBridgeParent::SocketProcessBridgeParent( + ProcessId aId, Endpoint<PSocketProcessBridgeParent>&& aEndpoint) + : mId(aId), mClosed(false) { + LOG(( + "CONSTRUCT SocketProcessBridgeParent::SocketProcessBridgeParent mId=%d\n", + mId)); + MOZ_COUNT_CTOR(SocketProcessBridgeParent); + DebugOnly<bool> ok = aEndpoint.Bind(this); + MOZ_ASSERT(ok); +} + +SocketProcessBridgeParent::~SocketProcessBridgeParent() { + LOG(("DESTRUCT SocketProcessBridgeParent::SocketProcessBridgeParent mId=%d\n", + mId)); + MOZ_COUNT_DTOR(SocketProcessBridgeParent); +} + +mozilla::ipc::IPCResult SocketProcessBridgeParent::RecvTest() { + LOG(("SocketProcessBridgeParent::RecvTest\n")); + Unused << SendTest(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessBridgeParent::RecvInitBackground( + Endpoint<PBackgroundParent>&& aEndpoint) { + LOG(("SocketProcessBridgeParent::RecvInitBackground mId=%d\n", mId)); + if (!ipc::BackgroundParent::Alloc(nullptr, std::move(aEndpoint))) { + return IPC_FAIL(this, "BackgroundParent::Alloc failed"); + } + + return IPC_OK(); +} + +void SocketProcessBridgeParent::ActorDestroy(ActorDestroyReason aWhy) { + LOG(("SocketProcessBridgeParent::ActorDestroy mId=%d\n", mId)); + + mClosed = true; + GetCurrentSerialEventTarget()->Dispatch( + NewRunnableMethod("net::SocketProcessBridgeParent::DeferredDestroy", this, + &SocketProcessBridgeParent::DeferredDestroy)); +} + +void SocketProcessBridgeParent::DeferredDestroy() { + if (SocketProcessChild* child = SocketProcessChild::GetSingleton()) { + child->DestroySocketProcessBridgeParent(mId); + } +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/SocketProcessBridgeParent.h b/netwerk/ipc/SocketProcessBridgeParent.h new file mode 100644 index 0000000000..f35559d464 --- /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) + + explicit SocketProcessBridgeParent( + ProcessId aId, Endpoint<PSocketProcessBridgeParent>&& aEndpoint); + + mozilla::ipc::IPCResult RecvTest(); + mozilla::ipc::IPCResult RecvInitBackground( + Endpoint<PBackgroundParent>&& aEndpoint); + + void ActorDestroy(ActorDestroyReason aWhy) override; + void DeferredDestroy(); + + bool Closed() const { return mClosed; } + + private: + ~SocketProcessBridgeParent(); + + ProcessId mId; + bool mClosed; +}; + +} // 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..3d422a0f7c --- /dev/null +++ b/netwerk/ipc/SocketProcessChild.cpp @@ -0,0 +1,618 @@ +/* -*- 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/dom/MemoryReportRequest.h" +#include "mozilla/ipc/CrashReporterClient.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/ipc/FileDescriptorSetChild.h" +#include "mozilla/ipc/IPCStreamAlloc.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/TRRServiceChild.h" +#include "mozilla/ipc/PChildToParentStreamChild.h" +#include "mozilla/ipc/PParentToChildStreamChild.h" +#include "mozilla/Preferences.h" +#include "mozilla/RemoteLazyInputStreamChild.h" +#include "mozilla/Telemetry.h" +#include "nsDebugImpl.h" +#include "nsHttpConnectionInfo.h" +#include "nsHttpHandler.h" +#include "nsIDNSService.h" +#include "nsIHttpActivityObserver.h" +#include "nsNetUtil.h" +#include "nsNSSComponent.h" +#include "nsSocketTransportService2.h" +#include "nsThreadManager.h" +#include "ProcessUtils.h" +#include "SocketProcessBridgeParent.h" + +#if defined(XP_WIN) +# include <process.h> +#else +# include <unistd.h> +#endif + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) +# include "mozilla/Sandbox.h" +#endif + +#ifdef MOZ_GECKO_PROFILER +# include "ChildProfilerController.h" +#endif + +#ifdef MOZ_WEBRTC +# include "mozilla/net/WebrtcTCPSocketChild.h" +#endif + +namespace mozilla { +namespace net { + +using namespace ipc; + +SocketProcessChild* sSocketProcessChild; + +SocketProcessChild::SocketProcessChild() + : mShuttingDown(false), mMutex("SocketProcessChild::mMutex") { + 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 + +bool SocketProcessChild::Init(base::ProcessId aParentPid, + const char* aParentBuildID, MessageLoop* aIOLoop, + UniquePtr<IPC::Channel> aChannel) { + if (NS_WARN_IF(NS_FAILED(nsThreadManager::get().Init()))) { + return false; + } + if (NS_WARN_IF(!Open(std::move(aChannel), aParentPid, aIOLoop))) { + 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; + } + + BackgroundChild::Startup(); + 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; + } + + return true; +} + +void SocketProcessChild::ActorDestroy(ActorDestroyReason aWhy) { + LOG(("SocketProcessChild::ActorDestroy\n")); + + mShuttingDown = true; + + if (AbnormalShutdown == aWhy) { + NS_WARNING("Shutting down Socket process early due to a crash!"); + ProcessChild::QuickExit(); + } + +#ifdef MOZ_GECKO_PROFILER + if (mProfilerController) { + mProfilerController->Shutdown(); + mProfilerController = nullptr; + } +#endif + + CrashReporterClient::DestroySingleton(); + XRE_ShutdownChildProcess(); +} + +void SocketProcessChild::CleanUp() { + LOG(("SocketProcessChild::CleanUp\n")); + + for (auto iter = mSocketProcessBridgeParentMap.Iter(); !iter.Done(); + iter.Next()) { + if (!iter.Data()->Closed()) { + iter.Data()->Close(); + } + } + + { + MutexAutoLock lock(mMutex); + mBackgroundDataBridgeMap.Clear(); + } + NS_ShutdownXPCOM(nullptr); +} + +mozilla::ipc::IPCResult SocketProcessChild::RecvInit( + const SocketPorcessInitAttributes& aAttributes) { + Unused << RecvSetOffline(aAttributes.mOffline()); + Unused << RecvSetConnectivity(aAttributes.mConnectivity()); + if (aAttributes.mInitSandbox()) { + Unused << RecvInitLinuxSandbox(aAttributes.mSandboxBroker()); + } + 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.Get(aContentProcessId, nullptr)); + + mSocketProcessBridgeParentMap.Put( + aContentProcessId, MakeRefPtr<SocketProcessBridgeParent>( + aContentProcessId, std::move(aEndpoint))); + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessChild::RecvInitProfiler( + Endpoint<PProfilerChild>&& aEndpoint) { +#ifdef MOZ_GECKO_PROFILER + mProfilerController = + mozilla::ChildProfilerController::Create(std::move(aEndpoint)); +#endif + return IPC_OK(); +} + +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(); +} + +PFileDescriptorSetChild* SocketProcessChild::AllocPFileDescriptorSetChild( + const FileDescriptor& aFD) { + return new FileDescriptorSetChild(aFD); +} + +bool SocketProcessChild::DeallocPFileDescriptorSetChild( + PFileDescriptorSetChild* aActor) { + delete aActor; + return true; +} + +PChildToParentStreamChild* +SocketProcessChild::AllocPChildToParentStreamChild() { + MOZ_CRASH("PChildToParentStreamChild actors should be manually constructed!"); +} + +bool SocketProcessChild::DeallocPChildToParentStreamChild( + PChildToParentStreamChild* aActor) { + delete aActor; + return true; +} + +PParentToChildStreamChild* +SocketProcessChild::AllocPParentToChildStreamChild() { + return mozilla::ipc::AllocPParentToChildStreamChild(); +} + +bool SocketProcessChild::DeallocPParentToChildStreamChild( + PParentToChildStreamChild* aActor) { + delete aActor; + return true; +} + +PChildToParentStreamChild* +SocketProcessChild::SendPChildToParentStreamConstructor( + PChildToParentStreamChild* aActor) { + MOZ_ASSERT(NS_IsMainThread()); + return PSocketProcessChild::SendPChildToParentStreamConstructor(aActor); +} + +PFileDescriptorSetChild* SocketProcessChild::SendPFileDescriptorSetConstructor( + const FileDescriptor& aFD) { + MOZ_ASSERT(NS_IsMainThread()); + return PSocketProcessChild::SendPFileDescriptorSetConstructor(aFD); +} + +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 nsCString& aModelId) { + MOZ_ASSERT(gHttpHandler); + gHttpHandler->SetDeviceModelId(aModelId); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +SocketProcessChild::RecvOnHttpActivityDistributorActivated( + const bool& aIsActivated) { + if (nsCOMPtr<nsIHttpActivityObserver> distributor = + services::GetHttpActivityDistributor()) { + distributor->SetIsActive(aIsActivated); + } + 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 nsCString& aHost, const nsCString& aTrrServer, const uint16_t& aType, + const OriginAttributes& aOriginAttributes, const uint32_t& aFlags) { + RefPtr<DNSRequestHandler> handler = new DNSRequestHandler(); + RefPtr<DNSRequestChild> actor = new DNSRequestChild(handler); + return actor.forget(); +} + +mozilla::ipc::IPCResult SocketProcessChild::RecvPDNSRequestConstructor( + PDNSRequestChild* aActor, const nsCString& aHost, + const nsCString& aTrrServer, const uint16_t& aType, + const OriginAttributes& aOriginAttributes, const uint32_t& aFlags) { + RefPtr<DNSRequestChild> actor = static_cast<DNSRequestChild*>(aActor); + RefPtr<DNSRequestHandler> handler = + actor->GetDNSRequest()->AsDNSRequestHandler(); + handler->DoAsyncResolve(aHost, aTrrServer, aType, aOriginAttributes, aFlags); + return IPC_OK(); +} + +void SocketProcessChild::AddDataBridgeToMap( + uint64_t aChannelId, BackgroundDataBridgeParent* aActor) { + ipc::AssertIsOnBackgroundThread(); + MutexAutoLock lock(mMutex); + mBackgroundDataBridgeMap.Put(aChannelId, aActor); +} + +void SocketProcessChild::RemoveDataBridgeFromMap(uint64_t aChannelId) { + ipc::AssertIsOnBackgroundThread(); + MutexAutoLock lock(mMutex); + mBackgroundDataBridgeMap.Remove(aChannelId); +} + +Maybe<RefPtr<BackgroundDataBridgeParent>> +SocketProcessChild::GetAndRemoveDataBridge(uint64_t aChannelId) { + MutexAutoLock lock(mMutex); + return mBackgroundDataBridgeMap.GetAndRemove(aChannelId); +} + +mozilla::ipc::IPCResult SocketProcessChild::RecvClearSessionCache() { + if (EnsureNSSInitializedChromeOrContent()) { + nsNSSComponent::DoClearSSLExternalAndInternalSessionCache(); + } + 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 nsCString& aTopic, const nsString& aData) { + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, aTopic.get(), aData.get()); + } + return IPC_OK(); +} + +already_AddRefed<PRemoteLazyInputStreamChild> +SocketProcessChild::AllocPRemoteLazyInputStreamChild(const nsID& aID, + const uint64_t& aSize) { + RefPtr<RemoteLazyInputStreamChild> actor = + new RemoteLazyInputStreamChild(aID, aSize); + return actor.forget(); +} + +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(); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/SocketProcessChild.h b/netwerk/ipc/SocketProcessChild.h new file mode 100644 index 0000000000..2cee1d18a5 --- /dev/null +++ b/netwerk/ipc/SocketProcessChild.h @@ -0,0 +1,163 @@ +/* -*- 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/Mutex.h" +#include "nsRefPtrHashtable.h" + +namespace mozilla { +class ChildProfilerController; +} + +namespace mozilla { +namespace net { + +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 mozilla::ipc::ChildToParentStreamActorManager { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SocketProcessChild) + + SocketProcessChild(); + + static SocketProcessChild* GetSingleton(); + + bool Init(base::ProcessId aParentPid, const char* aParentBuildID, + MessageLoop* aIOLoop, UniquePtr<IPC::Channel> aChannel); + + 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); + mozilla::ipc::IPCResult RecvSocketProcessTelemetryPing(); + + PWebrtcTCPSocketChild* AllocPWebrtcTCPSocketChild(const Maybe<TabId>& tabId); + bool DeallocPWebrtcTCPSocketChild(PWebrtcTCPSocketChild* aActor); + + already_AddRefed<PHttpTransactionChild> AllocPHttpTransactionChild(); + + PFileDescriptorSetChild* AllocPFileDescriptorSetChild( + const FileDescriptor& fd); + bool DeallocPFileDescriptorSetChild(PFileDescriptorSetChild* aActor); + + PChildToParentStreamChild* AllocPChildToParentStreamChild(); + bool DeallocPChildToParentStreamChild(PChildToParentStreamChild* aActor); + PParentToChildStreamChild* AllocPParentToChildStreamChild(); + bool DeallocPParentToChildStreamChild(PParentToChildStreamChild* aActor); + + void CleanUp(); + void DestroySocketProcessBridgeParent(ProcessId aId); + + PChildToParentStreamChild* SendPChildToParentStreamConstructor( + PChildToParentStreamChild* aActor) override; + PFileDescriptorSetChild* SendPFileDescriptorSetConstructor( + const FileDescriptor& aFD) override; + already_AddRefed<PHttpConnectionMgrChild> AllocPHttpConnectionMgrChild( + const HttpHandlerInitArgs& aArgs); + mozilla::ipc::IPCResult RecvUpdateDeviceModelId(const nsCString& aModelId); + mozilla::ipc::IPCResult RecvOnHttpActivityDistributorActivated( + const bool& aIsActivated); + + 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() { return mShuttingDown; } + + already_AddRefed<PDNSRequestChild> AllocPDNSRequestChild( + const nsCString& aHost, const nsCString& aTrrServer, + const uint16_t& aType, const OriginAttributes& aOriginAttributes, + const uint32_t& aFlags); + mozilla::ipc::IPCResult RecvPDNSRequestConstructor( + PDNSRequestChild* aActor, const nsCString& aHost, + const nsCString& aTrrServer, const uint16_t& aType, + const OriginAttributes& aOriginAttributes, + const uint32_t& 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(); + + 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 nsCString& aTopic, + const nsString& aData); + + virtual already_AddRefed<PRemoteLazyInputStreamChild> + AllocPRemoteLazyInputStreamChild(const nsID& aID, const uint64_t& aSize); + + mozilla::ipc::IPCResult RecvGetSocketData(GetSocketDataResolver&& aResolve); + mozilla::ipc::IPCResult RecvGetDNSCacheEntries( + GetDNSCacheEntriesResolver&& aResolve); + mozilla::ipc::IPCResult RecvGetHttpConnectionData( + GetHttpConnectionDataResolver&& aResolve); + + protected: + friend class SocketProcessImpl; + ~SocketProcessChild(); + + private: + // Mapping of content process id and the SocketProcessBridgeParent. + // This table keeps SocketProcessBridgeParent alive in socket process. + nsRefPtrHashtable<nsUint32HashKey, SocketProcessBridgeParent> + mSocketProcessBridgeParentMap; + +#ifdef MOZ_GECKO_PROFILER + RefPtr<ChildProfilerController> mProfilerController; +#endif + + bool mShuttingDown; + // Protect the table below. + Mutex mMutex; + nsDataHashtable<nsUint64HashKey, RefPtr<BackgroundDataBridgeParent>> + mBackgroundDataBridgeMap; +}; + +} // 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..bf8ca10085 --- /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 "ProcessUtils.h" +#include "SocketProcessParent.h" +#include "mozilla/ipc/FileDescriptor.h" +#include "nsAppRunner.h" +#include "nsIOService.h" +#include "nsIObserverService.h" + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) +# include "mozilla/SandboxBroker.h" +# include "mozilla/SandboxBrokerPolicyFactory.h" +# include "mozilla/SandboxSettings.h" +#endif + +#ifdef MOZ_GECKO_PROFILER +# include "ProfilerParent.h" +#endif + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +# include "mozilla/Sandbox.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(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; + + nsAutoCString parentBuildID(mozilla::PlatformBuildID()); + extraArgs.push_back("-parentBuildID"); + extraArgs.push_back(parentBuildID.get()); + + SharedPreferenceSerializer prefSerializer; + if (!prefSerializer.SerializeToSharedMemory()) { + return false; + } + prefSerializer.AddSharedPrefCmdLineArgs(*this, extraArgs); + + mLaunchPhase = LaunchPhase::Waiting; + if (!GeckoChildProcessHost::LaunchAndWaitForProcessHandle(extraArgs)) { + mLaunchPhase = LaunchPhase::Complete; + return false; + } + + return true; +} + +void SocketProcessHost::OnChannelConnected(int32_t 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); + runnable = mTaskFactory.NewRunnableMethod( + &SocketProcessHost::OnChannelConnectedTask); + } + NS_DispatchToMainThread(runnable); +} + +void SocketProcessHost::OnChannelError() { + MOZ_ASSERT(!NS_IsMainThread()); + GeckoChildProcessHost::OnChannelError(); + + // Post a task to the main thread. Take the lock because mTaskFactory is not + // thread-safe. + RefPtr<Runnable> runnable; + { + MonitorAutoLock lock(mMonitor); + runnable = + mTaskFactory.NewRunnableMethod(&SocketProcessHost::OnChannelErrorTask); + } + NS_DispatchToMainThread(runnable); +} + +void SocketProcessHost::OnChannelConnectedTask() { + MOZ_ASSERT(NS_IsMainThread()); + + if (mLaunchPhase == LaunchPhase::Waiting) { + InitAfterConnect(true); + } +} + +void SocketProcessHost::OnChannelErrorTask() { + MOZ_ASSERT(NS_IsMainThread()); + + if (mLaunchPhase == LaunchPhase::Waiting) { + InitAfterConnect(false); + } +} + +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 = MakeUnique<SocketProcessParent>(this); + DebugOnly<bool> rv = mSocketProcessParent->Open( + TakeChannel(), base::GetProcId(GetChildProcessHandle())); + 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_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); + +#ifdef MOZ_GECKO_PROFILER + Unused << GetActor()->SendInitProfiler( + ProfilerParent::CreateForProcess(GetActor()->OtherPid())); +#endif + + 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.RevokeAll(); + } + + 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..beb1e13c89 --- /dev/null +++ b/netwerk/ipc/SocketProcessHost.h @@ -0,0 +1,151 @@ +/* -*- 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/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(int32_t peer_pid) override; + void OnChannelError() 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(); + void OnChannelErrorTask(); + + // 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::ipc::TaskFactory<SocketProcessHost> mTaskFactory; + + enum class LaunchPhase { Unlaunched, Waiting, Complete }; + LaunchPhase mLaunchPhase; + + UniquePtr<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..a67dd9c9bd --- /dev/null +++ b/netwerk/ipc/SocketProcessImpl.cpp @@ -0,0 +1,108 @@ +/* -*- 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 "ProcessUtils.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 "ProcessUtils.h" +#include "mozilla/ipc/IOThreadChild.h" + +#if defined(OS_WIN) && defined(MOZ_SANDBOX) +# include "mozilla/sandboxTarget.h" +#endif + +#ifdef OS_POSIX +# include <unistd.h> // For sleep(). +#endif + +using mozilla::ipc::IOThreadChild; + +namespace mozilla { +namespace net { + +LazyLogModule gSocketProcessLog("socketprocess"); + +SocketProcessImpl::SocketProcessImpl(ProcessId aParentPid) + : ProcessChild(aParentPid) {} + +SocketProcessImpl::~SocketProcessImpl() = default; + +bool SocketProcessImpl::Init(int aArgc, char* aArgv[]) { +#ifdef OS_POSIX + 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(OS_WIN) + LoadLibraryW(L"nss3.dll"); + LoadLibraryW(L"softokn3.dll"); + LoadLibraryW(L"freebl3.dll"); + mozilla::SandboxTarget::Instance()->StartSandbox(); +#endif + char* parentBuildID = nullptr; + char* prefsHandle = nullptr; + char* prefMapHandle = nullptr; + char* prefsLen = nullptr; + char* prefMapSize = nullptr; + + for (int i = 1; i < aArgc; i++) { + if (!aArgv[i]) { + continue; + } + + if (strcmp(aArgv[i], "-parentBuildID") == 0) { + if (++i == aArgc) { + return false; + } + + parentBuildID = aArgv[i]; + +#ifdef XP_WIN + } else if (strcmp(aArgv[i], "-prefsHandle") == 0) { + if (++i == aArgc) { + return false; + } + prefsHandle = aArgv[i]; + } else if (strcmp(aArgv[i], "-prefMapHandle") == 0) { + if (++i == aArgc) { + return false; + } + prefMapHandle = aArgv[i]; +#endif + } else if (strcmp(aArgv[i], "-prefsLen") == 0) { + if (++i == aArgc) { + return false; + } + prefsLen = aArgv[i]; + } else if (strcmp(aArgv[i], "-prefMapSize") == 0) { + if (++i == aArgc) { + return false; + } + prefMapSize = aArgv[i]; + } + } + + ipc::SharedPreferenceDeserializer deserializer; + if (!deserializer.DeserializeFromSharedMemory(prefsHandle, prefMapHandle, + prefsLen, prefMapSize)) { + return false; + } + + return mSocketProcessChild.Init(ParentPid(), parentBuildID, + IOThreadChild::message_loop(), + IOThreadChild::TakeChannel()); +} + +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..51d609b4a9 --- /dev/null +++ b/netwerk/ipc/SocketProcessImpl.h @@ -0,0 +1,36 @@ +/* -*- 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: + typedef mozilla::ipc::ProcessChild ProcessChild; + + public: + explicit SocketProcessImpl(ProcessId aParentPid); + ~SocketProcessImpl(); + + bool Init(int aArgc, char* aArgv[]) override; + void CleanUp() override; + + private: + SocketProcessChild mSocketProcessChild; + DISALLOW_COPY_AND_ASSIGN(SocketProcessImpl); +}; + +} // 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..7e3e0ac63c --- /dev/null +++ b/netwerk/ipc/SocketProcessParent.cpp @@ -0,0 +1,429 @@ +/* -*- 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 "mozilla/dom/MemoryReportRequest.h" +#include "mozilla/ipc/FileDescriptorSetParent.h" +#include "mozilla/ipc/IPCStreamAlloc.h" +#include "mozilla/ipc/PChildToParentStreamParent.h" +#include "mozilla/ipc/PParentToChildStreamParent.h" +#include "mozilla/net/DNSRequestParent.h" +#include "mozilla/net/ProxyConfigLookupParent.h" +#include "mozilla/RemoteLazyInputStreamParent.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TelemetryIPC.h" +#include "nsIAppStartup.h" +#include "nsIHttpActivityObserver.h" +#include "nsNSSIOLayer.h" +#include "PSMIPCCommon.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) + +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()); + + if (PR_GetEnv("MOZ_CRASHREPORTER_SHUTDOWN")) { + printf_stderr("Shutting down due to socket process crash.\n"); + nsCOMPtr<nsIAppStartup> appService = + do_GetService("@mozilla.org/toolkit/app-startup;1"); + if (appService) { + bool userAllowedQuit = true; + appService->Quit(nsIAppStartup::eForceQuit, 1, &userAllowedQuit); + } + } + } + + 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 nsCString& aHost, const nsCString& aTrrServer, const uint16_t& aType, + const OriginAttributes& aOriginAttributes, const uint32_t& aFlags) { + RefPtr<DNSRequestHandler> handler = new DNSRequestHandler(); + RefPtr<DNSRequestParent> actor = new DNSRequestParent(handler); + return actor.forget(); +} + +mozilla::ipc::IPCResult SocketProcessParent::RecvPDNSRequestConstructor( + PDNSRequestParent* aActor, const nsCString& aHost, + const nsCString& aTrrServer, const uint16_t& aType, + const OriginAttributes& aOriginAttributes, const uint32_t& aFlags) { + RefPtr<DNSRequestParent> actor = static_cast<DNSRequestParent*>(aActor); + RefPtr<DNSRequestHandler> handler = + actor->GetDNSRequest()->AsDNSRequestHandler(); + handler->DoAsyncResolve(aHost, aTrrServer, aType, aOriginAttributes, aFlags); + return IPC_OK(); +} + +mozilla::ipc::PFileDescriptorSetParent* +SocketProcessParent::AllocPFileDescriptorSetParent(const FileDescriptor& aFD) { + return new mozilla::ipc::FileDescriptorSetParent(aFD); +} + +bool SocketProcessParent::DeallocPFileDescriptorSetParent( + PFileDescriptorSetParent* aActor) { + delete static_cast<mozilla::ipc::FileDescriptorSetParent*>(aActor); + return true; +} + +mozilla::ipc::PChildToParentStreamParent* +SocketProcessParent::AllocPChildToParentStreamParent() { + return mozilla::ipc::AllocPChildToParentStreamParent(); +} + +bool SocketProcessParent::DeallocPChildToParentStreamParent( + PChildToParentStreamParent* aActor) { + delete aActor; + return true; +} + +mozilla::ipc::PParentToChildStreamParent* +SocketProcessParent::AllocPParentToChildStreamParent() { + MOZ_CRASH("PParentToChildStreamChild actors should be manually constructed!"); +} + +bool SocketProcessParent::DeallocPParentToChildStreamParent( + PParentToChildStreamParent* aActor) { + delete aActor; + return true; +} + +mozilla::ipc::PParentToChildStreamParent* +SocketProcessParent::SendPParentToChildStreamConstructor( + PParentToChildStreamParent* aActor) { + MOZ_ASSERT(NS_IsMainThread()); + return PSocketProcessParent::SendPParentToChildStreamConstructor(aActor); +} + +mozilla::ipc::PFileDescriptorSetParent* +SocketProcessParent::SendPFileDescriptorSetConstructor( + const FileDescriptor& aFD) { + MOZ_ASSERT(NS_IsMainThread()); + return PSocketProcessParent::SendPFileDescriptorSetConstructor(aFD); +} + +mozilla::ipc::IPCResult SocketProcessParent::RecvObserveHttpActivity( + const HttpActivityArgs& aArgs, const uint32_t& aActivityType, + const uint32_t& aActivitySubtype, const PRTime& aTimestamp, + const uint64_t& aExtraSizeData, const nsCString& aExtraStringData) { + nsCOMPtr<nsIHttpActivityDistributor> activityDistributor = + services::GetHttpActivityDistributor(); + MOZ_ASSERT(activityDistributor); + + Unused << activityDistributor->ObserveActivityWithArgs( + aArgs, aActivityType, aActivitySubtype, aTimestamp, aExtraSizeData, + aExtraStringData); + return IPC_OK(); +} + +mozilla::ipc::IPCResult SocketProcessParent::RecvInitBackground( + Endpoint<PBackgroundParent>&& aEndpoint) { + LOG(("SocketProcessParent::RecvInitBackground\n")); + if (!ipc::BackgroundParent::Alloc(nullptr, std::move(aEndpoint))) { + return IPC_FAIL(this, "BackgroundParent::Alloc failed"); + } + + return IPC_OK(); +} + +already_AddRefed<PAltServiceParent> +SocketProcessParent::AllocPAltServiceParent() { + RefPtr<AltServiceParent> actor = new AltServiceParent(); + return actor.forget(); +} + +mozilla::ipc::IPCResult SocketProcessParent::RecvGetTLSClientCert( + const nsCString& aHostName, const OriginAttributes& aOriginAttributes, + const int32_t& aPort, const uint32_t& aProviderFlags, + const uint32_t& aProviderTlsFlags, const ByteArray& aServerCert, + Maybe<ByteArray>&& aClientCert, nsTArray<ByteArray>&& aCollectedCANames, + bool* aSucceeded, ByteArray* aOutCert, ByteArray* aOutKey, + nsTArray<ByteArray>* aBuiltChain) { + *aSucceeded = false; + + SECItem serverCertItem = { + siBuffer, const_cast<uint8_t*>(aServerCert.data().Elements()), + static_cast<unsigned int>(aServerCert.data().Length())}; + UniqueCERTCertificate serverCert(CERT_NewTempCertificate( + CERT_GetDefaultCertDB(), &serverCertItem, nullptr, false, true)); + if (!serverCert) { + return IPC_OK(); + } + + RefPtr<nsIX509Cert> clientCert; + if (aClientCert) { + clientCert = nsNSSCertificate::ConstructFromDER( + BitwiseCast<char*, uint8_t*>(aClientCert->data().Elements()), + aClientCert->data().Length()); + if (!clientCert) { + return IPC_OK(); + } + } + + ClientAuthInfo info(aHostName, aOriginAttributes, aPort, aProviderFlags, + aProviderTlsFlags, clientCert); + nsTArray<nsTArray<uint8_t>> collectedCANames; + for (auto& name : aCollectedCANames) { + collectedCANames.AppendElement(std::move(name.data())); + } + + UniqueCERTCertificate cert; + UniqueSECKEYPrivateKey key; + UniqueCERTCertList builtChain; + SECStatus status = + DoGetClientAuthData(std::move(info), serverCert, + std::move(collectedCANames), cert, key, builtChain); + if (status != SECSuccess) { + return IPC_OK(); + } + + SerializeClientCertAndKey(cert, key, *aOutCert, *aOutKey); + + if (builtChain) { + for (CERTCertListNode* n = CERT_LIST_HEAD(builtChain); + !CERT_LIST_END(n, builtChain); n = CERT_LIST_NEXT(n)) { + ByteArray array; + array.data().AppendElements(n->cert->derCert.data, n->cert->derCert.len); + aBuiltChain->AppendElement(std::move(array)); + } + } + + *aSucceeded = true; + return IPC_OK(); +} + +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( + UniquePtr<SocketProcessParent>&& aParent) + : Runnable("net::DeferredDeleteSocketProcessParent"), + mParent(std::move(aParent)) {} + + NS_IMETHODIMP Run() override { return NS_OK; } + + private: + UniquePtr<SocketProcessParent> mParent; +}; + +/* static */ +void SocketProcessParent::Destroy(UniquePtr<SocketProcessParent>&& aParent) { + NS_DispatchToMainThread( + new DeferredDeleteSocketProcessParent(std::move(aParent))); +} + +already_AddRefed<PRemoteLazyInputStreamParent> +SocketProcessParent::AllocPRemoteLazyInputStreamParent(const nsID& aID, + const uint64_t& aSize) { + RefPtr<RemoteLazyInputStreamParent> actor = + RemoteLazyInputStreamParent::Create(aID, aSize, this); + return actor.forget(); +} + +mozilla::ipc::IPCResult +SocketProcessParent::RecvPRemoteLazyInputStreamConstructor( + PRemoteLazyInputStreamParent* aActor, const nsID& aID, + const uint64_t& aSize) { + if (!static_cast<RemoteLazyInputStreamParent*>(aActor)->HasValidStream()) { + return IPC_FAIL_NO_REASON(this); + } + + return IPC_OK(); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/ipc/SocketProcessParent.h b/netwerk/ipc/SocketProcessParent.h new file mode 100644 index 0000000000..679ccefac5 --- /dev/null +++ b/netwerk/ipc/SocketProcessParent.h @@ -0,0 +1,132 @@ +/* -*- 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/BackgroundParent.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 ipc::ParentToChildStreamActorManager { + public: + friend class SocketProcessHost; + + explicit SocketProcessParent(SocketProcessHost* aHost); + ~SocketProcessParent(); + + 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 nsCString& aHost, const nsCString& aTrrServer, + const uint16_t& aType, const OriginAttributes& aOriginAttributes, + const uint32_t& aFlags); + virtual mozilla::ipc::IPCResult RecvPDNSRequestConstructor( + PDNSRequestParent* actor, const nsCString& hostName, + const nsCString& trrServer, const uint16_t& type, + const OriginAttributes& aOriginAttributes, + const uint32_t& flags) override; + + void ActorDestroy(ActorDestroyReason aWhy) override; + bool SendRequestMemoryReport(const uint32_t& aGeneration, + const bool& aAnonymize, + const bool& aMinimizeMemoryUsage, + const Maybe<ipc::FileDescriptor>& aDMDFile); + + PFileDescriptorSetParent* AllocPFileDescriptorSetParent( + const FileDescriptor& fd); + bool DeallocPFileDescriptorSetParent(PFileDescriptorSetParent* aActor); + + PChildToParentStreamParent* AllocPChildToParentStreamParent(); + bool DeallocPChildToParentStreamParent(PChildToParentStreamParent* aActor); + PParentToChildStreamParent* AllocPParentToChildStreamParent(); + bool DeallocPParentToChildStreamParent(PParentToChildStreamParent* aActor); + + PParentToChildStreamParent* SendPParentToChildStreamConstructor( + PParentToChildStreamParent* aActor) override; + PFileDescriptorSetParent* SendPFileDescriptorSetConstructor( + const FileDescriptor& aFD) override; + + mozilla::ipc::IPCResult RecvObserveHttpActivity( + const HttpActivityArgs& aArgs, const uint32_t& aActivityType, + const uint32_t& aActivitySubtype, const PRTime& aTimestamp, + const uint64_t& aExtraSizeData, const nsCString& aExtraStringData); + + mozilla::ipc::IPCResult RecvInitBackground( + Endpoint<PBackgroundParent>&& aEndpoint); + + already_AddRefed<PAltServiceParent> AllocPAltServiceParent(); + + mozilla::ipc::IPCResult RecvGetTLSClientCert( + const nsCString& aHostName, const OriginAttributes& aOriginAttributes, + const int32_t& aPort, const uint32_t& aProviderFlags, + const uint32_t& aProviderTlsFlags, const ByteArray& aServerCert, + Maybe<ByteArray>&& aClientCert, nsTArray<ByteArray>&& aCollectedCANames, + bool* aSucceeded, ByteArray* aOutCert, ByteArray* aOutKey, + nsTArray<ByteArray>* aBuiltChain); + + 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); + + already_AddRefed<PRemoteLazyInputStreamParent> + AllocPRemoteLazyInputStreamParent(const nsID& aID, const uint64_t& aSize); + + mozilla::ipc::IPCResult RecvPRemoteLazyInputStreamConstructor( + PRemoteLazyInputStreamParent* aActor, const nsID& aID, + const uint64_t& aSize); + + private: + SocketProcessHost* mHost; + UniquePtr<dom::MemoryReportRequestHost> mMemoryReportRequest; + + static void Destroy(UniquePtr<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..6124d4ff2a --- /dev/null +++ b/netwerk/ipc/moz.build @@ -0,0 +1,99 @@ +# -*- 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", + "ProxyConfigLookup.h", + "ProxyConfigLookupChild.h", + "ProxyConfigLookupParent.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", + "NeckoParent.cpp", + "NeckoTargetHolder.cpp", + "ParentChannelWrapper.cpp", + "ParentProcessDocumentChannel.cpp", + "ProxyConfigLookup.cpp", + "ProxyConfigLookupChild.cpp", + "ProxyConfigLookupParent.cpp", + "SocketProcessBridgeChild.cpp", + "SocketProcessBridgeParent.cpp", + "SocketProcessChild.cpp", + "SocketProcessHost.cpp", + "SocketProcessImpl.cpp", + "SocketProcessParent.cpp", +] + +IPDL_SOURCES = [ + "NeckoChannelParams.ipdlh", + "PDataChannel.ipdl", + "PDocumentChannel.ipdl", + "PFileChannel.ipdl", + "PInputChannelThrottleQueue.ipdl", + "PNecko.ipdl", + "PProxyConfigLookup.ipdl", + "PSimpleChannel.ipdl", + "PSocketProcess.ipdl", + "PSocketProcessBridge.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") |