diff options
Diffstat (limited to 'dom/ipc')
247 files changed, 59100 insertions, 0 deletions
diff --git a/dom/ipc/BrowserBridgeChild.cpp b/dom/ipc/BrowserBridgeChild.cpp new file mode 100644 index 0000000000..3a2f7d8f84 --- /dev/null +++ b/dom/ipc/BrowserBridgeChild.cpp @@ -0,0 +1,249 @@ +/* -*- 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/. */ + +#ifdef ACCESSIBILITY +# include "mozilla/a11y/DocAccessible.h" +# include "mozilla/a11y/DocManager.h" +# include "mozilla/a11y/OuterDocAccessible.h" +#endif +#include "mozilla/dom/BrowserBridgeChild.h" +#include "mozilla/dom/BrowserBridgeHost.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/MozFrameLoaderOwnerBinding.h" +#include "mozilla/PresShell.h" +#include "nsFocusManager.h" +#include "nsFrameLoader.h" +#include "nsFrameLoaderOwner.h" +#include "nsObjectLoadingContent.h" +#include "nsQueryObject.h" +#include "nsSubDocumentFrame.h" +#include "nsView.h" + +using namespace mozilla::ipc; + +mozilla::LazyLogModule gBrowserChildFocusLog("BrowserChildFocus"); + +#define LOGBROWSERCHILDFOCUS(args) \ + MOZ_LOG(gBrowserChildFocusLog, mozilla::LogLevel::Debug, args) + +namespace mozilla::dom { + +BrowserBridgeChild::BrowserBridgeChild(BrowsingContext* aBrowsingContext, + TabId aId, const LayersId& aLayersId) + : mId{aId}, mLayersId{aLayersId}, mBrowsingContext(aBrowsingContext) {} + +BrowserBridgeChild::~BrowserBridgeChild() {} + +already_AddRefed<BrowserBridgeHost> BrowserBridgeChild::FinishInit( + nsFrameLoader* aFrameLoader) { + MOZ_DIAGNOSTIC_ASSERT(!mFrameLoader); + mFrameLoader = aFrameLoader; + + RefPtr<Element> owner = mFrameLoader->GetOwnerContent(); + Document* doc = owner->OwnerDoc(); + doc->OOPChildLoadStarted(this); + +#if defined(ACCESSIBILITY) + if (a11y::DocAccessible* docAcc = a11y::GetExistingDocAccessible(doc)) { + if (a11y::LocalAccessible* ownerAcc = docAcc->GetAccessible(owner)) { + if (a11y::OuterDocAccessible* outerAcc = ownerAcc->AsOuterDoc()) { + outerAcc->SendEmbedderAccessible(this); + } + } + } +#endif // defined(ACCESSIBILITY) + + return MakeAndAddRef<BrowserBridgeHost>(this); +} + +nsILoadContext* BrowserBridgeChild::GetLoadContext() { + return mBrowsingContext; +} + +void BrowserBridgeChild::NavigateByKey(bool aForward, + bool aForDocumentNavigation) { + Unused << SendNavigateByKey(aForward, aForDocumentNavigation); +} + +void BrowserBridgeChild::Activate(uint64_t aActionId) { + LOGBROWSERCHILDFOCUS( + ("BrowserBridgeChild::Activate actionid: %" PRIu64, aActionId)); + Unused << SendActivate(aActionId); +} + +void BrowserBridgeChild::Deactivate(bool aWindowLowering, uint64_t aActionId) { + Unused << SendDeactivate(aWindowLowering, aActionId); +} + +/*static*/ +BrowserBridgeChild* BrowserBridgeChild::GetFrom(nsFrameLoader* aFrameLoader) { + if (!aFrameLoader) { + return nullptr; + } + return aFrameLoader->GetBrowserBridgeChild(); +} + +/*static*/ +BrowserBridgeChild* BrowserBridgeChild::GetFrom(nsIContent* aContent) { + RefPtr<nsFrameLoaderOwner> loaderOwner = do_QueryObject(aContent); + if (!loaderOwner) { + return nullptr; + } + RefPtr<nsFrameLoader> frameLoader = loaderOwner->GetFrameLoader(); + return GetFrom(frameLoader); +} + +mozilla::ipc::IPCResult BrowserBridgeChild::RecvRequestFocus( + const bool& aCanRaise, const CallerType aCallerType) { + // Adapted from BrowserParent + RefPtr<Element> owner = mFrameLoader->GetOwnerContent(); + if (!owner) { + return IPC_OK(); + } + nsContentUtils::RequestFrameFocus(*owner, aCanRaise, aCallerType); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserBridgeChild::RecvMoveFocus( + const bool& aForward, const bool& aForDocumentNavigation) { + // Adapted from BrowserParent + RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager(); + if (!fm) { + return IPC_OK(); + } + + RefPtr<Element> owner = mFrameLoader->GetOwnerContent(); + if (!owner) { + return IPC_OK(); + } + + RefPtr<Element> dummy; + + uint32_t type = + aForward + ? (aForDocumentNavigation + ? static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_FORWARDDOC) + : static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_FORWARD)) + : (aForDocumentNavigation + ? static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_BACKWARDDOC) + : static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_BACKWARD)); + fm->MoveFocus(nullptr, owner, type, nsIFocusManager::FLAG_BYKEY, + getter_AddRefs(dummy)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserBridgeChild::RecvMaybeFireEmbedderLoadEvents( + EmbedderElementEventType aFireEventAtEmbeddingElement) { + RefPtr<Element> owner = mFrameLoader->GetOwnerContent(); + if (!owner) { + return IPC_OK(); + } + + if (aFireEventAtEmbeddingElement == EmbedderElementEventType::LoadEvent) { + nsEventStatus status = nsEventStatus_eIgnore; + WidgetEvent event(/* aIsTrusted = */ true, eLoad); + event.mFlags.mBubbles = false; + event.mFlags.mCancelable = false; + EventDispatcher::Dispatch(owner, nullptr, &event, nullptr, &status); + } else if (aFireEventAtEmbeddingElement == + EmbedderElementEventType::ErrorEvent) { + mFrameLoader->FireErrorEvent(); + } + + UnblockOwnerDocsLoadEvent(); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserBridgeChild::RecvScrollRectIntoView( + const nsRect& aRect, const ScrollAxis& aVertical, + const ScrollAxis& aHorizontal, const ScrollFlags& aScrollFlags, + const int32_t& aAppUnitsPerDevPixel) { + RefPtr<Element> owner = mFrameLoader->GetOwnerContent(); + if (!owner) { + return IPC_OK(); + } + + nsIFrame* frame = owner->GetPrimaryFrame(); + if (!frame) { + return IPC_OK(); + } + + nsSubDocumentFrame* subdocumentFrame = do_QueryFrame(frame); + if (!subdocumentFrame) { + return IPC_OK(); + } + + nsPoint extraOffset = subdocumentFrame->GetExtraOffset(); + + int32_t parentAPD = frame->PresContext()->AppUnitsPerDevPixel(); + nsRect rect = + aRect.ScaleToOtherAppUnitsRoundOut(aAppUnitsPerDevPixel, parentAPD); + rect += extraOffset; + RefPtr<PresShell> presShell = frame->PresShell(); + presShell->ScrollFrameIntoView(frame, Some(rect), aVertical, aHorizontal, + aScrollFlags); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserBridgeChild::RecvSubFrameCrashed() { + if (RefPtr<nsFrameLoaderOwner> frameLoaderOwner = + do_QueryObject(mFrameLoader->GetOwnerContent())) { + frameLoaderOwner->SubframeCrashed(); + } + return IPC_OK(); +} + +void BrowserBridgeChild::ActorDestroy(ActorDestroyReason aWhy) { + if (mFrameLoader) { + mFrameLoader->DestroyComplete(); + } + + if (!mBrowsingContext) { + // This BBC was never valid, skip teardown. + return; + } + + // Ensure we unblock our document's 'load' event (in case the OOP-iframe has + // been removed before it finished loading, or its subprocess crashed): + UnblockOwnerDocsLoadEvent(); +} + +void BrowserBridgeChild::UnblockOwnerDocsLoadEvent() { + if (!mHadInitialLoad) { + mHadInitialLoad = true; + + if (Document* doc = mBrowsingContext->GetParent()->GetExtantDocument()) { + doc->OOPChildLoadDone(this); + } + } +} + +mozilla::ipc::IPCResult BrowserBridgeChild::RecvIntrinsicSizeOrRatioChanged( + const Maybe<IntrinsicSize>& aIntrinsicSize, + const Maybe<AspectRatio>& aIntrinsicRatio) { + if (RefPtr<Element> owner = mFrameLoader->GetOwnerContent()) { + if (nsCOMPtr<nsIObjectLoadingContent> olc = do_QueryInterface(owner)) { + static_cast<nsObjectLoadingContent*>(olc.get()) + ->SubdocumentIntrinsicSizeOrRatioChanged(aIntrinsicSize, + aIntrinsicRatio); + } + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserBridgeChild::RecvImageLoadComplete( + const nsresult& aResult) { + if (RefPtr<Element> owner = mFrameLoader->GetOwnerContent()) { + if (nsCOMPtr<nsIObjectLoadingContent> olc = do_QueryInterface(owner)) { + static_cast<nsObjectLoadingContent*>(olc.get()) + ->SubdocumentImageLoadComplete(aResult); + } + } + return IPC_OK(); +} + +} // namespace mozilla::dom diff --git a/dom/ipc/BrowserBridgeChild.h b/dom/ipc/BrowserBridgeChild.h new file mode 100644 index 0000000000..60e29d2483 --- /dev/null +++ b/dom/ipc/BrowserBridgeChild.h @@ -0,0 +1,119 @@ +/* -*- 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_dom_BrowserBridgeChild_h +#define mozilla_dom_BrowserBridgeChild_h + +#include "mozilla/dom/PBrowserBridgeChild.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/dom/ipc/IdType.h" + +namespace mozilla::dom { +class BrowsingContext; +class ContentChild; +class BrowserBridgeHost; + +/** + * BrowserBridgeChild implements the child actor part of the PBrowserBridge + * protocol. See PBrowserBridge for more information. + */ +class BrowserBridgeChild : public PBrowserBridgeChild { + public: + typedef mozilla::layers::LayersId LayersId; + + NS_INLINE_DECL_REFCOUNTING(BrowserBridgeChild, final); + + BrowserChild* Manager() { + MOZ_ASSERT(CanSend()); + return static_cast<BrowserChild*>(PBrowserBridgeChild::Manager()); + } + + TabId GetTabId() { return mId; } + + LayersId GetLayersId() { return mLayersId; } + + nsFrameLoader* GetFrameLoader() const { return mFrameLoader; } + + BrowsingContext* GetBrowsingContext() { return mBrowsingContext; } + + nsILoadContext* GetLoadContext(); + + void NavigateByKey(bool aForward, bool aForDocumentNavigation); + + void Activate(uint64_t aActionId); + + void Deactivate(bool aWindowLowering, uint64_t aActionId); + + already_AddRefed<BrowserBridgeHost> FinishInit(nsFrameLoader* aFrameLoader); + +#if defined(ACCESSIBILITY) + void SetEmbedderAccessible(PDocAccessibleChild* aDoc, uint64_t aID) { + MOZ_ASSERT((aDoc && aID) || (!aDoc && !aID)); + mEmbedderAccessibleID = aID; + Unused << SendSetEmbedderAccessible(aDoc, aID); + } + + uint64_t GetEmbedderAccessibleID() { return mEmbedderAccessibleID; } +#endif // defined(ACCESSIBILITY) + + static BrowserBridgeChild* GetFrom(nsFrameLoader* aFrameLoader); + + static BrowserBridgeChild* GetFrom(nsIContent* aContent); + + BrowserBridgeChild(BrowsingContext* aBrowsingContext, TabId aId, + const LayersId& aLayersId); + + protected: + friend class ContentChild; + friend class PBrowserBridgeChild; + + mozilla::ipc::IPCResult RecvRequestFocus(const bool& aCanRaise, + const CallerType aCallerType); + + mozilla::ipc::IPCResult RecvMoveFocus(const bool& aForward, + const bool& aForDocumentNavigation); + + // TODO: Use MOZ_CAN_RUN_SCRIPT when it gains IPDL support (bug 1539864) + MOZ_CAN_RUN_SCRIPT_BOUNDARY mozilla::ipc::IPCResult + RecvMaybeFireEmbedderLoadEvents( + EmbedderElementEventType aFireEventAtEmbeddingElement); + + mozilla::ipc::IPCResult RecvIntrinsicSizeOrRatioChanged( + const Maybe<IntrinsicSize>& aIntrinsicSize, + const Maybe<AspectRatio>& aIntrinsicRatio); + + mozilla::ipc::IPCResult RecvImageLoadComplete(const nsresult& aResult); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY + mozilla::ipc::IPCResult RecvScrollRectIntoView( + const nsRect& aRect, const ScrollAxis& aVertical, + const ScrollAxis& aHorizontal, const ScrollFlags& aScrollFlags, + const int32_t& aAppUnitsPerDevPixel); + + mozilla::ipc::IPCResult RecvSubFrameCrashed(); + + void ActorDestroy(ActorDestroyReason aWhy) override; + + private: + ~BrowserBridgeChild(); + + void UnblockOwnerDocsLoadEvent(); + + TabId mId; + LayersId mLayersId; + bool mHadInitialLoad = false; + RefPtr<nsFrameLoader> mFrameLoader; + RefPtr<BrowsingContext> mBrowsingContext; +#if defined(ACCESSIBILITY) + // We need to keep track of the embedder accessible id we last sent to the + // parent process. + uint64_t mEmbedderAccessibleID = 0; +#endif // defined(ACCESSIBILITY) +}; + +} // namespace mozilla::dom + +#endif // !defined(mozilla_dom_BrowserBridgeParent_h) diff --git a/dom/ipc/BrowserBridgeHost.cpp b/dom/ipc/BrowserBridgeHost.cpp new file mode 100644 index 0000000000..2cb8decd58 --- /dev/null +++ b/dom/ipc/BrowserBridgeHost.cpp @@ -0,0 +1,91 @@ +/* -*- 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 "mozilla/dom/BrowserBridgeHost.h" + +#include "mozilla/Unused.h" +#include "mozilla/dom/Element.h" +#include "nsFrameLoader.h" + +namespace mozilla::dom { + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BrowserBridgeHost) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION(BrowserBridgeHost) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(BrowserBridgeHost) +NS_IMPL_CYCLE_COLLECTING_RELEASE(BrowserBridgeHost) + +BrowserBridgeHost::BrowserBridgeHost(BrowserBridgeChild* aChild) + : mBridge(aChild) {} + +TabId BrowserBridgeHost::GetTabId() const { return mBridge->GetTabId(); } + +mozilla::layers::LayersId BrowserBridgeHost::GetLayersId() const { + return mBridge->GetLayersId(); +} + +BrowsingContext* BrowserBridgeHost::GetBrowsingContext() const { + return mBridge->GetBrowsingContext(); +} + +nsILoadContext* BrowserBridgeHost::GetLoadContext() const { + return mBridge->GetLoadContext(); +} + +bool BrowserBridgeHost::CanRecv() const { + return mBridge && mBridge->CanRecv(); +} + +void BrowserBridgeHost::LoadURL(nsDocShellLoadState* aLoadState) { + MOZ_ASSERT(aLoadState); + Unused << mBridge->SendLoadURL(WrapNotNull(aLoadState)); +} + +void BrowserBridgeHost::ResumeLoad(uint64_t aPendingSwitchId) { + Unused << mBridge->SendResumeLoad(aPendingSwitchId); +} + +void BrowserBridgeHost::DestroyStart() { + // We don't clear the bridge until BrowserBridgeChild::ActorDestroy is called, + // which will end up calling DestroyComplete(). + if (mBridge) { + Unused << mBridge->SendBeginDestroy(); + } +} + +void BrowserBridgeHost::DestroyComplete() { mBridge = nullptr; } + +bool BrowserBridgeHost::Show(const OwnerShowInfo& aShowInfo) { + Unused << mBridge->SendShow(aShowInfo); + return true; +} + +void BrowserBridgeHost::UpdateDimensions(const nsIntRect& aRect, + const ScreenIntSize& aSize) { + Unused << mBridge->SendUpdateDimensions(aRect, aSize); +} + +void BrowserBridgeHost::UpdateEffects(EffectsInfo aEffects) { + if (!mBridge || mEffectsInfo == aEffects) { + return; + } + mEffectsInfo = aEffects; + Unused << mBridge->SendUpdateEffects(mEffectsInfo); +} + +already_AddRefed<nsIWidget> BrowserBridgeHost::GetWidget() const { + RefPtr<Element> owner = mBridge->GetFrameLoader()->GetOwnerContent(); + nsCOMPtr<nsIWidget> widget = nsContentUtils::WidgetForContent(owner); + if (!widget) { + widget = nsContentUtils::WidgetForDocument(owner->OwnerDoc()); + } + return widget.forget(); +} + +} // namespace mozilla::dom diff --git a/dom/ipc/BrowserBridgeHost.h b/dom/ipc/BrowserBridgeHost.h new file mode 100644 index 0000000000..09f2f4dfdd --- /dev/null +++ b/dom/ipc/BrowserBridgeHost.h @@ -0,0 +1,68 @@ +/* -*- 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_dom_BrowserBridgeHost_h +#define mozilla_dom_BrowserBridgeHost_h + +#include "mozilla/dom/RemoteBrowser.h" +#include "mozilla/dom/BrowserBridgeChild.h" + +namespace mozilla::dom { + +/** + * BrowserBridgeHost manages a remote browser from a content process. + * + * It is used via the RemoteBrowser interface in nsFrameLoader and proxies + * work to the chrome process via PBrowserBridge. + * + * See `dom/docs/Fission-IPC-Diagram.svg` for an overview of the DOM IPC + * actors. + */ +class BrowserBridgeHost : public RemoteBrowser { + public: + typedef mozilla::layers::LayersId LayersId; + + explicit BrowserBridgeHost(BrowserBridgeChild* aChild); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(BrowserBridgeHost) + + // Get the IPDL actor for the BrowserBridgeChild. + BrowserBridgeChild* GetActor() { return mBridge; } + + BrowserHost* AsBrowserHost() override { return nullptr; } + BrowserBridgeHost* AsBrowserBridgeHost() override { return this; } + + TabId GetTabId() const override; + LayersId GetLayersId() const override; + BrowsingContext* GetBrowsingContext() const override; + nsILoadContext* GetLoadContext() const override; + bool CanRecv() const override; + + void LoadURL(nsDocShellLoadState* aLoadState) override; + void ResumeLoad(uint64_t aPendingSwitchId) override; + void DestroyStart() override; + void DestroyComplete() override; + + bool Show(const OwnerShowInfo&) override; + void UpdateDimensions(const nsIntRect& aRect, + const ScreenIntSize& aSize) override; + + void UpdateEffects(EffectsInfo aInfo) override; + + private: + virtual ~BrowserBridgeHost() = default; + + already_AddRefed<nsIWidget> GetWidget() const; + + // The IPDL actor for proxying browser operations + RefPtr<BrowserBridgeChild> mBridge; + EffectsInfo mEffectsInfo; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_BrowserBridgeHost_h diff --git a/dom/ipc/BrowserBridgeParent.cpp b/dom/ipc/BrowserBridgeParent.cpp new file mode 100644 index 0000000000..4a687d557d --- /dev/null +++ b/dom/ipc/BrowserBridgeParent.cpp @@ -0,0 +1,315 @@ +/* -*- 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/. */ + +#ifdef ACCESSIBILITY +# include "mozilla/a11y/DocAccessibleParent.h" +# include "nsAccessibilityService.h" +#endif + +#include "mozilla/Monitor.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/dom/BrowserBridgeParent.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ContentProcessManager.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/BrowsingContextGroup.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/layers/InputAPZContext.h" + +using namespace mozilla::ipc; +using namespace mozilla::layout; +using namespace mozilla::hal; + +namespace mozilla::dom { + +BrowserBridgeParent::BrowserBridgeParent() = default; + +BrowserBridgeParent::~BrowserBridgeParent() { Destroy(); } + +nsresult BrowserBridgeParent::InitWithProcess( + BrowserParent* aParentBrowser, ContentParent* aContentParent, + const WindowGlobalInit& aWindowInit, uint32_t aChromeFlags, TabId aTabId) { + MOZ_ASSERT(!CanSend(), + "This should be called before the object is connected to IPC"); + MOZ_DIAGNOSTIC_ASSERT(!aContentParent->IsLaunching()); + MOZ_DIAGNOSTIC_ASSERT(!aContentParent->IsDead()); + + RefPtr<CanonicalBrowsingContext> browsingContext = + CanonicalBrowsingContext::Get(aWindowInit.context().mBrowsingContextId); + if (!browsingContext || browsingContext->IsDiscarded()) { + return NS_ERROR_UNEXPECTED; + } + + MOZ_DIAGNOSTIC_ASSERT( + !browsingContext->GetBrowserParent(), + "BrowsingContext must have had previous BrowserParent cleared"); + + MOZ_DIAGNOSTIC_ASSERT( + aParentBrowser->Manager() != aContentParent, + "Cannot create OOP iframe in the same process as its parent document"); + + // Unfortunately, due to the current racy destruction of BrowsingContext + // instances when Fission is enabled, while `browsingContext` may not be + // discarded, an ancestor might be. + // + // A discarded ancestor will cause us issues when creating our `BrowserParent` + // in the new content process, so abort the attempt if we have one. + // + // FIXME: We should never have a non-discarded BrowsingContext with discarded + // ancestors. (bug 1634759) + if (NS_WARN_IF(!browsingContext->AncestorsAreCurrent())) { + return NS_ERROR_UNEXPECTED; + } + + // Ensure that our content process is subscribed to our newly created + // BrowsingContextGroup. + browsingContext->Group()->EnsureHostProcess(aContentParent); + browsingContext->SetOwnerProcessId(aContentParent->ChildID()); + + // Construct the BrowserParent object for our subframe. + auto browserParent = MakeRefPtr<BrowserParent>( + aContentParent, aTabId, *aParentBrowser, browsingContext, aChromeFlags); + browserParent->SetBrowserBridgeParent(this); + + // Open a remote endpoint for our PBrowser actor. + ManagedEndpoint<PBrowserChild> childEp = + aContentParent->OpenPBrowserEndpoint(browserParent); + if (NS_WARN_IF(!childEp.IsValid())) { + MOZ_ASSERT(false, "Browser Open Endpoint Failed"); + return NS_ERROR_FAILURE; + } + + ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); + if (!cpm) { + return NS_ERROR_UNEXPECTED; + } + cpm->RegisterRemoteFrame(browserParent); + + RefPtr<WindowGlobalParent> windowParent = + WindowGlobalParent::CreateDisconnected(aWindowInit); + if (!windowParent) { + return NS_ERROR_UNEXPECTED; + } + + ManagedEndpoint<PWindowGlobalChild> windowChildEp = + browserParent->OpenPWindowGlobalEndpoint(windowParent); + if (NS_WARN_IF(!windowChildEp.IsValid())) { + MOZ_ASSERT(false, "WindowGlobal Open Endpoint Failed"); + return NS_ERROR_FAILURE; + } + + MOZ_DIAGNOSTIC_ASSERT(!browsingContext->IsDiscarded(), + "bc cannot have become discarded"); + + // Tell the content process to set up its PBrowserChild. + bool ok = aContentParent->SendConstructBrowser( + std::move(childEp), std::move(windowChildEp), aTabId, + browserParent->AsIPCTabContext(), aWindowInit, aChromeFlags, + aContentParent->ChildID(), aContentParent->IsForBrowser(), + /* aIsTopLevel */ false); + if (NS_WARN_IF(!ok)) { + MOZ_ASSERT(false, "Browser Constructor Failed"); + return NS_ERROR_FAILURE; + } + + // Set our BrowserParent object to the newly created browser. + mBrowserParent = std::move(browserParent); + mBrowserParent->SetOwnerElement(aParentBrowser->GetOwnerElement()); + mBrowserParent->InitRendering(); + + GetBrowsingContext()->SetCurrentBrowserParent(mBrowserParent); + + windowParent->Init(); + return NS_OK; +} + +CanonicalBrowsingContext* BrowserBridgeParent::GetBrowsingContext() { + return mBrowserParent->GetBrowsingContext(); +} + +BrowserParent* BrowserBridgeParent::Manager() { + MOZ_ASSERT(CanSend()); + return static_cast<BrowserParent*>(PBrowserBridgeParent::Manager()); +} + +void BrowserBridgeParent::Destroy() { + if (mBrowserParent) { +#ifdef ACCESSIBILITY + if (mEmbedderAccessibleDoc && !mEmbedderAccessibleDoc->IsShutdown()) { + mEmbedderAccessibleDoc->RemovePendingOOPChildDoc(this); + } +#endif + mBrowserParent->Destroy(); + mBrowserParent->SetBrowserBridgeParent(nullptr); + mBrowserParent = nullptr; + } + if (CanSend()) { + Unused << Send__delete__(this); + } +} + +IPCResult BrowserBridgeParent::RecvShow(const OwnerShowInfo& aOwnerInfo) { + mBrowserParent->AttachWindowRenderer(); + Unused << mBrowserParent->SendShow(mBrowserParent->GetShowInfo(), aOwnerInfo); + return IPC_OK(); +} + +IPCResult BrowserBridgeParent::RecvScrollbarPreferenceChanged( + ScrollbarPreference aPref) { + Unused << mBrowserParent->SendScrollbarPreferenceChanged(aPref); + return IPC_OK(); +} + +IPCResult BrowserBridgeParent::RecvLoadURL(nsDocShellLoadState* aLoadState) { + Unused << mBrowserParent->SendLoadURL(WrapNotNull(aLoadState), + mBrowserParent->GetShowInfo()); + return IPC_OK(); +} + +IPCResult BrowserBridgeParent::RecvResumeLoad(uint64_t aPendingSwitchID) { + mBrowserParent->ResumeLoad(aPendingSwitchID); + return IPC_OK(); +} + +IPCResult BrowserBridgeParent::RecvUpdateDimensions( + const nsIntRect& aRect, const ScreenIntSize& aSize) { + mBrowserParent->UpdateDimensions(aRect, aSize); + return IPC_OK(); +} + +IPCResult BrowserBridgeParent::RecvUpdateEffects(const EffectsInfo& aEffects) { + Unused << mBrowserParent->SendUpdateEffects(aEffects); + return IPC_OK(); +} + +IPCResult BrowserBridgeParent::RecvUpdateRemotePrintSettings( + const embedding::PrintData& aPrintData) { + Unused << mBrowserParent->SendUpdateRemotePrintSettings(aPrintData); + return IPC_OK(); +} + +IPCResult BrowserBridgeParent::RecvRenderLayers(const bool& aEnabled) { + Unused << mBrowserParent->SendRenderLayers(aEnabled); + return IPC_OK(); +} + +IPCResult BrowserBridgeParent::RecvNavigateByKey( + const bool& aForward, const bool& aForDocumentNavigation) { + Unused << mBrowserParent->SendNavigateByKey(aForward, aForDocumentNavigation); + return IPC_OK(); +} + +IPCResult BrowserBridgeParent::RecvBeginDestroy() { + Destroy(); + return IPC_OK(); +} + +IPCResult BrowserBridgeParent::RecvDispatchSynthesizedMouseEvent( + const WidgetMouseEvent& aEvent) { + if (aEvent.mMessage != eMouseMove || + aEvent.mReason != WidgetMouseEvent::eSynthesized) { + return IPC_FAIL(this, "Unexpected event type"); + } + + nsCOMPtr<nsIWidget> widget = Manager()->GetWidget(); + if (!widget) { + return IPC_OK(); + } + + WidgetMouseEvent event = aEvent; + event.mWidget = widget; + // Convert mRefPoint from the dispatching child process coordinate space + // to the parent coordinate space. The SendRealMouseEvent call will convert + // it into the dispatchee child process coordinate space + event.mRefPoint = Manager()->TransformChildToParent(event.mRefPoint); + // We need to set up an InputAPZContext on the stack because + // BrowserParent::SendRealMouseEvent requires one. But the only thing in + // that context that is actually used in this scenario is the layers id, + // and we already have that on the mouse event. + layers::InputAPZContext context( + layers::ScrollableLayerGuid(event.mLayersId, 0, + layers::ScrollableLayerGuid::NULL_SCROLL_ID), + 0, nsEventStatus_eIgnore); + mBrowserParent->SendRealMouseEvent(event); + return IPC_OK(); +} + +IPCResult BrowserBridgeParent::RecvWillChangeProcess() { + Unused << mBrowserParent->SendWillChangeProcess(); + return IPC_OK(); +} + +IPCResult BrowserBridgeParent::RecvActivate(uint64_t aActionId) { + mBrowserParent->Activate(aActionId); + return IPC_OK(); +} + +IPCResult BrowserBridgeParent::RecvDeactivate(const bool& aWindowLowering, + uint64_t aActionId) { + mBrowserParent->Deactivate(aWindowLowering, aActionId); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserBridgeParent::RecvUpdateRemoteStyle( + const StyleImageRendering& aImageRendering) { + Unused << mBrowserParent->SendUpdateRemoteStyle(aImageRendering); + return IPC_OK(); +} + +#ifdef ACCESSIBILITY +a11y::DocAccessibleParent* BrowserBridgeParent::GetDocAccessibleParent() { + auto* embeddedBrowser = GetBrowserParent(); + if (!embeddedBrowser) { + return nullptr; + } + a11y::DocAccessibleParent* docAcc = + embeddedBrowser->GetTopLevelDocAccessible(); + return docAcc && !docAcc->IsShutdown() ? docAcc : nullptr; +} + +IPCResult BrowserBridgeParent::RecvSetEmbedderAccessible( + PDocAccessibleParent* aDoc, uint64_t aID) { +# if defined(ANDROID) + MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor()); +# endif + MOZ_ASSERT(aDoc || mEmbedderAccessibleDoc, + "Embedder doc shouldn't be cleared if it wasn't set"); + MOZ_ASSERT(!mEmbedderAccessibleDoc || !aDoc || mEmbedderAccessibleDoc == aDoc, + "Embedder doc shouldn't change from one doc to another"); + if (!aDoc && mEmbedderAccessibleDoc && + !mEmbedderAccessibleDoc->IsShutdown()) { + // We're clearing the embedder doc, so remove the pending child doc addition + // (if any). + mEmbedderAccessibleDoc->RemovePendingOOPChildDoc(this); + } + mEmbedderAccessibleDoc = static_cast<a11y::DocAccessibleParent*>(aDoc); + mEmbedderAccessibleID = aID; + if (!aDoc) { + MOZ_ASSERT(!aID); + return IPC_OK(); + } + MOZ_ASSERT(aID); + if (GetDocAccessibleParent()) { + // The embedded DocAccessibleParent has already been created. This can + // happen if, for example, an iframe is hidden and then shown or + // an iframe is reflowed by layout. + mEmbedderAccessibleDoc->AddChildDoc(this); + } + return IPC_OK(); +} + +a11y::DocAccessibleParent* BrowserBridgeParent::GetEmbedderAccessibleDoc() { + return mEmbedderAccessibleDoc && !mEmbedderAccessibleDoc->IsShutdown() + ? mEmbedderAccessibleDoc.get() + : nullptr; +} +#endif + +void BrowserBridgeParent::ActorDestroy(ActorDestroyReason aWhy) { Destroy(); } + +} // namespace mozilla::dom diff --git a/dom/ipc/BrowserBridgeParent.h b/dom/ipc/BrowserBridgeParent.h new file mode 100644 index 0000000000..35dfd7636f --- /dev/null +++ b/dom/ipc/BrowserBridgeParent.h @@ -0,0 +1,123 @@ +/* -*- 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_dom_BrowserBridgeParent_h +#define mozilla_dom_BrowserBridgeParent_h + +#include "mozilla/dom/PBrowserBridgeParent.h" +#include "mozilla/dom/ipc/IdType.h" +#include "mozilla/dom/WindowGlobalTypes.h" + +namespace mozilla { + +namespace a11y { +class DocAccessibleParent; +} + +namespace embedding { +class PrintData; +} + +namespace dom { + +class BrowserParent; + +/** + * BrowserBridgeParent implements the parent actor part of the PBrowserBridge + * protocol. See PBrowserBridge for more information. + */ +class BrowserBridgeParent : public PBrowserBridgeParent { + public: + NS_INLINE_DECL_REFCOUNTING(BrowserBridgeParent, final); + + BrowserBridgeParent(); + + nsresult InitWithProcess(BrowserParent* aParentBrowser, + ContentParent* aContentParent, + const WindowGlobalInit& aWindowInit, + uint32_t aChromeFlags, TabId aTabId); + + BrowserParent* GetBrowserParent() { return mBrowserParent; } + + CanonicalBrowsingContext* GetBrowsingContext(); + + // Get our manager actor. + BrowserParent* Manager(); + +#if defined(ACCESSIBILITY) + /** + * Get the DocAccessibleParent which contains this iframe. + */ + a11y::DocAccessibleParent* GetEmbedderAccessibleDoc(); + + /** + * Get the unique id of the OuterDocAccessible associated with this iframe. + * This is the id of the RemoteAccessible inside the document returned by + * GetEmbedderAccessibleDoc. + */ + uint64_t GetEmbedderAccessibleId() { return mEmbedderAccessibleID; } + + /** + * Get the DocAccessibleParent for the embedded document. + */ + a11y::DocAccessibleParent* GetDocAccessibleParent(); +#endif // defined(ACCESSIBILITY) + + // Tear down this BrowserBridgeParent. + void Destroy(); + + protected: + friend class PBrowserBridgeParent; + + mozilla::ipc::IPCResult RecvShow(const OwnerShowInfo&); + mozilla::ipc::IPCResult RecvScrollbarPreferenceChanged(ScrollbarPreference); + mozilla::ipc::IPCResult RecvLoadURL(nsDocShellLoadState* aLoadState); + mozilla::ipc::IPCResult RecvResumeLoad(uint64_t aPendingSwitchID); + mozilla::ipc::IPCResult RecvUpdateDimensions(const nsIntRect& aRect, + const ScreenIntSize& aSize); + mozilla::ipc::IPCResult RecvUpdateEffects(const EffectsInfo& aEffects); + mozilla::ipc::IPCResult RecvUpdateRemotePrintSettings( + const embedding::PrintData&); + mozilla::ipc::IPCResult RecvRenderLayers(const bool& aEnabled); + + mozilla::ipc::IPCResult RecvNavigateByKey(const bool& aForward, + const bool& aForDocumentNavigation); + mozilla::ipc::IPCResult RecvBeginDestroy(); + + mozilla::ipc::IPCResult RecvDispatchSynthesizedMouseEvent( + const WidgetMouseEvent& aEvent); + + mozilla::ipc::IPCResult RecvWillChangeProcess(); + + mozilla::ipc::IPCResult RecvActivate(uint64_t aActionId); + + mozilla::ipc::IPCResult RecvDeactivate(const bool& aWindowLowering, + uint64_t aActionId); + + mozilla::ipc::IPCResult RecvUpdateRemoteStyle( + const StyleImageRendering& aImageRendering); + +#ifdef ACCESSIBILITY + mozilla::ipc::IPCResult RecvSetEmbedderAccessible(PDocAccessibleParent* aDoc, + uint64_t aID); +#endif + + void ActorDestroy(ActorDestroyReason aWhy) override; + + private: + ~BrowserBridgeParent(); + + RefPtr<BrowserParent> mBrowserParent; +#ifdef ACCESSIBILITY + RefPtr<a11y::DocAccessibleParent> mEmbedderAccessibleDoc; + uint64_t mEmbedderAccessibleID = 0; +#endif // ACCESSIBILITY +}; + +} // namespace dom +} // namespace mozilla + +#endif // !defined(mozilla_dom_BrowserBridgeParent_h) diff --git a/dom/ipc/BrowserChild.cpp b/dom/ipc/BrowserChild.cpp new file mode 100644 index 0000000000..830ad8579a --- /dev/null +++ b/dom/ipc/BrowserChild.cpp @@ -0,0 +1,3771 @@ +/* -*- 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 "BrowserChild.h" + +#ifdef ACCESSIBILITY +# include "mozilla/a11y/DocAccessibleChild.h" +#endif +#include <utility> + +#include "BrowserParent.h" +#include "ContentChild.h" +#include "EventStateManager.h" +#include "MMPrinter.h" +#include "PuppetWidget.h" +#include "StructuredCloneData.h" +#include "UnitTransforms.h" +#include "Units.h" +#include "VRManagerChild.h" +#include "mozilla/Assertions.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/EventForwards.h" +#include "mozilla/EventListenerManager.h" +#include "mozilla/HoldDropJSObjects.h" +#include "mozilla/IMEStateManager.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/NativeKeyBindingsType.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/PresShell.h" +#include "mozilla/ProcessHangMonitor.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/TextEvents.h" +#include "mozilla/ToString.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/AutoPrintEventDispatcher.h" +#include "mozilla/dom/BrowserBridgeChild.h" +#include "mozilla/dom/DataTransfer.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/JSWindowActorChild.h" +#include "mozilla/dom/ImageDocument.h" +#include "mozilla/dom/LoadURIOptionsBinding.h" +#include "mozilla/dom/MessageManagerBinding.h" +#include "mozilla/dom/MouseEventBinding.h" +#include "mozilla/dom/Nullable.h" +#include "mozilla/dom/PaymentRequestChild.h" +#include "mozilla/dom/PBrowser.h" +#include "mozilla/dom/PointerEventHandler.h" +#include "mozilla/dom/SessionStoreUtils.h" +#include "mozilla/dom/SessionStoreChild.h" +#include "mozilla/dom/UserActivation.h" +#include "mozilla/dom/WindowGlobalChild.h" +#include "mozilla/dom/WindowProxyHolder.h" +#include "mozilla/gfx/CrossProcessPaint.h" +#include "mozilla/gfx/Matrix.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/ipc/PBackgroundChild.h" +#include "mozilla/layers/APZCCallbackHelper.h" +#include "mozilla/layers/TouchActionHelper.h" +#include "mozilla/layers/APZCTreeManagerChild.h" +#include "mozilla/layers/APZChild.h" +#include "mozilla/layers/APZEventState.h" +#include "mozilla/layers/CompositorBridgeChild.h" +#include "mozilla/layers/ContentProcessController.h" +#include "mozilla/layers/DoubleTapToZoom.h" +#include "mozilla/layers/IAPZCTreeManager.h" +#include "mozilla/layers/ImageBridgeChild.h" +#include "mozilla/layers/InputAPZContext.h" +#include "mozilla/layers/WebRenderLayerManager.h" +#include "nsBrowserStatusFilter.h" +#include "nsCommandParams.h" +#include "nsContentPermissionHelper.h" +#include "nsContentUtils.h" +#include "nsDeviceContext.h" +#include "nsDocShell.h" +#include "nsDocShellLoadState.h" +#include "nsExceptionHandler.h" +#include "nsFilePickerProxy.h" +#include "nsFocusManager.h" +#include "nsGlobalWindowOuter.h" +#include "nsIBaseWindow.h" +#include "nsIBrowserDOMWindow.h" +#include "nsIClassifiedChannel.h" +#include "nsIDocShell.h" +#include "nsIFrame.h" +#include "nsILoadContext.h" +#include "nsISHEntry.h" +#include "nsISHistory.h" +#include "nsIScreenManager.h" +#include "nsIScriptError.h" +#include "nsIURI.h" +#include "nsIURIMutator.h" +#include "nsIWeakReferenceUtils.h" +#include "nsIWebBrowser.h" +#include "nsIWebProgress.h" +#include "nsLayoutUtils.h" +#include "nsNetUtil.h" +#include "nsIOpenWindowInfo.h" +#include "nsPIDOMWindow.h" +#include "nsPIWindowRoot.h" +#include "nsPrintfCString.h" +#include "nsRefreshDriver.h" +#include "nsThreadManager.h" +#include "nsThreadUtils.h" +#include "nsViewManager.h" +#include "nsWebBrowser.h" +#include "nsWindowWatcher.h" +#include "nsIXULRuntime.h" + +#ifdef MOZ_WAYLAND +# include "nsAppRunner.h" +#endif + +#ifdef NS_PRINTING +# include "mozilla/layout/RemotePrintJobChild.h" +# include "nsIPrintSettings.h" +# include "nsIPrintSettingsService.h" +# include "nsIWebBrowserPrint.h" +#endif + +static mozilla::LazyLogModule sApzChildLog("apz.child"); + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::dom::ipc; +using namespace mozilla::ipc; +using namespace mozilla::layers; +using namespace mozilla::layout; +using namespace mozilla::widget; +using mozilla::layers::GeckoContentController; + +static const char BEFORE_FIRST_PAINT[] = "before-first-paint"; + +static uint32_t sConsecutiveTouchMoveCount = 0; + +using BrowserChildMap = nsTHashMap<nsUint64HashKey, BrowserChild*>; +static BrowserChildMap* sBrowserChildren; +StaticMutex sBrowserChildrenMutex; + +already_AddRefed<Document> BrowserChild::GetTopLevelDocument() const { + nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation()); + nsCOMPtr<Document> doc = docShell ? docShell->GetExtantDocument() : nullptr; + return doc.forget(); +} + +PresShell* BrowserChild::GetTopLevelPresShell() const { + if (RefPtr<Document> doc = GetTopLevelDocument()) { + return doc->GetPresShell(); + } + return nullptr; +} + +bool BrowserChild::UpdateFrame(const RepaintRequest& aRequest) { + MOZ_ASSERT(aRequest.GetScrollId() != ScrollableLayerGuid::NULL_SCROLL_ID); + + if (aRequest.IsRootContent()) { + if (PresShell* presShell = GetTopLevelPresShell()) { + // Guard against stale updates (updates meant for a pres shell which + // has since been torn down and destroyed). + if (aRequest.GetPresShellId() == presShell->GetPresShellId()) { + APZCCallbackHelper::UpdateRootFrame(aRequest); + return true; + } + } + } else { + // aRequest.mIsRoot is false, so we are trying to update a subframe. + // This requires special handling. + APZCCallbackHelper::UpdateSubFrame(aRequest); + return true; + } + return true; +} + +class BrowserChild::DelayedDeleteRunnable final : public Runnable, + public nsIRunnablePriority { + RefPtr<BrowserChild> mBrowserChild; + + // In order to try that this runnable runs after everything that could + // possibly touch this tab, we send it through the event queue twice. + bool mReadyToDelete = false; + + public: + explicit DelayedDeleteRunnable(BrowserChild* aBrowserChild) + : Runnable("BrowserChild::DelayedDeleteRunnable"), + mBrowserChild(aBrowserChild) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aBrowserChild); + } + + NS_DECL_ISUPPORTS_INHERITED + + private: + ~DelayedDeleteRunnable() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mBrowserChild); + } + + NS_IMETHOD GetPriority(uint32_t* aPriority) override { + *aPriority = nsIRunnablePriority::PRIORITY_NORMAL; + return NS_OK; + } + + NS_IMETHOD + Run() override { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mBrowserChild); + + if (!mReadyToDelete) { + // This time run this runnable at input priority. + mReadyToDelete = true; + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(this)); + return NS_OK; + } + + // Check in case ActorDestroy was called after RecvDestroy message. + if (mBrowserChild->IPCOpen()) { + Unused << PBrowserChild::Send__delete__(mBrowserChild); + } + + mBrowserChild = nullptr; + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS_INHERITED(BrowserChild::DelayedDeleteRunnable, Runnable, + nsIRunnablePriority) + +namespace { +std::map<TabId, RefPtr<BrowserChild>>& NestedBrowserChildMap() { + MOZ_ASSERT(NS_IsMainThread()); + static std::map<TabId, RefPtr<BrowserChild>> sNestedBrowserChildMap; + return sNestedBrowserChildMap; +} +} // namespace + +already_AddRefed<BrowserChild> BrowserChild::FindBrowserChild( + const TabId& aTabId) { + auto iter = NestedBrowserChildMap().find(aTabId); + if (iter == NestedBrowserChildMap().end()) { + return nullptr; + } + RefPtr<BrowserChild> browserChild = iter->second; + return browserChild.forget(); +} + +/*static*/ +already_AddRefed<BrowserChild> BrowserChild::Create( + ContentChild* aManager, const TabId& aTabId, const TabContext& aContext, + BrowsingContext* aBrowsingContext, uint32_t aChromeFlags, + bool aIsTopLevel) { + RefPtr<BrowserChild> iframe = new BrowserChild( + aManager, aTabId, aContext, aBrowsingContext, aChromeFlags, aIsTopLevel); + return iframe.forget(); +} + +BrowserChild::BrowserChild(ContentChild* aManager, const TabId& aTabId, + const TabContext& aContext, + BrowsingContext* aBrowsingContext, + uint32_t aChromeFlags, bool aIsTopLevel) + : TabContext(aContext), + mBrowserChildMessageManager(nullptr), + mManager(aManager), + mBrowsingContext(aBrowsingContext), + mChromeFlags(aChromeFlags), + mMaxTouchPoints(0), + mLayersId{0}, + mEffectsInfo{EffectsInfo::FullyHidden()}, + mDynamicToolbarMaxHeight(0), + mUniqueId(aTabId), + mDidFakeShow(false), + mTriedBrowserInit(false), + mIgnoreKeyPressEvent(false), + mHasValidInnerSize(false), + mDestroyed(false), + mIsTopLevel(aIsTopLevel), + mIsTransparent(false), + mIPCOpen(false), + mDidSetRealShowInfo(false), + mDidLoadURLInit(false), + mSkipKeyPress(false), + mShouldSendWebProgressEventsToParent(false), + mRenderLayers(true), + mIsPreservingLayers(false), +#if defined(XP_WIN) && defined(ACCESSIBILITY) + mNativeWindowHandle(0), +#endif + mCancelContentJSEpoch(0) { + mozilla::HoldJSObjects(this); + + // preloaded BrowserChild should not be added to child map + if (mUniqueId) { + MOZ_ASSERT(NestedBrowserChildMap().find(mUniqueId) == + NestedBrowserChildMap().end()); + NestedBrowserChildMap()[mUniqueId] = this; + } + mCoalesceMouseMoveEvents = StaticPrefs::dom_events_coalesce_mousemove(); + if (mCoalesceMouseMoveEvents) { + mCoalescedMouseEventFlusher = new CoalescedMouseMoveFlusher(this); + } + + if (StaticPrefs::dom_events_coalesce_touchmove()) { + mCoalescedTouchMoveEventFlusher = new CoalescedTouchMoveFlusher(this); + } +} + +const CompositorOptions& BrowserChild::GetCompositorOptions() const { + // If you're calling this before mCompositorOptions is set, well.. don't. + MOZ_ASSERT(mCompositorOptions); + return mCompositorOptions.ref(); +} + +bool BrowserChild::AsyncPanZoomEnabled() const { + // This might get called by the TouchEvent::PrefEnabled code before we have + // mCompositorOptions populated (bug 1370089). In that case we just assume + // APZ is enabled because we're in a content process (because BrowserChild) + // and APZ is probably going to be enabled here since e10s is enabled. + return mCompositorOptions ? mCompositorOptions->UseAPZ() : true; +} + +NS_IMETHODIMP +BrowserChild::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (!strcmp(aTopic, BEFORE_FIRST_PAINT)) { + if (AsyncPanZoomEnabled()) { + nsCOMPtr<Document> subject(do_QueryInterface(aSubject)); + nsCOMPtr<Document> doc(GetTopLevelDocument()); + + if (subject == doc) { + RefPtr<PresShell> presShell = doc->GetPresShell(); + if (presShell) { + presShell->SetIsFirstPaint(true); + } + + APZCCallbackHelper::InitializeRootDisplayport(presShell); + } + } + } + + return NS_OK; +} + +void BrowserChild::ContentReceivedInputBlock(uint64_t aInputBlockId, + bool aPreventDefault) const { + if (mApzcTreeManager) { + mApzcTreeManager->ContentReceivedInputBlock(aInputBlockId, aPreventDefault); + } +} + +void BrowserChild::SetTargetAPZC( + uint64_t aInputBlockId, + const nsTArray<ScrollableLayerGuid>& aTargets) const { + if (mApzcTreeManager) { + mApzcTreeManager->SetTargetAPZC(aInputBlockId, aTargets); + } +} + +bool BrowserChild::DoUpdateZoomConstraints( + const uint32_t& aPresShellId, const ViewID& aViewId, + const Maybe<ZoomConstraints>& aConstraints) { + if (!mApzcTreeManager || mDestroyed) { + return false; + } + + ScrollableLayerGuid guid = + ScrollableLayerGuid(mLayersId, aPresShellId, aViewId); + + mApzcTreeManager->UpdateZoomConstraints(guid, aConstraints); + return true; +} + +nsresult BrowserChild::Init(mozIDOMWindowProxy* aParent, + WindowGlobalChild* aInitialWindowChild) { + MOZ_ASSERT_IF(aInitialWindowChild, + aInitialWindowChild->BrowsingContext() == mBrowsingContext); + + nsCOMPtr<nsIWidget> widget = nsIWidget::CreatePuppetWidget(this); + mPuppetWidget = static_cast<PuppetWidget*>(widget.get()); + if (!mPuppetWidget) { + NS_ERROR("couldn't create fake widget"); + return NS_ERROR_FAILURE; + } + mPuppetWidget->InfallibleCreate(nullptr, + nullptr, // no parents + LayoutDeviceIntRect(0, 0, 0, 0), + nullptr); // HandleWidgetEvent + + mWebBrowser = nsWebBrowser::Create(this, mPuppetWidget, mBrowsingContext, + aInitialWindowChild); + nsIWebBrowser* webBrowser = mWebBrowser; + + mWebNav = do_QueryInterface(webBrowser); + NS_ASSERTION(mWebNav, "nsWebBrowser doesn't implement nsIWebNavigation?"); + + // IPC uses a WebBrowser object for which DNS prefetching is turned off + // by default. But here we really want it, so enable it explicitly + mWebBrowser->SetAllowDNSPrefetch(true); + + nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation()); + MOZ_ASSERT(docShell); + + mStatusFilter = new nsBrowserStatusFilter(); + + nsresult rv = + mStatusFilter->AddProgressListener(this, nsIWebProgress::NOTIFY_ALL); + NS_ENSURE_SUCCESS(rv, rv); + + { + nsCOMPtr<nsIWebProgress> webProgress = do_QueryInterface(docShell); + rv = webProgress->AddProgressListener(mStatusFilter, + nsIWebProgress::NOTIFY_ALL); + NS_ENSURE_SUCCESS(rv, rv); + } + +#ifdef DEBUG + nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(WebNavigation()); + MOZ_ASSERT(loadContext); + MOZ_ASSERT(loadContext->UseRemoteTabs() == + !!(mChromeFlags & nsIWebBrowserChrome::CHROME_REMOTE_WINDOW)); + MOZ_ASSERT(loadContext->UseRemoteSubframes() == + !!(mChromeFlags & nsIWebBrowserChrome::CHROME_FISSION_WINDOW)); +#endif // defined(DEBUG) + + // Few lines before, baseWindow->Create() will end up creating a new + // window root in nsGlobalWindowOuter::SetDocShell. + // Then this chrome event handler, will be inherited to inner windows. + // We want to also set it to the docshell so that inner windows + // and any code that has access to the docshell + // can all listen to the same chrome event handler. + // XXX: ideally, we would set a chrome event handler earlier, + // and all windows, even the root one, will use the docshell one. + nsCOMPtr<nsPIDOMWindowOuter> window = do_GetInterface(WebNavigation()); + NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); + nsCOMPtr<EventTarget> chromeHandler = window->GetChromeEventHandler(); + docShell->SetChromeEventHandler(chromeHandler); + + // Window scrollbar flags only affect top level remote frames, not fission + // frames. + if (mIsTopLevel) { + nsContentUtils::SetScrollbarsVisibility( + docShell, !!(mChromeFlags & nsIWebBrowserChrome::CHROME_SCROLLBARS)); + } + + nsWeakPtr weakPtrThis = do_GetWeakReference( + static_cast<nsIBrowserChild*>(this)); // for capture by the lambda + ContentReceivedInputBlockCallback callback( + [weakPtrThis](uint64_t aInputBlockId, bool aPreventDefault) { + if (nsCOMPtr<nsIBrowserChild> browserChild = + do_QueryReferent(weakPtrThis)) { + static_cast<BrowserChild*>(browserChild.get()) + ->ContentReceivedInputBlock(aInputBlockId, aPreventDefault); + } + }); + mAPZEventState = new APZEventState(mPuppetWidget, std::move(callback)); + + mIPCOpen = true; + + if (StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) { + mSessionStoreChild = SessionStoreChild::GetOrCreate(mBrowsingContext); + } + + // We've all set up, make sure our visibility state is consistent. This is + // important for OOP iframes, which start off as hidden. + UpdateVisibility(); + + return NS_OK; +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(BrowserChild) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(BrowserChild) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowserChildMessageManager) + tmp->nsMessageManagerScriptExecutor::Unlink(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mStatusFilter) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mWebNav) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowsingContext) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mSessionStoreChild) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mContentTransformPromise) + NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(BrowserChild) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowserChildMessageManager) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStatusFilter) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWebNav) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowsingContext) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSessionStoreChild) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContentTransformPromise) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(BrowserChild) + tmp->nsMessageManagerScriptExecutor::Trace(aCallbacks, aClosure); +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BrowserChild) + NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChrome) + NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChromeFocus) + NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY(nsIWindowProvider) + NS_INTERFACE_MAP_ENTRY(nsIBrowserChild) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY(nsITooltipListener) + NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) + NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener2) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIBrowserChild) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(BrowserChild) +NS_IMPL_CYCLE_COLLECTING_RELEASE(BrowserChild) + +NS_IMETHODIMP +BrowserChild::GetChromeFlags(uint32_t* aChromeFlags) { + *aChromeFlags = mChromeFlags; + return NS_OK; +} + +NS_IMETHODIMP +BrowserChild::SetChromeFlags(uint32_t aChromeFlags) { + NS_WARNING("trying to SetChromeFlags from content process?"); + + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +BrowserChild::RemoteDropLinks( + const nsTArray<RefPtr<nsIDroppedLinkItem>>& aLinks) { + nsTArray<nsString> linksArray; + nsresult rv = NS_OK; + for (nsIDroppedLinkItem* link : aLinks) { + nsString tmp; + rv = link->GetUrl(tmp); + if (NS_FAILED(rv)) { + return rv; + } + linksArray.AppendElement(tmp); + + rv = link->GetName(tmp); + if (NS_FAILED(rv)) { + return rv; + } + linksArray.AppendElement(tmp); + + rv = link->GetType(tmp); + if (NS_FAILED(rv)) { + return rv; + } + linksArray.AppendElement(tmp); + } + bool sent = SendDropLinks(linksArray); + + return sent ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +BrowserChild::ShowAsModal() { + NS_WARNING("BrowserChild::ShowAsModal not supported in BrowserChild"); + + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +BrowserChild::IsWindowModal(bool* aRetVal) { + *aRetVal = false; + return NS_OK; +} + +NS_IMETHODIMP +BrowserChild::SetLinkStatus(const nsAString& aStatusText) { + // We can only send the status after the ipc machinery is set up + if (IPCOpen()) { + SendSetLinkStatus(aStatusText); + } + return NS_OK; +} + +NS_IMETHODIMP +BrowserChild::SetDimensions(DimensionRequest&& aRequest) { + // The parent is in charge of the dimension changes. If JS code wants to + // change the dimensions (moveTo, screenX, etc.) we send a message to the + // parent about the new requested dimension, the parent does the resize/move + // then send a message to the child to update itself. For APIs like screenX + // this function is called with only the changed values. In a series of calls + // like window.screenX = 10; window.screenY = 10; for the second call, since + // screenX is not yet updated we might accidentally reset back screenX to it's + // old value. To avoid this, if a parameter did not change, we want the parent + // to handle the unchanged values. + + double scale = mPuppetWidget ? mPuppetWidget->GetDefaultScale().scale : 1.0; + SendSetDimensions(aRequest, scale); + return NS_OK; +} + +NS_IMETHODIMP +BrowserChild::GetDimensions(DimensionKind aDimensionKind, int32_t* aX, + int32_t* aY, int32_t* aCx, int32_t* aCy) { + ScreenIntRect rect = GetOuterRect(); + if (aDimensionKind == DimensionKind::Inner) { + if (aX || aY) { + return NS_ERROR_NOT_IMPLEMENTED; + } + rect.SizeTo(GetInnerSize()); + } + if (aX) { + *aX = rect.x; + } + if (aY) { + *aY = rect.y; + } + if (aCx) { + *aCx = rect.width; + } + if (aCy) { + *aCy = rect.height; + } + return NS_OK; +} + +NS_IMETHODIMP +BrowserChild::Blur() { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +BrowserChild::FocusNextElement(bool aForDocumentNavigation) { + SendMoveFocus(true, aForDocumentNavigation); + return NS_OK; +} + +NS_IMETHODIMP +BrowserChild::FocusPrevElement(bool aForDocumentNavigation) { + SendMoveFocus(false, aForDocumentNavigation); + return NS_OK; +} + +NS_IMETHODIMP +BrowserChild::GetInterface(const nsIID& aIID, void** aSink) { + // XXXbz should we restrict the set of interfaces we hand out here? + // See bug 537429 + return QueryInterface(aIID, aSink); +} + +NS_IMETHODIMP +BrowserChild::ProvideWindow(nsIOpenWindowInfo* aOpenWindowInfo, + uint32_t aChromeFlags, bool aCalledFromJS, + nsIURI* aURI, const nsAString& aName, + const nsACString& aFeatures, + const UserActivation::Modifiers& aModifiers, + bool aForceNoOpener, bool aForceNoReferrer, + bool aIsPopupRequested, + nsDocShellLoadState* aLoadState, bool* aWindowIsNew, + BrowsingContext** aReturn) { + *aReturn = nullptr; + + RefPtr<BrowsingContext> parent = aOpenWindowInfo->GetParent(); + + int32_t openLocation = nsWindowWatcher::GetWindowOpenLocation( + parent->GetDOMWindow(), aChromeFlags, aModifiers, aCalledFromJS, + aOpenWindowInfo->GetIsForPrinting()); + + // If it turns out we're opening in the current browser, just hand over the + // current browser's docshell. + if (openLocation == nsIBrowserDOMWindow::OPEN_CURRENTWINDOW) { + nsCOMPtr<nsIWebBrowser> browser = do_GetInterface(WebNavigation()); + *aWindowIsNew = false; + + nsCOMPtr<mozIDOMWindowProxy> win; + MOZ_TRY(browser->GetContentDOMWindow(getter_AddRefs(win))); + + RefPtr<BrowsingContext> bc( + nsPIDOMWindowOuter::From(win)->GetBrowsingContext()); + bc.forget(aReturn); + return NS_OK; + } + + // Note that ProvideWindowCommon may return NS_ERROR_ABORT if the + // open window call was canceled. It's important that we pass this error + // code back to our caller. + ContentChild* cc = ContentChild::GetSingleton(); + return cc->ProvideWindowCommon( + WrapNotNull(this), aOpenWindowInfo, aChromeFlags, aCalledFromJS, aURI, + aName, aFeatures, aModifiers, aForceNoOpener, aForceNoReferrer, + aIsPopupRequested, aLoadState, aWindowIsNew, aReturn); +} + +void BrowserChild::DestroyWindow() { + mBrowsingContext = nullptr; + + if (mStatusFilter) { + if (nsCOMPtr<nsIWebProgress> webProgress = + do_QueryInterface(WebNavigation())) { + webProgress->RemoveProgressListener(mStatusFilter); + } + + mStatusFilter->RemoveProgressListener(this); + mStatusFilter = nullptr; + } + + if (mCoalescedMouseEventFlusher) { + mCoalescedMouseEventFlusher->RemoveObserver(); + mCoalescedMouseEventFlusher = nullptr; + } + + if (mCoalescedTouchMoveEventFlusher) { + mCoalescedTouchMoveEventFlusher->RemoveObserver(); + mCoalescedTouchMoveEventFlusher = nullptr; + } + + if (mSessionStoreChild) { + mSessionStoreChild->Stop(); + mSessionStoreChild = nullptr; + } + + // In case we don't have chance to process all entries, clean all data in + // the queue. + while (mToBeDispatchedMouseData.GetSize() > 0) { + UniquePtr<CoalescedMouseData> data( + static_cast<CoalescedMouseData*>(mToBeDispatchedMouseData.PopFront())); + data.reset(); + } + + nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(WebNavigation()); + if (baseWindow) baseWindow->Destroy(); + + if (mPuppetWidget) { + mPuppetWidget->Destroy(); + } + + mLayersConnected = Nothing(); + + if (mLayersId.IsValid()) { + StaticMutexAutoLock lock(sBrowserChildrenMutex); + + MOZ_ASSERT(sBrowserChildren); + sBrowserChildren->Remove(uint64_t(mLayersId)); + if (!sBrowserChildren->Count()) { + delete sBrowserChildren; + sBrowserChildren = nullptr; + } + mLayersId = layers::LayersId{0}; + } + + if (mAPZEventState) { + mAPZEventState->Destroy(); + mAPZEventState = nullptr; + } +} + +void BrowserChild::ActorDestroy(ActorDestroyReason why) { + mIPCOpen = false; + + DestroyWindow(); + + if (mBrowserChildMessageManager) { + // We should have a message manager if the global is alive, but it + // seems sometimes we don't. Assert in aurora/nightly, but don't + // crash in release builds. + MOZ_DIAGNOSTIC_ASSERT(mBrowserChildMessageManager->GetMessageManager()); + if (mBrowserChildMessageManager->GetMessageManager()) { + // The messageManager relays messages via the BrowserChild which + // no longer exists. + mBrowserChildMessageManager->DisconnectMessageManager(); + } + } + + if (GetTabId() != 0) { + NestedBrowserChildMap().erase(GetTabId()); + } +} + +BrowserChild::~BrowserChild() { + mAnonymousGlobalScopes.Clear(); + + DestroyWindow(); + + nsCOMPtr<nsIWebBrowser> webBrowser = do_QueryInterface(WebNavigation()); + if (webBrowser) { + webBrowser->SetContainerWindow(nullptr); + } + + mozilla::DropJSObjects(this); +} + +mozilla::ipc::IPCResult BrowserChild::RecvWillChangeProcess() { + if (mWebBrowser) { + mWebBrowser->SetWillChangeProcess(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvLoadURL( + nsDocShellLoadState* aLoadState, const ParentShowInfo& aInfo) { + if (!mDidLoadURLInit) { + mDidLoadURLInit = true; + if (!InitBrowserChildMessageManager()) { + return IPC_FAIL_NO_REASON(this); + } + + ApplyParentShowInfo(aInfo); + } + nsAutoCString spec; + aLoadState->URI()->GetSpec(spec); + + nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation()); + if (!docShell) { + NS_WARNING("WebNavigation does not have a docshell"); + return IPC_OK(); + } + docShell->LoadURI(aLoadState, true); + + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::URL, spec); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvCreateAboutBlankDocumentViewer( + nsIPrincipal* aPrincipal, nsIPrincipal* aPartitionedPrincipal) { + if (aPrincipal->GetIsExpandedPrincipal() || + aPartitionedPrincipal->GetIsExpandedPrincipal()) { + return IPC_FAIL(this, "Cannot create document with an expanded principal"); + } + if (aPrincipal->IsSystemPrincipal() || + aPartitionedPrincipal->IsSystemPrincipal()) { + MOZ_ASSERT_UNREACHABLE( + "Cannot use CreateAboutBlankDocumentViewer to create system principal " + "document in content"); + return IPC_OK(); + } + + nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation()); + if (!docShell) { + MOZ_ASSERT_UNREACHABLE("WebNavigation does not have a docshell"); + return IPC_OK(); + } + + nsCOMPtr<nsIURI> currentURI; + MOZ_ALWAYS_SUCCEEDS( + WebNavigation()->GetCurrentURI(getter_AddRefs(currentURI))); + if (!currentURI || !NS_IsAboutBlank(currentURI)) { + NS_WARNING("Can't create a ContentViewer unless on about:blank"); + return IPC_OK(); + } + + docShell->CreateAboutBlankDocumentViewer(aPrincipal, aPartitionedPrincipal, + nullptr); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvResumeLoad( + const uint64_t& aPendingSwitchID, const ParentShowInfo& aInfo) { + if (!mDidLoadURLInit) { + mDidLoadURLInit = true; + if (!InitBrowserChildMessageManager()) { + return IPC_FAIL_NO_REASON(this); + } + + ApplyParentShowInfo(aInfo); + } + + nsresult rv = WebNavigation()->ResumeRedirectedLoad(aPendingSwitchID, -1); + if (NS_FAILED(rv)) { + NS_WARNING("WebNavigation()->ResumeRedirectedLoad failed"); + } + + return IPC_OK(); +} + +nsresult BrowserChild::CloneDocumentTreeIntoSelf( + const MaybeDiscarded<BrowsingContext>& aSourceBC, + const embedding::PrintData& aPrintData) { +#ifdef NS_PRINTING + if (NS_WARN_IF(aSourceBC.IsNullOrDiscarded())) { + return NS_ERROR_FAILURE; + } + nsCOMPtr<Document> sourceDocument = aSourceBC.get()->GetDocument(); + if (NS_WARN_IF(!sourceDocument)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIDocShell> ourDocShell = do_GetInterface(WebNavigation()); + if (NS_WARN_IF(!ourDocShell)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIDocumentViewer> viewer; + ourDocShell->GetDocViewer(getter_AddRefs(viewer)); + if (NS_WARN_IF(!viewer)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIPrintSettingsService> printSettingsSvc = + do_GetService("@mozilla.org/gfx/printsettings-service;1"); + if (NS_WARN_IF(!printSettingsSvc)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIPrintSettings> printSettings; + nsresult rv = + printSettingsSvc->CreateNewPrintSettings(getter_AddRefs(printSettings)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + printSettingsSvc->DeserializeToPrintSettings(aPrintData, printSettings); + + RefPtr<Document> clone; + { + AutoPrintEventDispatcher dispatcher(*sourceDocument); + nsAutoScriptBlocker scriptBlocker; + bool hasInProcessCallbacks = false; + clone = sourceDocument->CreateStaticClone( + ourDocShell, viewer, printSettings, &hasInProcessCallbacks); + if (NS_WARN_IF(!clone)) { + return NS_ERROR_FAILURE; + } + } + + rv = UpdateRemotePrintSettings(aPrintData); + if (NS_FAILED(rv)) { + return rv; + } + +#endif + return NS_OK; +} + +mozilla::ipc::IPCResult BrowserChild::RecvCloneDocumentTreeIntoSelf( + const MaybeDiscarded<BrowsingContext>& aSourceBC, + const embedding::PrintData& aPrintData, + CloneDocumentTreeIntoSelfResolver&& aResolve) { + nsresult rv = NS_OK; + +#ifdef NS_PRINTING + rv = CloneDocumentTreeIntoSelf(aSourceBC, aPrintData); +#endif + + aResolve(NS_SUCCEEDED(rv)); + return IPC_OK(); +} + +nsresult BrowserChild::UpdateRemotePrintSettings( + const embedding::PrintData& aPrintData) { +#ifdef NS_PRINTING + nsCOMPtr<nsIDocShell> ourDocShell = do_GetInterface(WebNavigation()); + if (NS_WARN_IF(!ourDocShell)) { + return NS_ERROR_FAILURE; + } + + RefPtr<Document> doc = ourDocShell->GetExtantDocument(); + if (NS_WARN_IF(!doc) || NS_WARN_IF(!doc->IsStaticDocument())) { + return NS_ERROR_FAILURE; + } + + RefPtr<BrowsingContext> bc = ourDocShell->GetBrowsingContext(); + if (NS_WARN_IF(!bc)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIPrintSettingsService> printSettingsSvc = + do_GetService("@mozilla.org/gfx/printsettings-service;1"); + if (NS_WARN_IF(!printSettingsSvc)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIPrintSettings> printSettings; + nsresult rv = + printSettingsSvc->CreateNewPrintSettings(getter_AddRefs(printSettings)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + printSettingsSvc->DeserializeToPrintSettings(aPrintData, printSettings); + + bc->PreOrderWalk([&](BrowsingContext* aBc) { + if (nsCOMPtr<nsIDocShell> inProcess = aBc->GetDocShell()) { + nsCOMPtr<nsIDocumentViewer> viewer; + inProcess->GetDocViewer(getter_AddRefs(viewer)); + if (NS_WARN_IF(!viewer)) { + return BrowsingContext::WalkFlag::Skip; + } + // The CanRunScript analysis is not smart enough to see across + // the std::function PreOrderWalk uses, so we cheat a bit here, but it is + // fine because PreOrderWalk does deal with arbitrary script changing the + // BC tree, and our code above is simple enough and keeps strong refs to + // everything. + ([&]() MOZ_CAN_RUN_SCRIPT_BOUNDARY { + RefPtr<RemotePrintJobChild> printJob = + static_cast<RemotePrintJobChild*>( + aPrintData.remotePrintJob().AsChild()); + viewer->SetPrintSettingsForSubdocument(printSettings, printJob); + }()); + } else if (RefPtr<BrowserBridgeChild> remoteChild = + BrowserBridgeChild::GetFrom(aBc->GetEmbedderElement())) { + Unused << remoteChild->SendUpdateRemotePrintSettings(aPrintData); + return BrowsingContext::WalkFlag::Skip; + } + return BrowsingContext::WalkFlag::Next; + }); +#endif + + return NS_OK; +} + +mozilla::ipc::IPCResult BrowserChild::RecvUpdateRemotePrintSettings( + const embedding::PrintData& aPrintData) { +#ifdef NS_PRINTING + UpdateRemotePrintSettings(aPrintData); +#endif + + return IPC_OK(); +} + +void BrowserChild::DoFakeShow(const ParentShowInfo& aParentShowInfo) { + OwnerShowInfo ownerInfo{ScreenIntSize(), ScrollbarPreference::Auto, + nsSizeMode_Normal}; + RecvShow(aParentShowInfo, ownerInfo); + mDidFakeShow = true; +} + +void BrowserChild::ApplyParentShowInfo(const ParentShowInfo& aInfo) { + // Even if we already set real show info, the dpi / rounding & scale may still + // be invalid (if BrowserParent wasn't able to get widget it would just send + // 0). So better to always set up-to-date values here. + if (aInfo.dpi() > 0) { + mPuppetWidget->UpdateBackingScaleCache(aInfo.dpi(), aInfo.widgetRounding(), + aInfo.defaultScale()); + } + + if (mDidSetRealShowInfo) { + return; + } + + if (!aInfo.fakeShowInfo()) { + // Once we've got one ShowInfo from parent, no need to update the values + // anymore. + mDidSetRealShowInfo = true; + } + + mIsTransparent = aInfo.isTransparent(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvShow( + const ParentShowInfo& aParentInfo, const OwnerShowInfo& aOwnerInfo) { + bool res = true; + + mPuppetWidget->SetSizeMode(aOwnerInfo.sizeMode()); + if (!mDidFakeShow) { + nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(WebNavigation()); + if (!baseWindow) { + NS_ERROR("WebNavigation() doesn't QI to nsIBaseWindow"); + return IPC_FAIL_NO_REASON(this); + } + + baseWindow->SetVisibility(true); + res = InitBrowserChildMessageManager(); + } + + ApplyParentShowInfo(aParentInfo); + + if (!mIsTopLevel) { + RecvScrollbarPreferenceChanged(aOwnerInfo.scrollbarPreference()); + } + + if (!res) { + return IPC_FAIL_NO_REASON(this); + } + + UpdateVisibility(); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvInitRendering( + const TextureFactoryIdentifier& aTextureFactoryIdentifier, + const layers::LayersId& aLayersId, + const CompositorOptions& aCompositorOptions, const bool& aLayersConnected) { + mLayersConnected = Some(aLayersConnected); + mLayersConnectRequested = Some(aLayersConnected); + InitRenderingState(aTextureFactoryIdentifier, aLayersId, aCompositorOptions); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvScrollbarPreferenceChanged( + ScrollbarPreference aPreference) { + MOZ_ASSERT(!mIsTopLevel, + "Scrollbar visibility should be derived from chrome flags for " + "top-level windows"); + if (nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation())) { + nsDocShell::Cast(docShell)->SetScrollbarPreference(aPreference); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvCompositorOptionsChanged( + const CompositorOptions& aNewOptions) { + MOZ_ASSERT(mCompositorOptions); + + // The only compositor option we currently support changing is APZ + // enablement. Even that is only partially supported for now: + // * Going from APZ to non-APZ is fine - we just flip the stored flag. + // Note that we keep the actors (mApzcTreeManager, and the APZChild + // created in InitAPZState()) around (read on for why). + // * Going from non-APZ to APZ is only supported if we were using + // APZ initially (at InitRendering() time) and we are transitioning + // back. In this case, we just reuse the actors which we kept around. + // Fully supporting a non-APZ to APZ transition (i.e. even in cases + // where we initialized as non-APZ) would require setting up the actors + // here. (In that case, we would also have the options of destroying + // the actors in the APZ --> non-APZ case, and always re-creating them + // during a non-APZ --> APZ transition). + mCompositorOptions->SetUseAPZ(aNewOptions.UseAPZ()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvUpdateDimensions( + const DimensionInfo& aDimensionInfo) { + if (mLayersConnected.isNothing()) { + return IPC_OK(); + } + + mUnscaledOuterRect = aDimensionInfo.rect(); + mClientOffset = aDimensionInfo.clientOffset(); + mChromeOffset = aDimensionInfo.chromeOffset(); + MOZ_ASSERT_IF(!IsTopLevel(), mChromeOffset == LayoutDeviceIntPoint()); + + SetUnscaledInnerSize(aDimensionInfo.size()); + if (!mHasValidInnerSize && aDimensionInfo.size().width != 0 && + aDimensionInfo.size().height != 0) { + mHasValidInnerSize = true; + } + + ScreenIntSize screenSize = GetInnerSize(); + ScreenIntRect screenRect = GetOuterRect(); + + // Make sure to set the size on the document viewer first. The + // MobileViewportManager needs the content viewer size to be updated before + // the reflow, otherwise it gets a stale size when it computes a new CSS + // viewport. + nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(WebNavigation()); + baseWin->SetPositionAndSize(0, 0, screenSize.width, screenSize.height, + nsIBaseWindow::eRepaint); + + mPuppetWidget->Resize(screenRect.x + mClientOffset.x + mChromeOffset.x, + screenRect.y + mClientOffset.y + mChromeOffset.y, + screenSize.width, screenSize.height, true); + + RecvSafeAreaInsetsChanged(mPuppetWidget->GetSafeAreaInsets()); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvSizeModeChanged( + const nsSizeMode& aSizeMode) { + mPuppetWidget->SetSizeMode(aSizeMode); + if (!mPuppetWidget->IsVisible()) { + return IPC_OK(); + } + nsCOMPtr<Document> document(GetTopLevelDocument()); + if (!document) { + return IPC_OK(); + } + nsPresContext* presContext = document->GetPresContext(); + if (presContext) { + presContext->SizeModeChanged(aSizeMode); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvChildToParentMatrix( + const Maybe<gfx::Matrix4x4>& aMatrix, + const ScreenRect& aTopLevelViewportVisibleRectInBrowserCoords) { + mChildToParentConversionMatrix = + LayoutDeviceToLayoutDeviceMatrix4x4::FromUnknownMatrix(aMatrix); + mTopLevelViewportVisibleRectInBrowserCoords = + aTopLevelViewportVisibleRectInBrowserCoords; + + if (mContentTransformPromise) { + mContentTransformPromise->MaybeResolveWithUndefined(); + mContentTransformPromise = nullptr; + } + + // Trigger an intersection observation update since ancestor viewports + // changed. + if (RefPtr<Document> toplevelDoc = GetTopLevelDocument()) { + if (nsPresContext* pc = toplevelDoc->GetPresContext()) { + pc->RefreshDriver()->EnsureIntersectionObservationsUpdateHappens(); + } + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvUpdateRemoteStyle( + const StyleImageRendering& aImageRendering) { + BrowsingContext* context = GetBrowsingContext(); + if (!context) { + return IPC_OK(); + } + + Document* document = context->GetDocument(); + if (!document) { + return IPC_OK(); + } + + if (document->IsImageDocument()) { + document->AsImageDocument()->UpdateRemoteStyle(aImageRendering); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvDynamicToolbarMaxHeightChanged( + const ScreenIntCoord& aHeight) { +#if defined(MOZ_WIDGET_ANDROID) + mDynamicToolbarMaxHeight = aHeight; + + RefPtr<Document> document = GetTopLevelDocument(); + if (!document) { + return IPC_OK(); + } + + if (RefPtr<nsPresContext> presContext = document->GetPresContext()) { + presContext->SetDynamicToolbarMaxHeight(aHeight); + } +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvDynamicToolbarOffsetChanged( + const ScreenIntCoord& aOffset) { +#if defined(MOZ_WIDGET_ANDROID) + RefPtr<Document> document = GetTopLevelDocument(); + if (!document) { + return IPC_OK(); + } + + if (nsPresContext* presContext = document->GetPresContext()) { + presContext->UpdateDynamicToolbarOffset(aOffset); + } +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvSuppressDisplayport( + const bool& aEnabled) { + if (RefPtr<PresShell> presShell = GetTopLevelPresShell()) { + presShell->SuppressDisplayport(aEnabled); + } + return IPC_OK(); +} + +void BrowserChild::HandleDoubleTap(const CSSPoint& aPoint, + const Modifiers& aModifiers, + const ScrollableLayerGuid& aGuid, + const DoubleTapToZoomMetrics& aMetrics) { + MOZ_LOG( + sApzChildLog, LogLevel::Debug, + ("Handling double tap at %s with %p %p\n", ToString(aPoint).c_str(), + mBrowserChildMessageManager ? mBrowserChildMessageManager->GetWrapper() + : nullptr, + mBrowserChildMessageManager.get())); + + if (!mBrowserChildMessageManager) { + return; + } + + // Note: there is nothing to do with the modifiers here, as we are not + // synthesizing any sort of mouse event. + RefPtr<Document> document = GetTopLevelDocument(); + ZoomTarget zoomTarget = CalculateRectToZoomTo(document, aPoint, aMetrics); + // The double-tap can be dispatched by any scroll frame (so |aGuid| could be + // the guid of any scroll frame), but the zoom-to-rect operation must be + // performed by the root content scroll frame, so query its identifiers + // for the SendZoomToRect() call rather than using the ones from |aGuid|. + uint32_t presShellId; + ViewID viewId; + if (APZCCallbackHelper::GetOrCreateScrollIdentifiers( + document->GetDocumentElement(), &presShellId, &viewId) && + mApzcTreeManager) { + ScrollableLayerGuid guid(mLayersId, presShellId, viewId); + + mApzcTreeManager->ZoomToRect(guid, zoomTarget, + ZoomToRectBehavior::DEFAULT_BEHAVIOR); + } +} + +mozilla::ipc::IPCResult BrowserChild::RecvHandleTap( + const GeckoContentController::TapType& aType, + const LayoutDevicePoint& aPoint, const Modifiers& aModifiers, + const ScrollableLayerGuid& aGuid, const uint64_t& aInputBlockId, + const Maybe<DoubleTapToZoomMetrics>& aDoubleTapToZoomMetrics) { + // IPDL doesn't hold a strong reference to protocols as they're not required + // to be refcounted. This function can run script, which may trigger a nested + // event loop, which may release this, so we hold a strong reference here. + RefPtr<BrowserChild> kungFuDeathGrip(this); + RefPtr<PresShell> presShell = GetTopLevelPresShell(); + if (!presShell || !presShell->GetPresContext() || !mAPZEventState) { + return IPC_OK(); + } + CSSToLayoutDeviceScale scale( + presShell->GetPresContext()->CSSToDevPixelScale()); + CSSPoint point = aPoint / scale; + + // Stash the guid in InputAPZContext so that when the visual-to-layout + // transform is applied to the event's coordinates, we use the right transform + // based on the scroll frame being targeted. + // The other values don't really matter. + InputAPZContext context(aGuid, aInputBlockId, nsEventStatus_eSentinel); + + switch (aType) { + case GeckoContentController::TapType::eSingleTap: + if (mBrowserChildMessageManager) { + RefPtr<APZEventState> eventState(mAPZEventState); + eventState->ProcessSingleTap(point, scale, aModifiers, 1, + aInputBlockId); + } + break; + case GeckoContentController::TapType::eDoubleTap: + HandleDoubleTap(point, aModifiers, aGuid, *aDoubleTapToZoomMetrics); + break; + case GeckoContentController::TapType::eSecondTap: + if (mBrowserChildMessageManager) { + RefPtr<APZEventState> eventState(mAPZEventState); + eventState->ProcessSingleTap(point, scale, aModifiers, 2, + aInputBlockId); + } + break; + case GeckoContentController::TapType::eLongTap: + if (mBrowserChildMessageManager) { + RefPtr<APZEventState> eventState(mAPZEventState); + eventState->ProcessLongTap(presShell, point, scale, aModifiers, + aInputBlockId); + } + break; + case GeckoContentController::TapType::eLongTapUp: + if (mBrowserChildMessageManager) { + RefPtr<APZEventState> eventState(mAPZEventState); + eventState->ProcessLongTapUp(presShell, point, scale, aModifiers); + } + break; + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvNormalPriorityHandleTap( + const GeckoContentController::TapType& aType, + const LayoutDevicePoint& aPoint, const Modifiers& aModifiers, + const ScrollableLayerGuid& aGuid, const uint64_t& aInputBlockId, + const Maybe<DoubleTapToZoomMetrics>& aDoubleTapToZoomMetrics) { + // IPDL doesn't hold a strong reference to protocols as they're not required + // to be refcounted. This function can run script, which may trigger a nested + // event loop, which may release this, so we hold a strong reference here. + RefPtr<BrowserChild> kungFuDeathGrip(this); + return RecvHandleTap(aType, aPoint, aModifiers, aGuid, aInputBlockId, + aDoubleTapToZoomMetrics); +} + +void BrowserChild::NotifyAPZStateChange( + const ViewID& aViewId, + const layers::GeckoContentController::APZStateChange& aChange, + const int& aArg, Maybe<uint64_t> aInputBlockId) { + if (mAPZEventState) { + mAPZEventState->ProcessAPZStateChange(aViewId, aChange, aArg, + aInputBlockId); + } + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (aChange == + layers::GeckoContentController::APZStateChange::eTransformEnd) { + // This is used by tests to determine when the APZ is done doing whatever + // it's doing. XXX generify this as needed when writing additional tests. + observerService->NotifyObservers(nullptr, "APZ:TransformEnd", nullptr); + observerService->NotifyObservers(nullptr, "PanZoom:StateChange", + u"NOTHING"); + } else if (aChange == + layers::GeckoContentController::APZStateChange::eTransformBegin) { + observerService->NotifyObservers(nullptr, "PanZoom:StateChange", + u"PANNING"); + } +} + +void BrowserChild::StartScrollbarDrag( + const layers::AsyncDragMetrics& aDragMetrics) { + ScrollableLayerGuid guid(mLayersId, aDragMetrics.mPresShellId, + aDragMetrics.mViewId); + + if (mApzcTreeManager) { + mApzcTreeManager->StartScrollbarDrag(guid, aDragMetrics); + } +} + +void BrowserChild::ZoomToRect(const uint32_t& aPresShellId, + const ScrollableLayerGuid::ViewID& aViewId, + const CSSRect& aRect, const uint32_t& aFlags) { + ScrollableLayerGuid guid(mLayersId, aPresShellId, aViewId); + + if (mApzcTreeManager) { + mApzcTreeManager->ZoomToRect(guid, ZoomTarget{aRect}, aFlags); + } +} + +mozilla::ipc::IPCResult BrowserChild::RecvActivate(uint64_t aActionId) { + MOZ_ASSERT(mWebBrowser); + mWebBrowser->FocusActivate(aActionId); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvDeactivate(uint64_t aActionId) { + MOZ_ASSERT(mWebBrowser); + mWebBrowser->FocusDeactivate(aActionId); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvStopIMEStateManagement() { + IMEStateManager::StopIMEStateManagement(); + return IPC_OK(); +} + +void BrowserChild::ProcessPendingCoalescedTouchData() { + MOZ_ASSERT(StaticPrefs::dom_events_coalesce_touchmove()); + + if (mCoalescedTouchData.IsEmpty()) { + return; + } + + if (mCoalescedTouchMoveEventFlusher) { + mCoalescedTouchMoveEventFlusher->RemoveObserver(); + } + + UniquePtr<WidgetTouchEvent> touchMoveEvent = + mCoalescedTouchData.TakeCoalescedEvent(); + Unused << RecvRealTouchEvent(*touchMoveEvent, + mCoalescedTouchData.GetScrollableLayerGuid(), + mCoalescedTouchData.GetInputBlockId(), + mCoalescedTouchData.GetApzResponse()); +} + +void BrowserChild::ProcessPendingCoalescedMouseDataAndDispatchEvents() { + if (!mCoalesceMouseMoveEvents || !mCoalescedMouseEventFlusher) { + // We don't enable mouse coalescing or we are destroying BrowserChild. + return; + } + + // We may reentry the event loop and push more data to + // mToBeDispatchedMouseData while dispatching an event. + + // We may have some pending coalesced data while dispatch an event and reentry + // the event loop. In that case we don't have chance to consume the remainding + // pending data until we get new mouse events. Get some helps from + // mCoalescedMouseEventFlusher to trigger it. + mCoalescedMouseEventFlusher->StartObserver(); + + while (mToBeDispatchedMouseData.GetSize() > 0) { + UniquePtr<CoalescedMouseData> data( + static_cast<CoalescedMouseData*>(mToBeDispatchedMouseData.PopFront())); + + UniquePtr<WidgetMouseEvent> event = data->TakeCoalescedEvent(); + if (event) { + // Dispatch the pending events. Using HandleRealMouseButtonEvent + // to bypass the coalesce handling in RecvRealMouseMoveEvent. Can't use + // RecvRealMouseButtonEvent because we may also put some mouse events + // other than mousemove. + HandleRealMouseButtonEvent(*event, data->GetScrollableLayerGuid(), + data->GetInputBlockId()); + } + } + // mCoalescedMouseEventFlusher may be destroyed when reentrying the event + // loop. + if (mCoalescedMouseEventFlusher) { + mCoalescedMouseEventFlusher->RemoveObserver(); + } +} + +LayoutDeviceToLayoutDeviceMatrix4x4 +BrowserChild::GetChildToParentConversionMatrix() const { + if (mChildToParentConversionMatrix) { + return *mChildToParentConversionMatrix; + } + LayoutDevicePoint offset(GetChromeOffset()); + return LayoutDeviceToLayoutDeviceMatrix4x4::Translation(offset); +} + +Maybe<ScreenRect> BrowserChild::GetTopLevelViewportVisibleRectInBrowserCoords() + const { + if (!mChildToParentConversionMatrix) { + return Nothing(); + } + return Some(mTopLevelViewportVisibleRectInBrowserCoords); +} + +void BrowserChild::FlushAllCoalescedMouseData() { + MOZ_ASSERT(mCoalesceMouseMoveEvents); + + // Move all entries from mCoalescedMouseData to mToBeDispatchedMouseData. + for (const auto& data : mCoalescedMouseData.Values()) { + if (!data || data->IsEmpty()) { + continue; + } + UniquePtr<CoalescedMouseData> dispatchData = + MakeUnique<CoalescedMouseData>(); + + dispatchData->RetrieveDataFrom(*data); + mToBeDispatchedMouseData.Push(dispatchData.release()); + } + mCoalescedMouseData.Clear(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvRealMouseMoveEvent( + const WidgetMouseEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId) { + if (mCoalesceMouseMoveEvents && mCoalescedMouseEventFlusher) { + CoalescedMouseData* data = + mCoalescedMouseData.GetOrInsertNew(aEvent.pointerId); + MOZ_ASSERT(data); + if (data->CanCoalesce(aEvent, aGuid, aInputBlockId)) { + data->Coalesce(aEvent, aGuid, aInputBlockId); + mCoalescedMouseEventFlusher->StartObserver(); + return IPC_OK(); + } + // Can't coalesce current mousemove event. Put the coalesced mousemove data + // with the same pointer id to mToBeDispatchedMouseData, coalesce the + // current one, and process all pending data in mToBeDispatchedMouseData. + UniquePtr<CoalescedMouseData> dispatchData = + MakeUnique<CoalescedMouseData>(); + + dispatchData->RetrieveDataFrom(*data); + mToBeDispatchedMouseData.Push(dispatchData.release()); + + // Put new data to replace the old one in the hash table. + CoalescedMouseData* newData = + mCoalescedMouseData + .InsertOrUpdate(aEvent.pointerId, MakeUnique<CoalescedMouseData>()) + .get(); + newData->Coalesce(aEvent, aGuid, aInputBlockId); + + // Dispatch all pending mouse events. + ProcessPendingCoalescedMouseDataAndDispatchEvents(); + mCoalescedMouseEventFlusher->StartObserver(); + } else if (!RecvRealMouseButtonEvent(aEvent, aGuid, aInputBlockId)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvRealMouseMoveEventForTests( + const WidgetMouseEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId) { + return RecvRealMouseMoveEvent(aEvent, aGuid, aInputBlockId); +} + +mozilla::ipc::IPCResult BrowserChild::RecvNormalPriorityRealMouseMoveEvent( + const WidgetMouseEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId) { + return RecvRealMouseMoveEvent(aEvent, aGuid, aInputBlockId); +} + +mozilla::ipc::IPCResult +BrowserChild::RecvNormalPriorityRealMouseMoveEventForTests( + const WidgetMouseEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId) { + return RecvRealMouseMoveEvent(aEvent, aGuid, aInputBlockId); +} + +mozilla::ipc::IPCResult BrowserChild::RecvSynthMouseMoveEvent( + const WidgetMouseEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId) { + if (!RecvRealMouseButtonEvent(aEvent, aGuid, aInputBlockId)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvNormalPrioritySynthMouseMoveEvent( + const WidgetMouseEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId) { + return RecvSynthMouseMoveEvent(aEvent, aGuid, aInputBlockId); +} + +mozilla::ipc::IPCResult BrowserChild::RecvRealMouseButtonEvent( + const WidgetMouseEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId) { + if (mCoalesceMouseMoveEvents && mCoalescedMouseEventFlusher && + aEvent.mMessage != eMouseMove) { + // When receiving a mouse event other than mousemove, we have to dispatch + // all coalesced events before it. However, we can't dispatch all pending + // coalesced events directly because we may reentry the event loop while + // dispatching. To make sure we won't dispatch disorder events, we move all + // coalesced mousemove events and current event to a deque to dispatch them. + // When reentrying the event loop and dispatching more events, we put new + // events in the end of the nsQueue and dispatch events from the beginning. + FlushAllCoalescedMouseData(); + + UniquePtr<CoalescedMouseData> dispatchData = + MakeUnique<CoalescedMouseData>(); + + dispatchData->Coalesce(aEvent, aGuid, aInputBlockId); + mToBeDispatchedMouseData.Push(dispatchData.release()); + + ProcessPendingCoalescedMouseDataAndDispatchEvents(); + return IPC_OK(); + } + HandleRealMouseButtonEvent(aEvent, aGuid, aInputBlockId); + return IPC_OK(); +} + +void BrowserChild::HandleRealMouseButtonEvent(const WidgetMouseEvent& aEvent, + const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId) { + WidgetMouseEvent localEvent(aEvent); + localEvent.mWidget = mPuppetWidget; + + // We need one InputAPZContext here to propagate |aGuid| to places in + // SendSetTargetAPZCNotification() which apply the visual-to-layout transform, + // and another below to propagate the |postLayerization| flag (whose value + // we don't know until SendSetTargetAPZCNotification() returns) into + // the event dispatch code. + InputAPZContext context1(aGuid, aInputBlockId, nsEventStatus_eSentinel); + + // Mouse events like eMouseEnterIntoWidget, that are created in the parent + // process EventStateManager code, have an input block id which they get from + // the InputAPZContext in the parent process stack. However, they did not + // actually go through the APZ code and so their mHandledByAPZ flag is false. + // Since thos events didn't go through APZ, we don't need to send + // notifications for them. + RefPtr<DisplayportSetListener> postLayerization; + if (aInputBlockId && localEvent.mFlags.mHandledByAPZ) { + nsCOMPtr<Document> document(GetTopLevelDocument()); + postLayerization = APZCCallbackHelper::SendSetTargetAPZCNotification( + mPuppetWidget, document, localEvent, aGuid.mLayersId, aInputBlockId); + } + + InputAPZContext context2(aGuid, aInputBlockId, nsEventStatus_eSentinel, + postLayerization != nullptr); + + DispatchWidgetEventViaAPZ(localEvent); + + if (aInputBlockId && localEvent.mFlags.mHandledByAPZ && mAPZEventState) { + mAPZEventState->ProcessMouseEvent(localEvent, aInputBlockId); + } + + // Do this after the DispatchWidgetEventViaAPZ call above, so that if the + // mouse event triggered a post-refresh AsyncDragMetrics message to be sent + // to APZ (from scrollbar dragging in nsSliderFrame), then that will reach + // APZ before the SetTargetAPZC message. This ensures the drag input block + // gets the drag metrics before handling the input events. + if (postLayerization) { + postLayerization->Register(); + } +} + +mozilla::ipc::IPCResult BrowserChild::RecvNormalPriorityRealMouseButtonEvent( + const WidgetMouseEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId) { + return RecvRealMouseButtonEvent(aEvent, aGuid, aInputBlockId); +} + +mozilla::ipc::IPCResult BrowserChild::RecvRealMouseEnterExitWidgetEvent( + const WidgetMouseEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId) { + return RecvRealMouseButtonEvent(aEvent, aGuid, aInputBlockId); +} + +mozilla::ipc::IPCResult +BrowserChild::RecvNormalPriorityRealMouseEnterExitWidgetEvent( + const WidgetMouseEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId) { + return RecvRealMouseButtonEvent(aEvent, aGuid, aInputBlockId); +} + +nsEventStatus BrowserChild::DispatchWidgetEventViaAPZ(WidgetGUIEvent& aEvent) { + aEvent.ResetWaitingReplyFromRemoteProcessState(); + return APZCCallbackHelper::DispatchWidgetEvent(aEvent); +} + +void BrowserChild::DispatchCoalescedWheelEvent() { + UniquePtr<WidgetWheelEvent> wheelEvent = + mCoalescedWheelData.TakeCoalescedEvent(); + MOZ_ASSERT(wheelEvent); + DispatchWheelEvent(*wheelEvent, mCoalescedWheelData.GetScrollableLayerGuid(), + mCoalescedWheelData.GetInputBlockId()); +} + +void BrowserChild::DispatchWheelEvent(const WidgetWheelEvent& aEvent, + const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId) { + WidgetWheelEvent localEvent(aEvent); + if (aInputBlockId && aEvent.mFlags.mHandledByAPZ) { + nsCOMPtr<Document> document(GetTopLevelDocument()); + RefPtr<DisplayportSetListener> postLayerization = + APZCCallbackHelper::SendSetTargetAPZCNotification( + mPuppetWidget, document, aEvent, aGuid.mLayersId, aInputBlockId); + if (postLayerization) { + postLayerization->Register(); + } + } + + localEvent.mWidget = mPuppetWidget; + + // Stash the guid in InputAPZContext so that when the visual-to-layout + // transform is applied to the event's coordinates, we use the right transform + // based on the scroll frame being targeted. + // The other values don't really matter. + InputAPZContext context(aGuid, aInputBlockId, nsEventStatus_eSentinel); + + DispatchWidgetEventViaAPZ(localEvent); + + if (localEvent.mCanTriggerSwipe) { + SendRespondStartSwipeEvent(aInputBlockId, localEvent.TriggersSwipe()); + } + + if (aInputBlockId && aEvent.mFlags.mHandledByAPZ && mAPZEventState) { + mAPZEventState->ProcessWheelEvent(localEvent, aInputBlockId); + } +} + +mozilla::ipc::IPCResult BrowserChild::RecvMouseWheelEvent( + const WidgetWheelEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId) { + bool isNextWheelEvent = false; + // We only coalesce the current event when + // 1. It's eWheel (we don't coalesce eOperationStart and eWheelOperationEnd) + // 2. It has same attributes as the coalesced wheel event which is not yet + // fired. + if (aEvent.mMessage == eWheel) { + GetIPCChannel()->PeekMessages( + [&isNextWheelEvent](const IPC::Message& aMsg) -> bool { + if (aMsg.type() == mozilla::dom::PBrowser::Msg_MouseWheelEvent__ID) { + isNextWheelEvent = true; + } + return false; // Stop peeking. + }); + + if (!mCoalescedWheelData.IsEmpty() && + !mCoalescedWheelData.CanCoalesce(aEvent, aGuid, aInputBlockId)) { + DispatchCoalescedWheelEvent(); + MOZ_ASSERT(mCoalescedWheelData.IsEmpty()); + } + mCoalescedWheelData.Coalesce(aEvent, aGuid, aInputBlockId); + + MOZ_ASSERT(!mCoalescedWheelData.IsEmpty()); + // If the next event isn't a wheel event, make sure we dispatch. + if (!isNextWheelEvent) { + DispatchCoalescedWheelEvent(); + } + } else { + DispatchWheelEvent(aEvent, aGuid, aInputBlockId); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvNormalPriorityMouseWheelEvent( + const WidgetWheelEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId) { + return RecvMouseWheelEvent(aEvent, aGuid, aInputBlockId); +} + +mozilla::ipc::IPCResult BrowserChild::RecvRealTouchEvent( + const WidgetTouchEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId, const nsEventStatus& aApzResponse) { + MOZ_LOG(sApzChildLog, LogLevel::Debug, + ("Receiving touch event of type %d\n", aEvent.mMessage)); + + if (StaticPrefs::dom_events_coalesce_touchmove()) { + if (aEvent.mMessage == eTouchEnd || aEvent.mMessage == eTouchStart) { + ProcessPendingCoalescedTouchData(); + } + + if (aEvent.mMessage != eTouchMove) { + sConsecutiveTouchMoveCount = 0; + } + } + + WidgetTouchEvent localEvent(aEvent); + localEvent.mWidget = mPuppetWidget; + + // Stash the guid in InputAPZContext so that when the visual-to-layout + // transform is applied to the event's coordinates, we use the right transform + // based on the scroll frame being targeted. + // The other values don't really matter. + InputAPZContext context(aGuid, aInputBlockId, aApzResponse); + + nsTArray<TouchBehaviorFlags> allowedTouchBehaviors; + if (localEvent.mMessage == eTouchStart && AsyncPanZoomEnabled()) { + nsCOMPtr<Document> document = GetTopLevelDocument(); + allowedTouchBehaviors = TouchActionHelper::GetAllowedTouchBehavior( + mPuppetWidget, document, localEvent); + if (!allowedTouchBehaviors.IsEmpty() && mApzcTreeManager) { + mApzcTreeManager->SetAllowedTouchBehavior(aInputBlockId, + allowedTouchBehaviors); + } + RefPtr<DisplayportSetListener> postLayerization = + APZCCallbackHelper::SendSetTargetAPZCNotification( + mPuppetWidget, document, localEvent, aGuid.mLayersId, + aInputBlockId); + if (postLayerization) { + postLayerization->Register(); + } + } + + // Dispatch event to content (potentially a long-running operation) + nsEventStatus status = DispatchWidgetEventViaAPZ(localEvent); + + if (!AsyncPanZoomEnabled()) { + // We shouldn't have any e10s platforms that have touch events enabled + // without APZ. + MOZ_ASSERT(false); + return IPC_OK(); + } + + if (mAPZEventState) { + mAPZEventState->ProcessTouchEvent(localEvent, aGuid, aInputBlockId, + aApzResponse, status, + std::move(allowedTouchBehaviors)); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvNormalPriorityRealTouchEvent( + const WidgetTouchEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId, const nsEventStatus& aApzResponse) { + return RecvRealTouchEvent(aEvent, aGuid, aInputBlockId, aApzResponse); +} + +mozilla::ipc::IPCResult BrowserChild::RecvRealTouchMoveEvent( + const WidgetTouchEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId, const nsEventStatus& aApzResponse) { + if (StaticPrefs::dom_events_coalesce_touchmove()) { + ++sConsecutiveTouchMoveCount; + if (mCoalescedTouchMoveEventFlusher) { + MOZ_ASSERT(aEvent.mMessage == eTouchMove); + if (mCoalescedTouchData.IsEmpty() || + mCoalescedTouchData.CanCoalesce(aEvent, aGuid, aInputBlockId, + aApzResponse)) { + mCoalescedTouchData.Coalesce(aEvent, aGuid, aInputBlockId, + aApzResponse); + } else { + UniquePtr<WidgetTouchEvent> touchMoveEvent = + mCoalescedTouchData.TakeCoalescedEvent(); + + mCoalescedTouchData.Coalesce(aEvent, aGuid, aInputBlockId, + aApzResponse); + + if (!RecvRealTouchEvent(*touchMoveEvent, + mCoalescedTouchData.GetScrollableLayerGuid(), + mCoalescedTouchData.GetInputBlockId(), + mCoalescedTouchData.GetApzResponse())) { + return IPC_FAIL_NO_REASON(this); + } + } + + if (sConsecutiveTouchMoveCount > 1) { + mCoalescedTouchMoveEventFlusher->StartObserver(); + } else { + // Flush the pending coalesced touch in order to avoid the first + // touchmove be overridden by the second one. + ProcessPendingCoalescedTouchData(); + } + return IPC_OK(); + } + } + + if (!RecvRealTouchEvent(aEvent, aGuid, aInputBlockId, aApzResponse)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvNormalPriorityRealTouchMoveEvent( + const WidgetTouchEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId, const nsEventStatus& aApzResponse) { + return RecvRealTouchMoveEvent(aEvent, aGuid, aInputBlockId, aApzResponse); +} + +mozilla::ipc::IPCResult BrowserChild::RecvRealDragEvent( + const WidgetDragEvent& aEvent, const uint32_t& aDragAction, + const uint32_t& aDropEffect, nsIPrincipal* aPrincipal, + nsIContentSecurityPolicy* aCsp) { + WidgetDragEvent localEvent(aEvent); + localEvent.mWidget = mPuppetWidget; + + nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession(); + if (dragSession) { + dragSession->SetDragAction(aDragAction); + dragSession->SetTriggeringPrincipal(aPrincipal); + dragSession->SetCsp(aCsp); + RefPtr<DataTransfer> initialDataTransfer = dragSession->GetDataTransfer(); + if (initialDataTransfer) { + initialDataTransfer->SetDropEffectInt(aDropEffect); + } + } + + if (aEvent.mMessage == eDrop) { + bool canDrop = true; + if (!dragSession || NS_FAILED(dragSession->GetCanDrop(&canDrop)) || + !canDrop) { + localEvent.mMessage = eDragExit; + } + } else if (aEvent.mMessage == eDragOver) { + nsCOMPtr<nsIDragService> dragService = + do_GetService("@mozilla.org/widget/dragservice;1"); + if (dragService) { + // This will dispatch 'drag' event at the source if the + // drag transaction started in this process. + dragService->FireDragEventAtSource(eDrag, aEvent.mModifiers); + } + } + + DispatchWidgetEventViaAPZ(localEvent); + return IPC_OK(); +} + +void BrowserChild::RequestEditCommands(NativeKeyBindingsType aType, + const WidgetKeyboardEvent& aEvent, + nsTArray<CommandInt>& aCommands) { + MOZ_ASSERT(aCommands.IsEmpty()); + + if (NS_WARN_IF(aEvent.IsEditCommandsInitialized(aType))) { + aCommands = aEvent.EditCommandsConstRef(aType).Clone(); + return; + } + + switch (aType) { + case NativeKeyBindingsType::SingleLineEditor: + case NativeKeyBindingsType::MultiLineEditor: + case NativeKeyBindingsType::RichTextEditor: + break; + default: + MOZ_ASSERT_UNREACHABLE("Invalid native key bindings type"); + } + + // Don't send aEvent to the parent process directly because it'll be marked + // as posted to remote process. + WidgetKeyboardEvent localEvent(aEvent); + SendRequestNativeKeyBindings(static_cast<uint32_t>(aType), localEvent, + &aCommands); +} + +mozilla::ipc::IPCResult BrowserChild::RecvNativeSynthesisResponse( + const uint64_t& aObserverId, const nsCString& aResponse) { + mozilla::widget::AutoObserverNotifier::NotifySavedObserver(aObserverId, + aResponse.get()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvUpdateSHistory() { + if (mSessionStoreChild) { + mSessionStoreChild->UpdateSHistoryChanges(); + } + return IPC_OK(); +} + +// In case handling repeated keys takes much time, we skip firing new ones. +bool BrowserChild::SkipRepeatedKeyEvent(const WidgetKeyboardEvent& aEvent) { + if (mRepeatedKeyEventTime.IsNull() || !aEvent.CanSkipInRemoteProcess() || + (aEvent.mMessage != eKeyDown && aEvent.mMessage != eKeyPress)) { + mRepeatedKeyEventTime = TimeStamp(); + mSkipKeyPress = false; + return false; + } + + if ((aEvent.mMessage == eKeyDown && + (mRepeatedKeyEventTime > aEvent.mTimeStamp)) || + (mSkipKeyPress && (aEvent.mMessage == eKeyPress))) { + // If we skip a keydown event, also the following keypress events should be + // skipped. + mSkipKeyPress |= aEvent.mMessage == eKeyDown; + return true; + } + + if (aEvent.mMessage == eKeyDown) { + // If keydown wasn't skipped, nor should the possible following keypress. + mRepeatedKeyEventTime = TimeStamp(); + mSkipKeyPress = false; + } + return false; +} + +void BrowserChild::UpdateRepeatedKeyEventEndTime( + const WidgetKeyboardEvent& aEvent) { + if (aEvent.mIsRepeat && + (aEvent.mMessage == eKeyDown || aEvent.mMessage == eKeyPress)) { + mRepeatedKeyEventTime = TimeStamp::Now(); + } +} + +mozilla::ipc::IPCResult BrowserChild::RecvRealKeyEvent( + const WidgetKeyboardEvent& aEvent, const nsID& aUUID) { + MOZ_ASSERT_IF(aEvent.mMessage == eKeyPress, + aEvent.AreAllEditCommandsInitialized()); + + // If content code called preventDefault() on a keydown event, then we don't + // want to process any following keypress events. + const bool isPrecedingKeyDownEventConsumed = + aEvent.mMessage == eKeyPress && mIgnoreKeyPressEvent; + + WidgetKeyboardEvent localEvent(aEvent); + localEvent.mWidget = mPuppetWidget; + localEvent.mUniqueId = aEvent.mUniqueId; + + if (!SkipRepeatedKeyEvent(aEvent) && !isPrecedingKeyDownEventConsumed) { + nsEventStatus status = DispatchWidgetEventViaAPZ(localEvent); + + // Update the end time of the possible repeated event so that we can skip + // some incoming events in case event handling took long time. + UpdateRepeatedKeyEventEndTime(localEvent); + + if (aEvent.mMessage == eKeyDown) { + mIgnoreKeyPressEvent = status == nsEventStatus_eConsumeNoDefault; + } + + if (localEvent.mFlags.mIsSuppressedOrDelayed) { + localEvent.PreventDefault(); + } + + // If the event's default isn't prevented but the status is no default, + // That means that the event was consumed by EventStateManager or something + // which is not a usual event handler. In such case, prevent its default + // as a default handler. For example, when an eKeyPress event matches + // with a content accesskey, and it's executed, preventDefault() of the + // event won't be called but the status is set to "no default". Then, + // the event shouldn't be handled by nsMenuBarListener in the main process. + if (!localEvent.DefaultPrevented() && + status == nsEventStatus_eConsumeNoDefault) { + localEvent.PreventDefault(); + } + + MOZ_DIAGNOSTIC_ASSERT(!localEvent.PropagationStopped()); + } + // The keyboard event which we ignore should not be handled in the main + // process for shortcut key handling. For notifying if we skipped it, we can + // use "stop propagation" flag here because it must be cleared by + // `EventTargetChainItem` if we've dispatched it. + else { + localEvent.StopPropagation(); + } + + // If we don't need to send a rely for the given keyboard event, we do nothing + // anymore here. + if (!aEvent.WantReplyFromContentProcess()) { + return IPC_OK(); + } + + // This is an ugly hack, mNoRemoteProcessDispatch is set to true when the + // event's PreventDefault() or StopScrollProcessForwarding() is called. + // And then, it'll be checked by ParamTraits<mozilla::WidgetEvent>::Write() + // whether the event is being sent to remote process unexpectedly. + // However, unfortunately, it cannot check the destination. Therefore, + // we need to clear the flag explicitly here because ParamTraits should + // keep checking the flag for avoiding regression. + localEvent.mFlags.mNoRemoteProcessDispatch = false; + SendReplyKeyEvent(localEvent, aUUID); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvNormalPriorityRealKeyEvent( + const WidgetKeyboardEvent& aEvent, const nsID& aUUID) { + return RecvRealKeyEvent(aEvent, aUUID); +} + +mozilla::ipc::IPCResult BrowserChild::RecvCompositionEvent( + const WidgetCompositionEvent& aEvent) { + WidgetCompositionEvent localEvent(aEvent); + localEvent.mWidget = mPuppetWidget; + DispatchWidgetEventViaAPZ(localEvent); + Unused << SendOnEventNeedingAckHandled(aEvent.mMessage, + localEvent.mCompositionId); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvNormalPriorityCompositionEvent( + const WidgetCompositionEvent& aEvent) { + return RecvCompositionEvent(aEvent); +} + +mozilla::ipc::IPCResult BrowserChild::RecvSelectionEvent( + const WidgetSelectionEvent& aEvent) { + WidgetSelectionEvent localEvent(aEvent); + localEvent.mWidget = mPuppetWidget; + DispatchWidgetEventViaAPZ(localEvent); + Unused << SendOnEventNeedingAckHandled(aEvent.mMessage, 0u); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvNormalPrioritySelectionEvent( + const WidgetSelectionEvent& aEvent) { + return RecvSelectionEvent(aEvent); +} + +mozilla::ipc::IPCResult BrowserChild::RecvInsertText( + const nsAString& aStringToInsert) { + // Use normal event path to reach focused document. + WidgetContentCommandEvent localEvent(true, eContentCommandInsertText, + mPuppetWidget); + localEvent.mString = Some(nsString(aStringToInsert)); + DispatchWidgetEventViaAPZ(localEvent); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvNormalPriorityInsertText( + const nsAString& aStringToInsert) { + return RecvInsertText(aStringToInsert); +} + +mozilla::ipc::IPCResult BrowserChild::RecvPasteTransferable( + const IPCTransferable& aTransferable) { + nsresult rv; + nsCOMPtr<nsITransferable> trans = + do_CreateInstance("@mozilla.org/widget/transferable;1", &rv); + NS_ENSURE_SUCCESS(rv, IPC_OK()); + trans->Init(nullptr); + + rv = nsContentUtils::IPCTransferableToTransferable( + aTransferable, true /* aAddDataFlavor */, trans, + false /* aFilterUnknownFlavors */); + NS_ENSURE_SUCCESS(rv, IPC_OK()); + + nsCOMPtr<nsIDocShell> ourDocShell = do_GetInterface(WebNavigation()); + if (NS_WARN_IF(!ourDocShell)) { + return IPC_OK(); + } + + RefPtr<nsCommandParams> params = new nsCommandParams(); + rv = params->SetISupports("transferable", trans); + NS_ENSURE_SUCCESS(rv, IPC_OK()); + + ourDocShell->DoCommandWithParams("cmd_pasteTransferable", params); + return IPC_OK(); +} + +#ifdef ACCESSIBILITY +a11y::PDocAccessibleChild* BrowserChild::AllocPDocAccessibleChild( + PDocAccessibleChild*, const uint64_t&, + const MaybeDiscardedBrowsingContext&) { + MOZ_ASSERT(false, "should never call this!"); + return nullptr; +} + +bool BrowserChild::DeallocPDocAccessibleChild( + a11y::PDocAccessibleChild* aChild) { + delete static_cast<mozilla::a11y::DocAccessibleChild*>(aChild); + return true; +} +#endif + +RefPtr<VsyncMainChild> BrowserChild::GetVsyncChild() { + // Initializing mVsyncChild here turns on per-BrowserChild Vsync for a + // given platform. Note: this only makes sense if nsWindow returns a + // window-specific VsyncSource. +#if defined(MOZ_WAYLAND) + if (IsWaylandEnabled() && !mVsyncChild) { + mVsyncChild = MakeRefPtr<VsyncMainChild>(); + if (!SendPVsyncConstructor(mVsyncChild)) { + mVsyncChild = nullptr; + } + } +#endif + return mVsyncChild; +} + +mozilla::ipc::IPCResult BrowserChild::RecvLoadRemoteScript( + const nsAString& aURL, const bool& aRunInGlobalScope) { + if (!InitBrowserChildMessageManager()) + // This can happen if we're half-destroyed. It's not a fatal + // error. + return IPC_OK(); + + JS::Rooted<JSObject*> mm(RootingCx(), + mBrowserChildMessageManager->GetOrCreateWrapper()); + if (!mm) { + // This can happen if we're half-destroyed. It's not a fatal error. + return IPC_OK(); + } + + LoadScriptInternal(mm, aURL, !aRunInGlobalScope); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvAsyncMessage( + const nsAString& aMessage, const ClonedMessageData& aData) { + AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING("BrowserChild::RecvAsyncMessage", + OTHER, aMessage); + MMPrinter::Print("BrowserChild::RecvAsyncMessage", aMessage, aData); + + if (!mBrowserChildMessageManager) { + return IPC_OK(); + } + + RefPtr<nsFrameMessageManager> mm = + mBrowserChildMessageManager->GetMessageManager(); + + // We should have a message manager if the global is alive, but it + // seems sometimes we don't. Assert in aurora/nightly, but don't + // crash in release builds. + MOZ_DIAGNOSTIC_ASSERT(mm); + if (!mm) { + return IPC_OK(); + } + + JS::Rooted<JSObject*> kungFuDeathGrip( + dom::RootingCx(), mBrowserChildMessageManager->GetWrapper()); + StructuredCloneData data; + UnpackClonedMessageData(aData, data); + mm->ReceiveMessage(static_cast<EventTarget*>(mBrowserChildMessageManager), + nullptr, aMessage, false, &data, nullptr, IgnoreErrors()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvSwappedWithOtherRemoteLoader( + const IPCTabContext& aContext) { + nsCOMPtr<nsIDocShell> ourDocShell = do_GetInterface(WebNavigation()); + if (NS_WARN_IF(!ourDocShell)) { + return IPC_OK(); + } + + nsCOMPtr<nsPIDOMWindowOuter> ourWindow = ourDocShell->GetWindow(); + if (NS_WARN_IF(!ourWindow)) { + return IPC_OK(); + } + + RefPtr<nsDocShell> docShell = static_cast<nsDocShell*>(ourDocShell.get()); + + nsCOMPtr<EventTarget> ourEventTarget = nsGlobalWindowOuter::Cast(ourWindow); + + docShell->SetInFrameSwap(true); + + nsContentUtils::FirePageShowEventForFrameLoaderSwap( + ourDocShell, ourEventTarget, false, true); + nsContentUtils::FirePageHideEventForFrameLoaderSwap(ourDocShell, + ourEventTarget, true); + + // Owner content type may have changed, so store the possibly updated context + // and notify others. + MaybeInvalidTabContext maybeContext(aContext); + if (!maybeContext.IsValid()) { + NS_ERROR(nsPrintfCString("Received an invalid TabContext from " + "the parent process. (%s)", + maybeContext.GetInvalidReason()) + .get()); + MOZ_CRASH("Invalid TabContext received from the parent process."); + } + + if (!UpdateTabContextAfterSwap(maybeContext.GetTabContext())) { + MOZ_CRASH("Update to TabContext after swap was denied."); + } + + // Ignore previous value of mTriedBrowserInit since owner content has changed. + mTriedBrowserInit = true; + + nsContentUtils::FirePageShowEventForFrameLoaderSwap( + ourDocShell, ourEventTarget, true, true); + + docShell->SetInFrameSwap(false); + + // This is needed to get visibility state right in cases when we swapped a + // visible tab (foreground in visible window) with a non-visible tab. + if (RefPtr<Document> doc = docShell->GetDocument()) { + doc->UpdateVisibilityState(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvHandleAccessKey( + const WidgetKeyboardEvent& aEvent, nsTArray<uint32_t>&& aCharCodes) { + nsCOMPtr<Document> document(GetTopLevelDocument()); + RefPtr<nsPresContext> pc = document->GetPresContext(); + if (pc) { + if (!pc->EventStateManager()->HandleAccessKey( + &(const_cast<WidgetKeyboardEvent&>(aEvent)), pc, aCharCodes)) { + // If no accesskey was found, inform the parent so that accesskeys on + // menus can be handled. + WidgetKeyboardEvent localEvent(aEvent); + localEvent.mWidget = mPuppetWidget; + SendAccessKeyNotHandled(localEvent); + } + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvPrintPreview( + const PrintData& aPrintData, const MaybeDiscardedBrowsingContext& aSourceBC, + PrintPreviewResolver&& aCallback) { +#ifdef NS_PRINTING + // If we didn't succeed in passing off ownership of aCallback, then something + // went wrong. + auto sendCallbackError = MakeScopeExit([&] { + if (aCallback) { + // signal error + aCallback(PrintPreviewResultInfo(0, 0, false, false, false, {}, {}, {})); + } + }); + + if (NS_WARN_IF(aSourceBC.IsDiscarded())) { + return IPC_OK(); + } + + RefPtr<nsGlobalWindowOuter> sourceWindow; + if (!aSourceBC.IsNull()) { + sourceWindow = nsGlobalWindowOuter::Cast(aSourceBC.get()->GetDOMWindow()); + if (NS_WARN_IF(!sourceWindow)) { + return IPC_OK(); + } + } else { + nsCOMPtr<nsPIDOMWindowOuter> ourWindow = do_GetInterface(WebNavigation()); + if (NS_WARN_IF(!ourWindow)) { + return IPC_OK(); + } + sourceWindow = nsGlobalWindowOuter::Cast(ourWindow); + } + + RefPtr<nsIPrintSettings> printSettings; + nsCOMPtr<nsIPrintSettingsService> printSettingsSvc = + do_GetService("@mozilla.org/gfx/printsettings-service;1"); + if (NS_WARN_IF(!printSettingsSvc)) { + return IPC_OK(); + } + printSettingsSvc->CreateNewPrintSettings(getter_AddRefs(printSettings)); + if (NS_WARN_IF(!printSettings)) { + return IPC_OK(); + } + printSettingsSvc->DeserializeToPrintSettings(aPrintData, printSettings); + + nsCOMPtr<nsIDocShell> docShellToCloneInto; + if (!aSourceBC.IsNull()) { + docShellToCloneInto = do_GetInterface(WebNavigation()); + if (NS_WARN_IF(!docShellToCloneInto)) { + return IPC_OK(); + } + } + + sourceWindow->Print(printSettings, + /* aRemotePrintJob = */ nullptr, + /* aListener = */ nullptr, docShellToCloneInto, + nsGlobalWindowOuter::IsPreview::Yes, + nsGlobalWindowOuter::IsForWindowDotPrint::No, + std::move(aCallback), IgnoreErrors()); +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvExitPrintPreview() { +#ifdef NS_PRINTING + nsCOMPtr<nsIWebBrowserPrint> webBrowserPrint = + do_GetInterface(ToSupports(WebNavigation())); + if (NS_WARN_IF(!webBrowserPrint)) { + return IPC_OK(); + } + webBrowserPrint->ExitPrintPreview(); +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvPrint( + const MaybeDiscardedBrowsingContext& aBc, const PrintData& aPrintData) { +#ifdef NS_PRINTING + if (NS_WARN_IF(aBc.IsNullOrDiscarded())) { + return IPC_OK(); + } + RefPtr<nsGlobalWindowOuter> outerWindow = + nsGlobalWindowOuter::Cast(aBc.get()->GetDOMWindow()); + if (NS_WARN_IF(!outerWindow)) { + return IPC_OK(); + } + + nsCOMPtr<nsIPrintSettingsService> printSettingsSvc = + do_GetService("@mozilla.org/gfx/printsettings-service;1"); + if (NS_WARN_IF(!printSettingsSvc)) { + return IPC_OK(); + } + + nsCOMPtr<nsIPrintSettings> printSettings; + nsresult rv = + printSettingsSvc->CreateNewPrintSettings(getter_AddRefs(printSettings)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return IPC_OK(); + } + + printSettingsSvc->DeserializeToPrintSettings(aPrintData, printSettings); + { + IgnoredErrorResult rv; + RefPtr printJob = static_cast<RemotePrintJobChild*>( + aPrintData.remotePrintJob().AsChild()); + outerWindow->Print(printSettings, printJob, + /* aListener = */ nullptr, + /* aWindowToCloneInto = */ nullptr, + nsGlobalWindowOuter::IsPreview::No, + nsGlobalWindowOuter::IsForWindowDotPrint::No, + /* aPrintPreviewCallback = */ nullptr, rv); + if (NS_WARN_IF(rv.Failed())) { + return IPC_OK(); + } + } +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvUpdateNativeWindowHandle( + const uintptr_t& aNewHandle) { +#if defined(XP_WIN) && defined(ACCESSIBILITY) + mNativeWindowHandle = aNewHandle; + return IPC_OK(); +#else + return IPC_FAIL_NO_REASON(this); +#endif +} + +mozilla::ipc::IPCResult BrowserChild::RecvDestroy() { + MOZ_ASSERT(!mDestroyed); + mDestroyed = true; + + nsTArray<PContentPermissionRequestChild*> childArray = + nsContentPermissionUtils::GetContentPermissionRequestChildById( + GetTabId()); + + // Need to close undeleted ContentPermissionRequestChilds before tab is + // closed. + for (auto& permissionRequestChild : childArray) { + auto* child = static_cast<RemotePermissionRequest*>(permissionRequestChild); + child->Destroy(); + } + + if (mBrowserChildMessageManager) { + // Message handlers are called from the event loop, so it better be safe to + // run script. + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); + mBrowserChildMessageManager->DispatchTrustedEvent(u"unload"_ns); + } + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + + observerService->RemoveObserver(this, BEFORE_FIRST_PAINT); + + // XXX what other code in ~BrowserChild() should we be running here? + DestroyWindow(); + + // Bounce through the event loop once to allow any delayed teardown runnables + // that were just generated to have a chance to run. + nsCOMPtr<nsIRunnable> deleteRunnable = new DelayedDeleteRunnable(this); + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(deleteRunnable)); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvRenderLayers(const bool& aEnabled) { + auto clearPaintWhileInterruptingJS = MakeScopeExit([&] { + // We might force a paint, or we might already have painted and this is a + // no-op. In either case, once we exit this scope, we need to alert the + // ProcessHangMonitor that we've finished responding to what might have + // been a request to force paint. This is so that the BackgroundHangMonitor + // for force painting can be made to wait again. + if (aEnabled) { + ProcessHangMonitor::ClearPaintWhileInterruptingJS(); + } + }); + + if (aEnabled) { + ProcessHangMonitor::MaybeStartPaintWhileInterruptingJS(); + } + + mRenderLayers = aEnabled; + const bool wasVisible = IsVisible(); + + UpdateVisibility(); + + // If we just became visible, try to trigger a paint as soon as possible. + const bool becameVisible = !wasVisible && IsVisible(); + if (!becameVisible) { + return IPC_OK(); + } + + nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation()); + if (!docShell) { + return IPC_OK(); + } + + // We don't use BrowserChildBase::GetPresShell() here because that would + // create a content viewer if one doesn't exist yet. Creating a content + // viewer can cause JS to run, which we want to avoid. + // nsIDocShell::GetPresShell returns null if no content viewer exists yet. + RefPtr<PresShell> presShell = docShell->GetPresShell(); + if (!presShell) { + return IPC_OK(); + } + + if (nsIFrame* root = presShell->GetRootFrame()) { + root->SchedulePaint(); + } + + Telemetry::AutoTimer<Telemetry::TABCHILD_PAINT_TIME> timer; + // If we need to repaint, let's do that right away. No sense waiting until + // we get back to the event loop again. We suppress the display port so + // that we only paint what's visible. This ensures that the tab we're + // switching to paints as quickly as possible. + presShell->SuppressDisplayport(true); + if (nsContentUtils::IsSafeToRunScript()) { + WebWidget()->PaintNowIfNeeded(); + } else { + RefPtr<nsViewManager> vm = presShell->GetViewManager(); + if (nsView* view = vm->GetRootView()) { + presShell->PaintAndRequestComposite(view, PaintFlags::None); + } + } + presShell->SuppressDisplayport(false); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvNavigateByKey( + const bool& aForward, const bool& aForDocumentNavigation) { + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (!fm) { + return IPC_OK(); + } + + RefPtr<Element> result; + nsCOMPtr<nsPIDOMWindowOuter> window = do_GetInterface(WebNavigation()); + + // Move to the first or last document. + { + uint32_t type = + aForward + ? (aForDocumentNavigation + ? static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_FIRSTDOC) + : static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_ROOT)) + : (aForDocumentNavigation + ? static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_LASTDOC) + : static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_LAST)); + uint32_t flags = nsIFocusManager::FLAG_BYKEY; + if (aForward || aForDocumentNavigation) { + flags |= nsIFocusManager::FLAG_NOSCROLL; + } + fm->MoveFocus(window, nullptr, type, flags, getter_AddRefs(result)); + } + + // No valid root element was found, so move to the first focusable element. + if (!result && aForward && !aForDocumentNavigation) { + fm->MoveFocus(window, nullptr, nsIFocusManager::MOVEFOCUS_FIRST, + nsIFocusManager::FLAG_BYKEY, getter_AddRefs(result)); + } + + SendRequestFocus(false, CallerType::System); + return IPC_OK(); +} + +bool BrowserChild::InitBrowserChildMessageManager() { + mShouldSendWebProgressEventsToParent = true; + + if (!mBrowserChildMessageManager) { + nsCOMPtr<nsPIDOMWindowOuter> window = do_GetInterface(WebNavigation()); + NS_ENSURE_TRUE(window, false); + nsCOMPtr<EventTarget> chromeHandler = window->GetChromeEventHandler(); + NS_ENSURE_TRUE(chromeHandler, false); + + RefPtr<BrowserChildMessageManager> scope = mBrowserChildMessageManager = + new BrowserChildMessageManager(this); + + MOZ_ALWAYS_TRUE(nsMessageManagerScriptExecutor::Init()); + + nsCOMPtr<nsPIWindowRoot> root = do_QueryInterface(chromeHandler); + if (NS_WARN_IF(!root)) { + mBrowserChildMessageManager = nullptr; + return false; + } + root->SetParentTarget(scope); + } + + if (!mTriedBrowserInit) { + mTriedBrowserInit = true; + } + + return true; +} + +void BrowserChild::InitRenderingState( + const TextureFactoryIdentifier& aTextureFactoryIdentifier, + const layers::LayersId& aLayersId, + const CompositorOptions& aCompositorOptions) { + mPuppetWidget->InitIMEState(); + + MOZ_ASSERT(aLayersId.IsValid()); + mTextureFactoryIdentifier = aTextureFactoryIdentifier; + + // Pushing layers transactions directly to a separate + // compositor context. + PCompositorBridgeChild* compositorChild = CompositorBridgeChild::Get(); + if (!compositorChild) { + mLayersConnected = Some(false); + NS_WARNING("failed to get CompositorBridgeChild instance"); + return; + } + + mCompositorOptions = Some(aCompositorOptions); + + if (aLayersId.IsValid()) { + StaticMutexAutoLock lock(sBrowserChildrenMutex); + + if (!sBrowserChildren) { + sBrowserChildren = new BrowserChildMap; + } + MOZ_ASSERT(!sBrowserChildren->Contains(uint64_t(aLayersId))); + sBrowserChildren->InsertOrUpdate(uint64_t(aLayersId), this); + mLayersId = aLayersId; + } + + // Depending on timing, we might paint too early and fall back to basic + // layers. CreateRemoteLayerManager will destroy us if we manage to get a + // remote layer manager though, so that's fine. + MOZ_ASSERT(!mPuppetWidget->HasWindowRenderer() || + mPuppetWidget->GetWindowRenderer()->GetBackendType() == + layers::LayersBackend::LAYERS_NONE); + bool success = false; + if (mLayersConnected == Some(true)) { + success = CreateRemoteLayerManager(compositorChild); + } + + if (success) { + MOZ_ASSERT(mLayersConnected == Some(true)); + // Succeeded to create "remote" layer manager + ImageBridgeChild::IdentifyCompositorTextureHost(mTextureFactoryIdentifier); + gfx::VRManagerChild::IdentifyTextureHost(mTextureFactoryIdentifier); + InitAPZState(); + } else { + NS_WARNING("Fallback to FallbackRenderer"); + mLayersConnected = Some(false); + } + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + + if (observerService) { + observerService->AddObserver(this, BEFORE_FIRST_PAINT, false); + } +} + +bool BrowserChild::CreateRemoteLayerManager( + mozilla::layers::PCompositorBridgeChild* aCompositorChild) { + MOZ_ASSERT(aCompositorChild); + + return mPuppetWidget->CreateRemoteLayerManager( + [&](WebRenderLayerManager* aLayerManager) -> bool { + nsCString error; + return aLayerManager->Initialize(aCompositorChild, + wr::AsPipelineId(mLayersId), + &mTextureFactoryIdentifier, error); + }); +} + +void BrowserChild::InitAPZState() { + if (!mCompositorOptions->UseAPZ()) { + return; + } + auto* cbc = CompositorBridgeChild::Get(); + + // Initialize the ApzcTreeManager. This takes multiple casts because of ugly + // multiple inheritance. + PAPZCTreeManagerChild* baseProtocol = + cbc->SendPAPZCTreeManagerConstructor(mLayersId); + if (!baseProtocol) { + MOZ_ASSERT(false, + "Allocating a TreeManager should not fail with APZ enabled"); + return; + } + APZCTreeManagerChild* derivedProtocol = + static_cast<APZCTreeManagerChild*>(baseProtocol); + + mApzcTreeManager = RefPtr<IAPZCTreeManager>(derivedProtocol); + + // Initialize the GeckoContentController for this tab. We don't hold a + // reference because we don't need it. The ContentProcessController will hold + // a reference to the tab, and will be destroyed by the compositor or ipdl + // during destruction. + RefPtr<GeckoContentController> contentController = + new ContentProcessController(this); + APZChild* apzChild = new APZChild(contentController); + cbc->SendPAPZConstructor(apzChild, mLayersId); +} + +IPCResult BrowserChild::RecvUpdateEffects(const EffectsInfo& aEffects) { + bool needInvalidate = false; + if (mEffectsInfo.IsVisible() && aEffects.IsVisible() && + mEffectsInfo != aEffects) { + // If we are staying visible and either the visrect or scale changed we need + // to invalidate + needInvalidate = true; + } + + mEffectsInfo = aEffects; + UpdateVisibility(); + + if (needInvalidate) { + if (nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation())) { + // We don't use BrowserChildBase::GetPresShell() here because that would + // create a content viewer if one doesn't exist yet. Creating a content + // viewer can cause JS to run, which we want to avoid. + // nsIDocShell::GetPresShell returns null if no content viewer exists yet. + if (RefPtr<PresShell> presShell = docShell->GetPresShell()) { + if (nsIFrame* root = presShell->GetRootFrame()) { + root->InvalidateFrame(); + } + } + } + } + + return IPC_OK(); +} + +bool BrowserChild::IsVisible() { + return mPuppetWidget && mPuppetWidget->IsVisible(); +} + +void BrowserChild::UpdateVisibility() { + const bool shouldBeVisible = [&] { + // If we're known to be visibility: hidden / display: none, just return + // false here, we're pretty sure we don't want to be considered visible + // here. + if (mBrowsingContext && mBrowsingContext->IsUnderHiddenEmbedderElement()) { + return false; + } + // For OOP iframes, include viewport visibility. For top-level <browser> + // elements we don't use this, because the front-end relies on using + // `mRenderLayers` when invisible for tab warming purposes. + // + // An alternative, maybe more consistent approach would be to add an opt-in + // into this behavior for top-level tabs managed by the tab-switcher + // instead... + if (!mIsTopLevel && !mEffectsInfo.IsVisible()) { + return false; + } + // If we're explicitly told not to render layers, we're also invisible. + if (!mRenderLayers) { + return false; + } + return true; + }(); + + const bool isVisible = IsVisible(); + if (shouldBeVisible == isVisible) { + return; + } + if (shouldBeVisible) { + MakeVisible(); + } else { + MakeHidden(); + } +} + +void BrowserChild::MakeVisible() { + if (IsVisible()) { + return; + } + + if (mPuppetWidget) { + mPuppetWidget->Show(true); + } + + PresShellActivenessMaybeChanged(); +} + +void BrowserChild::MakeHidden() { + if (!IsVisible()) { + return; + } + + // Due to the nested event loop in ContentChild::ProvideWindowCommon, + // it's possible to be told to become hidden before we're finished + // setting up a layer manager. We should skip clearing cached layers + // in that case, since doing so might accidentally put is into + // BasicLayers mode. + if (mPuppetWidget) { + if (mPuppetWidget->HasWindowRenderer()) { + ClearCachedResources(); + } + mPuppetWidget->Show(false); + } + + PresShellActivenessMaybeChanged(); +} + +IPCResult BrowserChild::RecvPreserveLayers(bool aPreserve) { + mIsPreservingLayers = aPreserve; + + PresShellActivenessMaybeChanged(); + + return IPC_OK(); +} + +void BrowserChild::PresShellActivenessMaybeChanged() { + // We don't use BrowserChildBase::GetPresShell() here because that would + // create a content viewer if one doesn't exist yet. Creating a content + // viewer can cause JS to run, which we want to avoid. + // nsIDocShell::GetPresShell returns null if no content viewer exists yet. + // + // When this method is called we don't want to go through the browsing context + // because we don't want to change the visibility state of the document, which + // has side effects like firing events to content, unblocking media playback, + // unthrottling timeouts... PresShell activeness has a lot less side effects. + nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation()); + if (!docShell) { + return; + } + RefPtr<PresShell> presShell = docShell->GetPresShell(); + if (!presShell) { + return; + } + presShell->ActivenessMaybeChanged(); +} + +NS_IMETHODIMP +BrowserChild::GetMessageManager(ContentFrameMessageManager** aResult) { + RefPtr<ContentFrameMessageManager> mm(mBrowserChildMessageManager); + mm.forget(aResult); + return *aResult ? NS_OK : NS_ERROR_FAILURE; +} + +void BrowserChild::SendRequestFocus(bool aCanFocus, CallerType aCallerType) { + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (!fm) { + return; + } + + nsCOMPtr<nsPIDOMWindowOuter> window = do_GetInterface(WebNavigation()); + if (!window) { + return; + } + + BrowsingContext* focusedBC = fm->GetFocusedBrowsingContext(); + if (focusedBC == window->GetBrowsingContext()) { + // BrowsingContext has the focus already, do not request again. + return; + } + + PBrowserChild::SendRequestFocus(aCanFocus, aCallerType); +} + +NS_IMETHODIMP +BrowserChild::GetTabId(uint64_t* aId) { + *aId = GetTabId(); + return NS_OK; +} + +NS_IMETHODIMP +BrowserChild::GetChromeOuterWindowID(uint64_t* aId) { + *aId = ChromeOuterWindowID(); + return NS_OK; +} + +bool BrowserChild::DoSendBlockingMessage( + const nsAString& aMessage, StructuredCloneData& aData, + nsTArray<StructuredCloneData>* aRetVal) { + ClonedMessageData data; + if (!BuildClonedMessageData(aData, data)) { + return false; + } + return SendSyncMessage(PromiseFlatString(aMessage), data, aRetVal); +} + +nsresult BrowserChild::DoSendAsyncMessage(const nsAString& aMessage, + StructuredCloneData& aData) { + ClonedMessageData data; + if (!BuildClonedMessageData(aData, data)) { + return NS_ERROR_DOM_DATA_CLONE_ERR; + } + if (!SendAsyncMessage(PromiseFlatString(aMessage), data)) { + return NS_ERROR_UNEXPECTED; + } + return NS_OK; +} + +/* static */ +nsTArray<RefPtr<BrowserChild>> BrowserChild::GetAll() { + StaticMutexAutoLock lock(sBrowserChildrenMutex); + + if (!sBrowserChildren) { + return {}; + } + + return ToTArray<nsTArray<RefPtr<BrowserChild>>>(sBrowserChildren->Values()); +} + +BrowserChild* BrowserChild::GetFrom(PresShell* aPresShell) { + Document* doc = aPresShell->GetDocument(); + if (!doc) { + return nullptr; + } + nsCOMPtr<nsIDocShell> docShell(doc->GetDocShell()); + return GetFrom(docShell); +} + +BrowserChild* BrowserChild::GetFrom(layers::LayersId aLayersId) { + StaticMutexAutoLock lock(sBrowserChildrenMutex); + if (!sBrowserChildren) { + return nullptr; + } + return sBrowserChildren->Get(uint64_t(aLayersId)); +} + +void BrowserChild::DidComposite(mozilla::layers::TransactionId aTransactionId, + const TimeStamp& aCompositeStart, + const TimeStamp& aCompositeEnd) { + MOZ_ASSERT(mPuppetWidget); + RefPtr<WebRenderLayerManager> lm = + mPuppetWidget->GetWindowRenderer()->AsWebRender(); + MOZ_ASSERT(lm); + + if (lm) { + lm->DidComposite(aTransactionId, aCompositeStart, aCompositeEnd); + } +} + +void BrowserChild::ClearCachedResources() { + MOZ_ASSERT(mPuppetWidget); + RefPtr<WebRenderLayerManager> lm = + mPuppetWidget->GetWindowRenderer()->AsWebRender(); + if (lm) { + lm->ClearCachedResources(); + } + + if (nsCOMPtr<Document> document = GetTopLevelDocument()) { + nsPresContext* presContext = document->GetPresContext(); + if (presContext) { + presContext->NotifyPaintStatusReset(); + } + } +} + +void BrowserChild::SchedulePaint() { + nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation()); + if (!docShell) { + return; + } + + // We don't use BrowserChildBase::GetPresShell() here because that would + // create a content viewer if one doesn't exist yet. Creating a content viewer + // can cause JS to run, which we want to avoid. nsIDocShell::GetPresShell + // returns null if no content viewer exists yet. + if (RefPtr<PresShell> presShell = docShell->GetPresShell()) { + if (nsIFrame* root = presShell->GetRootFrame()) { + root->SchedulePaint(); + } + } +} + +void BrowserChild::ReinitRendering() { + MOZ_ASSERT(mLayersId.IsValid()); + + // In some cases, like when we create a windowless browser, + // RemoteLayerTreeOwner/BrowserChild is not connected to a compositor. + if (mLayersConnectRequested.isNothing() || + mLayersConnectRequested == Some(false)) { + return; + } + + // Before we establish a new PLayerTransaction, we must connect our layer tree + // id, CompositorBridge, and the widget compositor all together again. + // Normally this happens in BrowserParent before BrowserChild is given + // rendering information. + // + // In this case, we will send a sync message to our BrowserParent, which in + // turn will send a sync message to the Compositor of the widget owning this + // tab. This guarantees the correct association is in place before our + // PLayerTransaction constructor message arrives on the cross-process + // compositor bridge. + CompositorOptions options; + SendEnsureLayersConnected(&options); + mCompositorOptions = Some(options); + + bool success = false; + RefPtr<CompositorBridgeChild> cb = CompositorBridgeChild::Get(); + + if (cb) { + success = CreateRemoteLayerManager(cb); + } + + if (!success) { + NS_WARNING("failed to recreate layer manager"); + return; + } + + mLayersConnected = Some(true); + ImageBridgeChild::IdentifyCompositorTextureHost(mTextureFactoryIdentifier); + gfx::VRManagerChild::IdentifyTextureHost(mTextureFactoryIdentifier); + + InitAPZState(); + if (nsCOMPtr<Document> doc = GetTopLevelDocument()) { + doc->NotifyLayerManagerRecreated(); + } + + if (mRenderLayers) { + SchedulePaint(); + } +} + +void BrowserChild::ReinitRenderingForDeviceReset() { + RefPtr<WebRenderLayerManager> lm = + mPuppetWidget->GetWindowRenderer()->AsWebRender(); + if (lm) { + lm->DoDestroy(/* aIsSync */ true); + } + + // Proceed with destroying and recreating the layer manager. + ReinitRendering(); +} + +NS_IMETHODIMP +BrowserChild::OnShowTooltip(int32_t aXCoords, int32_t aYCoords, + const nsAString& aTipText, + const nsAString& aTipDir) { + nsString str(aTipText); + nsString dir(aTipDir); + SendShowTooltip(aXCoords, aYCoords, str, dir); + return NS_OK; +} + +NS_IMETHODIMP +BrowserChild::OnHideTooltip() { + SendHideTooltip(); + return NS_OK; +} + +void BrowserChild::NotifyJankedAnimations( + const nsTArray<uint64_t>& aJankedAnimations) { + MOZ_ASSERT(mPuppetWidget); + RefPtr<WebRenderLayerManager> lm = + mPuppetWidget->GetWindowRenderer()->AsWebRender(); + if (lm) { + lm->UpdatePartialPrerenderedAnimations(aJankedAnimations); + } +} + +mozilla::ipc::IPCResult BrowserChild::RecvUIResolutionChanged( + const float& aDpi, const int32_t& aRounding, const double& aScale) { + ScreenIntSize oldScreenSize = GetInnerSize(); + if (aDpi > 0) { + mPuppetWidget->UpdateBackingScaleCache(aDpi, aRounding, aScale); + } + + ScreenIntSize screenSize = GetInnerSize(); + if (mHasValidInnerSize && oldScreenSize != screenSize) { + ScreenIntRect screenRect = GetOuterRect(); + + // See RecvUpdateDimensions for the order of these operations. + nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(WebNavigation()); + baseWin->SetPositionAndSize(0, 0, screenSize.width, screenSize.height, + nsIBaseWindow::eRepaint); + + mPuppetWidget->Resize(screenRect.x + mClientOffset.x + mChromeOffset.x, + screenRect.y + mClientOffset.y + mChromeOffset.y, + screenSize.width, screenSize.height, true); + } + + nsCOMPtr<Document> document(GetTopLevelDocument()); + RefPtr<nsPresContext> presContext = + document ? document->GetPresContext() : nullptr; + if (presContext) { + presContext->UIResolutionChangedSync(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvSafeAreaInsetsChanged( + const mozilla::ScreenIntMargin& aSafeAreaInsets) { + mPuppetWidget->UpdateSafeAreaInsets(aSafeAreaInsets); + + nsCOMPtr<nsIScreenManager> screenMgr = + do_GetService("@mozilla.org/gfx/screenmanager;1"); + ScreenIntMargin currentSafeAreaInsets; + if (screenMgr) { + // aSafeAreaInsets is for current screen. But we have to calculate + // safe insets for content window. + int32_t x, y, cx, cy; + GetDimensions(DimensionKind::Outer, &x, &y, &cx, &cy); + nsCOMPtr<nsIScreen> screen; + screenMgr->ScreenForRect(x, y, cx, cy, getter_AddRefs(screen)); + + if (screen) { + LayoutDeviceIntRect windowRect(x + mClientOffset.x + mChromeOffset.x, + y + mClientOffset.y + mChromeOffset.y, cx, + cy); + currentSafeAreaInsets = nsContentUtils::GetWindowSafeAreaInsets( + screen, aSafeAreaInsets, windowRect); + } + } + + if (nsCOMPtr<Document> document = GetTopLevelDocument()) { + nsPresContext* presContext = document->GetPresContext(); + if (presContext) { + presContext->SetSafeAreaInsets(currentSafeAreaInsets); + } + } + + // https://github.com/w3c/csswg-drafts/issues/4670 + // Actually we don't set this value on sub document. This behaviour is + // same as Blink that safe area insets isn't set on sub document. + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvAllowScriptsToClose() { + nsCOMPtr<nsPIDOMWindowOuter> window = do_GetInterface(WebNavigation()); + if (window) { + nsGlobalWindowOuter::Cast(window)->AllowScriptsToClose(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvReleaseAllPointerCapture() { + PointerEventHandler::ReleaseAllPointerCapture(); + return IPC_OK(); +} + +PPaymentRequestChild* BrowserChild::AllocPPaymentRequestChild() { + MOZ_CRASH( + "We should never be manually allocating PPaymentRequestChild actors"); + return nullptr; +} + +bool BrowserChild::DeallocPPaymentRequestChild(PPaymentRequestChild* actor) { + delete actor; + return true; +} + +ScreenIntSize BrowserChild::GetInnerSize() { + LayoutDeviceIntSize innerSize = + RoundedToInt(mUnscaledInnerSize * mPuppetWidget->GetDefaultScale()); + return ViewAs<ScreenPixel>( + innerSize, PixelCastJustification::LayoutDeviceIsScreenForTabDims); +}; + +Maybe<nsRect> BrowserChild::GetVisibleRect() const { + if (mIsTopLevel) { + // We are conservative about visible rects for top-level browsers to avoid + // artifacts when resizing + return Nothing(); + } + return mEffectsInfo.mVisibleRect; +} + +Maybe<LayoutDeviceRect> +BrowserChild::GetTopLevelViewportVisibleRectInSelfCoords() const { + if (mIsTopLevel) { + return Nothing(); + } + + if (!mChildToParentConversionMatrix) { + // We have no way to tell this remote document visible rect right now. + return Nothing(); + } + + Maybe<LayoutDeviceToLayoutDeviceMatrix4x4> inverse = + mChildToParentConversionMatrix->MaybeInverse(); + if (!inverse) { + return Nothing(); + } + + // Convert the remote document visible rect to the coordinate system of the + // iframe document. + Maybe<LayoutDeviceRect> rect = UntransformBy( + *inverse, + ViewAs<LayoutDevicePixel>( + mTopLevelViewportVisibleRectInBrowserCoords, + PixelCastJustification::ContentProcessIsLayerInUiProcess), + LayoutDeviceRect::MaxIntRect()); + if (!rect) { + return Nothing(); + } + + return rect; +} + +ScreenIntRect BrowserChild::GetOuterRect() { + LayoutDeviceIntRect outerRect = + RoundedToInt(mUnscaledOuterRect * mPuppetWidget->GetDefaultScale()); + return ViewAs<ScreenPixel>( + outerRect, PixelCastJustification::LayoutDeviceIsScreenForTabDims); +} + +void BrowserChild::PaintWhileInterruptingJS() { + if (!IPCOpen() || !mPuppetWidget || !mPuppetWidget->HasWindowRenderer()) { + // Don't bother doing anything now. Better to wait until we receive the + // message on the PContent channel. + return; + } + + MOZ_DIAGNOSTIC_ASSERT(nsContentUtils::IsSafeToRunScript()); + nsAutoScriptBlocker scriptBlocker; + RecvRenderLayers(/* aEnabled = */ true); +} + +void BrowserChild::UnloadLayersWhileInterruptingJS() { + if (!IPCOpen() || !mPuppetWidget || !mPuppetWidget->HasWindowRenderer()) { + // Don't bother doing anything now. Better to wait until we receive the + // message on the PContent channel. + return; + } + + MOZ_DIAGNOSTIC_ASSERT(nsContentUtils::IsSafeToRunScript()); + nsAutoScriptBlocker scriptBlocker; + RecvRenderLayers(/* aEnabled = */ false); +} + +nsresult BrowserChild::CanCancelContentJS( + nsIRemoteTab::NavigationType aNavigationType, int32_t aNavigationIndex, + nsIURI* aNavigationURI, int32_t aEpoch, bool* aCanCancel) { + nsresult rv; + *aCanCancel = false; + + if (aEpoch <= mCancelContentJSEpoch) { + // The next page loaded before we got here, so we shouldn't try to cancel + // the content JS. + return NS_OK; + } + + // If we have session history in the parent we've already performed + // the checks following, so we can return early. + if (mozilla::SessionHistoryInParent()) { + *aCanCancel = true; + return NS_OK; + } + + nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation()); + nsCOMPtr<nsISHistory> history; + if (docShell) { + history = nsDocShell::Cast(docShell)->GetSessionHistory()->LegacySHistory(); + } + + if (!history) { + return NS_ERROR_FAILURE; + } + + int32_t current; + rv = history->GetIndex(¤t); + NS_ENSURE_SUCCESS(rv, rv); + + if (current == -1) { + // This tab has no history! Just return. + return NS_OK; + } + + nsCOMPtr<nsISHEntry> entry; + rv = history->GetEntryAtIndex(current, getter_AddRefs(entry)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIURI> currentURI = entry->GetURI(); + if (!currentURI->SchemeIs("http") && !currentURI->SchemeIs("https") && + !currentURI->SchemeIs("file")) { + // Only cancel content JS for http(s) and file URIs. Other URIs are probably + // internal and we should just let them run to completion. + return NS_OK; + } + + if (aNavigationType == nsIRemoteTab::NAVIGATE_BACK) { + aNavigationIndex = current - 1; + } else if (aNavigationType == nsIRemoteTab::NAVIGATE_FORWARD) { + aNavigationIndex = current + 1; + } else if (aNavigationType == nsIRemoteTab::NAVIGATE_URL) { + if (!aNavigationURI) { + return NS_ERROR_FAILURE; + } + + if (aNavigationURI->SchemeIs("javascript")) { + // "javascript:" URIs don't (necessarily) trigger navigation to a + // different page, so don't allow the current page's JS to terminate. + return NS_OK; + } + + // If navigating directly to a URL (e.g. via hitting Enter in the location + // bar), then we can cancel anytime the next URL is different from the + // current, *excluding* the ref ("#"). + bool equals; + rv = currentURI->EqualsExceptRef(aNavigationURI, &equals); + NS_ENSURE_SUCCESS(rv, rv); + *aCanCancel = !equals; + return NS_OK; + } + // Note: aNavigationType may also be NAVIGATE_INDEX, in which case we don't + // need to do anything special. + + int32_t delta = aNavigationIndex > current ? 1 : -1; + for (int32_t i = current + delta; i != aNavigationIndex + delta; i += delta) { + nsCOMPtr<nsISHEntry> nextEntry; + // If `i` happens to be negative, this call will fail (which is what we + // would want to happen). + rv = history->GetEntryAtIndex(i, getter_AddRefs(nextEntry)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISHEntry> laterEntry = delta == 1 ? nextEntry : entry; + nsCOMPtr<nsIURI> thisURI = entry->GetURI(); + nsCOMPtr<nsIURI> nextURI = nextEntry->GetURI(); + + // If we changed origin and the load wasn't in a subframe, we know it was + // a full document load, so we can cancel the content JS safely. + if (!laterEntry->GetIsSubFrame()) { + nsAutoCString thisHost; + rv = thisURI->GetPrePath(thisHost); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString nextHost; + rv = nextURI->GetPrePath(nextHost); + NS_ENSURE_SUCCESS(rv, rv); + + if (!thisHost.Equals(nextHost)) { + *aCanCancel = true; + return NS_OK; + } + } + + entry = nextEntry; + } + + return NS_OK; +} + +NS_IMETHODIMP BrowserChild::OnStateChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aStateFlags, + nsresult aStatus) { + if (!IPCOpen() || !mShouldSendWebProgressEventsToParent) { + return NS_OK; + } + + // We shouldn't need to notify the parent of redirect state changes, since + // with DocumentChannel that only happens when we switch to the real channel, + // and that's an implementation detail that we can hide. + if (aStateFlags & nsIWebProgressListener::STATE_IS_REDIRECTED_DOCUMENT) { + return NS_OK; + } + + // Our OnStateChange call must have provided the nsIDocShell which the source + // comes from. We'll use this to locate the corresponding BrowsingContext in + // the parent process. + nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aWebProgress); + if (!docShell) { + MOZ_ASSERT_UNREACHABLE("aWebProgress is null or not a nsIDocShell?"); + return NS_ERROR_UNEXPECTED; + } + + WebProgressData webProgressData; + Maybe<WebProgressStateChangeData> stateChangeData; + RequestData requestData; + + MOZ_TRY(PrepareProgressListenerData(aWebProgress, aRequest, webProgressData, + requestData)); + + RefPtr<BrowsingContext> browsingContext = docShell->GetBrowsingContext(); + if (browsingContext->IsTopContent()) { + stateChangeData.emplace(); + + stateChangeData->isNavigating() = docShell->GetIsNavigating(); + stateChangeData->mayEnableCharacterEncodingMenu() = + docShell->GetMayEnableCharacterEncodingMenu(); + + RefPtr<Document> document = browsingContext->GetExtantDocument(); + if (document && aStateFlags & nsIWebProgressListener::STATE_STOP) { + document->GetContentType(stateChangeData->contentType()); + document->GetCharacterSet(stateChangeData->charset()); + stateChangeData->documentURI() = document->GetDocumentURIObject(); + } else { + stateChangeData->contentType().SetIsVoid(true); + stateChangeData->charset().SetIsVoid(true); + } + } + + Unused << SendOnStateChange(webProgressData, requestData, aStateFlags, + aStatus, stateChangeData); + + return NS_OK; +} + +NS_IMETHODIMP BrowserChild::OnProgressChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + int32_t aCurSelfProgress, + int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) { + if (!IPCOpen() || !mShouldSendWebProgressEventsToParent) { + return NS_OK; + } + + // FIXME: We currently ignore ProgressChange events from out-of-process + // subframes both here and in BrowserParent. We may want to change this + // behaviour in the future. + if (!GetBrowsingContext()->IsTopContent()) { + return NS_OK; + } + + // As we're being filtered by nsBrowserStatusFilter, we will be passed either + // nullptr or 0 for all arguments other than aCurTotalProgress and + // aMaxTotalProgress. Don't bother sending them. + MOZ_ASSERT(!aWebProgress); + MOZ_ASSERT(!aRequest); + MOZ_ASSERT(aCurSelfProgress == 0); + MOZ_ASSERT(aMaxSelfProgress == 0); + + Unused << SendOnProgressChange(aCurTotalProgress, aMaxTotalProgress); + + return NS_OK; +} + +NS_IMETHODIMP BrowserChild::OnLocationChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsIURI* aLocation, + uint32_t aFlags) { + if (!IPCOpen() || !mShouldSendWebProgressEventsToParent) { + return NS_OK; + } + + nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aWebProgress); + if (!docShell) { + MOZ_ASSERT_UNREACHABLE("aWebProgress is null or not a nsIDocShell?"); + return NS_ERROR_UNEXPECTED; + } + + RefPtr<BrowsingContext> browsingContext = docShell->GetBrowsingContext(); + RefPtr<Document> document = browsingContext->GetExtantDocument(); + if (!document) { + return NS_OK; + } + + WebProgressData webProgressData; + RequestData requestData; + + MOZ_TRY(PrepareProgressListenerData(aWebProgress, aRequest, webProgressData, + requestData)); + + Maybe<WebProgressLocationChangeData> locationChangeData; + + bool canGoBack = false; + bool canGoForward = false; + if (!mozilla::SessionHistoryInParent()) { + MOZ_TRY(WebNavigation()->GetCanGoBack(&canGoBack)); + MOZ_TRY(WebNavigation()->GetCanGoForward(&canGoForward)); + } + + if (browsingContext->IsTopContent()) { + MOZ_ASSERT( + browsingContext == GetBrowsingContext(), + "Toplevel content BrowsingContext which isn't GetBrowsingContext()?"); + + locationChangeData.emplace(); + + document->GetContentType(locationChangeData->contentType()); + locationChangeData->isNavigating() = docShell->GetIsNavigating(); + locationChangeData->documentURI() = document->GetDocumentURIObject(); + document->GetTitle(locationChangeData->title()); + document->GetCharacterSet(locationChangeData->charset()); + + locationChangeData->mayEnableCharacterEncodingMenu() = + docShell->GetMayEnableCharacterEncodingMenu(); + + locationChangeData->contentPrincipal() = document->NodePrincipal(); + locationChangeData->contentPartitionedPrincipal() = + document->PartitionedPrincipal(); + locationChangeData->csp() = document->GetCsp(); + locationChangeData->referrerInfo() = document->ReferrerInfo(); + locationChangeData->isSyntheticDocument() = document->IsSyntheticDocument(); + + if (nsCOMPtr<nsILoadGroup> loadGroup = document->GetDocumentLoadGroup()) { + uint64_t requestContextID = 0; + MOZ_TRY(loadGroup->GetRequestContextID(&requestContextID)); + locationChangeData->requestContextID() = Some(requestContextID); + } + +#ifdef MOZ_CRASHREPORTER + if (CrashReporter::GetEnabled()) { + nsCOMPtr<nsIURI> annotationURI; + + nsresult rv = + NS_MutateURI(aLocation).SetUserPass(""_ns).Finalize(annotationURI); + + if (NS_FAILED(rv)) { + // Ignore failures on about: URIs. + annotationURI = aLocation; + } + + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::URL, + annotationURI->GetSpecOrDefault()); + } +#endif + } + + Unused << SendOnLocationChange(webProgressData, requestData, aLocation, + aFlags, canGoBack, canGoForward, + locationChangeData); + + return NS_OK; +} + +NS_IMETHODIMP BrowserChild::OnStatusChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsresult aStatus, + const char16_t* aMessage) { + if (!IPCOpen() || !mShouldSendWebProgressEventsToParent) { + return NS_OK; + } + + // FIXME: We currently ignore StatusChange from out-of-process subframes both + // here and in BrowserParent. We may want to change this behaviour in the + // future. + if (!GetBrowsingContext()->IsTopContent()) { + return NS_OK; + } + + // As we're being filtered by nsBrowserStatusFilter, we will be passed either + // nullptr or NS_OK for all arguments other than aMessage. Don't bother + // sending them. + MOZ_ASSERT(!aWebProgress); + MOZ_ASSERT(!aRequest); + MOZ_ASSERT(aStatus == NS_OK); + + Unused << SendOnStatusChange(nsDependentString(aMessage)); + + return NS_OK; +} + +NS_IMETHODIMP BrowserChild::OnSecurityChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aState) { + // Security changes are now handled entirely in the parent process + // so we don't need to worry about forwarding them (and we shouldn't + // be receiving any to forward). + return NS_OK; +} + +NS_IMETHODIMP BrowserChild::OnContentBlockingEvent(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aEvent) { + // The OnContentBlockingEvent only happenes in the parent process. It should + // not be seen in the content process. + MOZ_DIAGNOSTIC_ASSERT( + false, "OnContentBlockingEvent should not be seen in content process."); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP BrowserChild::OnProgressChange64(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + int64_t aCurSelfProgress, + int64_t aMaxSelfProgress, + int64_t aCurTotalProgress, + int64_t aMaxTotalProgress) { + // All the events we receive are filtered through an nsBrowserStatusFilter, + // which accepts ProgressChange64 events, but truncates the progress values to + // uint32_t and calls OnProgressChange. + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP BrowserChild::OnRefreshAttempted(nsIWebProgress* aWebProgress, + nsIURI* aRefreshURI, + uint32_t aMillis, bool aSameURI, + bool* aOut) { + NS_ENSURE_ARG_POINTER(aOut); + *aOut = true; + + return NS_OK; +} + +NS_IMETHODIMP BrowserChild::NotifyNavigationFinished() { + Unused << SendNavigationFinished(); + return NS_OK; +} + +nsresult BrowserChild::PrepareRequestData(nsIRequest* aRequest, + RequestData& aRequestData) { + nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); + if (!channel) { + aRequestData.requestURI() = nullptr; + return NS_OK; + } + + nsresult rv = channel->GetURI(getter_AddRefs(aRequestData.requestURI())); + NS_ENSURE_SUCCESS(rv, rv); + + rv = channel->GetOriginalURI( + getter_AddRefs(aRequestData.originalRequestURI())); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIClassifiedChannel> classifiedChannel = do_QueryInterface(channel); + if (classifiedChannel) { + rv = classifiedChannel->GetMatchedList(aRequestData.matchedList()); + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; +} + +nsresult BrowserChild::PrepareProgressListenerData( + nsIWebProgress* aWebProgress, nsIRequest* aRequest, + WebProgressData& aWebProgressData, RequestData& aRequestData) { + nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aWebProgress); + if (!docShell) { + MOZ_ASSERT_UNREACHABLE("aWebProgress is null or not a nsIDocShell?"); + return NS_ERROR_UNEXPECTED; + } + + aWebProgressData.browsingContext() = docShell->GetBrowsingContext(); + nsresult rv = aWebProgress->GetLoadType(&aWebProgressData.loadType()); + NS_ENSURE_SUCCESS(rv, rv); + + return PrepareRequestData(aRequest, aRequestData); +} + +void BrowserChild::UpdateSessionStore() { + if (mSessionStoreChild) { + mSessionStoreChild->UpdateSessionStore(); + } +} + +#ifdef XP_WIN +RefPtr<PBrowserChild::IsWindowSupportingProtectedMediaPromise> +BrowserChild::DoesWindowSupportProtectedMedia() { + MOZ_ASSERT( + NS_IsMainThread(), + "Protected media support check should be done on main thread only."); + if (mWindowSupportsProtectedMedia) { + // If we've already checked and have a cached result, resolve with that. + return IsWindowSupportingProtectedMediaPromise::CreateAndResolve( + mWindowSupportsProtectedMedia.value(), __func__); + } + RefPtr<BrowserChild> self = this; + // We chain off the promise rather than passing it directly so we can cache + // the result and use that for future calls. + return SendIsWindowSupportingProtectedMedia(ChromeOuterWindowID()) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [self](bool isSupported) { + // If a result was cached while this check was inflight, ensure the + // results match. + MOZ_ASSERT_IF( + self->mWindowSupportsProtectedMedia, + self->mWindowSupportsProtectedMedia.value() == isSupported); + // Cache the response as it will not change during the lifetime + // of this object. + self->mWindowSupportsProtectedMedia = Some(isSupported); + return IsWindowSupportingProtectedMediaPromise::CreateAndResolve( + self->mWindowSupportsProtectedMedia.value(), __func__); + }, + [](ResponseRejectReason reason) { + return IsWindowSupportingProtectedMediaPromise::CreateAndReject( + reason, __func__); + }); +} +#endif + +void BrowserChild::NotifyContentBlockingEvent( + uint32_t aEvent, nsIChannel* aChannel, bool aBlocked, + const nsACString& aTrackingOrigin, + const nsTArray<nsCString>& aTrackingFullHashes, + const Maybe< + mozilla::ContentBlockingNotifier::StorageAccessPermissionGrantedReason>& + aReason, + const Maybe<ContentBlockingNotifier::CanvasFingerprinter>& + aCanvasFingerprinter, + const Maybe<bool> aCanvasFingerprinterKnownText) { + if (!IPCOpen()) { + return; + } + + RequestData requestData; + if (NS_SUCCEEDED(PrepareRequestData(aChannel, requestData))) { + Unused << SendNotifyContentBlockingEvent( + aEvent, requestData, aBlocked, PromiseFlatCString(aTrackingOrigin), + aTrackingFullHashes, aReason, aCanvasFingerprinter, + aCanvasFingerprinterKnownText); + } +} + +NS_IMETHODIMP +BrowserChild::ContentTransformsReceived(JSContext* aCx, + dom::Promise** aPromise) { + auto* globalObject = xpc::CurrentNativeGlobal(aCx); + ErrorResult rv; + if (mChildToParentConversionMatrix) { + // Already received content transforms + RefPtr<Promise> promise = + Promise::CreateResolvedWithUndefined(globalObject, rv); + promise.forget(aPromise); + return rv.StealNSResult(); + } + + if (!mContentTransformPromise) { + mContentTransformPromise = Promise::Create(globalObject, rv); + } + + MOZ_ASSERT(globalObject == mContentTransformPromise->GetGlobalObject()); + NS_IF_ADDREF(*aPromise = mContentTransformPromise); + return rv.StealNSResult(); +} + +BrowserChildMessageManager::BrowserChildMessageManager( + BrowserChild* aBrowserChild) + : ContentFrameMessageManager(new nsFrameMessageManager(aBrowserChild)), + mBrowserChild(aBrowserChild) {} + +BrowserChildMessageManager::~BrowserChildMessageManager() = default; + +NS_IMPL_CYCLE_COLLECTION_CLASS(BrowserChildMessageManager) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(BrowserChildMessageManager, + DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessageManager); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowserChild); + NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(BrowserChildMessageManager, + DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessageManager) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowserChild) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BrowserChildMessageManager) + NS_INTERFACE_MAP_ENTRY(nsIMessageSender) + NS_INTERFACE_MAP_ENTRY_CONCRETE(ContentFrameMessageManager) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +NS_IMPL_ADDREF_INHERITED(BrowserChildMessageManager, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(BrowserChildMessageManager, DOMEventTargetHelper) + +JSObject* BrowserChildMessageManager::WrapObject( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return ContentFrameMessageManager_Binding::Wrap(aCx, this, aGivenProto); +} + +void BrowserChildMessageManager::MarkForCC() { + if (mBrowserChild) { + mBrowserChild->MarkScopesForCC(); + } + EventListenerManager* elm = GetExistingListenerManager(); + if (elm) { + elm->MarkForCC(); + } + MessageManagerGlobal::MarkForCC(); +} + +Nullable<WindowProxyHolder> BrowserChildMessageManager::GetContent( + ErrorResult& aError) { + nsCOMPtr<nsIDocShell> docShell = GetDocShell(aError); + if (!docShell) { + return nullptr; + } + return WindowProxyHolder(docShell->GetBrowsingContext()); +} + +already_AddRefed<nsIDocShell> BrowserChildMessageManager::GetDocShell( + ErrorResult& aError) { + if (!mBrowserChild) { + aError.Throw(NS_ERROR_NULL_POINTER); + return nullptr; + } + nsCOMPtr<nsIDocShell> window = + do_GetInterface(mBrowserChild->WebNavigation()); + return window.forget(); +} + +already_AddRefed<nsIEventTarget> +BrowserChildMessageManager::GetTabEventTarget() { + return do_AddRef(GetMainThreadSerialEventTarget()); +} + +nsresult BrowserChildMessageManager::Dispatch( + already_AddRefed<nsIRunnable>&& aRunnable) const { + return SchedulerGroup::Dispatch(std::move(aRunnable)); +} diff --git a/dom/ipc/BrowserChild.h b/dom/ipc/BrowserChild.h new file mode 100644 index 0000000000..ddbf8f0b74 --- /dev/null +++ b/dom/ipc/BrowserChild.h @@ -0,0 +1,850 @@ +/* -*- 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_dom_BrowserChild_h +#define mozilla_dom_BrowserChild_h + +#include "mozilla/dom/ContentFrameMessageManager.h" +#include "mozilla/dom/PBrowserChild.h" +#include "nsIWebNavigation.h" +#include "nsCOMPtr.h" +#include "nsIWebBrowserChrome.h" +#include "nsIWebBrowserChromeFocus.h" +#include "nsIInterfaceRequestor.h" +#include "nsIWindowProvider.h" +#include "nsIDocShell.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsWeakReference.h" +#include "nsIBrowserChild.h" +#include "nsITooltipListener.h" +#include "nsIWebProgressListener.h" +#include "nsIWebProgressListener2.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/TabContext.h" +#include "mozilla/dom/CoalescedMouseData.h" +#include "mozilla/dom/CoalescedTouchData.h" +#include "mozilla/dom/CoalescedWheelData.h" +#include "mozilla/dom/MessageManagerCallback.h" +#include "mozilla/dom/VsyncMainChild.h" +#include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/EventForwards.h" +#include "mozilla/layers/APZCCallbackHelper.h" +#include "mozilla/layers/CompositorOptions.h" +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/GeckoContentControllerTypes.h" +#include "mozilla/dom/ipc/IdType.h" +#include "PuppetWidget.h" +#include "nsDeque.h" +#include "nsIRemoteTab.h" + +class nsBrowserStatusFilter; +class nsIDOMWindow; +class nsIHttpChannel; +class nsIRequest; +class nsISerialEventTarget; +class nsIWebProgress; +class nsWebBrowser; +class nsDocShellLoadState; + +template <typename T> +class nsTHashtable; +template <typename T> +class nsPtrHashKey; + +namespace mozilla { +enum class NativeKeyBindingsType : uint8_t; + +class AbstractThread; +class PresShell; + +namespace layers { +class APZChild; +class APZEventState; +class AsyncDragMetrics; +class IAPZCTreeManager; +class ImageCompositeNotification; +class PCompositorBridgeChild; +} // namespace layers + +namespace widget { +struct AutoCacheNativeKeyCommands; +} // namespace widget + +namespace dom { + +class BrowserChild; +class BrowsingContext; +class TabGroup; +class ClonedMessageData; +class CoalescedMouseData; +class CoalescedWheelData; +class SessionStoreChild; +class RequestData; +class WebProgressData; + +class BrowserChildMessageManager : public ContentFrameMessageManager, + public nsIMessageSender, + public nsSupportsWeakReference { + public: + explicit BrowserChildMessageManager(BrowserChild* aBrowserChild); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BrowserChildMessageManager, + DOMEventTargetHelper) + + void MarkForCC(); + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + Nullable<WindowProxyHolder> GetContent(ErrorResult& aError) override; + already_AddRefed<nsIDocShell> GetDocShell(ErrorResult& aError) override; + already_AddRefed<nsIEventTarget> GetTabEventTarget() override; + + NS_FORWARD_SAFE_NSIMESSAGESENDER(mMessageManager) + + void GetEventTargetParent(EventChainPreVisitor& aVisitor) override { + aVisitor.mForceContentDispatch = true; + } + + // Dispatch a runnable related to the global. + nsresult Dispatch(already_AddRefed<nsIRunnable>&& aRunnable) const; + + RefPtr<BrowserChild> mBrowserChild; + + protected: + ~BrowserChildMessageManager(); +}; + +/** + * BrowserChild implements the child actor part of the PBrowser protocol. See + * PBrowser for more information. + */ +class BrowserChild final : public nsMessageManagerScriptExecutor, + public ipc::MessageManagerCallback, + public PBrowserChild, + public nsIWebBrowserChrome, + public nsIWebBrowserChromeFocus, + public nsIInterfaceRequestor, + public nsIWindowProvider, + public nsSupportsWeakReference, + public nsIBrowserChild, + public nsIObserver, + public nsIWebProgressListener2, + public TabContext, + public nsITooltipListener, + public mozilla::ipc::IShmemAllocator { + using PuppetWidget = mozilla::widget::PuppetWidget; + using ClonedMessageData = mozilla::dom::ClonedMessageData; + using CoalescedMouseData = mozilla::dom::CoalescedMouseData; + using CoalescedWheelData = mozilla::dom::CoalescedWheelData; + using APZEventState = mozilla::layers::APZEventState; + using TouchBehaviorFlags = mozilla::layers::TouchBehaviorFlags; + + friend class PBrowserChild; + + public: + /** + * Find BrowserChild of aTabId in the same content process of the + * caller. + */ + static already_AddRefed<BrowserChild> FindBrowserChild(const TabId& aTabId); + + // Return a list of all active BrowserChildren. + static nsTArray<RefPtr<BrowserChild>> GetAll(); + + public: + /** + * Create a new BrowserChild object. + */ + BrowserChild(ContentChild* aManager, const TabId& aTabId, + const TabContext& aContext, + dom::BrowsingContext* aBrowsingContext, uint32_t aChromeFlags, + bool aIsTopLevel); + + MOZ_CAN_RUN_SCRIPT nsresult Init(mozIDOMWindowProxy* aParent, + WindowGlobalChild* aInitialWindowChild); + + /** Return a BrowserChild with the given attributes. */ + static already_AddRefed<BrowserChild> Create( + ContentChild* aManager, const TabId& aTabId, const TabContext& aContext, + BrowsingContext* aBrowsingContext, uint32_t aChromeFlags, + bool aIsTopLevel); + + // Let managees query if it is safe to send messages. + bool IsDestroyed() const { return mDestroyed; } + + TabId GetTabId() const { + MOZ_ASSERT(mUniqueId != 0); + return mUniqueId; + } + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_NSIWEBBROWSERCHROME + NS_DECL_NSIWEBBROWSERCHROMEFOCUS + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSIWINDOWPROVIDER + NS_DECL_NSIBROWSERCHILD + NS_DECL_NSIOBSERVER + NS_DECL_NSIWEBPROGRESSLISTENER + NS_DECL_NSIWEBPROGRESSLISTENER2 + NS_DECL_NSITOOLTIPLISTENER + + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(BrowserChild, + nsIBrowserChild) + + FORWARD_SHMEM_ALLOCATOR_TO(PBrowserChild) + + JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return mBrowserChildMessageManager->WrapObject(aCx, aGivenProto); + } + + // Get the Document for the top-level window in this tab. + already_AddRefed<Document> GetTopLevelDocument() const; + + // Get the pres-shell of the document for the top-level window in this tab. + PresShell* GetTopLevelPresShell() const; + + BrowserChildMessageManager* GetMessageManager() { + return mBrowserChildMessageManager; + } + + bool IsTopLevel() const { return mIsTopLevel; } + + bool ShouldSendWebProgressEventsToParent() const { + return mShouldSendWebProgressEventsToParent; + } + + /** + * MessageManagerCallback methods that we override. + */ + virtual bool DoSendBlockingMessage( + const nsAString& aMessage, StructuredCloneData& aData, + nsTArray<StructuredCloneData>* aRetVal) override; + + virtual nsresult DoSendAsyncMessage(const nsAString& aMessage, + StructuredCloneData& aData) override; + + bool DoUpdateZoomConstraints(const uint32_t& aPresShellId, + const ViewID& aViewId, + const Maybe<ZoomConstraints>& aConstraints); + + mozilla::ipc::IPCResult RecvLoadURL(nsDocShellLoadState* aLoadState, + const ParentShowInfo& aInfo); + + mozilla::ipc::IPCResult RecvCreateAboutBlankDocumentViewer( + nsIPrincipal* aPrincipal, nsIPrincipal* aPartitionedPrincipal); + + mozilla::ipc::IPCResult RecvResumeLoad(const uint64_t& aPendingSwitchID, + const ParentShowInfo&); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY mozilla::ipc::IPCResult + RecvCloneDocumentTreeIntoSelf( + const MaybeDiscarded<BrowsingContext>& aSourceBC, + const embedding::PrintData& aPrintData, + CloneDocumentTreeIntoSelfResolver&& aResolve); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY + mozilla::ipc::IPCResult RecvUpdateRemotePrintSettings( + const embedding::PrintData& aPrintData); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY + mozilla::ipc::IPCResult RecvShow(const ParentShowInfo&, const OwnerShowInfo&); + + mozilla::ipc::IPCResult RecvInitRendering( + const TextureFactoryIdentifier& aTextureFactoryIdentifier, + const layers::LayersId& aLayersId, + const mozilla::layers::CompositorOptions& aCompositorOptions, + const bool& aLayersConnected); + + mozilla::ipc::IPCResult RecvCompositorOptionsChanged( + const mozilla::layers::CompositorOptions& aNewOptions); + + mozilla::ipc::IPCResult RecvUpdateDimensions( + const mozilla::dom::DimensionInfo& aDimensionInfo); + mozilla::ipc::IPCResult RecvSizeModeChanged(const nsSizeMode& aSizeMode); + + mozilla::ipc::IPCResult RecvChildToParentMatrix( + const mozilla::Maybe<mozilla::gfx::Matrix4x4>& aMatrix, + const mozilla::ScreenRect& aTopLevelViewportVisibleRectInBrowserCoords); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY + mozilla::ipc::IPCResult RecvDynamicToolbarMaxHeightChanged( + const mozilla::ScreenIntCoord& aHeight); + + mozilla::ipc::IPCResult RecvDynamicToolbarOffsetChanged( + const mozilla::ScreenIntCoord& aOffset); + + mozilla::ipc::IPCResult RecvActivate(uint64_t aActionId); + + mozilla::ipc::IPCResult RecvDeactivate(uint64_t aActionId); + + mozilla::ipc::IPCResult RecvRealMouseMoveEvent( + const mozilla::WidgetMouseEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId); + mozilla::ipc::IPCResult RecvNormalPriorityRealMouseMoveEvent( + const mozilla::WidgetMouseEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId); + mozilla::ipc::IPCResult RecvRealMouseMoveEventForTests( + const mozilla::WidgetMouseEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId); + mozilla::ipc::IPCResult RecvNormalPriorityRealMouseMoveEventForTests( + const mozilla::WidgetMouseEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId); + + mozilla::ipc::IPCResult RecvSynthMouseMoveEvent( + const mozilla::WidgetMouseEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId); + mozilla::ipc::IPCResult RecvNormalPrioritySynthMouseMoveEvent( + const mozilla::WidgetMouseEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId); + + mozilla::ipc::IPCResult RecvRealMouseButtonEvent( + const mozilla::WidgetMouseEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId); + mozilla::ipc::IPCResult RecvNormalPriorityRealMouseButtonEvent( + const mozilla::WidgetMouseEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId); + + mozilla::ipc::IPCResult RecvRealMouseEnterExitWidgetEvent( + const mozilla::WidgetMouseEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId); + mozilla::ipc::IPCResult RecvNormalPriorityRealMouseEnterExitWidgetEvent( + const mozilla::WidgetMouseEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY + mozilla::ipc::IPCResult RecvRealDragEvent(const WidgetDragEvent& aEvent, + const uint32_t& aDragAction, + const uint32_t& aDropEffect, + nsIPrincipal* aPrincipal, + nsIContentSecurityPolicy* aCsp); + + mozilla::ipc::IPCResult RecvRealKeyEvent( + const mozilla::WidgetKeyboardEvent& aEvent, const nsID& aUUID); + + mozilla::ipc::IPCResult RecvNormalPriorityRealKeyEvent( + const mozilla::WidgetKeyboardEvent& aEvent, const nsID& aUUID); + + mozilla::ipc::IPCResult RecvMouseWheelEvent( + const mozilla::WidgetWheelEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId); + + mozilla::ipc::IPCResult RecvNormalPriorityMouseWheelEvent( + const mozilla::WidgetWheelEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId); + + mozilla::ipc::IPCResult RecvRealTouchEvent(const WidgetTouchEvent& aEvent, + const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId, + const nsEventStatus& aApzResponse); + + mozilla::ipc::IPCResult RecvNormalPriorityRealTouchEvent( + const WidgetTouchEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId, const nsEventStatus& aApzResponse); + + mozilla::ipc::IPCResult RecvRealTouchMoveEvent( + const WidgetTouchEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId, const nsEventStatus& aApzResponse); + + mozilla::ipc::IPCResult RecvNormalPriorityRealTouchMoveEvent( + const WidgetTouchEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId, const nsEventStatus& aApzResponse); + + mozilla::ipc::IPCResult RecvRealTouchMoveEvent2( + const WidgetTouchEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId, const nsEventStatus& aApzResponse) { + return RecvRealTouchMoveEvent(aEvent, aGuid, aInputBlockId, aApzResponse); + } + + mozilla::ipc::IPCResult RecvNormalPriorityRealTouchMoveEvent2( + const WidgetTouchEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId, const nsEventStatus& aApzResponse) { + return RecvNormalPriorityRealTouchMoveEvent(aEvent, aGuid, aInputBlockId, + aApzResponse); + } + + mozilla::ipc::IPCResult RecvUpdateSHistory(); + + mozilla::ipc::IPCResult RecvNativeSynthesisResponse( + const uint64_t& aObserverId, const nsCString& aResponse); + + mozilla::ipc::IPCResult RecvCompositionEvent( + const mozilla::WidgetCompositionEvent& aEvent); + + mozilla::ipc::IPCResult RecvNormalPriorityCompositionEvent( + const mozilla::WidgetCompositionEvent& aEvent); + + mozilla::ipc::IPCResult RecvSelectionEvent( + const mozilla::WidgetSelectionEvent& aEvent); + + mozilla::ipc::IPCResult RecvNormalPrioritySelectionEvent( + const mozilla::WidgetSelectionEvent& aEvent); + + mozilla::ipc::IPCResult RecvInsertText(const nsAString& aStringToInsert); + + mozilla::ipc::IPCResult RecvUpdateRemoteStyle( + const StyleImageRendering& aImageRendering); + + mozilla::ipc::IPCResult RecvNormalPriorityInsertText( + const nsAString& aStringToInsert); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY + mozilla::ipc::IPCResult RecvPasteTransferable( + const IPCTransferable& aTransferable); + + mozilla::ipc::IPCResult RecvLoadRemoteScript(const nsAString& aURL, + const bool& aRunInGlobalScope); + + mozilla::ipc::IPCResult RecvAsyncMessage(const nsAString& aMessage, + const ClonedMessageData& aData); + mozilla::ipc::IPCResult RecvSwappedWithOtherRemoteLoader( + const IPCTabContext& aContext); + + mozilla::ipc::IPCResult RecvSafeAreaInsetsChanged( + const mozilla::ScreenIntMargin& aSafeAreaInsets); + +#ifdef ACCESSIBILITY + PDocAccessibleChild* AllocPDocAccessibleChild( + PDocAccessibleChild*, const uint64_t&, + const MaybeDiscardedBrowsingContext&); + bool DeallocPDocAccessibleChild(PDocAccessibleChild*); +#endif + + RefPtr<VsyncMainChild> GetVsyncChild(); + + nsIWebNavigation* WebNavigation() const { return mWebNav; } + + PuppetWidget* WebWidget() { return mPuppetWidget; } + + bool IsTransparent() const { return mIsTransparent; } + + const EffectsInfo& GetEffectsInfo() const { return mEffectsInfo; } + + void SetBackgroundColor(const nscolor& aColor); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual mozilla::ipc::IPCResult RecvUpdateEffects( + const EffectsInfo& aEffects); + + void RequestEditCommands(NativeKeyBindingsType aType, + const WidgetKeyboardEvent& aEvent, + nsTArray<CommandInt>& aCommands); + + bool IsVisible(); + bool IsPreservingLayers() const { return mIsPreservingLayers; } + + /** + * Signal to this BrowserChild that it should be made visible: + * activated widget, retained layer tree, etc. (Respectively, + * made not visible.) + */ + void UpdateVisibility(); + void MakeVisible(); + void MakeHidden(); + void PresShellActivenessMaybeChanged(); + + ContentChild* Manager() const { return mManager; } + + static inline BrowserChild* GetFrom(nsIDocShell* aDocShell) { + if (!aDocShell) { + return nullptr; + } + + nsCOMPtr<nsIBrowserChild> tc = aDocShell->GetBrowserChild(); + return static_cast<BrowserChild*>(tc.get()); + } + + static inline BrowserChild* GetFrom(mozIDOMWindow* aWindow) { + nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(aWindow); + nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(webNav); + return GetFrom(docShell); + } + + static inline BrowserChild* GetFrom(mozIDOMWindowProxy* aWindow) { + nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(aWindow); + nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(webNav); + return GetFrom(docShell); + } + + static BrowserChild* GetFrom(PresShell* aPresShell); + static BrowserChild* GetFrom(layers::LayersId aLayersId); + + layers::LayersId GetLayersId() { return mLayersId; } + Maybe<bool> IsLayersConnected() { return mLayersConnected; } + + void DidComposite(mozilla::layers::TransactionId aTransactionId, + const TimeStamp& aCompositeStart, + const TimeStamp& aCompositeEnd); + + void ClearCachedResources(); + void SchedulePaint(); + void ReinitRendering(); + void ReinitRenderingForDeviceReset(); + + void NotifyJankedAnimations(const nsTArray<uint64_t>& aJankedAnimations); + + static inline BrowserChild* GetFrom(nsIDOMWindow* aWindow) { + nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(aWindow); + nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(webNav); + return GetFrom(docShell); + } + + mozilla::ipc::IPCResult RecvUIResolutionChanged(const float& aDpi, + const int32_t& aRounding, + const double& aScale); + + mozilla::ipc::IPCResult RecvHandleAccessKey(const WidgetKeyboardEvent& aEvent, + nsTArray<uint32_t>&& aCharCodes); + MOZ_CAN_RUN_SCRIPT_BOUNDARY + mozilla::ipc::IPCResult RecvPrintPreview(const PrintData& aPrintData, + const MaybeDiscardedBrowsingContext&, + PrintPreviewResolver&& aCallback); + + mozilla::ipc::IPCResult RecvExitPrintPreview(); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY mozilla::ipc::IPCResult RecvPrint( + const MaybeDiscardedBrowsingContext&, const PrintData&); + + mozilla::ipc::IPCResult RecvUpdateNativeWindowHandle( + const uintptr_t& aNewHandle); + + mozilla::ipc::IPCResult RecvWillChangeProcess(); + + PPaymentRequestChild* AllocPPaymentRequestChild(); + + bool DeallocPPaymentRequestChild(PPaymentRequestChild* aActor); + + LayoutDeviceIntPoint GetClientOffset() const { return mClientOffset; } + LayoutDeviceIntPoint GetChromeOffset() const { return mChromeOffset; }; + ScreenIntCoord GetDynamicToolbarMaxHeight() const { + return mDynamicToolbarMaxHeight; + }; + + bool IPCOpen() const { return mIPCOpen; } + + const mozilla::layers::CompositorOptions& GetCompositorOptions() const; + bool AsyncPanZoomEnabled() const; + + ScreenIntSize GetInnerSize(); + CSSSize GetUnscaledInnerSize() { return mUnscaledInnerSize; } + + Maybe<nsRect> GetVisibleRect() const; + + // Call RecvShow(nsIntSize(0, 0)) and block future calls to RecvShow(). + void DoFakeShow(const ParentShowInfo&); + + void ContentReceivedInputBlock(uint64_t aInputBlockId, + bool aPreventDefault) const; + void SetTargetAPZC( + uint64_t aInputBlockId, + const nsTArray<layers::ScrollableLayerGuid>& aTargets) const; + MOZ_CAN_RUN_SCRIPT_BOUNDARY + mozilla::ipc::IPCResult RecvHandleTap( + const layers::GeckoContentController_TapType& aType, + const LayoutDevicePoint& aPoint, const Modifiers& aModifiers, + const ScrollableLayerGuid& aGuid, const uint64_t& aInputBlockId, + const Maybe<DoubleTapToZoomMetrics>& aDoubleTapToZoomMetrics); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY + mozilla::ipc::IPCResult RecvNormalPriorityHandleTap( + const layers::GeckoContentController_TapType& aType, + const LayoutDevicePoint& aPoint, const Modifiers& aModifiers, + const ScrollableLayerGuid& aGuid, const uint64_t& aInputBlockId, + const Maybe<DoubleTapToZoomMetrics>& aDoubleTapToZoomMetrics); + + bool UpdateFrame(const layers::RepaintRequest& aRequest); + void NotifyAPZStateChange( + const ViewID& aViewId, + const layers::GeckoContentController_APZStateChange& aChange, + const int& aArg, Maybe<uint64_t> aInputBlockId); + void StartScrollbarDrag(const layers::AsyncDragMetrics& aDragMetrics); + void ZoomToRect(const uint32_t& aPresShellId, + const ScrollableLayerGuid::ViewID& aViewId, + const CSSRect& aRect, const uint32_t& aFlags); + + // Request that the docshell be marked as active. + void PaintWhileInterruptingJS(); + + void UnloadLayersWhileInterruptingJS(); + + nsresult CanCancelContentJS(nsIRemoteTab::NavigationType aNavigationType, + int32_t aNavigationIndex, nsIURI* aNavigationURI, + int32_t aEpoch, bool* aCanCancel); + +#if defined(XP_WIN) && defined(ACCESSIBILITY) + uintptr_t GetNativeWindowHandle() const { return mNativeWindowHandle; } +#endif + + BrowsingContext* GetBrowsingContext() const { return mBrowsingContext; } + + // The transform from the coordinate space of this BrowserChild to the + // coordinate space of the native window its BrowserParent is in. + mozilla::LayoutDeviceToLayoutDeviceMatrix4x4 + GetChildToParentConversionMatrix() const; + + // Returns the portion of the visible rect of this remote document in the + // top browser window coordinate system. This is the result of being + // clipped by all ancestor viewports. + Maybe<ScreenRect> GetTopLevelViewportVisibleRectInBrowserCoords() const; + + // Similar to above GetTopLevelViewportVisibleRectInBrowserCoords(), but + // in this out-of-process document's coordinate system. + Maybe<LayoutDeviceRect> GetTopLevelViewportVisibleRectInSelfCoords() const; + + // Prepare to dispatch all coalesced mousemove events. We'll move all data + // in mCoalescedMouseData to a nsDeque; then we start processing them. We + // can't fetch the coalesced event one by one and dispatch it because we + // may reentry the event loop and access to the same hashtable. It's + // called when dispatching some mouse events other than mousemove. + void FlushAllCoalescedMouseData(); + void ProcessPendingCoalescedMouseDataAndDispatchEvents(); + + void ProcessPendingCoalescedTouchData(); + + void HandleRealMouseButtonEvent(const WidgetMouseEvent& aEvent, + const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId); + + void SetCancelContentJSEpoch(int32_t aEpoch) { + mCancelContentJSEpoch = aEpoch; + } + + void UpdateSessionStore(); + + mozilla::dom::SessionStoreChild* GetSessionStoreChild() { + return mSessionStoreChild; + } + +#ifdef XP_WIN + // Check if the window this BrowserChild is associated with supports + // protected media (EME) or not. + // Returns a promise the will resolve true if the window supports + // protected media or false if it does not. The promise will be rejected + // with an ResponseRejectReason if the IPC needed to do the check fails. + // Callers should treat the reject case as if the window does not support + // protected media to ensure robust handling. + RefPtr<IsWindowSupportingProtectedMediaPromise> + DoesWindowSupportProtectedMedia(); +#endif + + // Notify the content blocking event in the parent process. This sends an + // IPC message to the BrowserParent in the parent. The BrowserParent will + // find the top-level WindowGlobalParent and notify the event from it. + void NotifyContentBlockingEvent( + uint32_t aEvent, nsIChannel* aChannel, bool aBlocked, + const nsACString& aTrackingOrigin, + const nsTArray<nsCString>& aTrackingFullHashes, + const Maybe< + ContentBlockingNotifier::StorageAccessPermissionGrantedReason>& + aReason, + const Maybe<ContentBlockingNotifier::CanvasFingerprinter>& + aCanvasFingerprinter, + const Maybe<bool> aCanvasFingerprinterKnownText); + + protected: + virtual ~BrowserChild(); + + mozilla::ipc::IPCResult RecvDestroy(); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY + mozilla::ipc::IPCResult RecvRenderLayers(const bool& aEnabled); + + mozilla::ipc::IPCResult RecvPreserveLayers(bool); + + mozilla::ipc::IPCResult RecvNavigateByKey(const bool& aForward, + const bool& aForDocumentNavigation); + + mozilla::ipc::IPCResult RecvSuppressDisplayport(const bool& aEnabled); + + mozilla::ipc::IPCResult RecvScrollbarPreferenceChanged(ScrollbarPreference); + + mozilla::ipc::IPCResult RecvStopIMEStateManagement(); + + mozilla::ipc::IPCResult RecvAllowScriptsToClose(); + + mozilla::ipc::IPCResult RecvReleaseAllPointerCapture(); + + private: + void HandleDoubleTap(const CSSPoint& aPoint, const Modifiers& aModifiers, + const ScrollableLayerGuid& aGuid, + const DoubleTapToZoomMetrics& aMetrics); + + void ActorDestroy(ActorDestroyReason why) override; + + bool InitBrowserChildMessageManager(); + + void InitRenderingState( + const TextureFactoryIdentifier& aTextureFactoryIdentifier, + const layers::LayersId& aLayersId, + const mozilla::layers::CompositorOptions& aCompositorOptions); + void InitAPZState(); + + void DestroyWindow(); + + void ApplyParentShowInfo(const ParentShowInfo&); + + bool HasValidInnerSize(); + + ScreenIntRect GetOuterRect(); + + void SetUnscaledInnerSize(const CSSSize& aSize) { + mUnscaledInnerSize = aSize; + } + + bool SkipRepeatedKeyEvent(const WidgetKeyboardEvent& aEvent); + + void UpdateRepeatedKeyEventEndTime(const WidgetKeyboardEvent& aEvent); + + void DispatchCoalescedWheelEvent(); + + /** + * Dispatch aEvent on aEvent.mWidget. + */ + nsEventStatus DispatchWidgetEventViaAPZ(WidgetGUIEvent& aEvent); + + void DispatchWheelEvent(const WidgetWheelEvent& aEvent, + const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId); + + void InternalSetDocShellIsActive(bool aIsActive); + + bool CreateRemoteLayerManager( + mozilla::layers::PCompositorBridgeChild* aCompositorChild); + + nsresult PrepareRequestData(nsIRequest* aRequest, RequestData& aRequestData); + nsresult PrepareProgressListenerData(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + WebProgressData& aWebProgressData, + RequestData& aRequestData); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY + nsresult UpdateRemotePrintSettings(const embedding::PrintData& aPrintData); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY + nsresult CloneDocumentTreeIntoSelf( + const MaybeDiscarded<BrowsingContext>& aSourceBC, + const embedding::PrintData& aPrintData); + + class DelayedDeleteRunnable; + + RefPtr<BrowserChildMessageManager> mBrowserChildMessageManager; + TextureFactoryIdentifier mTextureFactoryIdentifier; + RefPtr<nsWebBrowser> mWebBrowser; + nsCOMPtr<nsIWebNavigation> mWebNav; + RefPtr<PuppetWidget> mPuppetWidget; + nsCOMPtr<nsIURI> mLastURI; + RefPtr<ContentChild> mManager; + RefPtr<BrowsingContext> mBrowsingContext; + RefPtr<nsBrowserStatusFilter> mStatusFilter; + uint32_t mChromeFlags; + uint32_t mMaxTouchPoints; + layers::LayersId mLayersId; + CSSRect mUnscaledOuterRect; + Maybe<bool> mLayersConnected; + Maybe<bool> mLayersConnectRequested; + EffectsInfo mEffectsInfo; + + RefPtr<VsyncMainChild> mVsyncChild; + + RefPtr<APZEventState> mAPZEventState; + + // Position of client area relative to the outer window + LayoutDeviceIntPoint mClientOffset; + // Position of tab, relative to parent widget (typically the window) + // NOTE: This value is valuable only for the top level browser. + LayoutDeviceIntPoint mChromeOffset; + ScreenIntCoord mDynamicToolbarMaxHeight; + TabId mUniqueId; + + bool mDidFakeShow : 1; + bool mTriedBrowserInit : 1; + bool mIgnoreKeyPressEvent : 1; + bool mHasValidInnerSize : 1; + bool mDestroyed : 1; + + // Whether or not this browser is the child part of the top level PBrowser + // actor in a remote browser. + bool mIsTopLevel : 1; + + bool mIsTransparent : 1; + bool mIPCOpen : 1; + + bool mDidSetRealShowInfo : 1; + bool mDidLoadURLInit : 1; + + bool mSkipKeyPress : 1; + + bool mCoalesceMouseMoveEvents : 1; + + bool mShouldSendWebProgressEventsToParent : 1; + + // Whether we are rendering to the compositor or not. + bool mRenderLayers : 1; + + // Whether we're artificially preserving layers. + bool mIsPreservingLayers : 1; + + // Holds the compositor options for the compositor rendering this tab, + // once we find out which compositor that is. + Maybe<mozilla::layers::CompositorOptions> mCompositorOptions; + + friend class ContentChild; + + CSSSize mUnscaledInnerSize; + + // Store the end time of the handling of the last repeated + // keydown/keypress event so that in case event handling takes time, some + // repeated events can be skipped to not flood child process. + mozilla::TimeStamp mRepeatedKeyEventTime; + + // Similar to mRepeatedKeyEventTime, store the end time (from parent + // process) of handling the last repeated wheel event so that in case + // event handling takes time, some repeated events can be skipped to not + // flood child process. + mozilla::TimeStamp mLastWheelProcessedTimeFromParent; + mozilla::TimeDuration mLastWheelProcessingDuration; + + // Hash table to track coalesced mousemove events for different pointers. + nsClassHashtable<nsUint32HashKey, CoalescedMouseData> mCoalescedMouseData; + + nsDeque<CoalescedMouseData> mToBeDispatchedMouseData; + + CoalescedWheelData mCoalescedWheelData; + CoalescedTouchData mCoalescedTouchData; + + RefPtr<CoalescedMouseMoveFlusher> mCoalescedMouseEventFlusher; + RefPtr<CoalescedTouchMoveFlusher> mCoalescedTouchMoveEventFlusher; + + RefPtr<layers::IAPZCTreeManager> mApzcTreeManager; + RefPtr<SessionStoreChild> mSessionStoreChild; + +#if defined(XP_WIN) && defined(ACCESSIBILITY) + // The handle associated with the native window that contains this tab + uintptr_t mNativeWindowHandle; +#endif // defined(XP_WIN) + + int32_t mCancelContentJSEpoch; + + Maybe<LayoutDeviceToLayoutDeviceMatrix4x4> mChildToParentConversionMatrix; + // When mChildToParentConversionMatrix is Nothing() this value is invalid. + ScreenRect mTopLevelViewportVisibleRectInBrowserCoords; + +#ifdef XP_WIN + // Should only be accessed on main thread. + Maybe<bool> mWindowSupportsProtectedMedia; +#endif + + // If set, resolve when we receive ChildToParentMatrix. + RefPtr<dom::Promise> mContentTransformPromise; + + DISALLOW_EVIL_CONSTRUCTORS(BrowserChild); +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_BrowserChild_h diff --git a/dom/ipc/BrowserHost.cpp b/dom/ipc/BrowserHost.cpp new file mode 100644 index 0000000000..489f07a612 --- /dev/null +++ b/dom/ipc/BrowserHost.cpp @@ -0,0 +1,287 @@ +/* -*- 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 "mozilla/dom/BrowserHost.h" + +#include "mozilla/Unused.h" +#include "mozilla/dom/CancelContentJSOptionsBinding.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/ProcessPriorityManager.h" + +#include "nsIObserverService.h" + +namespace mozilla::dom { + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BrowserHost) + NS_INTERFACE_MAP_ENTRY(nsIRemoteTab) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, RemoteBrowser) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION_WEAK(BrowserHost, mRoot) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(BrowserHost) +NS_IMPL_CYCLE_COLLECTING_RELEASE(BrowserHost) + +BrowserHost::BrowserHost(BrowserParent* aParent) + : mId(aParent->GetTabId()), + mRoot(aParent), + mEffectsInfo{EffectsInfo::FullyHidden()} { + mRoot->SetBrowserHost(this); +} + +BrowserHost* BrowserHost::GetFrom(nsIRemoteTab* aRemoteTab) { + return static_cast<BrowserHost*>(aRemoteTab); +} + +TabId BrowserHost::GetTabId() const { return mId; } + +mozilla::layers::LayersId BrowserHost::GetLayersId() const { + return mRoot->GetLayersId(); +} + +BrowsingContext* BrowserHost::GetBrowsingContext() const { + return mRoot->GetBrowsingContext(); +} + +nsILoadContext* BrowserHost::GetLoadContext() const { + RefPtr<nsILoadContext> loadContext = mRoot->GetLoadContext(); + return loadContext; +} + +bool BrowserHost::CanRecv() const { return mRoot && mRoot->CanRecv(); } + +a11y::DocAccessibleParent* BrowserHost::GetTopLevelDocAccessible() const { + return mRoot ? mRoot->GetTopLevelDocAccessible() : nullptr; +} + +void BrowserHost::LoadURL(nsDocShellLoadState* aLoadState) { + MOZ_ASSERT(aLoadState); + mRoot->LoadURL(aLoadState); +} + +void BrowserHost::ResumeLoad(uint64_t aPendingSwitchId) { + mRoot->ResumeLoad(aPendingSwitchId); +} + +void BrowserHost::DestroyStart() { mRoot->Destroy(); } + +void BrowserHost::DestroyComplete() { + if (!mRoot) { + return; + } + mRoot->SetOwnerElement(nullptr); + mRoot->Destroy(); + mRoot->SetBrowserHost(nullptr); + mRoot = nullptr; + + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + if (os) { + os->NotifyObservers(NS_ISUPPORTS_CAST(nsIRemoteTab*, this), + "ipc:browser-destroyed", nullptr); + } +} + +bool BrowserHost::Show(const OwnerShowInfo& aShowInfo) { + return mRoot->Show(aShowInfo); +} + +void BrowserHost::UpdateDimensions(const nsIntRect& aRect, + const ScreenIntSize& aSize) { + mRoot->UpdateDimensions(aRect, aSize); +} + +void BrowserHost::UpdateEffects(EffectsInfo aEffects) { + if (!mRoot || mEffectsInfo == aEffects) { + return; + } + mEffectsInfo = aEffects; + Unused << mRoot->SendUpdateEffects(mEffectsInfo); +} + +/* attribute boolean renderLayers; */ +NS_IMETHODIMP +BrowserHost::GetRenderLayers(bool* aRenderLayers) { + if (!mRoot) { + *aRenderLayers = false; + return NS_OK; + } + *aRenderLayers = mRoot->GetRenderLayers(); + return NS_OK; +} + +NS_IMETHODIMP +BrowserHost::SetRenderLayers(bool aRenderLayers) { + if (!mRoot) { + return NS_OK; + } + + mRoot->SetRenderLayers(aRenderLayers); + return NS_OK; +} + +/* readonly attribute boolean hasLayers; */ +NS_IMETHODIMP +BrowserHost::GetHasLayers(bool* aHasLayers) { + *aHasLayers = mRoot && mRoot->GetHasLayers(); + return NS_OK; +} + +/* attribute boolean priorityHint; */ +NS_IMETHODIMP +BrowserHost::SetPriorityHint(bool aPriorityHint) { + if (!mRoot) { + return NS_OK; + } + mRoot->SetPriorityHint(aPriorityHint); + return NS_OK; +} + +NS_IMETHODIMP +BrowserHost::GetPriorityHint(bool* aPriorityHint) { + *aPriorityHint = mRoot && mRoot->GetPriorityHint(); + return NS_OK; +} + +/* void resolutionChanged (); */ +NS_IMETHODIMP +BrowserHost::NotifyResolutionChanged() { + if (!mRoot) { + return NS_OK; + } + VisitAll([](BrowserParent* aBrowserParent) { + aBrowserParent->NotifyResolutionChanged(); + }); + return NS_OK; +} + +/* void deprioritize (); */ +NS_IMETHODIMP +BrowserHost::Deprioritize() { + if (!mRoot) { + return NS_OK; + } + auto* bc = GetBrowsingContext()->Canonical(); + ProcessPriorityManager::BrowserPriorityChanged(bc, + /* aPriority = */ false); + return NS_OK; +} + +/* void preserveLayers (in boolean aPreserveLayers); */ +NS_IMETHODIMP +BrowserHost::PreserveLayers(bool aPreserveLayers) { + if (!mRoot) { + return NS_OK; + } + VisitAll([&](BrowserParent* aBrowserParent) { + aBrowserParent->PreserveLayers(aPreserveLayers); + }); + return NS_OK; +} + +/* readonly attribute uint64_t tabId; */ +NS_IMETHODIMP +BrowserHost::GetTabId(uint64_t* aTabId) { + *aTabId = mId; + return NS_OK; +} + +/* readonly attribute uint64_t contentProcessId; */ +NS_IMETHODIMP +BrowserHost::GetContentProcessId(uint64_t* aContentProcessId) { + if (!mRoot) { + *aContentProcessId = 0; + return NS_OK; + } + *aContentProcessId = GetContentParent()->ChildID(); + return NS_OK; +} + +/* readonly attribute int32_t osPid; */ +NS_IMETHODIMP +BrowserHost::GetOsPid(int32_t* aOsPid) { + if (!mRoot) { + *aOsPid = 0; + return NS_OK; + } + *aOsPid = GetContentParent()->Pid(); + return NS_OK; +} + +/* readonly attribute BrowsingContext browsingContext; */ +NS_IMETHODIMP +BrowserHost::GetBrowsingContext(BrowsingContext** aBc) { + if (!mRoot) { + *aBc = nullptr; + return NS_OK; + } + RefPtr<BrowsingContext> bc = mRoot->GetBrowsingContext(); + bc.forget(aBc); + return NS_OK; +} + +/* readonly attribute boolean hasPresented; */ +NS_IMETHODIMP +BrowserHost::GetHasPresented(bool* aHasPresented) { + if (!mRoot) { + *aHasPresented = false; + return NS_OK; + } + *aHasPresented = mRoot->GetHasPresented(); + return NS_OK; +} + +/* void transmitPermissionsForPrincipal (in nsIPrincipal aPrincipal); */ +NS_IMETHODIMP +BrowserHost::TransmitPermissionsForPrincipal(nsIPrincipal* aPrincipal) { + if (!mRoot) { + return NS_OK; + } + return GetContentParent()->TransmitPermissionsForPrincipal(aPrincipal); +} + +/* void createAboutBlankDocumentViewer(in nsIPrincipal aPrincipal, in + * nsIPrincipal aPartitionedPrincipal); */ +NS_IMETHODIMP +BrowserHost::CreateAboutBlankDocumentViewer( + nsIPrincipal* aPrincipal, nsIPrincipal* aPartitionedPrincipal) { + if (!mRoot) { + return NS_OK; + } + + // Ensure the content process has permisisons for the new document we're about + // to create in it. + nsresult rv = GetContentParent()->TransmitPermissionsForPrincipal(aPrincipal); + if (NS_FAILED(rv)) { + return rv; + } + + Unused << mRoot->SendCreateAboutBlankDocumentViewer(aPrincipal, + aPartitionedPrincipal); + return NS_OK; +} + +NS_IMETHODIMP +BrowserHost::MaybeCancelContentJSExecutionFromScript( + nsIRemoteTab::NavigationType aNavigationType, + JS::Handle<JS::Value> aCancelContentJSOptions, JSContext* aCx) { + // If we're in the process of creating a new window (via window.open), then + // the load that called this function isn't a "normal" load and should be + // ignored for the purposes of cancelling content JS. + if (!mRoot || mRoot->CreatingWindow()) { + return NS_OK; + } + dom::CancelContentJSOptions cancelContentJSOptions; + if (!cancelContentJSOptions.Init(aCx, aCancelContentJSOptions)) { + return NS_ERROR_INVALID_ARG; + } + GetContentParent()->CancelContentJSExecutionIfRunning(mRoot, aNavigationType, + cancelContentJSOptions); + return NS_OK; +} + +} // namespace mozilla::dom diff --git a/dom/ipc/BrowserHost.h b/dom/ipc/BrowserHost.h new file mode 100644 index 0000000000..5b651a1df0 --- /dev/null +++ b/dom/ipc/BrowserHost.h @@ -0,0 +1,109 @@ +/* -*- 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_dom_BrowserHost_h +#define mozilla_dom_BrowserHost_h + +#include "nsIRemoteTab.h" +#include "mozilla/dom/RemoteBrowser.h" +#include "mozilla/dom/BrowserParent.h" + +class nsPIDOMWindowOuter; + +namespace mozilla { + +namespace a11y { +class DocAccessibleParent; +} // namespace a11y + +namespace dom { + +class Element; + +/** + * BrowserHost manages a remote browser from the chrome process. + * + * It is used via the RemoteBrowser interface in nsFrameLoader and supports + * operations over the tree of BrowserParent/BrowserBridgeParent's. + * + * See `dom/docs/Fission-IPC-Diagram.svg` for an overview of the DOM IPC + * actors. + */ +class BrowserHost : public RemoteBrowser, + public nsIRemoteTab, + public nsSupportsWeakReference { + public: + typedef mozilla::layers::LayersId LayersId; + + explicit BrowserHost(BrowserParent* aParent); + + static BrowserHost* GetFrom(nsIRemoteTab* aRemoteTab); + + // NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + // nsIRemoteTab + NS_DECL_NSIREMOTETAB + + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(BrowserHost, RemoteBrowser) + + // Get the IPDL actor for the root BrowserParent. This method should be + // avoided and consumers migrated to use this class. + BrowserParent* GetActor() { return mRoot; } + ContentParent* GetContentParent() const { + return mRoot ? mRoot->Manager() : nullptr; + } + + BrowserHost* AsBrowserHost() override { return this; } + BrowserBridgeHost* AsBrowserBridgeHost() override { return nullptr; } + + TabId GetTabId() const override; + LayersId GetLayersId() const override; + BrowsingContext* GetBrowsingContext() const override; + nsILoadContext* GetLoadContext() const override; + bool CanRecv() const override; + + Element* GetOwnerElement() const { return mRoot->GetOwnerElement(); } + already_AddRefed<nsPIDOMWindowOuter> GetParentWindowOuter() const { + return mRoot->GetParentWindowOuter(); + } + a11y::DocAccessibleParent* GetTopLevelDocAccessible() const; + + // Visit each BrowserParent in the tree formed by PBrowser and + // PBrowserBridge that is anchored by `mRoot`. + template <typename Callback> + void VisitAll(Callback aCallback) { + if (!mRoot) { + return; + } + mRoot->VisitAll(aCallback); + } + + void LoadURL(nsDocShellLoadState* aLoadState) override; + void ResumeLoad(uint64_t aPendingSwitchId) override; + void DestroyStart() override; + void DestroyComplete() override; + + bool Show(const OwnerShowInfo&) override; + void UpdateDimensions(const nsIntRect& aRect, + const ScreenIntSize& aSize) override; + + void UpdateEffects(EffectsInfo aInfo) override; + + private: + virtual ~BrowserHost() = default; + + // The TabID for the root BrowserParent, we cache this so that we can access + // it after the remote browser has been destroyed + TabId mId; + // The root BrowserParent of this remote browser + RefPtr<BrowserParent> mRoot; + EffectsInfo mEffectsInfo; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_BrowserHost_h diff --git a/dom/ipc/BrowserParent.cpp b/dom/ipc/BrowserParent.cpp new file mode 100644 index 0000000000..d369556588 --- /dev/null +++ b/dom/ipc/BrowserParent.cpp @@ -0,0 +1,4118 @@ +/* -*- 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 "base/basictypes.h" + +#include "BrowserParent.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/EventForwards.h" + +#ifdef ACCESSIBILITY +# include "mozilla/a11y/DocAccessibleParent.h" +# include "mozilla/a11y/Platform.h" +# include "nsAccessibilityService.h" +#endif +#include "mozilla/Components.h" +#include "mozilla/dom/BrowserHost.h" +#include "mozilla/dom/BrowserSessionStore.h" +#include "mozilla/dom/BrowsingContextGroup.h" +#include "mozilla/dom/CancelContentJSOptionsBinding.h" +#include "mozilla/dom/ChromeMessageSender.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ContentProcessManager.h" +#include "mozilla/dom/DataTransfer.h" +#include "mozilla/dom/DataTransferItemList.h" +#include "mozilla/dom/DocumentInlines.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/indexedDB/ActorsParent.h" +#include "mozilla/dom/PaymentRequestParent.h" +#include "mozilla/dom/PointerEventHandler.h" +#include "mozilla/dom/BrowserBridgeParent.h" +#include "mozilla/dom/RemoteDragStartData.h" +#include "mozilla/dom/RemoteWebProgressRequest.h" +#include "mozilla/dom/SessionHistoryEntry.h" +#include "mozilla/dom/SessionStoreParent.h" +#include "mozilla/dom/UserActivation.h" +#include "mozilla/EventStateManager.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/DataSurfaceHelpers.h" +#include "mozilla/gfx/GPUProcessManager.h" +#include "mozilla/IMEStateManager.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/layers/AsyncDragMetrics.h" +#include "mozilla/layers/InputAPZContext.h" +#include "mozilla/layout/RemoteLayerTreeOwner.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/Maybe.h" +#include "mozilla/MiscEvents.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/NativeKeyBindingsType.h" +#include "mozilla/net/NeckoChild.h" +#include "mozilla/net/CookieJarSettings.h" +#include "mozilla/Preferences.h" +#include "mozilla/PresShell.h" +#include "mozilla/ProcessHangMonitor.h" +#include "mozilla/RecursiveMutex.h" +#include "mozilla/RefPtr.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/TextEventDispatcher.h" +#include "mozilla/TextEvents.h" +#include "mozilla/TouchEvents.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" +#include "nsCOMPtr.h" +#include "nsContentUtils.h" +#include "nsDebug.h" +#include "nsFocusManager.h" +#include "nsFrameLoader.h" +#include "nsFrameLoaderOwner.h" +#include "nsFrameManager.h" +#include "nsIBaseWindow.h" +#include "nsIBrowser.h" +#include "nsIBrowserController.h" +#include "nsIContent.h" +#include "nsICookieJarSettings.h" +#include "nsIDocShell.h" +#include "nsIDocShellTreeOwner.h" +#include "nsImportModule.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsILoadInfo.h" +#include "nsIPromptFactory.h" +#include "nsIURI.h" +#include "nsIWebBrowserChrome.h" +#include "nsIWebProtocolHandlerRegistrar.h" +#include "nsIWindowWatcher.h" +#include "nsIXPConnect.h" +#include "nsIXULBrowserWindow.h" +#include "nsIAppWindow.h" +#include "nsLayoutUtils.h" +#include "nsQueryActor.h" +#include "nsSHistory.h" +#include "nsViewManager.h" +#include "nsVariant.h" +#include "nsIWidget.h" +#include "nsNetUtil.h" +#ifndef XP_WIN +# include "nsJARProtocolHandler.h" +#endif +#include "nsPIDOMWindow.h" +#include "nsPrintfCString.h" +#include "nsQueryObject.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "PermissionMessageUtils.h" +#include "StructuredCloneData.h" +#include "ColorPickerParent.h" +#include "FilePickerParent.h" +#include "BrowserChild.h" +#include "nsNetCID.h" +#include "nsIAuthInformation.h" +#include "nsIAuthPromptCallback.h" +#include "nsAuthInformationHolder.h" +#include "nsICancelable.h" +#include "gfxUtils.h" +#include "nsILoginManagerAuthPrompter.h" +#include "nsPIWindowRoot.h" +#include "nsReadableUtils.h" +#include "nsIAuthPrompt2.h" +#include "gfxDrawable.h" +#include "ImageOps.h" +#include "UnitTransforms.h" +#include <algorithm> +#include "mozilla/NullPrincipal.h" +#include "mozilla/WebBrowserPersistDocumentParent.h" +#include "ProcessPriorityManager.h" +#include "nsString.h" +#include "IHistory.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/ProfilerLabels.h" +#include "MMPrinter.h" +#include "mozilla/dom/CrashReport.h" +#include "nsISecureBrowserUI.h" +#include "nsIXULRuntime.h" +#include "VsyncSource.h" +#include "nsSubDocumentFrame.h" + +#ifdef XP_WIN +# include "FxRWindowManager.h" +#endif + +#if defined(XP_WIN) && defined(ACCESSIBILITY) +# include "mozilla/a11y/AccessibleWrap.h" +# include "mozilla/a11y/Compatibility.h" +# include "mozilla/a11y/nsWinUtils.h" +#endif + +#ifdef MOZ_ANDROID_HISTORY +# include "GeckoViewHistory.h" +#endif + +#if defined(MOZ_WIDGET_ANDROID) +# include "mozilla/widget/nsWindow.h" +#endif // defined(MOZ_WIDGET_ANDROID) + +using namespace mozilla::dom; +using namespace mozilla::ipc; +using namespace mozilla::layers; +using namespace mozilla::layout; +using namespace mozilla::services; +using namespace mozilla::widget; +using namespace mozilla::gfx; + +using mozilla::LazyLogModule; + +extern mozilla::LazyLogModule gSHIPBFCacheLog; + +LazyLogModule gBrowserFocusLog("BrowserFocus"); + +#define LOGBROWSERFOCUS(args) \ + MOZ_LOG(gBrowserFocusLog, mozilla::LogLevel::Debug, args) + +/* static */ +BrowserParent* BrowserParent::sFocus = nullptr; +/* static */ +BrowserParent* BrowserParent::sTopLevelWebFocus = nullptr; +/* static */ +BrowserParent* BrowserParent::sLastMouseRemoteTarget = nullptr; + +// The flags passed by the webProgress notifications are 16 bits shifted +// from the ones registered by webProgressListeners. +#define NOTIFY_FLAG_SHIFT 16 + +namespace mozilla { + +/** + * Store data of a keypress event which is requesting to handled it in a remote + * process or some remote processes. + */ +class RequestingAccessKeyEventData { + public: + RequestingAccessKeyEventData() = delete; + + static void OnBrowserParentCreated() { + MOZ_ASSERT(sBrowserParentCount <= INT32_MAX); + sBrowserParentCount++; + } + static void OnBrowserParentDestroyed() { + MOZ_ASSERT(sBrowserParentCount > 0); + sBrowserParentCount--; + // To avoid memory leak, we need to reset sData when the last BrowserParent + // is destroyed. + if (!sBrowserParentCount) { + Clear(); + } + } + + static void Set(const WidgetKeyboardEvent& aKeyPressEvent) { + MOZ_ASSERT(aKeyPressEvent.mMessage == eKeyPress); + MOZ_ASSERT(sBrowserParentCount > 0); + sData = + Some(Data{aKeyPressEvent.mAlternativeCharCodes, aKeyPressEvent.mKeyCode, + aKeyPressEvent.mCharCode, aKeyPressEvent.mKeyNameIndex, + aKeyPressEvent.mCodeNameIndex, aKeyPressEvent.mKeyValue, + aKeyPressEvent.mModifiers}); + } + + static void Clear() { sData.reset(); } + + [[nodiscard]] static bool Equals(const WidgetKeyboardEvent& aKeyPressEvent) { + MOZ_ASSERT(sBrowserParentCount > 0); + return sData.isSome() && sData->Equals(aKeyPressEvent); + } + + [[nodiscard]] static bool IsSet() { + MOZ_ASSERT(sBrowserParentCount > 0); + return sData.isSome(); + } + + private: + struct Data { + [[nodiscard]] bool Equals(const WidgetKeyboardEvent& aKeyPressEvent) { + return mKeyCode == aKeyPressEvent.mKeyCode && + mCharCode == aKeyPressEvent.mCharCode && + mKeyNameIndex == aKeyPressEvent.mKeyNameIndex && + mCodeNameIndex == aKeyPressEvent.mCodeNameIndex && + mKeyValue == aKeyPressEvent.mKeyValue && + mModifiers == aKeyPressEvent.mModifiers && + mAlternativeCharCodes == aKeyPressEvent.mAlternativeCharCodes; + } + + CopyableTArray<AlternativeCharCode> mAlternativeCharCodes; + uint32_t mKeyCode; + uint32_t mCharCode; + KeyNameIndex mKeyNameIndex; + CodeNameIndex mCodeNameIndex; + nsString mKeyValue; + Modifiers mModifiers; + }; + static Maybe<Data> sData; + static int32_t sBrowserParentCount; +}; +int32_t RequestingAccessKeyEventData::sBrowserParentCount = 0; +Maybe<RequestingAccessKeyEventData::Data> RequestingAccessKeyEventData::sData; + +namespace dom { + +BrowserParent::LayerToBrowserParentTable* + BrowserParent::sLayerToBrowserParentTable = nullptr; + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BrowserParent) + NS_INTERFACE_MAP_ENTRY(nsIAuthPromptProvider) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventListener) +NS_INTERFACE_MAP_END +NS_IMPL_CYCLE_COLLECTION_WEAK(BrowserParent, mFrameLoader, mBrowsingContext) +NS_IMPL_CYCLE_COLLECTING_ADDREF(BrowserParent) +NS_IMPL_CYCLE_COLLECTING_RELEASE(BrowserParent) + +BrowserParent::BrowserParent(ContentParent* aManager, const TabId& aTabId, + const TabContext& aContext, + CanonicalBrowsingContext* aBrowsingContext, + uint32_t aChromeFlags) + : TabContext(aContext), + mTabId(aTabId), + mManager(aManager), + mBrowsingContext(aBrowsingContext), + mFrameElement(nullptr), + mBrowserDOMWindow(nullptr), + mFrameLoader(nullptr), + mChromeFlags(aChromeFlags), + mBrowserBridgeParent(nullptr), + mBrowserHost(nullptr), + mContentCache(*this), + mRect(0, 0, 0, 0), + mDimensions(0, 0), + mDPI(0), + mRounding(0), + mDefaultScale(0), + mUpdatedDimensions(false), + mSizeMode(nsSizeMode_Normal), + mCreatingWindow(false), + mVsyncParent(nullptr), + mMarkedDestroying(false), + mIsDestroyed(false), + mRemoteTargetSetsCursor(false), + mIsPreservingLayers(false), + mRenderLayers(true), + mPriorityHint(false), + mHasLayers(false), + mHasPresented(false), + mIsReadyToHandleInputEvents(false), + mIsMouseEnterIntoWidgetEventSuppressed(false), + mLockedNativePointer(false), + mShowingTooltip(false) { + MOZ_ASSERT(aManager); + + RequestingAccessKeyEventData::OnBrowserParentCreated(); + + // When the input event queue is disabled, we don't need to handle the case + // that some input events are dispatched before PBrowserConstructor. + mIsReadyToHandleInputEvents = !ContentParent::IsInputEventQueueSupported(); + + // Make sure to compute our process priority if needed before the block of + // code below. This makes sure the block below prioritizes our process if + // needed. + if (aBrowsingContext->IsTop()) { + RecomputeProcessPriority(); + } + + // If we're in a BC tree that is active with respect to the priority manager, + // ensure that this new BrowserParent is marked as active. This ensures that + // the process will be prioritized in a cross-site iframe navigation in an + // active tab, and also that the process is correctly prioritized if we got + // created for a browsing context which was already active. + if (aBrowsingContext->Top()->IsPriorityActive()) { + ProcessPriorityManager::BrowserPriorityChanged(this, true); + } +} + +BrowserParent::~BrowserParent() { + RequestingAccessKeyEventData::OnBrowserParentDestroyed(); +} + +/* static */ +BrowserParent* BrowserParent::GetFocused() { return sFocus; } + +/* static */ +BrowserParent* BrowserParent::GetLastMouseRemoteTarget() { + return sLastMouseRemoteTarget; +} + +/*static*/ +BrowserParent* BrowserParent::GetFrom(nsFrameLoader* aFrameLoader) { + if (!aFrameLoader) { + return nullptr; + } + return aFrameLoader->GetBrowserParent(); +} + +/*static*/ +BrowserParent* BrowserParent::GetFrom(PBrowserParent* aBrowserParent) { + return static_cast<BrowserParent*>(aBrowserParent); +} + +/*static*/ +BrowserParent* BrowserParent::GetFrom(nsIContent* aContent) { + RefPtr<nsFrameLoaderOwner> loaderOwner = do_QueryObject(aContent); + if (!loaderOwner) { + return nullptr; + } + RefPtr<nsFrameLoader> frameLoader = loaderOwner->GetFrameLoader(); + return GetFrom(frameLoader); +} + +/* static */ +BrowserParent* BrowserParent::GetBrowserParentFromLayersId( + layers::LayersId aLayersId) { + if (!sLayerToBrowserParentTable) { + return nullptr; + } + return sLayerToBrowserParentTable->Get(uint64_t(aLayersId)); +} + +/*static*/ +TabId BrowserParent::GetTabIdFrom(nsIDocShell* docShell) { + nsCOMPtr<nsIBrowserChild> browserChild(BrowserChild::GetFrom(docShell)); + if (browserChild) { + return static_cast<BrowserChild*>(browserChild.get())->GetTabId(); + } + return TabId(0); +} + +void BrowserParent::AddBrowserParentToTable(layers::LayersId aLayersId, + BrowserParent* aBrowserParent) { + if (!sLayerToBrowserParentTable) { + sLayerToBrowserParentTable = new LayerToBrowserParentTable(); + } + sLayerToBrowserParentTable->InsertOrUpdate(uint64_t(aLayersId), + aBrowserParent); +} + +void BrowserParent::RemoveBrowserParentFromTable(layers::LayersId aLayersId) { + if (!sLayerToBrowserParentTable) { + return; + } + sLayerToBrowserParentTable->Remove(uint64_t(aLayersId)); + if (sLayerToBrowserParentTable->Count() == 0) { + delete sLayerToBrowserParentTable; + sLayerToBrowserParentTable = nullptr; + } +} + +already_AddRefed<nsILoadContext> BrowserParent::GetLoadContext() { + return do_AddRef(mBrowsingContext); +} + +/** + * Will return nullptr if there is no outer window available for the + * document hosting the owner element of this BrowserParent. Also will return + * nullptr if that outer window is in the process of closing. + */ +already_AddRefed<nsPIDOMWindowOuter> BrowserParent::GetParentWindowOuter() { + nsCOMPtr<nsIContent> frame = GetOwnerElement(); + if (!frame) { + return nullptr; + } + + nsCOMPtr<nsPIDOMWindowOuter> parent = frame->OwnerDoc()->GetWindow(); + if (!parent || parent->Closed()) { + return nullptr; + } + + return parent.forget(); +} + +already_AddRefed<nsIWidget> BrowserParent::GetTopLevelWidget() { + if (RefPtr<Element> element = mFrameElement) { + if (PresShell* presShell = element->OwnerDoc()->GetPresShell()) { + return do_AddRef(presShell->GetViewManager()->GetRootWidget()); + } + } + return nullptr; +} + +already_AddRefed<nsIWidget> BrowserParent::GetTextInputHandlingWidget() const { + if (!mFrameElement) { + return nullptr; + } + PresShell* presShell = mFrameElement->OwnerDoc()->GetPresShell(); + if (!presShell) { + return nullptr; + } + nsPresContext* presContext = presShell->GetPresContext(); + if (!presContext) { + return nullptr; + } + nsCOMPtr<nsIWidget> widget = presContext->GetTextInputHandlingWidget(); + return widget.forget(); +} + +already_AddRefed<nsIWidget> BrowserParent::GetWidget() const { + if (!mFrameElement) { + return nullptr; + } + nsCOMPtr<nsIWidget> widget = nsContentUtils::WidgetForContent(mFrameElement); + if (!widget) { + widget = nsContentUtils::WidgetForDocument(mFrameElement->OwnerDoc()); + } + return widget.forget(); +} + +already_AddRefed<nsIWidget> BrowserParent::GetDocWidget() const { + if (!mFrameElement) { + return nullptr; + } + return do_AddRef( + nsContentUtils::WidgetForDocument(mFrameElement->OwnerDoc())); +} + +nsIXULBrowserWindow* BrowserParent::GetXULBrowserWindow() { + if (!mFrameElement) { + return nullptr; + } + + nsCOMPtr<nsIDocShell> docShell = mFrameElement->OwnerDoc()->GetDocShell(); + if (!docShell) { + return nullptr; + } + + nsCOMPtr<nsIDocShellTreeOwner> treeOwner; + docShell->GetTreeOwner(getter_AddRefs(treeOwner)); + if (!treeOwner) { + return nullptr; + } + + nsCOMPtr<nsIAppWindow> window = do_GetInterface(treeOwner); + if (!window) { + return nullptr; + } + + nsCOMPtr<nsIXULBrowserWindow> xulBrowserWindow; + window->GetXULBrowserWindow(getter_AddRefs(xulBrowserWindow)); + return xulBrowserWindow; +} + +uint32_t BrowserParent::GetMaxTouchPoints(Element* aElement) { + if (!aElement) { + return 0; + } + + if (StaticPrefs::dom_maxtouchpoints_testing_value() >= 0) { + return StaticPrefs::dom_maxtouchpoints_testing_value(); + } + + nsIWidget* widget = nsContentUtils::WidgetForDocument(aElement->OwnerDoc()); + return widget ? widget->GetMaxTouchPoints() : 0; +} + +a11y::DocAccessibleParent* BrowserParent::GetTopLevelDocAccessible() const { +#ifdef ACCESSIBILITY + // XXX Consider managing non top level PDocAccessibles with their parent + // document accessible. + const ManagedContainer<PDocAccessibleParent>& docs = + ManagedPDocAccessibleParent(); + for (auto* key : docs) { + auto* doc = static_cast<a11y::DocAccessibleParent*>(key); + // We want the document for this BrowserParent even if it's for an + // embedded out-of-process iframe. Therefore, we use + // IsTopLevelInContentProcess. In contrast, using IsToplevel would only + // include documents that aren't embedded; e.g. tab documents. + if (doc->IsTopLevelInContentProcess() && !doc->IsShutdown()) { + return doc; + } + } +#endif + return nullptr; +} + +LayersId BrowserParent::GetLayersId() const { + if (!mRemoteLayerTreeOwner.IsInitialized()) { + return LayersId{}; + } + return mRemoteLayerTreeOwner.GetLayersId(); +} + +BrowserBridgeParent* BrowserParent::GetBrowserBridgeParent() const { + return mBrowserBridgeParent; +} + +BrowserHost* BrowserParent::GetBrowserHost() const { return mBrowserHost; } + +ParentShowInfo BrowserParent::GetShowInfo() { + TryCacheDPIAndScale(); + if (mFrameElement) { + nsAutoString name; + mFrameElement->GetAttr(nsGkAtoms::name, name); + bool isTransparent = + nsContentUtils::IsChromeDoc(mFrameElement->OwnerDoc()) && + mFrameElement->HasAttr(nsGkAtoms::transparent); + return ParentShowInfo(name, false, isTransparent, mDPI, mRounding, + mDefaultScale.scale); + } + + return ParentShowInfo(u""_ns, false, false, mDPI, mRounding, + mDefaultScale.scale); +} + +already_AddRefed<nsIPrincipal> BrowserParent::GetContentPrincipal() const { + nsCOMPtr<nsIBrowser> browser = + mFrameElement ? mFrameElement->AsBrowser() : nullptr; + NS_ENSURE_TRUE(browser, nullptr); + + RefPtr<nsIPrincipal> principal; + + nsresult rv; + rv = browser->GetContentPrincipal(getter_AddRefs(principal)); + NS_ENSURE_SUCCESS(rv, nullptr); + + return principal.forget(); +} + +void BrowserParent::SetOwnerElement(Element* aElement) { + // If we held previous content then unregister for its events. + RemoveWindowListeners(); + + // If we change top-level documents then we need to change our + // registration with them. + RefPtr<nsPIWindowRoot> curTopLevelWin, newTopLevelWin; + if (mFrameElement) { + curTopLevelWin = nsContentUtils::GetWindowRoot(mFrameElement->OwnerDoc()); + } + if (aElement) { + newTopLevelWin = nsContentUtils::GetWindowRoot(aElement->OwnerDoc()); + } + bool isSameTopLevelWin = curTopLevelWin == newTopLevelWin; + if (mBrowserHost && curTopLevelWin && !isSameTopLevelWin) { + curTopLevelWin->RemoveBrowser(mBrowserHost); + } + + // Update to the new content, and register to listen for events from it. + mFrameElement = aElement; + + if (mBrowserHost && newTopLevelWin && !isSameTopLevelWin) { + newTopLevelWin->AddBrowser(mBrowserHost); + } + +#if defined(XP_WIN) && defined(ACCESSIBILITY) + if (!mIsDestroyed) { + uintptr_t newWindowHandle = 0; + if (nsCOMPtr<nsIWidget> widget = GetWidget()) { + newWindowHandle = + reinterpret_cast<uintptr_t>(widget->GetNativeData(NS_NATIVE_WINDOW)); + } + Unused << SendUpdateNativeWindowHandle(newWindowHandle); + a11y::DocAccessibleParent* doc = GetTopLevelDocAccessible(); + if (doc) { + HWND hWnd = reinterpret_cast<HWND>(doc->GetEmulatedWindowHandle()); + if (hWnd) { + HWND parentHwnd = reinterpret_cast<HWND>(newWindowHandle); + if (parentHwnd != ::GetParent(hWnd)) { + ::SetParent(hWnd, parentHwnd); + } + } + } + } +#endif + + AddWindowListeners(); + + // The DPI depends on our frame element's widget, so invalidate now in case + // we've tried to cache it already. + mDPI = -1; + TryCacheDPIAndScale(); + + if (mRemoteLayerTreeOwner.IsInitialized()) { + mRemoteLayerTreeOwner.OwnerContentChanged(); + } + + // Set our BrowsingContext's embedder if we're not embedded within a + // BrowserBridgeParent. + if (!GetBrowserBridgeParent() && mBrowsingContext && mFrameElement) { + mBrowsingContext->SetEmbedderElement(mFrameElement); + } + + UpdateVsyncParentVsyncDispatcher(); + + VisitChildren([aElement](BrowserBridgeParent* aBrowser) { + if (auto* browserParent = aBrowser->GetBrowserParent()) { + browserParent->SetOwnerElement(aElement); + } + }); +} + +void BrowserParent::CacheFrameLoader(nsFrameLoader* aFrameLoader) { + mFrameLoader = aFrameLoader; +} + +void BrowserParent::AddWindowListeners() { + if (mFrameElement) { + if (nsCOMPtr<nsPIDOMWindowOuter> window = + mFrameElement->OwnerDoc()->GetWindow()) { + nsCOMPtr<EventTarget> eventTarget = window->GetTopWindowRoot(); + if (eventTarget) { + eventTarget->AddEventListener(u"MozUpdateWindowPos"_ns, this, false, + false); + eventTarget->AddEventListener(u"fullscreenchange"_ns, this, false, + false); + } + } + } +} + +void BrowserParent::RemoveWindowListeners() { + if (mFrameElement && mFrameElement->OwnerDoc()->GetWindow()) { + nsCOMPtr<nsPIDOMWindowOuter> window = + mFrameElement->OwnerDoc()->GetWindow(); + nsCOMPtr<EventTarget> eventTarget = window->GetTopWindowRoot(); + if (eventTarget) { + eventTarget->RemoveEventListener(u"MozUpdateWindowPos"_ns, this, false); + eventTarget->RemoveEventListener(u"fullscreenchange"_ns, this, false); + } + } +} + +void BrowserParent::Deactivated() { + if (mShowingTooltip) { + // Reuse the normal tooltip hiding method. + mozilla::Unused << RecvHideTooltip(); + } + UnlockNativePointer(); + UnsetTopLevelWebFocus(this); + UnsetLastMouseRemoteTarget(this); + PointerLockManager::ReleaseLockedRemoteTarget(this); + PointerEventHandler::ReleasePointerCaptureRemoteTarget(this); + PresShell::ReleaseCapturingRemoteTarget(this); + ProcessPriorityManager::BrowserPriorityChanged(this, /* aPriority = */ false); +} + +void BrowserParent::Destroy() { + // Aggressively release the window to avoid leaking the world in shutdown + // corner cases. + mBrowserDOMWindow = nullptr; + + if (mIsDestroyed) { + return; + } + + Deactivated(); + + RemoveWindowListeners(); + +#ifdef ACCESSIBILITY + if (a11y::DocAccessibleParent* tabDoc = GetTopLevelDocAccessible()) { +# if defined(ANDROID) + MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor()); +# endif + tabDoc->Destroy(); + } +#endif + + { + // The following sequence assumes that the keepalive state does not change + // between the calls, but our ThreadsafeHandle might be accessed from other + // threads in the meantime. + RecursiveMutexAutoLock lock(Manager()->ThreadsafeHandleMutex()); + + // If we are shutting down everything or we know to be the last + // BrowserParent, signal the impending shutdown early to the content process + // to avoid to run the SendDestroy before we know we are ExpectingShutdown. + Manager()->NotifyTabWillDestroy(); + + // If this fails, it's most likely due to a content-process crash, and + // auto-cleanup will kick in. Otherwise, the child side will destroy itself + // and send back __delete__(). + (void)SendDestroy(); + mIsDestroyed = true; + + Manager()->NotifyTabDestroying(); + } + + // This `AddKeepAlive` will be cleared if `mMarkedDestroying` is set in + // `ActorDestroy`. Out of caution, we don't add the `KeepAlive` if our IPC + // actor has somehow already been destroyed, as that would mean `ActorDestroy` + // won't be called. + if (CanRecv()) { + mBrowsingContext->Group()->AddKeepAlive(); + } + + mMarkedDestroying = true; +} + +mozilla::ipc::IPCResult BrowserParent::RecvDidUnsuppressPainting() { + if (!mFrameElement) { + return IPC_OK(); + } + nsSubDocumentFrame* subdocFrame = + do_QueryFrame(mFrameElement->GetPrimaryFrame()); + if (subdocFrame && subdocFrame->HasRetainedPaintData()) { + subdocFrame->ClearRetainedPaintData(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvEnsureLayersConnected( + CompositorOptions* aCompositorOptions) { + if (mRemoteLayerTreeOwner.IsInitialized()) { + mRemoteLayerTreeOwner.EnsureLayersConnected(aCompositorOptions); + } + return IPC_OK(); +} + +void BrowserParent::ActorDestroy(ActorDestroyReason why) { + Manager()->NotifyTabDestroyed(mTabId, mMarkedDestroying); + + ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); + if (cpm) { + cpm->UnregisterRemoteFrame(mTabId); + } + + if (mRemoteLayerTreeOwner.IsInitialized()) { + auto layersId = mRemoteLayerTreeOwner.GetLayersId(); + if (mFrameElement) { + nsSubDocumentFrame* f = do_QueryFrame(mFrameElement->GetPrimaryFrame()); + if (f && f->HasRetainedPaintData() && + f->GetRemotePaintData().mLayersId == layersId) { + f->ClearRetainedPaintData(); + } + } + + // It's important to unmap layers after the remote browser has been + // destroyed, otherwise it may still send messages to the compositor which + // will reject them, causing assertions. + RemoveBrowserParentFromTable(layersId); + mRemoteLayerTreeOwner.Destroy(); + } + + // Even though BrowserParent::Destroy calls this, we need to do it here too in + // case of a crash. + Deactivated(); + + if (why == AbnormalShutdown) { + // dom_reporting_header must also be enabled for the report to be sent. + if (StaticPrefs::dom_reporting_crash_enabled()) { + nsCOMPtr<nsIPrincipal> principal = GetContentPrincipal(); + + if (principal) { + nsAutoCString crash_reason; + CrashReporter::GetAnnotation(OtherPid(), + CrashReporter::Annotation::MozCrashReason, + crash_reason); + // FIXME(arenevier): Find a less fragile way to identify that a crash + // was caused by OOM + bool is_oom = false; + if (crash_reason == "OOM" || crash_reason == "OOM!" || + StringBeginsWith(crash_reason, "[unhandlable oom]"_ns) || + StringBeginsWith(crash_reason, "Unhandlable OOM"_ns)) { + is_oom = true; + } + + CrashReport::Deliver(principal, is_oom); + } + } + } + + // If we were shutting down normally, we held a reference to our + // BrowsingContextGroup in `BrowserParent::Destroy`. Clear that reference + // here. + if (mMarkedDestroying) { + mBrowsingContext->Group()->RemoveKeepAlive(); + } + + // Tell our embedder that the tab is now going away unless we're an + // out-of-process iframe. + RefPtr<nsFrameLoader> frameLoader = GetFrameLoader(true); + if (frameLoader) { + ReceiveMessage(CHILD_PROCESS_SHUTDOWN_MESSAGE, false, nullptr); + + if (mBrowsingContext->IsTop()) { + // If this is a top-level BrowsingContext, tell the frameloader it's time + // to go away. Otherwise, this is a subframe crash, and we can keep the + // frameloader around. + frameLoader->DestroyComplete(); + } + + // If this was a crash, tell our nsFrameLoader to fire crash events. + if (why == AbnormalShutdown) { + frameLoader->MaybeNotifyCrashed(mBrowsingContext, Manager()->ChildID(), + GetIPCChannel()); + } else if (why == ManagedEndpointDropped) { + // If we instead failed due to a constructor error, don't include process + // information, as the process did not crash. + frameLoader->MaybeNotifyCrashed(mBrowsingContext, ContentParentId{}, + nullptr); + } + } + + mFrameLoader = nullptr; + + // If we were destroyed due to our ManagedEndpoints being dropped, make a + // point of showing the subframe crashed UI. We don't fire the full + // `MaybeNotifyCrashed` codepath, as the entire process hasn't crashed on us, + // and it may confuse the frontend. + mBrowsingContext->BrowserParentDestroyed( + this, why == AbnormalShutdown || why == ManagedEndpointDropped); +} + +mozilla::ipc::IPCResult BrowserParent::RecvMoveFocus( + const bool& aForward, const bool& aForDocumentNavigation) { + LOGBROWSERFOCUS(("RecvMoveFocus %p, aForward: %d, aForDocumentNavigation: %d", + this, aForward, aForDocumentNavigation)); + BrowserBridgeParent* bridgeParent = GetBrowserBridgeParent(); + if (bridgeParent) { + mozilla::Unused << bridgeParent->SendMoveFocus(aForward, + aForDocumentNavigation); + return IPC_OK(); + } + + RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager(); + if (fm) { + RefPtr<Element> dummy; + + uint32_t type = + aForward + ? (aForDocumentNavigation + ? static_cast<uint32_t>( + nsIFocusManager::MOVEFOCUS_FORWARDDOC) + : static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_FORWARD)) + : (aForDocumentNavigation + ? static_cast<uint32_t>( + nsIFocusManager::MOVEFOCUS_BACKWARDDOC) + : static_cast<uint32_t>( + nsIFocusManager::MOVEFOCUS_BACKWARD)); + fm->MoveFocus(nullptr, mFrameElement, type, nsIFocusManager::FLAG_BYKEY, + getter_AddRefs(dummy)); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvDropLinks( + nsTArray<nsString>&& aLinks) { + nsCOMPtr<nsIBrowser> browser = + mFrameElement ? mFrameElement->AsBrowser() : nullptr; + if (browser) { + // Verify that links have not been modified by the child. If links have + // not been modified then it's safe to load those links using the + // SystemPrincipal. If they have been modified by web content, then + // we use a NullPrincipal which still allows to load web links. + bool loadUsingSystemPrincipal = true; + if (aLinks.Length() != mVerifyDropLinks.Length()) { + loadUsingSystemPrincipal = false; + } + for (uint32_t i = 0; i < aLinks.Length(); i++) { + if (loadUsingSystemPrincipal) { + if (!aLinks[i].Equals(mVerifyDropLinks[i])) { + loadUsingSystemPrincipal = false; + } + } + } + mVerifyDropLinks.Clear(); + nsCOMPtr<nsIPrincipal> triggeringPrincipal; + if (loadUsingSystemPrincipal) { + triggeringPrincipal = nsContentUtils::GetSystemPrincipal(); + } else { + triggeringPrincipal = NullPrincipal::CreateWithoutOriginAttributes(); + } + browser->DropLinks(aLinks, triggeringPrincipal); + } + return IPC_OK(); +} + +bool BrowserParent::SendLoadRemoteScript(const nsAString& aURL, + const bool& aRunInGlobalScope) { + if (mCreatingWindow) { + mDelayedFrameScripts.AppendElement( + FrameScriptInfo(nsString(aURL), aRunInGlobalScope)); + return true; + } + + MOZ_ASSERT(mDelayedFrameScripts.IsEmpty()); + return PBrowserParent::SendLoadRemoteScript(aURL, aRunInGlobalScope); +} + +void BrowserParent::LoadURL(nsDocShellLoadState* aLoadState) { + MOZ_ASSERT(aLoadState); + MOZ_ASSERT(aLoadState->URI()); + if (mIsDestroyed) { + return; + } + + if (mCreatingWindow) { + // Don't send the message if the child wants to load its own URL. + return; + } + + Unused << SendLoadURL(WrapNotNull(aLoadState), GetShowInfo()); +} + +void BrowserParent::ResumeLoad(uint64_t aPendingSwitchID) { + MOZ_ASSERT(aPendingSwitchID != 0); + + if (NS_WARN_IF(mIsDestroyed)) { + return; + } + + Unused << SendResumeLoad(aPendingSwitchID, GetShowInfo()); +} + +void BrowserParent::InitRendering() { + if (mRemoteLayerTreeOwner.IsInitialized()) { + return; + } + mRemoteLayerTreeOwner.Initialize(this); + + layers::LayersId layersId = mRemoteLayerTreeOwner.GetLayersId(); + AddBrowserParentToTable(layersId, this); + + RefPtr<nsFrameLoader> frameLoader = GetFrameLoader(); + if (frameLoader) { + nsIFrame* frame = frameLoader->GetPrimaryFrameOfOwningContent(); + if (frame) { + frame->InvalidateFrame(); + } + } + + TextureFactoryIdentifier textureFactoryIdentifier; + mRemoteLayerTreeOwner.GetTextureFactoryIdentifier(&textureFactoryIdentifier); + Unused << SendInitRendering(textureFactoryIdentifier, layersId, + mRemoteLayerTreeOwner.GetCompositorOptions(), + mRemoteLayerTreeOwner.IsLayersConnected()); + + RefPtr<nsIWidget> widget = GetTopLevelWidget(); + if (widget) { + ScreenIntMargin safeAreaInsets = widget->GetSafeAreaInsets(); + Unused << SendSafeAreaInsetsChanged(safeAreaInsets); + } + +#if defined(MOZ_WIDGET_ANDROID) + MOZ_ASSERT(widget); + + if (GetBrowsingContext()->IsTopContent()) { + Unused << SendDynamicToolbarMaxHeightChanged( + widget->GetDynamicToolbarMaxHeight()); + } +#endif +} + +bool BrowserParent::AttachWindowRenderer() { + return mRemoteLayerTreeOwner.AttachWindowRenderer(); +} + +void BrowserParent::MaybeShowFrame() { + RefPtr<nsFrameLoader> frameLoader = GetFrameLoader(); + if (!frameLoader) { + return; + } + frameLoader->MaybeShowFrame(); +} + +bool BrowserParent::Show(const OwnerShowInfo& aOwnerInfo) { + mDimensions = aOwnerInfo.size(); + if (mIsDestroyed) { + return false; + } + + MOZ_ASSERT(mRemoteLayerTreeOwner.IsInitialized()); + if (!mRemoteLayerTreeOwner.AttachWindowRenderer()) { + return false; + } + + mSizeMode = aOwnerInfo.sizeMode(); + Unused << SendShow(GetShowInfo(), aOwnerInfo); + return true; +} + +mozilla::ipc::IPCResult BrowserParent::RecvSetDimensions( + mozilla::DimensionRequest aRequest, const double& aScale) { + NS_ENSURE_TRUE(mFrameElement, IPC_OK()); + nsCOMPtr<nsIDocShell> docShell = mFrameElement->OwnerDoc()->GetDocShell(); + NS_ENSURE_TRUE(docShell, IPC_OK()); + nsCOMPtr<nsIDocShellTreeOwner> treeOwner; + docShell->GetTreeOwner(getter_AddRefs(treeOwner)); + nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = do_QueryInterface(treeOwner); + NS_ENSURE_TRUE(treeOwnerAsWin, IPC_OK()); + + // `BrowserChild` only sends the values to actually be changed, see more + // details in `BrowserChild::SetDimensions()`. + // Note that `BrowserChild::SetDimensions()` may be called before receiving + // our `SendUIResolutionChanged()` call. Therefore, if given each coordinate + // shouldn't be ignored, we need to recompute it if DPI has been changed. + // And also note that don't use `mDefaultScale.scale` here since it may be + // different from the result of `GetWidgetCSSToDeviceScale()`. + // NOTE(emilio): We use GetWidgetCSSToDeviceScale() because the old scale is a + // widget scale, and we only use the current scale to scale up/down the + // relevant values. + + CSSToLayoutDeviceScale oldScale((float)aScale); + CSSToLayoutDeviceScale currentScale( + (float)treeOwnerAsWin->GetWidgetCSSToDeviceScale()); + + if (oldScale != currentScale) { + auto rescaleFunc = [&oldScale, ¤tScale](LayoutDeviceIntCoord& aVal) { + aVal = (LayoutDeviceCoord(aVal) / oldScale * currentScale).Rounded(); + }; + aRequest.mX.apply(rescaleFunc); + aRequest.mY.apply(rescaleFunc); + aRequest.mWidth.apply(rescaleFunc); + aRequest.mHeight.apply(rescaleFunc); + } + + // treeOwner is the chrome tree owner, but we wan't the content tree owner. + nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = do_GetInterface(treeOwner); + NS_ENSURE_TRUE(webBrowserChrome, IPC_OK()); + webBrowserChrome->SetDimensions(std::move(aRequest)); + return IPC_OK(); +} + +nsresult BrowserParent::UpdatePosition() { + RefPtr<nsFrameLoader> frameLoader = GetFrameLoader(); + if (!frameLoader) { + return NS_OK; + } + nsIntRect windowDims; + NS_ENSURE_SUCCESS(frameLoader->GetWindowDimensions(windowDims), + NS_ERROR_FAILURE); + // Avoid updating sizes here. + windowDims.SizeTo(mRect.Size()); + UpdateDimensions(windowDims, mDimensions); + return NS_OK; +} + +void BrowserParent::NotifyPositionUpdatedForContentsInPopup() { + if (CanonicalBrowsingContext* bc = GetBrowsingContext()) { + bc->PreOrderWalk([](BrowsingContext* aContext) { + if (WindowGlobalParent* windowGlobalParent = + aContext->Canonical()->GetCurrentWindowGlobal()) { + if (RefPtr<BrowserParent> browserParent = + windowGlobalParent->GetBrowserParent()) { + browserParent->UpdatePosition(); + } + } + }); + } +} + +void BrowserParent::UpdateDimensions(const nsIntRect& rect, + const ScreenIntSize& size) { + if (mIsDestroyed) { + return; + } + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (!widget) { + NS_WARNING("No widget found in BrowserParent::UpdateDimensions"); + return; + } + + LayoutDeviceIntPoint clientOffset = GetClientOffset(); + LayoutDeviceIntPoint chromeOffset = !GetBrowserBridgeParent() + ? -GetChildProcessOffset() + : LayoutDeviceIntPoint(); + + if (!mUpdatedDimensions || mDimensions != size || !mRect.IsEqualEdges(rect) || + clientOffset != mClientOffset || chromeOffset != mChromeOffset) { + mUpdatedDimensions = true; + mRect = rect; + mDimensions = size; + mClientOffset = clientOffset; + mChromeOffset = chromeOffset; + + Unused << SendUpdateDimensions(GetDimensionInfo()); + UpdateNativePointerLockCenter(widget); + } +} + +DimensionInfo BrowserParent::GetDimensionInfo() { + LayoutDeviceIntRect devicePixelRect = ViewAs<LayoutDevicePixel>( + mRect, PixelCastJustification::LayoutDeviceIsScreenForTabDims); + LayoutDeviceIntSize devicePixelSize = ViewAs<LayoutDevicePixel>( + mDimensions, PixelCastJustification::LayoutDeviceIsScreenForTabDims); + + CSSRect unscaledRect = devicePixelRect / mDefaultScale; + CSSSize unscaledSize = devicePixelSize / mDefaultScale; + DimensionInfo di(unscaledRect, unscaledSize, mClientOffset, mChromeOffset); + return di; +} + +void BrowserParent::UpdateNativePointerLockCenter(nsIWidget* aWidget) { + if (!mLockedNativePointer) { + return; + } + LayoutDeviceIntRect dims( + {0, 0}, + ViewAs<LayoutDevicePixel>( + mDimensions, PixelCastJustification::LayoutDeviceIsScreenForTabDims)); + aWidget->SetNativePointerLockCenter((dims + mChromeOffset).Center()); +} + +void BrowserParent::SizeModeChanged(const nsSizeMode& aSizeMode) { + if (!mIsDestroyed && aSizeMode != mSizeMode) { + mSizeMode = aSizeMode; + Unused << SendSizeModeChanged(aSizeMode); + } +} + +#if defined(MOZ_WIDGET_ANDROID) +void BrowserParent::DynamicToolbarMaxHeightChanged(ScreenIntCoord aHeight) { + if (!mIsDestroyed) { + Unused << SendDynamicToolbarMaxHeightChanged(aHeight); + } +} + +void BrowserParent::DynamicToolbarOffsetChanged(ScreenIntCoord aOffset) { + if (!mIsDestroyed) { + Unused << SendDynamicToolbarOffsetChanged(aOffset); + } +} +#endif + +void BrowserParent::HandleAccessKey(const WidgetKeyboardEvent& aEvent, + nsTArray<uint32_t>& aCharCodes) { + if (!mIsDestroyed) { + // Note that we don't need to mark aEvent is posted to a remote process + // because the event may be dispatched to it as normal keyboard event. + // Therefore, we should use local copy to send it. + WidgetKeyboardEvent localEvent(aEvent); + RequestingAccessKeyEventData::Set(localEvent); + Unused << SendHandleAccessKey(localEvent, aCharCodes); + } +} + +void BrowserParent::Activate(uint64_t aActionId) { + LOGBROWSERFOCUS(("Activate %p actionid: %" PRIu64, this, aActionId)); + if (!mIsDestroyed) { + SetTopLevelWebFocus(this); // Intentionally inside "if" + Unused << SendActivate(aActionId); + } +} + +void BrowserParent::Deactivate(bool aWindowLowering, uint64_t aActionId) { + LOGBROWSERFOCUS(("Deactivate %p actionid: %" PRIu64, this, aActionId)); + if (!aWindowLowering) { + UnsetTopLevelWebFocus(this); // Intentionally outside the next "if" + } + if (!mIsDestroyed) { + Unused << SendDeactivate(aActionId); + } +} + +#ifdef ACCESSIBILITY +a11y::PDocAccessibleParent* BrowserParent::AllocPDocAccessibleParent( + PDocAccessibleParent* aParent, const uint64_t&, + const MaybeDiscardedBrowsingContext&) { + // Reference freed in DeallocPDocAccessibleParent. + return a11y::DocAccessibleParent::New().take(); +} + +bool BrowserParent::DeallocPDocAccessibleParent(PDocAccessibleParent* aParent) { + // Free reference from AllocPDocAccessibleParent. + static_cast<a11y::DocAccessibleParent*>(aParent)->Release(); + return true; +} + +mozilla::ipc::IPCResult BrowserParent::RecvPDocAccessibleConstructor( + PDocAccessibleParent* aDoc, PDocAccessibleParent* aParentDoc, + const uint64_t& aParentID, + const MaybeDiscardedBrowsingContext& aBrowsingContext) { +# if defined(ANDROID) + MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor()); +# endif + auto doc = static_cast<a11y::DocAccessibleParent*>(aDoc); + + // If this tab is already shutting down just mark the new actor as shutdown + // and ignore it. When the tab actor is destroyed it will be too. + if (mIsDestroyed) { + doc->MarkAsShutdown(); + return IPC_OK(); + } + + if (aParentDoc) { + // Iframe document rendered in the same process as its embedder. + // A document should never directly be the parent of another document. + // There should always be an outer doc accessible child of the outer + // document containing the child. + MOZ_ASSERT(aParentID); + if (!aParentID) { + return IPC_FAIL_NO_REASON(this); + } + + auto parentDoc = static_cast<a11y::DocAccessibleParent*>(aParentDoc); + if (parentDoc->IsShutdown()) { + // This can happen if parentDoc is an OOP iframe, but its embedder has + // been destroyed. (DocAccessibleParent::Destroy destroys any child + // documents.) The OOP iframe (and anything it embeds) will die soon + // anyway, so mark this document as shutdown and ignore it. + doc->MarkAsShutdown(); + return IPC_OK(); + } + + if (aBrowsingContext) { + doc->SetBrowsingContext(aBrowsingContext.get_canonical()); + } + + mozilla::ipc::IPCResult added = parentDoc->AddChildDoc(doc, aParentID); + if (!added) { +# ifdef DEBUG + return added; +# else + return IPC_OK(); +# endif + } + +# ifdef XP_WIN + if (a11y::nsWinUtils::IsWindowEmulationStarted()) { + doc->SetEmulatedWindowHandle(parentDoc->GetEmulatedWindowHandle()); + } +# endif + + return IPC_OK(); + } + + if (aBrowsingContext) { + doc->SetBrowsingContext(aBrowsingContext.get_canonical()); + } + + if (auto* bridge = GetBrowserBridgeParent()) { + // Iframe document rendered in a different process to its embedder. + // In this case, we don't get aParentDoc and aParentID. + MOZ_ASSERT(!aParentDoc && !aParentID); + doc->SetTopLevelInContentProcess(); + a11y::ProxyCreated(doc); + // It's possible the embedder accessible hasn't been set yet; e.g. + // a hidden iframe. In that case, embedderDoc will be null and this will + // be handled when the embedder is set. + if (a11y::DocAccessibleParent* embedderDoc = + bridge->GetEmbedderAccessibleDoc()) { + mozilla::ipc::IPCResult added = embedderDoc->AddChildDoc(bridge); + if (!added) { +# ifdef DEBUG + return added; +# else + return IPC_OK(); +# endif + } + } + return IPC_OK(); + } else { + // null aParentDoc means this document is at the top level in the child + // process. That means it makes no sense to get an id for an accessible + // that is its parent. + MOZ_ASSERT(!aParentID); + if (aParentID) { + return IPC_FAIL_NO_REASON(this); + } + + if (auto* prevTopLevel = GetTopLevelDocAccessible()) { + // Sometimes, we can get a new top level DocAccessibleParent before the + // old one gets destroyed. The old one will die pretty shortly anyway, + // so just destroy it now. If we don't do this, GetTopLevelDocAccessible() + // might return the wrong document for a short while. + prevTopLevel->Destroy(); + } + doc->SetTopLevel(); + a11y::DocManager::RemoteDocAdded(doc); +# ifdef XP_WIN + doc->MaybeInitWindowEmulation(); +# endif + } + return IPC_OK(); +} +#endif + +already_AddRefed<PFilePickerParent> BrowserParent::AllocPFilePickerParent( + const nsString& aTitle, const nsIFilePicker::Mode& aMode) { + return MakeAndAddRef<FilePickerParent>(aTitle, aMode); +} + +already_AddRefed<PSessionStoreParent> +BrowserParent::AllocPSessionStoreParent() { + RefPtr<BrowserSessionStore> sessionStore = + BrowserSessionStore::GetOrCreate(mBrowsingContext->Top()); + if (!sessionStore) { + return nullptr; + } + + return do_AddRef(new SessionStoreParent(mBrowsingContext, sessionStore)); +} + +IPCResult BrowserParent::RecvNewWindowGlobal( + ManagedEndpoint<PWindowGlobalParent>&& aEndpoint, + const WindowGlobalInit& aInit) { + RefPtr<CanonicalBrowsingContext> browsingContext = + CanonicalBrowsingContext::Get(aInit.context().mBrowsingContextId); + if (!browsingContext) { + return IPC_FAIL(this, "Cannot create for missing BrowsingContext"); + } + if (!aInit.principal()) { + return IPC_FAIL(this, "Cannot create without valid principal"); + } + + // Ensure we never load a document with a content principal in + // the wrong type of webIsolated process + EnumSet<ContentParent::ValidatePrincipalOptions> validationOptions = {}; + nsCOMPtr<nsIURI> docURI = aInit.documentURI(); + if (docURI->SchemeIs("blob") || docURI->SchemeIs("chrome")) { + // XXXckerschb TODO - Do not use SystemPrincipal for: + // Bug 1699385: Remove allowSystem for blobs + // Bug 1698087: chrome://devtools/content/shared/webextension-fallback.html + // chrome reftests, e.g. + // * chrome://reftest/content/writing-mode/ua-style-sheet-button-1a-ref.html + // * chrome://reftest/content/xul-document-load/test003.xhtml + // * chrome://reftest/content/forms/input/text/centering-1.xhtml + validationOptions = {ContentParent::ValidatePrincipalOptions::AllowSystem}; + } + + // Some reftests have frames inside their chrome URIs and those load + // about:blank: + if (xpc::IsInAutomation() && docURI->SchemeIs("about")) { + WindowGlobalParent* wgp = browsingContext->GetParentWindowContext(); + nsAutoCString spec; + NS_ENSURE_SUCCESS(docURI->GetSpec(spec), + IPC_FAIL(this, "Should have spec for about: URI")); + if (spec.Equals("about:blank") && wgp && + wgp->DocumentPrincipal()->IsSystemPrincipal()) { + validationOptions = { + ContentParent::ValidatePrincipalOptions::AllowSystem}; + } + } + + if (!mManager->ValidatePrincipal(aInit.principal(), validationOptions)) { + ContentParent::LogAndAssertFailedPrincipalValidationInfo(aInit.principal(), + __func__); + } + + // Construct our new WindowGlobalParent, bind, and initialize it. + RefPtr<WindowGlobalParent> wgp = + WindowGlobalParent::CreateDisconnected(aInit); + BindPWindowGlobalEndpoint(std::move(aEndpoint), wgp); + wgp->Init(); + return IPC_OK(); +} + +PVsyncParent* BrowserParent::AllocPVsyncParent() { + MOZ_ASSERT(!mVsyncParent); + mVsyncParent = new VsyncParent(); + UpdateVsyncParentVsyncDispatcher(); + return mVsyncParent.get(); +} + +bool BrowserParent::DeallocPVsyncParent(PVsyncParent* aActor) { + MOZ_ASSERT(aActor); + mVsyncParent = nullptr; + return true; +} + +void BrowserParent::UpdateVsyncParentVsyncDispatcher() { + if (!mVsyncParent) { + return; + } + + if (nsCOMPtr<nsIWidget> widget = GetWidget()) { + RefPtr<VsyncDispatcher> vsyncDispatcher = widget->GetVsyncDispatcher(); + if (!vsyncDispatcher) { + vsyncDispatcher = gfxPlatform::GetPlatform()->GetGlobalVsyncDispatcher(); + } + mVsyncParent->UpdateVsyncDispatcher(vsyncDispatcher); + } +} + +void BrowserParent::MouseEnterIntoWidget() { + if (nsCOMPtr<nsIWidget> widget = GetWidget()) { + // When we mouseenter the remote target, the remote target's cursor should + // become the current cursor. When we mouseexit, we stop. + mRemoteTargetSetsCursor = true; + widget->SetCursor(mCursor); + EventStateManager::ClearCursorSettingManager(); + } + + // Mark that we have missed a mouse enter event, so that + // the next mouse event will create a replacement mouse + // enter event and send it to the child. + mIsMouseEnterIntoWidgetEventSuppressed = true; +} + +void BrowserParent::SendRealMouseEvent(WidgetMouseEvent& aEvent) { + if (mIsDestroyed) { + return; + } + + // XXXedgar, if the synthesized mouse events could deliver to the correct + // process directly (see + // https://bugzilla.mozilla.org/show_bug.cgi?id=1549355), we probably don't + // need to check mReason then. + if (aEvent.mReason == WidgetMouseEvent::eReal) { + if (aEvent.mMessage == eMouseExitFromWidget) { + // Since we are leaving this remote target, so don't need to update + // sLastMouseRemoteTarget, and if we are sLastMouseRemoteTarget, reset it + // to null. + BrowserParent::UnsetLastMouseRemoteTarget(this); + } else { + // Last remote target should not be changed without eMouseExitFromWidget. + MOZ_ASSERT_IF(sLastMouseRemoteTarget, sLastMouseRemoteTarget == this); + sLastMouseRemoteTarget = this; + } + } + + aEvent.mRefPoint = TransformParentToChild(aEvent); + + if (nsCOMPtr<nsIWidget> widget = GetWidget()) { + // When we mouseenter the remote target, the remote target's cursor should + // become the current cursor. When we mouseexit, we stop. + if (eMouseEnterIntoWidget == aEvent.mMessage) { + mRemoteTargetSetsCursor = true; + widget->SetCursor(mCursor); + EventStateManager::ClearCursorSettingManager(); + } else if (eMouseExitFromWidget == aEvent.mMessage) { + mRemoteTargetSetsCursor = false; + } + } + if (!mIsReadyToHandleInputEvents) { + if (eMouseEnterIntoWidget == aEvent.mMessage) { + mIsMouseEnterIntoWidgetEventSuppressed = true; + } else if (eMouseExitFromWidget == aEvent.mMessage) { + mIsMouseEnterIntoWidgetEventSuppressed = false; + } + return; + } + + ScrollableLayerGuid guid; + uint64_t blockId; + ApzAwareEventRoutingToChild(&guid, &blockId, nullptr); + + bool isInputPriorityEventEnabled = Manager()->IsInputPriorityEventEnabled(); + + if (mIsMouseEnterIntoWidgetEventSuppressed) { + // In the case that the BrowserParent suppressed the eMouseEnterWidget event + // due to its corresponding BrowserChild wasn't ready to handle it, we have + // to resend it when the BrowserChild is ready. + mIsMouseEnterIntoWidgetEventSuppressed = false; + WidgetMouseEvent localEvent(aEvent); + localEvent.mMessage = eMouseEnterIntoWidget; + DebugOnly<bool> ret = + isInputPriorityEventEnabled + ? SendRealMouseEnterExitWidgetEvent(localEvent, guid, blockId) + : SendNormalPriorityRealMouseEnterExitWidgetEvent(localEvent, guid, + blockId); + NS_WARNING_ASSERTION(ret, "SendRealMouseEnterExitWidgetEvent() failed"); + MOZ_ASSERT(!ret || localEvent.HasBeenPostedToRemoteProcess()); + } + + if (eMouseMove == aEvent.mMessage) { + if (aEvent.mReason == WidgetMouseEvent::eSynthesized) { + DebugOnly<bool> ret = + isInputPriorityEventEnabled + ? SendSynthMouseMoveEvent(aEvent, guid, blockId) + : SendNormalPrioritySynthMouseMoveEvent(aEvent, guid, blockId); + NS_WARNING_ASSERTION(ret, "SendSynthMouseMoveEvent() failed"); + MOZ_ASSERT(!ret || aEvent.HasBeenPostedToRemoteProcess()); + return; + } + + if (!aEvent.mFlags.mIsSynthesizedForTests) { + DebugOnly<bool> ret = + isInputPriorityEventEnabled + ? SendRealMouseMoveEvent(aEvent, guid, blockId) + : SendNormalPriorityRealMouseMoveEvent(aEvent, guid, blockId); + NS_WARNING_ASSERTION(ret, "SendRealMouseMoveEvent() failed"); + MOZ_ASSERT(!ret || aEvent.HasBeenPostedToRemoteProcess()); + return; + } + + DebugOnly<bool> ret = + isInputPriorityEventEnabled + ? SendRealMouseMoveEventForTests(aEvent, guid, blockId) + : SendNormalPriorityRealMouseMoveEventForTests(aEvent, guid, + blockId); + NS_WARNING_ASSERTION(ret, "SendRealMouseMoveEventForTests() failed"); + MOZ_ASSERT(!ret || aEvent.HasBeenPostedToRemoteProcess()); + return; + } + + if (eMouseEnterIntoWidget == aEvent.mMessage || + eMouseExitFromWidget == aEvent.mMessage) { + DebugOnly<bool> ret = + isInputPriorityEventEnabled + ? SendRealMouseEnterExitWidgetEvent(aEvent, guid, blockId) + : SendNormalPriorityRealMouseEnterExitWidgetEvent(aEvent, guid, + blockId); + NS_WARNING_ASSERTION(ret, "SendRealMouseEnterExitWidgetEvent() failed"); + MOZ_ASSERT(!ret || aEvent.HasBeenPostedToRemoteProcess()); + return; + } + + DebugOnly<bool> ret = + isInputPriorityEventEnabled + ? SendRealMouseButtonEvent(aEvent, guid, blockId) + : SendNormalPriorityRealMouseButtonEvent(aEvent, guid, blockId); + NS_WARNING_ASSERTION(ret, "SendRealMouseButtonEvent() failed"); + MOZ_ASSERT(!ret || aEvent.HasBeenPostedToRemoteProcess()); +} + +LayoutDeviceToCSSScale BrowserParent::GetLayoutDeviceToCSSScale() { + Document* doc = (mFrameElement ? mFrameElement->OwnerDoc() : nullptr); + nsPresContext* ctx = (doc ? doc->GetPresContext() : nullptr); + return LayoutDeviceToCSSScale( + ctx ? (float)ctx->AppUnitsPerDevPixel() / AppUnitsPerCSSPixel() : 0.0f); +} + +bool BrowserParent::QueryDropLinksForVerification() { + // Before sending the dragEvent, we query the links being dragged and + // store them on the parent, to make sure the child can not modify links. + nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession(); + if (!dragSession) { + NS_WARNING("No dragSession to query links for verification"); + return false; + } + + RefPtr<DataTransfer> initialDataTransfer = dragSession->GetDataTransfer(); + if (!initialDataTransfer) { + NS_WARNING("No initialDataTransfer to query links for verification"); + return false; + } + + nsCOMPtr<nsIDroppedLinkHandler> dropHandler = + do_GetService("@mozilla.org/content/dropped-link-handler;1"); + if (!dropHandler) { + NS_WARNING("No dropHandler to query links for verification"); + return false; + } + + // No more than one drop event can happen simultaneously; reset the link + // verification array and store all links that are being dragged. + mVerifyDropLinks.Clear(); + + nsTArray<RefPtr<nsIDroppedLinkItem>> droppedLinkItems; + dropHandler->QueryLinks(initialDataTransfer, droppedLinkItems); + + // Since the entire event is cancelled if one of the links is invalid, + // we can store all links on the parent side without any prior + // validation checks. + nsresult rv = NS_OK; + for (nsIDroppedLinkItem* item : droppedLinkItems) { + nsString tmp; + rv = item->GetUrl(tmp); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to query url for verification"); + break; + } + mVerifyDropLinks.AppendElement(tmp); + + rv = item->GetName(tmp); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to query name for verification"); + break; + } + mVerifyDropLinks.AppendElement(tmp); + + rv = item->GetType(tmp); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to query type for verification"); + break; + } + mVerifyDropLinks.AppendElement(tmp); + } + if (NS_FAILED(rv)) { + mVerifyDropLinks.Clear(); + return false; + } + return true; +} + +void BrowserParent::SendRealDragEvent(WidgetDragEvent& aEvent, + uint32_t aDragAction, + uint32_t aDropEffect, + nsIPrincipal* aPrincipal, + nsIContentSecurityPolicy* aCsp) { + if (mIsDestroyed || !mIsReadyToHandleInputEvents) { + return; + } + MOZ_ASSERT(!Manager()->IsInputPriorityEventEnabled()); + aEvent.mRefPoint = TransformParentToChild(aEvent.mRefPoint); + if (aEvent.mMessage == eDrop) { + if (!QueryDropLinksForVerification()) { + return; + } + } + DebugOnly<bool> ret = PBrowserParent::SendRealDragEvent( + aEvent, aDragAction, aDropEffect, aPrincipal, aCsp); + NS_WARNING_ASSERTION(ret, "PBrowserParent::SendRealDragEvent() failed"); + MOZ_ASSERT(!ret || aEvent.HasBeenPostedToRemoteProcess()); +} + +void BrowserParent::SendMouseWheelEvent(WidgetWheelEvent& aEvent) { + if (mIsDestroyed || !mIsReadyToHandleInputEvents) { + return; + } + + ScrollableLayerGuid guid; + uint64_t blockId; + ApzAwareEventRoutingToChild(&guid, &blockId, nullptr); + aEvent.mRefPoint = TransformParentToChild(aEvent.mRefPoint); + DebugOnly<bool> ret = + Manager()->IsInputPriorityEventEnabled() + ? PBrowserParent::SendMouseWheelEvent(aEvent, guid, blockId) + : PBrowserParent::SendNormalPriorityMouseWheelEvent(aEvent, guid, + blockId); + + NS_WARNING_ASSERTION(ret, "PBrowserParent::SendMouseWheelEvent() failed"); + MOZ_ASSERT(!ret || aEvent.HasBeenPostedToRemoteProcess()); +} + +mozilla::ipc::IPCResult BrowserParent::RecvDispatchWheelEvent( + const mozilla::WidgetWheelEvent& aEvent) { + NS_ENSURE_TRUE(xpc::IsInAutomation(), IPC_FAIL(this, "Unexpected event")); + + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (!widget) { + return IPC_OK(); + } + + WidgetWheelEvent localEvent(aEvent); + localEvent.mWidget = widget; + localEvent.mRefPoint = TransformChildToParent(localEvent.mRefPoint); + + widget->DispatchInputEvent(&localEvent); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvDispatchMouseEvent( + const mozilla::WidgetMouseEvent& aEvent) { + NS_ENSURE_TRUE(xpc::IsInAutomation(), IPC_FAIL(this, "Unexpected event")); + + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (!widget) { + return IPC_OK(); + } + + WidgetMouseEvent localEvent(aEvent); + localEvent.mWidget = widget; + localEvent.mRefPoint = TransformChildToParent(localEvent.mRefPoint); + + widget->DispatchInputEvent(&localEvent); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvDispatchKeyboardEvent( + const mozilla::WidgetKeyboardEvent& aEvent) { + NS_ENSURE_TRUE(xpc::IsInAutomation(), IPC_FAIL(this, "Unexpected event")); + + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (!widget) { + return IPC_OK(); + } + + WidgetKeyboardEvent localEvent(aEvent); + localEvent.mWidget = widget; + localEvent.mRefPoint = TransformChildToParent(localEvent.mRefPoint); + + widget->DispatchInputEvent(&localEvent); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvDispatchTouchEvent( + const mozilla::WidgetTouchEvent& aEvent) { + NS_ENSURE_TRUE(xpc::IsInAutomation(), IPC_FAIL(this, "Unexpected event")); + + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (!widget) { + return IPC_OK(); + } + + WidgetTouchEvent localEvent(aEvent); + localEvent.mWidget = widget; + + for (uint32_t i = 0; i < localEvent.mTouches.Length(); i++) { + localEvent.mTouches[i]->mRefPoint = + TransformChildToParent(localEvent.mTouches[i]->mRefPoint); + } + + widget->DispatchInputEvent(&localEvent); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvRequestNativeKeyBindings( + const uint32_t& aType, const WidgetKeyboardEvent& aEvent, + nsTArray<CommandInt>* aCommands) { + MOZ_ASSERT(aCommands); + MOZ_ASSERT(aCommands->IsEmpty()); + + NS_ENSURE_TRUE(xpc::IsInAutomation(), IPC_FAIL(this, "Unexpected event")); + + NativeKeyBindingsType keyBindingsType = + static_cast<NativeKeyBindingsType>(aType); + switch (keyBindingsType) { + case NativeKeyBindingsType::SingleLineEditor: + case NativeKeyBindingsType::MultiLineEditor: + case NativeKeyBindingsType::RichTextEditor: + break; + default: + return IPC_FAIL(this, "Invalid aType value"); + } + + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (!widget) { + return IPC_OK(); + } + + WidgetKeyboardEvent localEvent(aEvent); + localEvent.mWidget = widget; + + if (NS_FAILED(widget->AttachNativeKeyEvent(localEvent))) { + return IPC_OK(); + } + + Maybe<WritingMode> writingMode; + if (RefPtr<widget::TextEventDispatcher> dispatcher = + widget->GetTextEventDispatcher()) { + writingMode = dispatcher->MaybeQueryWritingModeAtSelection(); + } + if (localEvent.InitEditCommandsFor(keyBindingsType, writingMode)) { + *aCommands = localEvent.EditCommandsConstRef(keyBindingsType).Clone(); + } + + return IPC_OK(); +} + +class SynthesizedEventObserver : public nsIObserver { + NS_DECL_ISUPPORTS + + public: + SynthesizedEventObserver(BrowserParent* aBrowserParent, + const uint64_t& aObserverId) + : mBrowserParent(aBrowserParent), mObserverId(aObserverId) { + MOZ_ASSERT(mBrowserParent); + } + + NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) override { + if (!mBrowserParent || !mObserverId) { + // We already sent the notification, or we don't actually need to + // send any notification at all. + return NS_OK; + } + + if (mBrowserParent->IsDestroyed()) { + // If this happens it's probably a bug in the test that's triggering this. + NS_WARNING( + "BrowserParent was unexpectedly destroyed during event " + "synthesization!"); + } else if (!mBrowserParent->SendNativeSynthesisResponse( + mObserverId, nsCString(aTopic))) { + NS_WARNING("Unable to send native event synthesization response!"); + } + // Null out browserParent to indicate we already sent the response + mBrowserParent = nullptr; + return NS_OK; + } + + private: + virtual ~SynthesizedEventObserver() = default; + + RefPtr<BrowserParent> mBrowserParent; + uint64_t mObserverId; +}; + +NS_IMPL_ISUPPORTS(SynthesizedEventObserver, nsIObserver) + +class MOZ_STACK_CLASS AutoSynthesizedEventResponder { + public: + AutoSynthesizedEventResponder(BrowserParent* aBrowserParent, + const uint64_t& aObserverId, const char* aTopic) + : mObserver(new SynthesizedEventObserver(aBrowserParent, aObserverId)), + mTopic(aTopic) {} + + ~AutoSynthesizedEventResponder() { + // This may be a no-op if the observer already sent a response. + mObserver->Observe(nullptr, mTopic, nullptr); + } + + nsIObserver* GetObserver() { return mObserver; } + + private: + nsCOMPtr<nsIObserver> mObserver; + const char* mTopic; +}; + +mozilla::ipc::IPCResult BrowserParent::RecvSynthesizeNativeKeyEvent( + const int32_t& aNativeKeyboardLayout, const int32_t& aNativeKeyCode, + const uint32_t& aModifierFlags, const nsString& aCharacters, + const nsString& aUnmodifiedCharacters, const uint64_t& aObserverId) { + NS_ENSURE_TRUE(xpc::IsInAutomation(), IPC_FAIL(this, "Unexpected event")); + + AutoSynthesizedEventResponder responder(this, aObserverId, "keyevent"); + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (widget) { + widget->SynthesizeNativeKeyEvent( + aNativeKeyboardLayout, aNativeKeyCode, aModifierFlags, aCharacters, + aUnmodifiedCharacters, responder.GetObserver()); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvSynthesizeNativeMouseEvent( + const LayoutDeviceIntPoint& aPoint, const uint32_t& aNativeMessage, + const int16_t& aButton, const uint32_t& aModifierFlags, + const uint64_t& aObserverId) { + NS_ENSURE_TRUE(xpc::IsInAutomation(), IPC_FAIL(this, "Unexpected event")); + + const uint32_t last = + static_cast<uint32_t>(nsIWidget::NativeMouseMessage::LeaveWindow); + NS_ENSURE_TRUE(aNativeMessage <= last, IPC_FAIL(this, "Bogus message")); + AutoSynthesizedEventResponder responder(this, aObserverId, "mouseevent"); + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (widget) { + widget->SynthesizeNativeMouseEvent( + aPoint, static_cast<nsIWidget::NativeMouseMessage>(aNativeMessage), + static_cast<mozilla::MouseButton>(aButton), + static_cast<nsIWidget::Modifiers>(aModifierFlags), + responder.GetObserver()); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvSynthesizeNativeMouseMove( + const LayoutDeviceIntPoint& aPoint, const uint64_t& aObserverId) { + // This is used by pointer lock API. So, even if it's not in the automation + // mode, we need to accept the request. + AutoSynthesizedEventResponder responder(this, aObserverId, "mousemove"); + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (widget) { + widget->SynthesizeNativeMouseMove(aPoint, responder.GetObserver()); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvSynthesizeNativeMouseScrollEvent( + const LayoutDeviceIntPoint& aPoint, const uint32_t& aNativeMessage, + const double& aDeltaX, const double& aDeltaY, const double& aDeltaZ, + const uint32_t& aModifierFlags, const uint32_t& aAdditionalFlags, + const uint64_t& aObserverId) { + NS_ENSURE_TRUE(xpc::IsInAutomation(), IPC_FAIL(this, "Unexpected event")); + + AutoSynthesizedEventResponder responder(this, aObserverId, + "mousescrollevent"); + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (widget) { + widget->SynthesizeNativeMouseScrollEvent( + aPoint, aNativeMessage, aDeltaX, aDeltaY, aDeltaZ, aModifierFlags, + aAdditionalFlags, responder.GetObserver()); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvSynthesizeNativeTouchPoint( + const uint32_t& aPointerId, const TouchPointerState& aPointerState, + const LayoutDeviceIntPoint& aPoint, const double& aPointerPressure, + const uint32_t& aPointerOrientation, const uint64_t& aObserverId) { + // This is used by DevTools to emulate touch events from mouse events in the + // responsive design mode. Therefore, we should accept the IPC messages even + // if it's not in the automation mode but the browsing context is in RDM pane. + // And the IPC message could be just delayed after closing the responsive + // design mode. Therefore, we shouldn't return IPC_FAIL since doing it makes + // the tab crash. + if (!xpc::IsInAutomation()) { + NS_ENSURE_TRUE(mBrowsingContext, IPC_OK()); + NS_ENSURE_TRUE(mBrowsingContext->Top()->GetInRDMPane(), IPC_OK()); + } + + AutoSynthesizedEventResponder responder(this, aObserverId, "touchpoint"); + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (widget) { + widget->SynthesizeNativeTouchPoint(aPointerId, aPointerState, aPoint, + aPointerPressure, aPointerOrientation, + responder.GetObserver()); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvSynthesizeNativeTouchPadPinch( + const TouchpadGesturePhase& aEventPhase, const float& aScale, + const LayoutDeviceIntPoint& aPoint, const int32_t& aModifierFlags) { + NS_ENSURE_TRUE(xpc::IsInAutomation(), IPC_FAIL(this, "Unexpected event")); + + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (widget) { + widget->SynthesizeNativeTouchPadPinch(aEventPhase, aScale, aPoint, + aModifierFlags); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvSynthesizeNativeTouchTap( + const LayoutDeviceIntPoint& aPoint, const bool& aLongTap, + const uint64_t& aObserverId) { + NS_ENSURE_TRUE(xpc::IsInAutomation(), IPC_FAIL(this, "Unexpected event")); + + AutoSynthesizedEventResponder responder(this, aObserverId, "touchtap"); + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (widget) { + widget->SynthesizeNativeTouchTap(aPoint, aLongTap, responder.GetObserver()); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvClearNativeTouchSequence( + const uint64_t& aObserverId) { + NS_ENSURE_TRUE(xpc::IsInAutomation(), IPC_FAIL(this, "Unexpected event")); + + AutoSynthesizedEventResponder responder(this, aObserverId, "cleartouch"); + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (widget) { + widget->ClearNativeTouchSequence(responder.GetObserver()); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvSynthesizeNativePenInput( + const uint32_t& aPointerId, const TouchPointerState& aPointerState, + const LayoutDeviceIntPoint& aPoint, const double& aPressure, + const uint32_t& aRotation, const int32_t& aTiltX, const int32_t& aTiltY, + const int32_t& aButton, const uint64_t& aObserverId) { + NS_ENSURE_TRUE(xpc::IsInAutomation(), IPC_FAIL(this, "Unexpected event")); + + AutoSynthesizedEventResponder responder(this, aObserverId, "peninput"); + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (widget) { + widget->SynthesizeNativePenInput(aPointerId, aPointerState, aPoint, + aPressure, aRotation, aTiltX, aTiltY, + aButton, responder.GetObserver()); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvSynthesizeNativeTouchpadDoubleTap( + const LayoutDeviceIntPoint& aPoint, const uint32_t& aModifierFlags) { + NS_ENSURE_TRUE(xpc::IsInAutomation(), IPC_FAIL(this, "Unexpected event")); + + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (widget) { + widget->SynthesizeNativeTouchpadDoubleTap(aPoint, aModifierFlags); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvSynthesizeNativeTouchpadPan( + const TouchpadGesturePhase& aEventPhase, const LayoutDeviceIntPoint& aPoint, + const double& aDeltaX, const double& aDeltaY, const int32_t& aModifierFlags, + const uint64_t& aObserverId) { + NS_ENSURE_TRUE(xpc::IsInAutomation(), IPC_FAIL(this, "Unexpected event")); + + AutoSynthesizedEventResponder responder(this, aObserverId, + "touchpadpanevent"); + + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (widget) { + widget->SynthesizeNativeTouchpadPan(aEventPhase, aPoint, aDeltaX, aDeltaY, + aModifierFlags, + responder.GetObserver()); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvLockNativePointer() { + if (nsCOMPtr<nsIWidget> widget = GetWidget()) { + mLockedNativePointer = true; // do before updating the center + UpdateNativePointerLockCenter(widget); + widget->LockNativePointer(); + } + return IPC_OK(); +} + +void BrowserParent::UnlockNativePointer() { + if (!mLockedNativePointer) { + return; + } + if (nsCOMPtr<nsIWidget> widget = GetWidget()) { + widget->UnlockNativePointer(); + mLockedNativePointer = false; + } +} + +mozilla::ipc::IPCResult BrowserParent::RecvUnlockNativePointer() { + UnlockNativePointer(); + return IPC_OK(); +} + +void BrowserParent::SendRealKeyEvent(WidgetKeyboardEvent& aEvent) { + if (mIsDestroyed || !mIsReadyToHandleInputEvents) { + return; + } + aEvent.mRefPoint = TransformParentToChild(aEvent.mRefPoint); + + // NOTE: If you call `InitAllEditCommands()` for the other messages too, + // you also need to update + // TextEventDispatcher::DispatchKeyboardEventInternal(). + if (aEvent.mMessage == eKeyPress) { + // If current input context is editable, the edit commands are initialized + // by TextEventDispatcher::DispatchKeyboardEventInternal(). Otherwise, + // we need to do it here (they are not necessary for the parent process, + // therefore, we need to do it here for saving the runtime cost). + if (!aEvent.AreAllEditCommandsInitialized()) { + // XXX Is it good thing that the keypress event will be handled in an + // editor even though the user pressed the key combination before the + // focus change has not been completed in the parent process yet or + // focus change will happen? If no, we can stop doing this. + Maybe<WritingMode> writingMode; + if (aEvent.mWidget) { + if (RefPtr<widget::TextEventDispatcher> dispatcher = + aEvent.mWidget->GetTextEventDispatcher()) { + writingMode = dispatcher->MaybeQueryWritingModeAtSelection(); + } + } + aEvent.InitAllEditCommands(writingMode); + } + } else { + aEvent.PreventNativeKeyBindings(); + } + SentKeyEventData sendKeyEventData{ + aEvent.mKeyCode, aEvent.mCharCode, aEvent.mPseudoCharCode, + aEvent.mKeyNameIndex, aEvent.mCodeNameIndex, aEvent.mModifiers, + nsID::GenerateUUID()}; + const bool ok = + Manager()->IsInputPriorityEventEnabled() + ? PBrowserParent::SendRealKeyEvent(aEvent, sendKeyEventData.mUUID) + : PBrowserParent::SendNormalPriorityRealKeyEvent( + aEvent, sendKeyEventData.mUUID); + + NS_WARNING_ASSERTION(ok, "PBrowserParent::SendRealKeyEvent() failed"); + MOZ_ASSERT(!ok || aEvent.HasBeenPostedToRemoteProcess()); + if (ok && aEvent.IsWaitingReplyFromRemoteProcess()) { + mWaitingReplyKeyboardEvents.AppendElement(sendKeyEventData); + } +} + +void BrowserParent::SendRealTouchEvent(WidgetTouchEvent& aEvent) { + if (mIsDestroyed || !mIsReadyToHandleInputEvents) { + return; + } + + // PresShell::HandleEventInternal adds touches on touch end/cancel. This + // confuses remote content and the panning and zooming logic into thinking + // that the added touches are part of the touchend/cancel, when actually + // they're not. + if (aEvent.mMessage == eTouchEnd || aEvent.mMessage == eTouchCancel) { + aEvent.mTouches.RemoveElementsBy( + [](const auto& touch) { return !touch->mChanged; }); + } + + APZData apzData; + ApzAwareEventRoutingToChild(&apzData.guid, &apzData.blockId, + &apzData.apzResponse); + + if (mIsDestroyed) { + return; + } + + for (uint32_t i = 0; i < aEvent.mTouches.Length(); i++) { + aEvent.mTouches[i]->mRefPoint = + TransformParentToChild(aEvent.mTouches[i]->mRefPoint); + } + + static uint32_t sConsecutiveTouchMoveCount = 0; + if (aEvent.mMessage == eTouchMove) { + ++sConsecutiveTouchMoveCount; + SendRealTouchMoveEvent(aEvent, apzData, sConsecutiveTouchMoveCount); + return; + } + + sConsecutiveTouchMoveCount = 0; + DebugOnly<bool> ret = + Manager()->IsInputPriorityEventEnabled() + ? PBrowserParent::SendRealTouchEvent( + aEvent, apzData.guid, apzData.blockId, apzData.apzResponse) + : PBrowserParent::SendNormalPriorityRealTouchEvent( + aEvent, apzData.guid, apzData.blockId, apzData.apzResponse); + + NS_WARNING_ASSERTION(ret, "PBrowserParent::SendRealTouchEvent() failed"); + MOZ_ASSERT(!ret || aEvent.HasBeenPostedToRemoteProcess()); +} + +void BrowserParent::SendRealTouchMoveEvent( + WidgetTouchEvent& aEvent, APZData& aAPZData, + uint32_t aConsecutiveTouchMoveCount) { + // Touchmove handling is complicated, since IPC compression should be used + // only when there are consecutive touch objects for the same touch on the + // same BrowserParent. IPC compression can be disabled by switching to + // different IPC message. + static bool sIPCMessageType1 = true; + static TabId sLastTargetBrowserParent(0); + static Maybe<APZData> sPreviousAPZData; + // Artificially limit max touch points to 10. That should be in practise + // more than enough. + const uint32_t kMaxTouchMoveIdentifiers = 10; + static Maybe<int32_t> sLastTouchMoveIdentifiers[kMaxTouchMoveIdentifiers]; + + // Returns true if aIdentifiers contains all the touches in + // sLastTouchMoveIdentifiers. + auto LastTouchMoveIdentifiersContainedIn = + [&](const nsTArray<int32_t>& aIdentifiers) -> bool { + for (Maybe<int32_t>& entry : sLastTouchMoveIdentifiers) { + if (entry.isSome() && !aIdentifiers.Contains(entry.value())) { + return false; + } + } + return true; + }; + + // Cache touch identifiers in sLastTouchMoveIdentifiers array to be used + // when checking whether compression can be done for the next touchmove. + auto SetLastTouchMoveIdentifiers = + [&](const nsTArray<int32_t>& aIdentifiers) { + for (Maybe<int32_t>& entry : sLastTouchMoveIdentifiers) { + entry.reset(); + } + + MOZ_ASSERT(aIdentifiers.Length() <= kMaxTouchMoveIdentifiers); + for (uint32_t j = 0; j < aIdentifiers.Length(); ++j) { + sLastTouchMoveIdentifiers[j].emplace(aIdentifiers[j]); + } + }; + + AutoTArray<int32_t, kMaxTouchMoveIdentifiers> changedTouches; + bool preventCompression = !StaticPrefs::dom_events_compress_touchmove() || + // Ensure the very first touchmove isn't overridden + // by the second one, so that web pages can get + // accurate coordinates for the first touchmove. + aConsecutiveTouchMoveCount < 3 || + sPreviousAPZData.isNothing() || + sPreviousAPZData.value() != aAPZData || + sLastTargetBrowserParent != GetTabId() || + aEvent.mTouches.Length() > kMaxTouchMoveIdentifiers; + + if (!preventCompression) { + for (RefPtr<Touch>& touch : aEvent.mTouches) { + if (touch->mChanged) { + changedTouches.AppendElement(touch->mIdentifier); + } + } + + // Prevent compression if the new event has fewer or different touches + // than the old one. + preventCompression = !LastTouchMoveIdentifiersContainedIn(changedTouches); + } + + if (preventCompression) { + sIPCMessageType1 = !sIPCMessageType1; + } + + // Update the last touch move identifiers always, so that when the next + // event comes in, the new identifiers can be compared to the old ones. + // If the pref is disabled, this just does a quick small loop. + SetLastTouchMoveIdentifiers(changedTouches); + sPreviousAPZData.reset(); + sPreviousAPZData.emplace(aAPZData); + sLastTargetBrowserParent = GetTabId(); + + DebugOnly<bool> ret = true; + if (sIPCMessageType1) { + ret = + Manager()->IsInputPriorityEventEnabled() + ? PBrowserParent::SendRealTouchMoveEvent( + aEvent, aAPZData.guid, aAPZData.blockId, aAPZData.apzResponse) + : PBrowserParent::SendNormalPriorityRealTouchMoveEvent( + aEvent, aAPZData.guid, aAPZData.blockId, + aAPZData.apzResponse); + } else { + ret = + Manager()->IsInputPriorityEventEnabled() + ? PBrowserParent::SendRealTouchMoveEvent2( + aEvent, aAPZData.guid, aAPZData.blockId, aAPZData.apzResponse) + : PBrowserParent::SendNormalPriorityRealTouchMoveEvent2( + aEvent, aAPZData.guid, aAPZData.blockId, + aAPZData.apzResponse); + } + + NS_WARNING_ASSERTION(ret, "PBrowserParent::SendRealTouchMoveEvent() failed"); + MOZ_ASSERT(!ret || aEvent.HasBeenPostedToRemoteProcess()); +} + +bool BrowserParent::SendHandleTap( + TapType aType, const LayoutDevicePoint& aPoint, Modifiers aModifiers, + const ScrollableLayerGuid& aGuid, uint64_t aInputBlockId, + const Maybe<DoubleTapToZoomMetrics>& aDoubleTapToZoomMetrics) { + if (mIsDestroyed || !mIsReadyToHandleInputEvents) { + return false; + } + if ((aType == TapType::eSingleTap || aType == TapType::eSecondTap)) { + if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) { + if (RefPtr<nsFrameLoader> frameLoader = GetFrameLoader()) { + if (RefPtr<Element> element = frameLoader->GetOwnerContent()) { + fm->SetFocus(element, nsIFocusManager::FLAG_BYMOUSE | + nsIFocusManager::FLAG_BYTOUCH | + nsIFocusManager::FLAG_NOSCROLL); + } + } + } + } + return Manager()->IsInputPriorityEventEnabled() + ? PBrowserParent::SendHandleTap( + aType, TransformParentToChild(aPoint), aModifiers, aGuid, + aInputBlockId, aDoubleTapToZoomMetrics) + : PBrowserParent::SendNormalPriorityHandleTap( + aType, TransformParentToChild(aPoint), aModifiers, aGuid, + aInputBlockId, aDoubleTapToZoomMetrics); +} + +mozilla::ipc::IPCResult BrowserParent::RecvSyncMessage( + const nsString& aMessage, const ClonedMessageData& aData, + nsTArray<StructuredCloneData>* aRetVal) { + AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING("BrowserParent::RecvSyncMessage", + OTHER, aMessage); + MMPrinter::Print("BrowserParent::RecvSyncMessage", aMessage, aData); + + StructuredCloneData data; + ipc::UnpackClonedMessageData(aData, data); + + if (!ReceiveMessage(aMessage, true, &data, aRetVal)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvAsyncMessage( + const nsString& aMessage, const ClonedMessageData& aData) { + AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING("BrowserParent::RecvAsyncMessage", + OTHER, aMessage); + MMPrinter::Print("BrowserParent::RecvAsyncMessage", aMessage, aData); + + StructuredCloneData data; + ipc::UnpackClonedMessageData(aData, data); + + if (!ReceiveMessage(aMessage, false, &data, nullptr)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvSetCursor( + const nsCursor& aCursor, const bool& aHasCustomCursor, + Maybe<BigBuffer>&& aCursorData, const uint32_t& aWidth, + const uint32_t& aHeight, const float& aResolutionX, + const float& aResolutionY, const uint32_t& aStride, + const gfx::SurfaceFormat& aFormat, const uint32_t& aHotspotX, + const uint32_t& aHotspotY, const bool& aForce) { + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (!widget) { + return IPC_OK(); + } + + if (aForce) { + widget->ClearCachedCursor(); + } + + nsCOMPtr<imgIContainer> cursorImage; + if (aHasCustomCursor) { + const bool cursorDataValid = [&] { + if (!aCursorData) { + return false; + } + auto expectedSize = CheckedInt<uint32_t>(aHeight) * aStride; + if (!expectedSize.isValid() || + expectedSize.value() != aCursorData->Size()) { + return false; + } + auto minStride = + CheckedInt<uint32_t>(aWidth) * gfx::BytesPerPixel(aFormat); + if (!minStride.isValid() || aStride < minStride.value()) { + return false; + } + return true; + }(); + if (!cursorDataValid) { + return IPC_FAIL(this, "Invalid custom cursor data"); + } + const gfx::IntSize size(aWidth, aHeight); + RefPtr<gfx::DataSourceSurface> customCursor = + gfx::CreateDataSourceSurfaceFromData(size, aFormat, aCursorData->Data(), + aStride); + + RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(customCursor, size); + cursorImage = image::ImageOps::CreateFromDrawable(drawable); + } + + mCursor = nsIWidget::Cursor{aCursor, + std::move(cursorImage), + aHotspotX, + aHotspotY, + {aResolutionX, aResolutionY}}; + if (!mRemoteTargetSetsCursor) { + return IPC_OK(); + } + + widget->SetCursor(mCursor); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvSetLinkStatus( + const nsString& aStatus) { + nsCOMPtr<nsIXULBrowserWindow> xulBrowserWindow = GetXULBrowserWindow(); + if (!xulBrowserWindow) { + return IPC_OK(); + } + + xulBrowserWindow->SetOverLink(aStatus); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvShowTooltip( + const uint32_t& aX, const uint32_t& aY, const nsString& aTooltip, + const nsString& aDirection) { + nsCOMPtr<nsIXULBrowserWindow> xulBrowserWindow = GetXULBrowserWindow(); + if (!xulBrowserWindow) { + return IPC_OK(); + } + + // ShowTooltip will end up accessing XULElement properties in JS (specifically + // BoxObject). However, to get it to JS, we need to make sure we're a + // nsFrameLoaderOwner, which implies we're a XULFrameElement. We can then + // safely pass Element into JS. + RefPtr<nsFrameLoaderOwner> flo = do_QueryObject(mFrameElement); + if (!flo) return IPC_OK(); + + nsCOMPtr<Element> el = do_QueryInterface(flo); + if (!el) return IPC_OK(); + + if (NS_SUCCEEDED( + xulBrowserWindow->ShowTooltip(aX, aY, aTooltip, aDirection, el))) { + mShowingTooltip = true; + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvHideTooltip() { + mShowingTooltip = false; + + nsCOMPtr<nsIXULBrowserWindow> xulBrowserWindow = GetXULBrowserWindow(); + if (!xulBrowserWindow) { + return IPC_OK(); + } + + xulBrowserWindow->HideTooltip(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvNotifyIMEFocus( + const ContentCache& aContentCache, const IMENotification& aIMENotification, + NotifyIMEFocusResolver&& aResolve) { + if (mIsDestroyed) { + return IPC_OK(); + } + + nsCOMPtr<nsIWidget> widget = GetTextInputHandlingWidget(); + if (!widget) { + aResolve(IMENotificationRequests()); + return IPC_OK(); + } + if (NS_WARN_IF(!aContentCache.IsValid())) { + return IPC_FAIL(this, "Invalid content cache data"); + } + mContentCache.AssignContent(aContentCache, widget, &aIMENotification); + IMEStateManager::NotifyIME(aIMENotification, widget, this); + + IMENotificationRequests requests; + if (aIMENotification.mMessage == NOTIFY_IME_OF_FOCUS) { + requests = widget->IMENotificationRequestsRef(); + } + aResolve(requests); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvNotifyIMETextChange( + const ContentCache& aContentCache, + const IMENotification& aIMENotification) { + nsCOMPtr<nsIWidget> widget = GetTextInputHandlingWidget(); + if (!widget || !IMEStateManager::DoesBrowserParentHaveIMEFocus(this)) { + return IPC_OK(); + } + if (NS_WARN_IF(!aContentCache.IsValid())) { + return IPC_FAIL(this, "Invalid content cache data"); + } + mContentCache.AssignContent(aContentCache, widget, &aIMENotification); + mContentCache.MaybeNotifyIME(widget, aIMENotification); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvNotifyIMECompositionUpdate( + const ContentCache& aContentCache, + const IMENotification& aIMENotification) { + nsCOMPtr<nsIWidget> widget = GetTextInputHandlingWidget(); + if (!widget || !IMEStateManager::DoesBrowserParentHaveIMEFocus(this)) { + return IPC_OK(); + } + if (NS_WARN_IF(!aContentCache.IsValid())) { + return IPC_FAIL(this, "Invalid content cache data"); + } + mContentCache.AssignContent(aContentCache, widget, &aIMENotification); + mContentCache.MaybeNotifyIME(widget, aIMENotification); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvNotifyIMESelection( + const ContentCache& aContentCache, + const IMENotification& aIMENotification) { + nsCOMPtr<nsIWidget> widget = GetTextInputHandlingWidget(); + if (!widget || !IMEStateManager::DoesBrowserParentHaveIMEFocus(this)) { + return IPC_OK(); + } + if (NS_WARN_IF(!aContentCache.IsValid())) { + return IPC_FAIL(this, "Invalid content cache data"); + } + mContentCache.AssignContent(aContentCache, widget, &aIMENotification); + mContentCache.MaybeNotifyIME(widget, aIMENotification); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvUpdateContentCache( + const ContentCache& aContentCache) { + nsCOMPtr<nsIWidget> widget = GetTextInputHandlingWidget(); + if (!widget || !IMEStateManager::DoesBrowserParentHaveIMEFocus(this)) { + return IPC_OK(); + } + if (NS_WARN_IF(!aContentCache.IsValid())) { + return IPC_FAIL(this, "Invalid content cache data"); + } + mContentCache.AssignContent(aContentCache, widget); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvNotifyIMEMouseButtonEvent( + const IMENotification& aIMENotification, bool* aConsumedByIME) { + nsCOMPtr<nsIWidget> widget = GetTextInputHandlingWidget(); + if (!widget || !IMEStateManager::DoesBrowserParentHaveIMEFocus(this)) { + *aConsumedByIME = false; + return IPC_OK(); + } + nsresult rv = IMEStateManager::NotifyIME(aIMENotification, widget, this); + *aConsumedByIME = rv == NS_SUCCESS_EVENT_CONSUMED; + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvNotifyIMEPositionChange( + const ContentCache& aContentCache, + const IMENotification& aIMENotification) { + nsCOMPtr<nsIWidget> widget = GetTextInputHandlingWidget(); + if (!widget || !IMEStateManager::DoesBrowserParentHaveIMEFocus(this)) { + return IPC_OK(); + } + if (NS_WARN_IF(!aContentCache.IsValid())) { + return IPC_FAIL(this, "Invalid content cache data"); + } + mContentCache.AssignContent(aContentCache, widget, &aIMENotification); + mContentCache.MaybeNotifyIME(widget, aIMENotification); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvOnEventNeedingAckHandled( + const EventMessage& aMessage, const uint32_t& aCompositionId) { + // This is called when the child process receives WidgetCompositionEvent or + // WidgetSelectionEvent. + // FYI: Don't check if widget is nullptr here because it's more important to + // notify mContentCahce of this than handling something in it. + nsCOMPtr<nsIWidget> widget = GetTextInputHandlingWidget(); + + // While calling OnEventNeedingAckHandled(), BrowserParent *might* be + // destroyed since it may send notifications to IME. + RefPtr<BrowserParent> kungFuDeathGrip(this); + mContentCache.OnEventNeedingAckHandled(widget, aMessage, aCompositionId); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvRequestFocus( + const bool& aCanRaise, const CallerType aCallerType) { + LOGBROWSERFOCUS(("RecvRequestFocus %p, aCanRaise: %d", this, aCanRaise)); + if (BrowserBridgeParent* bridgeParent = GetBrowserBridgeParent()) { + mozilla::Unused << bridgeParent->SendRequestFocus(aCanRaise, aCallerType); + return IPC_OK(); + } + + if (!mFrameElement) { + return IPC_OK(); + } + + nsContentUtils::RequestFrameFocus(*mFrameElement, aCanRaise, aCallerType); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvWheelZoomChange(bool aIncrease) { + RefPtr<BrowsingContext> bc = GetBrowsingContext(); + if (!bc) { + return IPC_OK(); + } + + bc->Canonical()->DispatchWheelZoomChange(aIncrease); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvEnableDisableCommands( + const MaybeDiscarded<BrowsingContext>& aContext, const nsString& aAction, + nsTArray<nsCString>&& aEnabledCommands, + nsTArray<nsCString>&& aDisabledCommands) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + + nsCOMPtr<nsIBrowserController> browserController = do_QueryActor( + "Controllers", aContext.get_canonical()->GetCurrentWindowGlobal()); + if (browserController) { + browserController->EnableDisableCommands(aAction, aEnabledCommands, + aDisabledCommands); + } + + return IPC_OK(); +} + +LayoutDeviceIntPoint BrowserParent::TransformPoint( + const LayoutDeviceIntPoint& aPoint, + const LayoutDeviceToLayoutDeviceMatrix4x4& aMatrix) { + LayoutDevicePoint floatPoint(aPoint); + LayoutDevicePoint floatTransformed = TransformPoint(floatPoint, aMatrix); + // The next line loses precision if an out-of-process iframe + // has been scaled or rotated. + return RoundedToInt(floatTransformed); +} + +LayoutDevicePoint BrowserParent::TransformPoint( + const LayoutDevicePoint& aPoint, + const LayoutDeviceToLayoutDeviceMatrix4x4& aMatrix) { + return aMatrix.TransformPoint(aPoint); +} + +LayoutDeviceIntPoint BrowserParent::TransformParentToChild( + const WidgetMouseEvent& aEvent) { + MOZ_ASSERT(aEvent.mWidget); + + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (widget && widget != aEvent.mWidget) { + return TransformParentToChild( + aEvent.mRefPoint + + nsLayoutUtils::WidgetToWidgetOffset(aEvent.mWidget, widget)); + } + return TransformParentToChild(aEvent.mRefPoint); +} + +LayoutDeviceIntPoint BrowserParent::TransformParentToChild( + const LayoutDeviceIntPoint& aPoint) { + LayoutDeviceToLayoutDeviceMatrix4x4 matrix = + GetChildToParentConversionMatrix(); + if (!matrix.Invert()) { + return LayoutDeviceIntPoint(); + } + auto transformed = UntransformBy(matrix, aPoint); + if (!transformed) { + return LayoutDeviceIntPoint(); + } + return transformed.ref(); +} + +LayoutDevicePoint BrowserParent::TransformParentToChild( + const LayoutDevicePoint& aPoint) { + LayoutDeviceToLayoutDeviceMatrix4x4 matrix = + GetChildToParentConversionMatrix(); + if (!matrix.Invert()) { + return LayoutDevicePoint(); + } + auto transformed = UntransformBy(matrix, aPoint); + if (!transformed) { + return LayoutDeviceIntPoint(); + } + return transformed.ref(); +} + +LayoutDeviceIntPoint BrowserParent::TransformChildToParent( + const LayoutDeviceIntPoint& aPoint) { + return TransformPoint(aPoint, GetChildToParentConversionMatrix()); +} + +LayoutDevicePoint BrowserParent::TransformChildToParent( + const LayoutDevicePoint& aPoint) { + return TransformPoint(aPoint, GetChildToParentConversionMatrix()); +} + +LayoutDeviceIntRect BrowserParent::TransformChildToParent( + const LayoutDeviceIntRect& aRect) { + LayoutDeviceToLayoutDeviceMatrix4x4 matrix = + GetChildToParentConversionMatrix(); + LayoutDeviceRect floatRect(aRect); + // The outcome is not ideal if an out-of-process iframe has been rotated + LayoutDeviceRect floatTransformed = matrix.TransformBounds(floatRect); + // The next line loses precision if an out-of-process iframe + // has been scaled or rotated. + return RoundedToInt(floatTransformed); +} + +LayoutDeviceToLayoutDeviceMatrix4x4 +BrowserParent::GetChildToParentConversionMatrix() { + if (mChildToParentConversionMatrix) { + return *mChildToParentConversionMatrix; + } + LayoutDevicePoint offset(-GetChildProcessOffset()); + return LayoutDeviceToLayoutDeviceMatrix4x4::Translation(offset); +} + +void BrowserParent::SetChildToParentConversionMatrix( + const Maybe<LayoutDeviceToLayoutDeviceMatrix4x4>& aMatrix, + const ScreenRect& aRemoteDocumentRect) { + if (mChildToParentConversionMatrix == aMatrix && + mRemoteDocumentRect.isSome() && + mRemoteDocumentRect.value() == aRemoteDocumentRect) { + return; + } + + mChildToParentConversionMatrix = aMatrix; + mRemoteDocumentRect = Some(aRemoteDocumentRect); + if (mIsDestroyed) { + return; + } + mozilla::Unused << SendChildToParentMatrix(ToUnknownMatrix(aMatrix), + aRemoteDocumentRect); +} + +LayoutDeviceIntPoint BrowserParent::GetChildProcessOffset() { + // The "toplevel widget" in child processes is always at position + // 0,0. Map the event coordinates to match that. + + LayoutDeviceIntPoint offset(0, 0); + RefPtr<nsFrameLoader> frameLoader = GetFrameLoader(); + if (!frameLoader) { + return offset; + } + nsIFrame* targetFrame = frameLoader->GetPrimaryFrameOfOwningContent(); + if (!targetFrame) { + return offset; + } + + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (!widget) { + return offset; + } + + nsPresContext* presContext = targetFrame->PresContext(); + nsIFrame* rootFrame = presContext->PresShell()->GetRootFrame(); + nsView* rootView = rootFrame ? rootFrame->GetView() : nullptr; + if (!rootView) { + return offset; + } + + // Note that we don't want to take into account transforms here: +#if 0 + nsPoint pt(0, 0); + nsLayoutUtils::TransformPoint(targetFrame, rootFrame, pt); +#endif + // In practice, when transforms are applied to this frameLoader, we currently + // get the wrong results whether we take transforms into account here or not. + // But applying transforms here gives us the wrong results in all + // circumstances when transforms are applied, unless they're purely + // translational. It also gives us the wrong results whenever CSS transitions + // are used to apply transforms, since the offeets aren't updated as the + // transition is animated. + // + // What we actually need to do is apply the transforms to the coordinates of + // any events we send to the child, and reverse them for any screen + // coordinates that we retrieve from the child. + + // TODO: Once we take into account transforms here, set viewportType + // correctly. For now we use Visual as this means we don't apply + // the layout-to-visual transform in TranslateViewToWidget(). + ViewportType viewportType = ViewportType::Visual; + + nsPoint pt = targetFrame->GetOffsetTo(rootFrame); + return -nsLayoutUtils::TranslateViewToWidget(presContext, rootView, pt, + viewportType, widget); +} + +LayoutDeviceIntPoint BrowserParent::GetClientOffset() { + nsCOMPtr<nsIWidget> widget = GetWidget(); + nsCOMPtr<nsIWidget> docWidget = GetDocWidget(); + + if (widget == docWidget) { + return widget->GetClientOffset(); + } + + return (docWidget->GetClientOffset() + + nsLayoutUtils::WidgetToWidgetOffset(widget, docWidget)); +} + +void BrowserParent::StopIMEStateManagement() { + if (mIsDestroyed) { + return; + } + Unused << SendStopIMEStateManagement(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvReplyKeyEvent( + const WidgetKeyboardEvent& aEvent, const nsID& aUUID) { + NS_ENSURE_TRUE(mFrameElement, IPC_OK()); + + // First, verify aEvent is what we've sent to a remote process. + Maybe<size_t> index = [&]() -> Maybe<size_t> { + for (const size_t i : IntegerRange(mWaitingReplyKeyboardEvents.Length())) { + const SentKeyEventData& data = mWaitingReplyKeyboardEvents[i]; + if (data.mUUID.Equals(aUUID)) { + if (NS_WARN_IF(data.mKeyCode != aEvent.mKeyCode) || + NS_WARN_IF(data.mCharCode != aEvent.mCharCode) || + NS_WARN_IF(data.mPseudoCharCode != aEvent.mPseudoCharCode) || + NS_WARN_IF(data.mKeyNameIndex != aEvent.mKeyNameIndex) || + NS_WARN_IF(data.mCodeNameIndex != aEvent.mCodeNameIndex) || + NS_WARN_IF(data.mModifiers != aEvent.mModifiers)) { + // Got different event data from what we stored before dispatching an + // event with the ID. + return Nothing(); + } + return Some(i); + } + } + // No entry found. + return Nothing(); + }(); + if (MOZ_UNLIKELY(index.isNothing())) { + return IPC_FAIL(this, "Bogus reply keyboard event"); + } + // Don't discard the older keyboard events because the order may be changed if + // the remote process has a event listener which takes too long time and while + // the freezing, user may switch the tab, or if the remote process sends + // synchronous XMLHttpRequest. + mWaitingReplyKeyboardEvents.RemoveElementAt(*index); + + // If the event propagation was stopped by the child, it means that the event + // was ignored in the child. In the case, we should ignore it too because the + // focused web app didn't have a chance to prevent its default. + if (aEvent.PropagationStopped()) { + return IPC_OK(); + } + + WidgetKeyboardEvent localEvent(aEvent); + localEvent.MarkAsHandledInRemoteProcess(); + + // Here we convert the WidgetEvent that we received to an Event + // to be able to dispatch it to the <browser> element as the target element. + RefPtr<nsPresContext> presContext = + mFrameElement->OwnerDoc()->GetPresContext(); + NS_ENSURE_TRUE(presContext, IPC_OK()); + + AutoHandlingUserInputStatePusher userInpStatePusher(localEvent.IsTrusted(), + &localEvent); + + nsEventStatus status = nsEventStatus_eIgnore; + + // Handle access key in this process before dispatching reply event because + // ESM handles it before dispatching the event to the DOM tree. + if (localEvent.mMessage == eKeyPress && + (localEvent.ModifiersMatchWithAccessKey(AccessKeyType::eChrome) || + localEvent.ModifiersMatchWithAccessKey(AccessKeyType::eContent))) { + RefPtr<EventStateManager> esm = presContext->EventStateManager(); + AutoTArray<uint32_t, 10> accessCharCodes; + localEvent.GetAccessKeyCandidates(accessCharCodes); + if (esm->HandleAccessKey(&localEvent, presContext, accessCharCodes)) { + status = nsEventStatus_eConsumeNoDefault; + } + } + + RefPtr<Element> frameElement = mFrameElement; + EventDispatcher::Dispatch(frameElement, presContext, &localEvent, nullptr, + &status); + + if (!localEvent.DefaultPrevented() && + !localEvent.mFlags.mIsSynthesizedForTests) { + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (widget) { + widget->PostHandleKeyEvent(&localEvent); + localEvent.StopPropagation(); + } + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvAccessKeyNotHandled( + const WidgetKeyboardEvent& aEvent) { + NS_ENSURE_TRUE(mFrameElement, IPC_OK()); + + // This is called only when this process had focus and HandleAccessKey + // message was posted to all remote process and each remote process didn't + // execute any content access keys. + + if (MOZ_UNLIKELY(aEvent.mMessage != eKeyPress || !aEvent.IsTrusted())) { + return IPC_FAIL(this, "Called with unexpected event"); + } + + // If there is no requesting event, the event may have already been handled + // when it's returned from another remote process. + if (MOZ_UNLIKELY(!RequestingAccessKeyEventData::IsSet())) { + return IPC_OK(); + } + + // If the event does not match with the one which we requested a remote + // process to handle access key of (that means that we has already requested + // for another key press), we should ignore this call because user focuses + // to the last key press. + if (MOZ_UNLIKELY(!RequestingAccessKeyEventData::Equals(aEvent))) { + return IPC_OK(); + } + + RequestingAccessKeyEventData::Clear(); + + WidgetKeyboardEvent localEvent(aEvent); + localEvent.MarkAsHandledInRemoteProcess(); + localEvent.mMessage = eAccessKeyNotFound; + + // Here we convert the WidgetEvent that we received to an Event + // to be able to dispatch it to the <browser> element as the target element. + Document* doc = mFrameElement->OwnerDoc(); + PresShell* presShell = doc->GetPresShell(); + NS_ENSURE_TRUE(presShell, IPC_OK()); + + if (presShell->CanDispatchEvent()) { + RefPtr<nsPresContext> presContext = presShell->GetPresContext(); + NS_ENSURE_TRUE(presContext, IPC_OK()); + + RefPtr<Element> frameElement = mFrameElement; + EventDispatcher::Dispatch(frameElement, presContext, &localEvent); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvRegisterProtocolHandler( + const nsString& aScheme, nsIURI* aHandlerURI, const nsString& aTitle, + nsIURI* aDocURI) { + nsCOMPtr<nsIWebProtocolHandlerRegistrar> registrar = + do_GetService(NS_WEBPROTOCOLHANDLERREGISTRAR_CONTRACTID); + if (registrar) { + registrar->RegisterProtocolHandler(aScheme, aHandlerURI, aTitle, aDocURI, + mFrameElement); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvOnStateChange( + const WebProgressData& aWebProgressData, const RequestData& aRequestData, + const uint32_t aStateFlags, const nsresult aStatus, + const Maybe<WebProgressStateChangeData>& aStateChangeData) { + RefPtr<CanonicalBrowsingContext> browsingContext = + BrowsingContextForWebProgress(aWebProgressData); + if (!browsingContext) { + return IPC_OK(); + } + + nsCOMPtr<nsIRequest> request; + if (aRequestData.requestURI()) { + request = MakeAndAddRef<RemoteWebProgressRequest>( + aRequestData.requestURI(), aRequestData.originalRequestURI(), + aRequestData.matchedList()); + } + + if (aStateChangeData.isSome()) { + if (!browsingContext->IsTopContent()) { + return IPC_FAIL( + this, + "Unexpected WebProgressStateChangeData for non toplevel webProgress"); + } + + if (nsCOMPtr<nsIBrowser> browser = GetBrowser()) { + Unused << browser->SetIsNavigating(aStateChangeData->isNavigating()); + Unused << browser->SetMayEnableCharacterEncodingMenu( + aStateChangeData->mayEnableCharacterEncodingMenu()); + Unused << browser->UpdateForStateChange(aStateChangeData->charset(), + aStateChangeData->documentURI(), + aStateChangeData->contentType()); + } + } + + if (auto* listener = browsingContext->GetWebProgress()) { + listener->OnStateChange(listener, request, aStateFlags, aStatus); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvOnProgressChange( + const int32_t aCurTotalProgress, const int32_t aMaxTotalProgress) { + // We only collect progress change notifications for the toplevel + // BrowserParent. + // FIXME: In the future, consider merging in progress change information from + // oop subframes. + if (!GetBrowsingContext()->IsTopContent() || + !GetBrowsingContext()->GetWebProgress()) { + return IPC_OK(); + } + + GetBrowsingContext()->GetWebProgress()->OnProgressChange( + nullptr, nullptr, 0, 0, aCurTotalProgress, aMaxTotalProgress); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvOnLocationChange( + const WebProgressData& aWebProgressData, const RequestData& aRequestData, + nsIURI* aLocation, const uint32_t aFlags, const bool aCanGoBack, + const bool aCanGoForward, + const Maybe<WebProgressLocationChangeData>& aLocationChangeData) { + RefPtr<CanonicalBrowsingContext> browsingContext = + BrowsingContextForWebProgress(aWebProgressData); + if (!browsingContext) { + return IPC_OK(); + } + + nsCOMPtr<nsIRequest> request; + if (aRequestData.requestURI()) { + request = MakeAndAddRef<RemoteWebProgressRequest>( + aRequestData.requestURI(), aRequestData.originalRequestURI(), + aRequestData.matchedList()); + } + + browsingContext->SetCurrentRemoteURI(aLocation); + + nsCOMPtr<nsIBrowser> browser = GetBrowser(); + if (!mozilla::SessionHistoryInParent() && browser) { + Unused << browser->UpdateWebNavigationForLocationChange(aCanGoBack, + aCanGoForward); + } + + if (aLocationChangeData.isSome()) { + if (!browsingContext->IsTopContent()) { + return IPC_FAIL(this, + "Unexpected WebProgressLocationChangeData for non " + "toplevel webProgress"); + } + + if (browser) { + Unused << browser->SetIsNavigating(aLocationChangeData->isNavigating()); + Unused << browser->UpdateForLocationChange( + aLocation, aLocationChangeData->charset(), + aLocationChangeData->mayEnableCharacterEncodingMenu(), + aLocationChangeData->documentURI(), aLocationChangeData->title(), + aLocationChangeData->contentPrincipal(), + aLocationChangeData->contentPartitionedPrincipal(), + aLocationChangeData->csp(), aLocationChangeData->referrerInfo(), + aLocationChangeData->isSyntheticDocument(), + aLocationChangeData->requestContextID().isSome(), + aLocationChangeData->requestContextID().valueOr(0), + aLocationChangeData->contentType()); + } + } + + if (auto* listener = browsingContext->GetWebProgress()) { + listener->OnLocationChange(listener, request, aLocation, aFlags); + } + + // Since we've now changed Documents, notify the BrowsingContext that we've + // changed. Ideally we'd just let the BrowsingContext do this when it changes + // the current window global, but that happens before this and we have a lot + // of tests that depend on the specific ordering of messages. + if (browsingContext->IsTopContent() && + !(aFlags & nsIWebProgressListener::LOCATION_CHANGE_SAME_DOCUMENT)) { + browsingContext->UpdateSecurityState(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvOnStatusChange( + const nsString& aMessage) { + // We only collect status change notifications for the toplevel + // BrowserParent. + // FIXME: In the future, consider merging in status change information from + // oop subframes. + if (!GetBrowsingContext()->IsTopContent() || + !GetBrowsingContext()->GetWebProgress()) { + return IPC_OK(); + } + + GetBrowsingContext()->GetWebProgress()->OnStatusChange(nullptr, nullptr, + NS_OK, aMessage.get()); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvNavigationFinished() { + nsCOMPtr<nsIBrowser> browser = + mFrameElement ? mFrameElement->AsBrowser() : nullptr; + + if (browser) { + browser->SetIsNavigating(false); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvNotifyContentBlockingEvent( + const uint32_t& aEvent, const RequestData& aRequestData, + const bool aBlocked, const nsACString& aTrackingOrigin, + nsTArray<nsCString>&& aTrackingFullHashes, + const Maybe< + mozilla::ContentBlockingNotifier::StorageAccessPermissionGrantedReason>& + aReason, + const Maybe<mozilla::ContentBlockingNotifier::CanvasFingerprinter>& + aCanvasFingerprinter, + const Maybe<bool>& aCanvasFingerprinterKnownText) { + RefPtr<BrowsingContext> bc = GetBrowsingContext(); + + if (!bc || bc->IsDiscarded()) { + return IPC_OK(); + } + + // Get the top-level browsing context. + bc = bc->Top(); + RefPtr<dom::WindowGlobalParent> wgp = + bc->Canonical()->GetCurrentWindowGlobal(); + + // The WindowGlobalParent would be null while running the test + // browser_339445.js. This is unexpected and we will address this in a + // following bug. For now, we first workaround this issue. + if (!wgp) { + return IPC_OK(); + } + + nsCOMPtr<nsIRequest> request = MakeAndAddRef<RemoteWebProgressRequest>( + aRequestData.requestURI(), aRequestData.originalRequestURI(), + aRequestData.matchedList()); + + wgp->NotifyContentBlockingEvent( + aEvent, request, aBlocked, aTrackingOrigin, aTrackingFullHashes, aReason, + aCanvasFingerprinter, aCanvasFingerprinterKnownText); + + return IPC_OK(); +} + +already_AddRefed<nsIBrowser> BrowserParent::GetBrowser() { + nsCOMPtr<nsIBrowser> browser; + RefPtr<Element> currentElement = mFrameElement; + + // In Responsive Design Mode, mFrameElement will be the <iframe mozbrowser>, + // but we want the <xul:browser> that it is embedded in. + while (currentElement) { + browser = currentElement->AsBrowser(); + if (browser) { + break; + } + + BrowsingContext* browsingContext = + currentElement->OwnerDoc()->GetBrowsingContext(); + currentElement = + browsingContext ? browsingContext->GetEmbedderElement() : nullptr; + } + + return browser.forget(); +} + +already_AddRefed<CanonicalBrowsingContext> +BrowserParent::BrowsingContextForWebProgress( + const WebProgressData& aWebProgressData) { + // Look up the BrowsingContext which this notification was fired for. + if (aWebProgressData.browsingContext().IsNullOrDiscarded()) { + NS_WARNING("WebProgress Ignored: BrowsingContext is null or discarded"); + return nullptr; + } + RefPtr<CanonicalBrowsingContext> browsingContext = + aWebProgressData.browsingContext().get_canonical(); + + // Double-check that we actually manage this BrowsingContext, and are not + // receiving a malformed or out-of-date request. browsingContext should either + // be the toplevel one managed by this BrowserParent, or embedded within a + // WindowGlobalParent managed by this BrowserParent. + if (browsingContext != mBrowsingContext) { + WindowGlobalParent* embedder = browsingContext->GetParentWindowContext(); + if (!embedder || embedder->GetBrowserParent() != this) { + NS_WARNING("WebProgress Ignored: wrong embedder process"); + return nullptr; + } + } + + // The current process for this BrowsingContext may have changed since the + // notification was fired. Don't fire events for it anymore, as ownership of + // the BrowsingContext has been moved elsewhere. + if (RefPtr<WindowGlobalParent> current = + browsingContext->GetCurrentWindowGlobal(); + current && current->GetBrowserParent() != this) { + NS_WARNING("WebProgress Ignored: no longer current window global"); + return nullptr; + } + + if (RefPtr<BrowsingContextWebProgress> progress = + browsingContext->GetWebProgress()) { + progress->SetLoadType(aWebProgressData.loadType()); + } + + return browsingContext.forget(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvIntrinsicSizeOrRatioChanged( + const Maybe<IntrinsicSize>& aIntrinsicSize, + const Maybe<AspectRatio>& aIntrinsicRatio) { + BrowserBridgeParent* bridge = GetBrowserBridgeParent(); + if (!bridge || !bridge->CanSend()) { + return IPC_OK(); + } + + Unused << bridge->SendIntrinsicSizeOrRatioChanged(aIntrinsicSize, + aIntrinsicRatio); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvImageLoadComplete( + const nsresult& aResult) { + BrowserBridgeParent* bridge = GetBrowserBridgeParent(); + if (!bridge || !bridge->CanSend()) { + return IPC_OK(); + } + + Unused << bridge->SendImageLoadComplete(aResult); + + return IPC_OK(); +} + +bool BrowserParent::HandleQueryContentEvent(WidgetQueryContentEvent& aEvent) { + nsCOMPtr<nsIWidget> textInputHandlingWidget = GetTextInputHandlingWidget(); + if (!textInputHandlingWidget) { + return true; + } + if (!mContentCache.HandleQueryContentEvent(aEvent, textInputHandlingWidget) || + NS_WARN_IF(aEvent.Failed())) { + return true; + } + switch (aEvent.mMessage) { + case eQueryTextRect: + case eQueryCaretRect: + case eQueryEditorRect: { + nsCOMPtr<nsIWidget> browserWidget = GetWidget(); + if (browserWidget != textInputHandlingWidget) { + aEvent.mReply->mRect += nsLayoutUtils::WidgetToWidgetOffset( + browserWidget, textInputHandlingWidget); + } + aEvent.mReply->mRect = TransformChildToParent(aEvent.mReply->mRect); + break; + } + default: + break; + } + return true; +} + +bool BrowserParent::SendCompositionEvent(WidgetCompositionEvent& aEvent, + uint32_t aCompositionId) { + if (mIsDestroyed) { + return false; + } + + // When the composition is handled in a remote process, we need to handle + // commit/cancel result for composition with the composition ID to avoid + // to abort newer composition. Therefore, we need to let the remote process + // know the composition ID. + MOZ_ASSERT(aCompositionId != 0); + aEvent.mCompositionId = aCompositionId; + + if (!mContentCache.OnCompositionEvent(aEvent)) { + return true; + } + + bool ret = Manager()->IsInputPriorityEventEnabled() + ? PBrowserParent::SendCompositionEvent(aEvent) + : PBrowserParent::SendNormalPriorityCompositionEvent(aEvent); + if (NS_WARN_IF(!ret)) { + return false; + } + MOZ_ASSERT(aEvent.HasBeenPostedToRemoteProcess()); + return true; +} + +bool BrowserParent::SendSelectionEvent(WidgetSelectionEvent& aEvent) { + if (mIsDestroyed) { + return false; + } + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (!widget) { + return true; + } + mContentCache.OnSelectionEvent(aEvent); + bool ret = Manager()->IsInputPriorityEventEnabled() + ? PBrowserParent::SendSelectionEvent(aEvent) + : PBrowserParent::SendNormalPrioritySelectionEvent(aEvent); + if (NS_WARN_IF(!ret)) { + return false; + } + MOZ_ASSERT(aEvent.HasBeenPostedToRemoteProcess()); + aEvent.mSucceeded = true; + return true; +} + +bool BrowserParent::SendInsertText(const nsString& aStringToInsert) { + if (mIsDestroyed) { + return false; + } + return Manager()->IsInputPriorityEventEnabled() + ? PBrowserParent::SendInsertText(aStringToInsert) + : PBrowserParent::SendNormalPriorityInsertText(aStringToInsert); +} + +bool BrowserParent::SendPasteTransferable(IPCTransferable&& aTransferable) { + return PBrowserParent::SendPasteTransferable(std::move(aTransferable)); +} + +/* static */ +void BrowserParent::SetTopLevelWebFocus(BrowserParent* aBrowserParent) { + BrowserParent* old = GetFocused(); + if (aBrowserParent && !aBrowserParent->GetBrowserBridgeParent()) { + // top-level Web content + sTopLevelWebFocus = aBrowserParent; + BrowserParent* bp = UpdateFocus(); + if (old != bp) { + LOGBROWSERFOCUS( + ("SetTopLevelWebFocus updated focus; old: %p, new: %p", old, bp)); + IMEStateManager::OnFocusMovedBetweenBrowsers(old, bp); + } + } +} + +/* static */ +void BrowserParent::UnsetTopLevelWebFocus(BrowserParent* aBrowserParent) { + BrowserParent* old = GetFocused(); + if (sTopLevelWebFocus == aBrowserParent) { + // top-level Web content + sTopLevelWebFocus = nullptr; + sFocus = nullptr; + if (old) { + LOGBROWSERFOCUS( + ("UnsetTopLevelWebFocus moved focus to chrome; old: %p", old)); + IMEStateManager::OnFocusMovedBetweenBrowsers(old, nullptr); + } + } +} + +/* static */ +void BrowserParent::UpdateFocusFromBrowsingContext() { + BrowserParent* old = GetFocused(); + BrowserParent* bp = UpdateFocus(); + if (old != bp) { + LOGBROWSERFOCUS( + ("UpdateFocusFromBrowsingContext updated focus; old: %p, new: %p", old, + bp)); + IMEStateManager::OnFocusMovedBetweenBrowsers(old, bp); + } +} + +/* static */ +BrowserParent* BrowserParent::UpdateFocus() { + if (!sTopLevelWebFocus) { + sFocus = nullptr; + return nullptr; + } + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + BrowsingContext* bc = fm->GetFocusedBrowsingContextInChrome(); + if (bc) { + BrowsingContext* top = bc->Top(); + MOZ_ASSERT(top, "Should always have a top BrowsingContext."); + CanonicalBrowsingContext* canonicalTop = top->Canonical(); + MOZ_ASSERT(canonicalTop, + "Casting to canonical should always be possible in the parent " + "process (top case)."); + WindowGlobalParent* globalTop = canonicalTop->GetCurrentWindowGlobal(); + if (globalTop) { + RefPtr<BrowserParent> globalTopParent = globalTop->GetBrowserParent(); + if (sTopLevelWebFocus == globalTopParent) { + CanonicalBrowsingContext* canonical = bc->Canonical(); + MOZ_ASSERT( + canonical, + "Casting to canonical should always be possible in the parent " + "process."); + WindowGlobalParent* global = canonical->GetCurrentWindowGlobal(); + if (global) { + RefPtr<BrowserParent> parent = global->GetBrowserParent(); + sFocus = parent; + return sFocus; + } + LOGBROWSERFOCUS( + ("Focused BrowsingContext did not have WindowGlobalParent.")); + } + } else { + LOGBROWSERFOCUS( + ("Top-level BrowsingContext did not have WindowGlobalParent.")); + } + } + } + sFocus = sTopLevelWebFocus; + return sFocus; +} + +/* static */ +void BrowserParent::UnsetTopLevelWebFocusAll() { + if (sTopLevelWebFocus) { + UnsetTopLevelWebFocus(sTopLevelWebFocus); + } +} + +/* static */ +void BrowserParent::UnsetLastMouseRemoteTarget(BrowserParent* aBrowserParent) { + if (sLastMouseRemoteTarget == aBrowserParent) { + sLastMouseRemoteTarget = nullptr; + } +} + +mozilla::ipc::IPCResult BrowserParent::RecvRequestIMEToCommitComposition( + const bool& aCancel, const uint32_t& aCompositionId, bool* aIsCommitted, + nsString* aCommittedString) { + nsCOMPtr<nsIWidget> widget = GetTextInputHandlingWidget(); + if (!widget) { + *aIsCommitted = false; + return IPC_OK(); + } + + *aIsCommitted = mContentCache.RequestIMEToCommitComposition( + widget, aCancel, aCompositionId, *aCommittedString); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvGetInputContext( + widget::IMEState* aState) { + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (!widget) { + *aState = widget::IMEState(IMEEnabled::Disabled, + IMEState::OPEN_STATE_NOT_SUPPORTED); + return IPC_OK(); + } + + *aState = widget->GetInputContext().mIMEState; + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvSetInputContext( + const InputContext& aContext, const InputContextAction& aAction) { + IMEStateManager::SetInputContextForChildProcess(this, aContext, aAction); + return IPC_OK(); +} + +bool BrowserParent::ReceiveMessage(const nsString& aMessage, bool aSync, + StructuredCloneData* aData, + nsTArray<StructuredCloneData>* aRetVal) { + // If we're for an oop iframe, don't deliver messages to the wrong place. + if (mBrowserBridgeParent) { + return true; + } + + RefPtr<nsFrameLoader> frameLoader = GetFrameLoader(true); + if (frameLoader && frameLoader->GetFrameMessageManager()) { + RefPtr<nsFrameMessageManager> manager = + frameLoader->GetFrameMessageManager(); + + manager->ReceiveMessage(mFrameElement, frameLoader, aMessage, aSync, aData, + aRetVal, IgnoreErrors()); + } + return true; +} + +// nsIAuthPromptProvider + +// This method is largely copied from nsDocShell::GetAuthPrompt +NS_IMETHODIMP +BrowserParent::GetAuthPrompt(uint32_t aPromptReason, const nsIID& iid, + void** aResult) { + // we're either allowing auth, or it's a proxy request + nsresult rv; + nsCOMPtr<nsIPromptFactory> wwatch = + do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsPIDOMWindowOuter> window; + RefPtr<Element> frame = mFrameElement; + if (frame) window = frame->OwnerDoc()->GetWindow(); + + // Get an auth prompter for our window so that the parenting + // of the dialogs works as it should when using tabs. + nsCOMPtr<nsISupports> prompt; + rv = wwatch->GetPrompt(window, iid, getter_AddRefs(prompt)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsILoginManagerAuthPrompter> prompter = do_QueryInterface(prompt); + if (prompter) { + prompter->SetBrowser(mFrameElement); + } + + *aResult = prompt.forget().take(); + return NS_OK; +} + +already_AddRefed<PColorPickerParent> BrowserParent::AllocPColorPickerParent( + const nsString& aTitle, const nsString& aInitialColor, + const nsTArray<nsString>& aDefaultColors) { + return MakeAndAddRef<ColorPickerParent>(aTitle, aInitialColor, + aDefaultColors); +} + +already_AddRefed<nsFrameLoader> BrowserParent::GetFrameLoader( + bool aUseCachedFrameLoaderAfterDestroy) const { + if (mIsDestroyed && !aUseCachedFrameLoaderAfterDestroy) { + return nullptr; + } + + if (mFrameLoader) { + RefPtr<nsFrameLoader> fl = mFrameLoader; + return fl.forget(); + } + RefPtr<Element> frameElement(mFrameElement); + RefPtr<nsFrameLoaderOwner> frameLoaderOwner = do_QueryObject(frameElement); + return frameLoaderOwner ? frameLoaderOwner->GetFrameLoader() : nullptr; +} + +void BrowserParent::TryCacheDPIAndScale() { + if (mDPI > 0) { + return; + } + + const auto oldDefaultScale = mDefaultScale; + nsCOMPtr<nsIWidget> widget = GetWidget(); + mDPI = widget ? widget->GetDPI() : nsIWidget::GetFallbackDPI(); + mRounding = widget ? widget->RoundsWidgetCoordinatesTo() : 1; + mDefaultScale = + widget ? widget->GetDefaultScale() : nsIWidget::GetFallbackDefaultScale(); + + if (mDefaultScale != oldDefaultScale) { + // The change of the default scale factor will affect the child dimensions + // so we need to invalidate it. + mUpdatedDimensions = false; + } +} + +void BrowserParent::ApzAwareEventRoutingToChild( + ScrollableLayerGuid* aOutTargetGuid, uint64_t* aOutInputBlockId, + nsEventStatus* aOutApzResponse) { + // Let the widget know that the event will be sent to the child process, + // which will (hopefully) send a confirmation notice back to APZ. + // Do this even if APZ is off since we need it for swipe gesture support on + // OS X without APZ. + InputAPZContext::SetRoutedToChildProcess(); + + if (AsyncPanZoomEnabled()) { + if (aOutTargetGuid) { + *aOutTargetGuid = InputAPZContext::GetTargetLayerGuid(); + + // There may be cases where the APZ hit-testing code came to a different + // conclusion than the main-thread hit-testing code as to where the event + // is destined. In such cases the layersId of the APZ result may not match + // the layersId of this RemoteLayerTreeOwner. In such cases the + // main-thread hit- testing code "wins" so we need to update the guid to + // reflect this. + if (mRemoteLayerTreeOwner.IsInitialized()) { + if (aOutTargetGuid->mLayersId != mRemoteLayerTreeOwner.GetLayersId()) { + *aOutTargetGuid = + ScrollableLayerGuid(mRemoteLayerTreeOwner.GetLayersId(), 0, + ScrollableLayerGuid::NULL_SCROLL_ID); + } + } + } + if (aOutInputBlockId) { + *aOutInputBlockId = InputAPZContext::GetInputBlockId(); + } + if (aOutApzResponse) { + *aOutApzResponse = InputAPZContext::GetApzResponse(); + + // We can get here without there being an InputAPZContext on the stack + // if a non-native event synthesization function (such as + // nsIDOMWindowUtils.sendTouchEvent()) was used in the parent process to + // synthesize an event that's targeting a content process. Such events do + // not go through APZ. Without an InputAPZContext on the stack we pick up + // the default value "eSentinel" which cannot be sent over IPC, so replace + // it with "eIgnore" instead, which what APZ uses when it ignores an + // event. If a caller needs the ability to synthesize a event with a + // different APZ response, a native event synthesization function (such as + // sendNativeTouchPoint()) can be used. + if (*aOutApzResponse == nsEventStatus_eSentinel) { + *aOutApzResponse = nsEventStatus_eIgnore; + } + } + } else { + if (aOutInputBlockId) { + *aOutInputBlockId = 0; + } + if (aOutApzResponse) { + *aOutApzResponse = nsEventStatus_eIgnore; + } + } +} + +mozilla::ipc::IPCResult BrowserParent::RecvRespondStartSwipeEvent( + const uint64_t& aInputBlockId, const bool& aStartSwipe) { + if (nsCOMPtr<nsIWidget> widget = GetWidget()) { + widget->ReportSwipeStarted(aInputBlockId, aStartSwipe); + } + return IPC_OK(); +} + +bool BrowserParent::GetDocShellIsActive() { + return mBrowsingContext && mBrowsingContext->IsActive(); +} + +bool BrowserParent::GetHasPresented() { return mHasPresented; } + +bool BrowserParent::GetHasLayers() { return mHasLayers; } + +bool BrowserParent::GetRenderLayers() { return mRenderLayers; } + +void BrowserParent::SetRenderLayers(bool aEnabled) { + if (aEnabled == mRenderLayers) { + return; + } + + // Preserve layers means that attempts to stop rendering layers + // will be ignored. + if (!aEnabled && mIsPreservingLayers) { + return; + } + + mRenderLayers = aEnabled; + + SetRenderLayersInternal(aEnabled); +} + +void BrowserParent::SetRenderLayersInternal(bool aEnabled) { + Unused << SendRenderLayers(aEnabled); + + // Ask the child to repaint/unload layers using the PHangMonitor + // channel/thread (which may be less congested). + if (aEnabled) { + Manager()->PaintTabWhileInterruptingJS(this); + } else { + Manager()->UnloadLayersWhileInterruptingJS(this); + } +} + +bool BrowserParent::GetPriorityHint() { return mPriorityHint; } + +void BrowserParent::SetPriorityHint(bool aPriorityHint) { + mPriorityHint = aPriorityHint; + RecomputeProcessPriority(); +} + +void BrowserParent::RecomputeProcessPriority() { + auto* bc = GetBrowsingContext(); + ProcessPriorityManager::BrowserPriorityChanged( + bc, bc->IsActive() || mPriorityHint); +} + +void BrowserParent::PreserveLayers(bool aPreserveLayers) { + if (mIsPreservingLayers == aPreserveLayers) { + return; + } + mIsPreservingLayers = aPreserveLayers; + Unused << SendPreserveLayers(aPreserveLayers); +} + +void BrowserParent::NotifyResolutionChanged() { + if (mIsDestroyed) { + return; + } + // TryCacheDPIAndScale()'s cache is keyed off of + // mDPI being greater than 0, so this invalidates it. + mDPI = -1; + TryCacheDPIAndScale(); + // If mDPI was set to -1 to invalidate it and then TryCacheDPIAndScale + // fails to cache the values, then mDefaultScale.scale might be invalid. + // We don't want to send that value to content. Just send -1 for it too in + // that case. + Unused << SendUIResolutionChanged(mDPI, mRounding, + mDPI < 0 ? -1.0 : mDefaultScale.scale); +} + +bool BrowserParent::CanCancelContentJS( + nsIRemoteTab::NavigationType aNavigationType, int32_t aNavigationIndex, + nsIURI* aNavigationURI) const { + // Pre-checking if we can cancel content js in the parent is only + // supported when session history in the parent is enabled. + if (!mozilla::SessionHistoryInParent()) { + // If session history in the parent isn't enabled, this check will + // be fully done in BrowserChild::CanCancelContentJS + return true; + } + + nsCOMPtr<nsISHistory> history = mBrowsingContext->GetSessionHistory(); + + if (!history) { + // If there is no history we can't possibly know if it's ok to + // cancel content js. + return false; + } + + int32_t current; + NS_ENSURE_SUCCESS(history->GetIndex(¤t), false); + + if (current == -1) { + // This tab has no history! Just return. + return false; + } + + nsCOMPtr<nsISHEntry> entry; + NS_ENSURE_SUCCESS(history->GetEntryAtIndex(current, getter_AddRefs(entry)), + false); + + nsCOMPtr<nsIURI> currentURI = entry->GetURI(); + if (!currentURI->SchemeIs("http") && !currentURI->SchemeIs("https") && + !currentURI->SchemeIs("file")) { + // Only cancel content JS for http(s) and file URIs. Other URIs are probably + // internal and we should just let them run to completion. + return false; + } + + if (aNavigationType == nsIRemoteTab::NAVIGATE_BACK) { + aNavigationIndex = current - 1; + } else if (aNavigationType == nsIRemoteTab::NAVIGATE_FORWARD) { + aNavigationIndex = current + 1; + } else if (aNavigationType == nsIRemoteTab::NAVIGATE_URL) { + if (!aNavigationURI) { + return false; + } + + if (aNavigationURI->SchemeIs("javascript")) { + // "javascript:" URIs don't (necessarily) trigger navigation to a + // different page, so don't allow the current page's JS to terminate. + return false; + } + + // If navigating directly to a URL (e.g. via hitting Enter in the location + // bar), then we can cancel anytime the next URL is different from the + // current, *excluding* the ref ("#"). + bool equals; + NS_ENSURE_SUCCESS(currentURI->EqualsExceptRef(aNavigationURI, &equals), + false); + return !equals; + } + // Note: aNavigationType may also be NAVIGATE_INDEX, in which case we don't + // need to do anything special. + + int32_t delta = aNavigationIndex > current ? 1 : -1; + for (int32_t i = current + delta; i != aNavigationIndex + delta; i += delta) { + nsCOMPtr<nsISHEntry> nextEntry; + // If `i` happens to be negative, this call will fail (which is what we + // would want to happen). + NS_ENSURE_SUCCESS(history->GetEntryAtIndex(i, getter_AddRefs(nextEntry)), + false); + + nsCOMPtr<nsISHEntry> laterEntry = delta == 1 ? nextEntry : entry; + nsCOMPtr<nsIURI> thisURI = entry->GetURI(); + nsCOMPtr<nsIURI> nextURI = nextEntry->GetURI(); + + // If we changed origin and the load wasn't in a subframe, we know it was + // a full document load, so we can cancel the content JS safely. + if (!laterEntry->GetIsSubFrame()) { + nsAutoCString thisHost; + NS_ENSURE_SUCCESS(thisURI->GetPrePath(thisHost), false); + + nsAutoCString nextHost; + NS_ENSURE_SUCCESS(nextURI->GetPrePath(nextHost), false); + + if (!thisHost.Equals(nextHost)) { + return true; + } + } + + entry = nextEntry; + } + + return false; +} + +void BrowserParent::SuppressDisplayport(bool aEnabled) { + if (IsDestroyed()) { + return; + } + +#ifdef DEBUG + if (aEnabled) { + mActiveSupressDisplayportCount++; + } else { + mActiveSupressDisplayportCount--; + } + MOZ_ASSERT(mActiveSupressDisplayportCount >= 0); +#endif + + Unused << SendSuppressDisplayport(aEnabled); +} + +void BrowserParent::NavigateByKey(bool aForward, bool aForDocumentNavigation) { + Unused << SendNavigateByKey(aForward, aForDocumentNavigation); +} + +void BrowserParent::LayerTreeUpdate(bool aActive) { + if (NS_WARN_IF(mHasLayers == aActive)) { + return; + } + mHasPresented |= aActive; + mHasLayers = aActive; + if (GetBrowserBridgeParent()) { + // Ignore updates if we're an out-of-process iframe. For oop iframes, our + // |mFrameElement| is that of the top-level document, and so + // AsyncTabSwitcher will treat MozLayerTreeReady / MozLayerTreeCleared + // events as if they came from the top-level tab, which is wrong. + return; + } + + if (mIsDestroyed) { + return; + } + + RefPtr<Element> frameElement = mFrameElement; + if (NS_WARN_IF(!frameElement)) { + return; + } + + RefPtr<Event> event = NS_NewDOMEvent(frameElement, nullptr, nullptr); + if (aActive) { + event->InitEvent(u"MozLayerTreeReady"_ns, true, false); + } else { + event->InitEvent(u"MozLayerTreeCleared"_ns, true, false); + } + event->SetTrusted(true); + event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true; + frameElement->DispatchEvent(*event); +} + +mozilla::ipc::IPCResult BrowserParent::RecvRemoteIsReadyToHandleInputEvents() { + // When enabling input event prioritization, input events may preempt other + // normal priority IPC messages. To prevent the input events preempt + // PBrowserConstructor, we use an IPC 'RemoteIsReadyToHandleInputEvents' to + // notify the parent that BrowserChild is created and ready to handle input + // events. + SetReadyToHandleInputEvents(); + return IPC_OK(); +} + +PPaymentRequestParent* BrowserParent::AllocPPaymentRequestParent() { + RefPtr<PaymentRequestParent> actor = new PaymentRequestParent(); + return actor.forget().take(); +} + +bool BrowserParent::DeallocPPaymentRequestParent( + PPaymentRequestParent* aActor) { + RefPtr<PaymentRequestParent> actor = + dont_AddRef(static_cast<PaymentRequestParent*>(aActor)); + return true; +} + +nsresult BrowserParent::HandleEvent(Event* aEvent) { + if (mIsDestroyed) { + return NS_OK; + } + + nsAutoString eventType; + aEvent->GetType(eventType); + if (eventType.EqualsLiteral("MozUpdateWindowPos") || + eventType.EqualsLiteral("fullscreenchange")) { + // Events that signify the window moving are used to update the position + // and notify the BrowserChild. + return UpdatePosition(); + } + return NS_OK; +} + +mozilla::ipc::IPCResult BrowserParent::RecvInvokeDragSession( + nsTArray<IPCTransferableData>&& aTransferables, const uint32_t& aAction, + Maybe<BigBuffer>&& aVisualDnDData, const uint32_t& aStride, + const gfx::SurfaceFormat& aFormat, const LayoutDeviceIntRect& aDragRect, + nsIPrincipal* aPrincipal, nsIContentSecurityPolicy* aCsp, + const CookieJarSettingsArgs& aCookieJarSettingsArgs, + const MaybeDiscarded<WindowContext>& aSourceWindowContext, + const MaybeDiscarded<WindowContext>& aSourceTopWindowContext) { + PresShell* presShell = mFrameElement->OwnerDoc()->GetPresShell(); + if (!presShell) { + Unused << Manager()->SendEndDragSession( + true, true, LayoutDeviceIntPoint(), 0, + nsIDragService::DRAGDROP_ACTION_NONE); + // Continue sending input events with input priority when stopping the dnd + // session. + Manager()->SetInputPriorityEventEnabled(true); + return IPC_OK(); + } + + nsCOMPtr<nsICookieJarSettings> cookieJarSettings; + net::CookieJarSettings::Deserialize(aCookieJarSettingsArgs, + getter_AddRefs(cookieJarSettings)); + + RefPtr<RemoteDragStartData> dragStartData = new RemoteDragStartData( + this, std::move(aTransferables), aDragRect, aPrincipal, aCsp, + cookieJarSettings, aSourceWindowContext.GetMaybeDiscarded(), + aSourceTopWindowContext.GetMaybeDiscarded()); + + if (aVisualDnDData) { + const auto checkedSize = CheckedInt<size_t>(aDragRect.height) * aStride; + if (checkedSize.isValid() && + aVisualDnDData->Size() >= checkedSize.value()) { + dragStartData->SetVisualization(gfx::CreateDataSourceSurfaceFromData( + gfx::IntSize(aDragRect.width, aDragRect.height), aFormat, + aVisualDnDData->Data(), aStride)); + } + } + + nsCOMPtr<nsIDragService> dragService = + do_GetService("@mozilla.org/widget/dragservice;1"); + if (dragService) { + dragService->MaybeAddChildProcess(Manager()); + } + + presShell->GetPresContext() + ->EventStateManager() + ->BeginTrackingRemoteDragGesture(mFrameElement, dragStartData); + + return IPC_OK(); +} + +bool BrowserParent::AsyncPanZoomEnabled() const { + nsCOMPtr<nsIWidget> widget = GetWidget(); + return widget && widget->AsyncPanZoomEnabled(); +} + +void BrowserParent::StartPersistence( + CanonicalBrowsingContext* aContext, + nsIWebBrowserPersistDocumentReceiver* aRecv, ErrorResult& aRv) { + RefPtr<WebBrowserPersistDocumentParent> actor = + new WebBrowserPersistDocumentParent(); + actor->SetOnReady(aRecv); + bool ok = Manager()->SendPWebBrowserPersistDocumentConstructor(actor, this, + aContext); + if (!ok) { + aRv.Throw(NS_ERROR_FAILURE); + } + // (The actor will be destroyed on constructor failure.) +} + +mozilla::ipc::IPCResult BrowserParent::RecvLookUpDictionary( + const nsString& aText, nsTArray<FontRange>&& aFontRangeArray, + const bool& aIsVertical, const LayoutDeviceIntPoint& aPoint) { + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (!widget) { + return IPC_OK(); + } + + widget->LookUpDictionary(aText, aFontRangeArray, aIsVertical, + TransformChildToParent(aPoint)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvShowCanvasPermissionPrompt( + const nsCString& aOrigin, const bool& aHideDoorHanger) { + nsCOMPtr<nsIBrowser> browser = + mFrameElement ? mFrameElement->AsBrowser() : nullptr; + if (!browser) { + // If the tab is being closed, the browser may not be available. + // In this case we can ignore the request. + return IPC_OK(); + } + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + if (!os) { + return IPC_FAIL_NO_REASON(this); + } + nsresult rv = os->NotifyObservers( + browser, + aHideDoorHanger ? "canvas-permissions-prompt-hide-doorhanger" + : "canvas-permissions-prompt", + NS_ConvertUTF8toUTF16(aOrigin).get()); + if (NS_FAILED(rv)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvVisitURI( + nsIURI* aURI, nsIURI* aLastVisitedURI, const uint32_t& aFlags, + const uint64_t& aBrowserId) { + if (!aURI) { + return IPC_FAIL_NO_REASON(this); + } + RefPtr<nsIWidget> widget = GetWidget(); + if (NS_WARN_IF(!widget)) { + return IPC_OK(); + } + nsCOMPtr<IHistory> history = components::History::Service(); + if (history) { + Unused << history->VisitURI(widget, aURI, aLastVisitedURI, aFlags, + aBrowserId); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvQueryVisitedState( + nsTArray<RefPtr<nsIURI>>&& aURIs) { +#ifdef MOZ_ANDROID_HISTORY + nsCOMPtr<IHistory> history = components::History::Service(); + if (NS_WARN_IF(!history)) { + return IPC_OK(); + } + RefPtr<nsIWidget> widget = GetWidget(); + if (NS_WARN_IF(!widget)) { + return IPC_OK(); + } + + // FIXME(emilio): Is this check really needed? + for (nsIURI* uri : aURIs) { + if (!uri) { + return IPC_FAIL(this, "Received null URI"); + } + } + + auto* gvHistory = static_cast<GeckoViewHistory*>(history.get()); + gvHistory->QueryVisitedState(widget, mManager, std::move(aURIs)); + return IPC_OK(); +#else + return IPC_FAIL(this, "QueryVisitedState is Android-only"); +#endif +} + +void BrowserParent::LiveResizeStarted() { SuppressDisplayport(true); } + +void BrowserParent::LiveResizeStopped() { SuppressDisplayport(false); } + +void BrowserParent::SetBrowserBridgeParent(BrowserBridgeParent* aBrowser) { + // We should either be clearing out our reference to a browser bridge, or not + // have either a browser bridge, browser host, or owner content yet. + MOZ_ASSERT(!aBrowser || + (!mBrowserBridgeParent && !mBrowserHost && !mFrameElement)); + mBrowserBridgeParent = aBrowser; +} + +void BrowserParent::SetBrowserHost(BrowserHost* aBrowser) { + // We should either be clearing out our reference to a browser host, or not + // have either a browser bridge, browser host, or owner content yet. + MOZ_ASSERT(!aBrowser || + (!mBrowserBridgeParent && !mBrowserHost && !mFrameElement)); + mBrowserHost = aBrowser; +} + +mozilla::ipc::IPCResult BrowserParent::RecvSetSystemFont( + const nsCString& aFontName) { + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (widget) { + widget->SetSystemFont(aFontName); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvGetSystemFont(nsCString* aFontName) { + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (widget) { + widget->GetSystemFont(*aFontName); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvMaybeFireEmbedderLoadEvents( + EmbedderElementEventType aFireEventAtEmbeddingElement) { + BrowserBridgeParent* bridge = GetBrowserBridgeParent(); + if (!bridge) { + NS_WARNING("Received `load` event on unbridged BrowserParent!"); + return IPC_OK(); + } + + Unused << bridge->SendMaybeFireEmbedderLoadEvents( + aFireEventAtEmbeddingElement); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvScrollRectIntoView( + const nsRect& aRect, const ScrollAxis& aVertical, + const ScrollAxis& aHorizontal, const ScrollFlags& aScrollFlags, + const int32_t& aAppUnitsPerDevPixel) { + BrowserBridgeParent* bridge = GetBrowserBridgeParent(); + if (!bridge || !bridge->CanSend()) { + return IPC_OK(); + } + + Unused << bridge->SendScrollRectIntoView(aRect, aVertical, aHorizontal, + aScrollFlags, aAppUnitsPerDevPixel); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvIsWindowSupportingProtectedMedia( + const uint64_t& aOuterWindowID, + IsWindowSupportingProtectedMediaResolver&& aResolve) { +#ifdef XP_WIN + bool isFxrWindow = + FxRWindowManager::GetInstance()->IsFxRWindow(aOuterWindowID); + aResolve(!isFxrWindow); +#else +# ifdef FUZZING_SNAPSHOT + return IPC_FAIL(this, "Should only be called on Windows"); +# endif + MOZ_CRASH("Should only be called on Windows"); +#endif + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvIsWindowSupportingWebVR( + const uint64_t& aOuterWindowID, + IsWindowSupportingWebVRResolver&& aResolve) { +#ifdef XP_WIN + bool isFxrWindow = + FxRWindowManager::GetInstance()->IsFxRWindow(aOuterWindowID); + aResolve(!isFxrWindow); +#else + aResolve(true); +#endif + + return IPC_OK(); +} + +static BrowserParent* GetTopLevelBrowserParent(BrowserParent* aBrowserParent) { + MOZ_ASSERT(aBrowserParent); + BrowserParent* parent = aBrowserParent; + while (BrowserBridgeParent* bridge = parent->GetBrowserBridgeParent()) { + parent = bridge->Manager(); + } + return parent; +} + +mozilla::ipc::IPCResult BrowserParent::RecvRequestPointerLock( + RequestPointerLockResolver&& aResolve) { + nsCString error; + if (sTopLevelWebFocus != GetTopLevelBrowserParent(this)) { + error = "PointerLockDeniedNotFocused"; + } else if (!PointerLockManager::SetLockedRemoteTarget(this)) { + error = "PointerLockDeniedInUse"; + } else { + PointerEventHandler::ReleaseAllPointerCaptureRemoteTarget(); + } + aResolve(error); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvReleasePointerLock() { + MOZ_ASSERT_IF(PointerLockManager::GetLockedRemoteTarget(), + PointerLockManager::GetLockedRemoteTarget() == this); + PointerLockManager::ReleaseLockedRemoteTarget(this); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvRequestPointerCapture( + const uint32_t& aPointerId, RequestPointerCaptureResolver&& aResolve) { + aResolve( + PointerEventHandler::SetPointerCaptureRemoteTarget(aPointerId, this)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvReleasePointerCapture( + const uint32_t& aPointerId) { + PointerEventHandler::ReleasePointerCaptureRemoteTarget(aPointerId); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvShowDynamicToolbar() { +#if defined(MOZ_WIDGET_ANDROID) + nsCOMPtr<nsIWidget> widget = GetTopLevelWidget(); + if (!widget) { + return IPC_OK(); + } + + RefPtr<nsWindow> window = nsWindow::From(widget); + if (!window) { + return IPC_OK(); + } + + window->ShowDynamicToolbar(); +#endif // defined(MOZ_WIDGET_ANDROID) + return IPC_OK(); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/ipc/BrowserParent.h b/dom/ipc/BrowserParent.h new file mode 100644 index 0000000000..4d4ae287b4 --- /dev/null +++ b/dom/ipc/BrowserParent.h @@ -0,0 +1,1013 @@ +/* -*- 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_dom_BrowserParent_h +#define mozilla_dom_BrowserParent_h + +#include <utility> + +#include "LiveResizeListener.h" +#include "Units.h" +#include "js/TypeDecls.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/ContentCache.h" +#include "mozilla/EventForwards.h" +#include "mozilla/RefPtr.h" +#include "mozilla/dom/BrowserBridgeParent.h" +#include "mozilla/dom/PBrowserParent.h" +#include "mozilla/dom/TabContext.h" +#include "mozilla/dom/VsyncParent.h" +#include "mozilla/dom/ipc/IdType.h" +#include "mozilla/layout/RemoteLayerTreeOwner.h" +#include "nsCOMPtr.h" +#include "nsIAuthPromptProvider.h" +#include "nsIBrowserDOMWindow.h" +#include "nsIDOMEventListener.h" +#include "nsIFilePicker.h" +#include "nsIRemoteTab.h" +#include "nsIWidget.h" +#include "nsTArray.h" +#include "nsWeakReference.h" + +class imgIContainer; +class nsCycleCollectionTraversalCallback; +class nsDocShellLoadState; +class nsFrameLoader; +class nsIBrowser; +class nsIContent; +class nsIContentSecurityPolicy; +class nsIDocShell; +class nsILoadContext; +class nsIPrincipal; +class nsIRequest; +class nsIURI; +class nsIWebBrowserPersistDocumentReceiver; +class nsIWebProgress; +class nsIXULBrowserWindow; +class nsPIDOMWindowOuter; + +namespace mozilla { + +namespace a11y { +class DocAccessibleParent; +} + +namespace widget { +struct IMENotification; +} // namespace widget + +namespace gfx { +class SourceSurface; +} // namespace gfx + +namespace dom { + +class CanonicalBrowsingContext; +class ClonedMessageData; +class ContentParent; +class Element; +class DataTransfer; +class BrowserHost; +class BrowserBridgeParent; + +namespace ipc { +class StructuredCloneData; +} // namespace ipc + +/** + * BrowserParent implements the parent actor part of the PBrowser protocol. See + * PBrowser for more information. + */ +class BrowserParent final : public PBrowserParent, + public nsIDOMEventListener, + public nsIAuthPromptProvider, + public nsSupportsWeakReference, + public TabContext, + public LiveResizeListener { + typedef mozilla::dom::ClonedMessageData ClonedMessageData; + using TapType = GeckoContentController_TapType; + + friend class PBrowserParent; + + virtual ~BrowserParent(); + + public: + // Helper class for ContentParent::RecvCreateWindow. + struct AutoUseNewTab; + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_NSIAUTHPROMPTPROVIDER + // nsIDOMEventListener interfaces + NS_DECL_NSIDOMEVENTLISTENER + + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(BrowserParent, nsIDOMEventListener) + + BrowserParent(ContentParent* aManager, const TabId& aTabId, + const TabContext& aContext, + CanonicalBrowsingContext* aBrowsingContext, + uint32_t aChromeFlags); + + /** + * Returns the focused BrowserParent or nullptr if chrome or another app + * is focused. + */ + static BrowserParent* GetFocused(); + + static BrowserParent* GetLastMouseRemoteTarget(); + + static BrowserParent* GetFrom(nsFrameLoader* aFrameLoader); + + static BrowserParent* GetFrom(PBrowserParent* aBrowserParent); + + static BrowserParent* GetFrom(nsIContent* aContent); + + static BrowserParent* GetBrowserParentFromLayersId( + layers::LayersId aLayersId); + + static TabId GetTabIdFrom(nsIDocShell* docshell); + + const TabId GetTabId() const { return mTabId; } + + ContentParent* Manager() const { return mManager; } + + CanonicalBrowsingContext* GetBrowsingContext() { return mBrowsingContext; } + + void RecomputeProcessPriority(); + + already_AddRefed<nsILoadContext> GetLoadContext(); + + Element* GetOwnerElement() const { return mFrameElement; } + + nsIBrowserDOMWindow* GetBrowserDOMWindow() const { return mBrowserDOMWindow; } + + already_AddRefed<nsPIDOMWindowOuter> GetParentWindowOuter(); + + already_AddRefed<nsIWidget> GetTopLevelWidget(); + + // Returns the closest widget for our frameloader's content. + already_AddRefed<nsIWidget> GetWidget() const; + + // Returns the top-level widget for our frameloader's document. + already_AddRefed<nsIWidget> GetDocWidget() const; + + /** + * Returns the widget which may have native focus and handles text input + * like keyboard input, IME, etc. + */ + already_AddRefed<nsIWidget> GetTextInputHandlingWidget() const; + + nsIXULBrowserWindow* GetXULBrowserWindow(); + + static uint32_t GetMaxTouchPoints(Element* aElement); + uint32_t GetMaxTouchPoints() { return GetMaxTouchPoints(mFrameElement); } + + /** + * Return the top level DocAccessibleParent for this BrowserParent. + * Note that in the case of an out-of-process iframe, the returned actor + * might not be at the top level of the DocAccessibleParent tree; i.e. it + * might have a parent. However, it will be at the top level in its content + * process. That is, doc->IsTopLevelInContentProcess() will always be true, + * but doc->IsTopLevel() might not. + */ + a11y::DocAccessibleParent* GetTopLevelDocAccessible() const; + + LayersId GetLayersId() const; + + // Returns the BrowserBridgeParent if this BrowserParent is for an + // out-of-process iframe and nullptr otherwise. + BrowserBridgeParent* GetBrowserBridgeParent() const; + + // Returns the BrowserHost if this BrowserParent is for a top-level browser + // and nullptr otherwise. + BrowserHost* GetBrowserHost() const; + + ParentShowInfo GetShowInfo(); + + // Get the content principal from the owner element. + already_AddRefed<nsIPrincipal> GetContentPrincipal() const; + + /** + * Let managees query if Destroy() is already called so they don't send out + * messages when the PBrowser actor is being destroyed. + */ + bool IsDestroyed() const { return mIsDestroyed; } + + /** + * Returns whether we're in the process of creating a new window (from + * window.open). If so, LoadURL calls are being skipped until everything is + * set up. For further details, see `mCreatingWindow` below. + */ + bool CreatingWindow() const { return mCreatingWindow; } + + /* + * Visit each BrowserParent in the tree formed by PBrowser and + * PBrowserBridge, including `this`. + */ + template <typename Callback> + void VisitAll(Callback aCallback) { + aCallback(this); + VisitAllDescendants(aCallback); + } + + /* + * Visit each BrowserParent in the tree formed by PBrowser and + * PBrowserBridge, excluding `this`. + */ + template <typename Callback> + void VisitAllDescendants(Callback aCallback) { + const auto& browserBridges = ManagedPBrowserBridgeParent(); + for (const auto& key : browserBridges) { + BrowserBridgeParent* browserBridge = + static_cast<BrowserBridgeParent*>(key); + BrowserParent* browserParent = browserBridge->GetBrowserParent(); + + aCallback(browserParent); + browserParent->VisitAllDescendants(aCallback); + } + } + + /* + * Visit each BrowserBridgeParent that is a child of this BrowserParent. + */ + template <typename Callback> + void VisitChildren(Callback aCallback) { + const auto& browserBridges = ManagedPBrowserBridgeParent(); + for (const auto& key : browserBridges) { + BrowserBridgeParent* browserBridge = + static_cast<BrowserBridgeParent*>(key); + aCallback(browserBridge); + } + } + + void SetOwnerElement(Element* aElement); + + void SetBrowserDOMWindow(nsIBrowserDOMWindow* aBrowserDOMWindow) { + mBrowserDOMWindow = aBrowserDOMWindow; + } + + void SwapFrameScriptsFrom(nsTArray<FrameScriptInfo>& aFrameScripts) { + aFrameScripts.SwapElements(mDelayedFrameScripts); + } + + void CacheFrameLoader(nsFrameLoader* aFrameLoader); + + void Destroy(); + + void RemoveWindowListeners(); + + void AddWindowListeners(); + + mozilla::ipc::IPCResult RecvDidUnsuppressPainting(); + mozilla::ipc::IPCResult RecvDidUnsuppressPaintingNormalPriority() { + return RecvDidUnsuppressPainting(); + } + mozilla::ipc::IPCResult RecvMoveFocus(const bool& aForward, + const bool& aForDocumentNavigation); + + mozilla::ipc::IPCResult RecvDropLinks(nsTArray<nsString>&& aLinks); + + // TODO: Use MOZ_CAN_RUN_SCRIPT when it gains IPDL support (bug 1539864) + MOZ_CAN_RUN_SCRIPT_BOUNDARY mozilla::ipc::IPCResult RecvReplyKeyEvent( + const WidgetKeyboardEvent& aEvent, const nsID& aUUID); + + // TODO: Use MOZ_CAN_RUN_SCRIPT when it gains IPDL support (bug 1539864) + MOZ_CAN_RUN_SCRIPT_BOUNDARY mozilla::ipc::IPCResult RecvAccessKeyNotHandled( + const WidgetKeyboardEvent& aEvent); + + mozilla::ipc::IPCResult RecvRegisterProtocolHandler(const nsString& aScheme, + nsIURI* aHandlerURI, + const nsString& aTitle, + nsIURI* aDocURI); + + mozilla::ipc::IPCResult RecvOnStateChange( + const WebProgressData& aWebProgressData, const RequestData& aRequestData, + const uint32_t aStateFlags, const nsresult aStatus, + const Maybe<WebProgressStateChangeData>& aStateChangeData); + + mozilla::ipc::IPCResult RecvOnProgressChange(const int32_t aCurTotalProgres, + const int32_t aMaxTotalProgress); + + mozilla::ipc::IPCResult RecvOnLocationChange( + const WebProgressData& aWebProgressData, const RequestData& aRequestData, + nsIURI* aLocation, const uint32_t aFlags, const bool aCanGoBack, + const bool aCanGoForward, + const Maybe<WebProgressLocationChangeData>& aLocationChangeData); + + mozilla::ipc::IPCResult RecvOnStatusChange(const nsString& aMessage); + + mozilla::ipc::IPCResult RecvNotifyContentBlockingEvent( + const uint32_t& aEvent, const RequestData& aRequestData, + const bool aBlocked, const nsACString& aTrackingOrigin, + nsTArray<nsCString>&& aTrackingFullHashes, + const Maybe<mozilla::ContentBlockingNotifier:: + StorageAccessPermissionGrantedReason>& aReason, + const Maybe<mozilla::ContentBlockingNotifier::CanvasFingerprinter>& + aCanvasFingerprinter, + const Maybe<bool>& aCanvasFingerprinterKnownText); + + mozilla::ipc::IPCResult RecvNavigationFinished(); + + already_AddRefed<nsIBrowser> GetBrowser(); + + already_AddRefed<CanonicalBrowsingContext> BrowsingContextForWebProgress( + const WebProgressData& aWebProgressData); + + mozilla::ipc::IPCResult RecvIntrinsicSizeOrRatioChanged( + const Maybe<IntrinsicSize>& aIntrinsicSize, + const Maybe<AspectRatio>& aIntrinsicRatio); + + mozilla::ipc::IPCResult RecvImageLoadComplete(const nsresult& aResult); + + mozilla::ipc::IPCResult RecvSyncMessage( + const nsString& aMessage, const ClonedMessageData& aData, + nsTArray<ipc::StructuredCloneData>* aRetVal); + + mozilla::ipc::IPCResult RecvAsyncMessage(const nsString& aMessage, + const ClonedMessageData& aData); + + mozilla::ipc::IPCResult RecvNotifyIMEFocus( + const ContentCache& aContentCache, + const widget::IMENotification& aEventMessage, + NotifyIMEFocusResolver&& aResolve); + + mozilla::ipc::IPCResult RecvNotifyIMETextChange( + const ContentCache& aContentCache, + const widget::IMENotification& aEventMessage); + + mozilla::ipc::IPCResult RecvNotifyIMECompositionUpdate( + const ContentCache& aContentCache, + const widget::IMENotification& aEventMessage); + + mozilla::ipc::IPCResult RecvNotifyIMESelection( + const ContentCache& aContentCache, + const widget::IMENotification& aEventMessage); + + mozilla::ipc::IPCResult RecvUpdateContentCache( + const ContentCache& aContentCache); + + mozilla::ipc::IPCResult RecvNotifyIMEMouseButtonEvent( + const widget::IMENotification& aEventMessage, bool* aConsumedByIME); + + mozilla::ipc::IPCResult RecvNotifyIMEPositionChange( + const ContentCache& aContentCache, + const widget::IMENotification& aEventMessage); + + mozilla::ipc::IPCResult RecvOnEventNeedingAckHandled( + const EventMessage& aMessage, const uint32_t& aCompositionId); + + mozilla::ipc::IPCResult RecvRequestIMEToCommitComposition( + const bool& aCancel, const uint32_t& aCompositionId, bool* aIsCommitted, + nsString* aCommittedString); + + mozilla::ipc::IPCResult RecvGetInputContext(widget::IMEState* aIMEState); + + mozilla::ipc::IPCResult RecvSetInputContext( + const widget::InputContext& aContext, + const widget::InputContextAction& aAction); + + mozilla::ipc::IPCResult RecvRequestFocus(const bool& aCanRaise, + const CallerType aCallerType); + + mozilla::ipc::IPCResult RecvWheelZoomChange(bool aIncrease); + + mozilla::ipc::IPCResult RecvLookUpDictionary( + const nsString& aText, nsTArray<mozilla::FontRange>&& aFontRangeArray, + const bool& aIsVertical, const LayoutDeviceIntPoint& aPoint); + + mozilla::ipc::IPCResult RecvEnableDisableCommands( + const MaybeDiscarded<BrowsingContext>& aContext, const nsString& aAction, + nsTArray<nsCString>&& aEnabledCommands, + nsTArray<nsCString>&& aDisabledCommands); + + mozilla::ipc::IPCResult RecvSetCursor( + const nsCursor& aValue, const bool& aHasCustomCursor, + Maybe<BigBuffer>&& aCursorData, const uint32_t& aWidth, + const uint32_t& aHeight, const float& aResolutionX, + const float& aResolutionY, const uint32_t& aStride, + const gfx::SurfaceFormat& aFormat, const uint32_t& aHotspotX, + const uint32_t& aHotspotY, const bool& aForce); + + mozilla::ipc::IPCResult RecvSetLinkStatus(const nsString& aStatus); + + mozilla::ipc::IPCResult RecvShowTooltip(const uint32_t& aX, + const uint32_t& aY, + const nsString& aTooltip, + const nsString& aDirection); + + mozilla::ipc::IPCResult RecvHideTooltip(); + + mozilla::ipc::IPCResult RecvRespondStartSwipeEvent( + const uint64_t& aInputBlockId, const bool& aStartSwipe); + + mozilla::ipc::IPCResult RecvDispatchWheelEvent( + const mozilla::WidgetWheelEvent& aEvent); + + mozilla::ipc::IPCResult RecvDispatchMouseEvent( + const mozilla::WidgetMouseEvent& aEvent); + + mozilla::ipc::IPCResult RecvDispatchKeyboardEvent( + const mozilla::WidgetKeyboardEvent& aEvent); + + mozilla::ipc::IPCResult RecvDispatchTouchEvent( + const mozilla::WidgetTouchEvent& aEvent); + + mozilla::ipc::IPCResult RecvScrollRectIntoView( + const nsRect& aRect, const ScrollAxis& aVertical, + const ScrollAxis& aHorizontal, const ScrollFlags& aScrollFlags, + const int32_t& aAppUnitsPerDevPixel); + + already_AddRefed<PColorPickerParent> AllocPColorPickerParent( + const nsString& aTitle, const nsString& aInitialColor, + const nsTArray<nsString>& aDefaultColors); + + PVsyncParent* AllocPVsyncParent(); + + bool DeallocPVsyncParent(PVsyncParent* aActor); + +#ifdef ACCESSIBILITY + PDocAccessibleParent* AllocPDocAccessibleParent( + PDocAccessibleParent*, const uint64_t&, + const MaybeDiscardedBrowsingContext&); + bool DeallocPDocAccessibleParent(PDocAccessibleParent*); + virtual mozilla::ipc::IPCResult RecvPDocAccessibleConstructor( + PDocAccessibleParent* aDoc, PDocAccessibleParent* aParentDoc, + const uint64_t& aParentID, + const MaybeDiscardedBrowsingContext& aBrowsingContext) override; +#endif + + already_AddRefed<PSessionStoreParent> AllocPSessionStoreParent(); + + mozilla::ipc::IPCResult RecvNewWindowGlobal( + ManagedEndpoint<PWindowGlobalParent>&& aEndpoint, + const WindowGlobalInit& aInit); + + mozilla::ipc::IPCResult RecvIsWindowSupportingProtectedMedia( + const uint64_t& aOuterWindowID, + IsWindowSupportingProtectedMediaResolver&& aResolve); + + mozilla::ipc::IPCResult RecvIsWindowSupportingWebVR( + const uint64_t& aOuterWindowID, + IsWindowSupportingWebVRResolver&& aResolve); + + void LoadURL(nsDocShellLoadState* aLoadState); + + void ResumeLoad(uint64_t aPendingSwitchID); + + void InitRendering(); + bool AttachWindowRenderer(); + void MaybeShowFrame(); + + bool Show(const OwnerShowInfo&); + + void UpdateDimensions(const nsIntRect& aRect, const ScreenIntSize& aSize); + + DimensionInfo GetDimensionInfo(); + + nsresult UpdatePosition(); + + // Notify position update to all descendant documents in this browser parent. + // NOTE: This should use only for browsers in popup windows attached to the + // main browser window. + void NotifyPositionUpdatedForContentsInPopup(); + + void SizeModeChanged(const nsSizeMode& aSizeMode); + + void HandleAccessKey(const WidgetKeyboardEvent& aEvent, + nsTArray<uint32_t>& aCharCodes); + +#if defined(MOZ_WIDGET_ANDROID) + void DynamicToolbarMaxHeightChanged(ScreenIntCoord aHeight); + void DynamicToolbarOffsetChanged(ScreenIntCoord aOffset); +#endif + + void Activate(uint64_t aActionId); + + void Deactivate(bool aWindowLowering, uint64_t aActionId); + + void MouseEnterIntoWidget(); + + bool MapEventCoordinatesForChildProcess(mozilla::WidgetEvent* aEvent); + + void MapEventCoordinatesForChildProcess(const LayoutDeviceIntPoint& aOffset, + mozilla::WidgetEvent* aEvent); + + LayoutDeviceToCSSScale GetLayoutDeviceToCSSScale(); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY mozilla::ipc::IPCResult + RecvRequestNativeKeyBindings(const uint32_t& aType, + const mozilla::WidgetKeyboardEvent& aEvent, + nsTArray<mozilla::CommandInt>* aCommands); + + mozilla::ipc::IPCResult RecvSynthesizeNativeKeyEvent( + const int32_t& aNativeKeyboardLayout, const int32_t& aNativeKeyCode, + const uint32_t& aModifierFlags, const nsString& aCharacters, + const nsString& aUnmodifiedCharacters, const uint64_t& aObserverId); + + mozilla::ipc::IPCResult RecvSynthesizeNativeMouseEvent( + const LayoutDeviceIntPoint& aPoint, const uint32_t& aNativeMessage, + const int16_t& aButton, const uint32_t& aModifierFlags, + const uint64_t& aObserverId); + + mozilla::ipc::IPCResult RecvSynthesizeNativeMouseMove( + const LayoutDeviceIntPoint& aPoint, const uint64_t& aObserverId); + + mozilla::ipc::IPCResult RecvSynthesizeNativeMouseScrollEvent( + const LayoutDeviceIntPoint& aPoint, const uint32_t& aNativeMessage, + const double& aDeltaX, const double& aDeltaY, const double& aDeltaZ, + const uint32_t& aModifierFlags, const uint32_t& aAdditionalFlags, + const uint64_t& aObserverId); + + mozilla::ipc::IPCResult RecvSynthesizeNativeTouchPoint( + const uint32_t& aPointerId, const TouchPointerState& aPointerState, + const LayoutDeviceIntPoint& aPoint, const double& aPointerPressure, + const uint32_t& aPointerOrientation, const uint64_t& aObserverId); + + mozilla::ipc::IPCResult RecvSynthesizeNativeTouchPadPinch( + const TouchpadGesturePhase& aEventPhase, const float& aScale, + const LayoutDeviceIntPoint& aPoint, const int32_t& aModifierFlags); + + mozilla::ipc::IPCResult RecvSynthesizeNativeTouchTap( + const LayoutDeviceIntPoint& aPoint, const bool& aLongTap, + const uint64_t& aObserverId); + + mozilla::ipc::IPCResult RecvClearNativeTouchSequence( + const uint64_t& aObserverId); + + mozilla::ipc::IPCResult RecvSynthesizeNativePenInput( + const uint32_t& aPointerId, const TouchPointerState& aPointerState, + const LayoutDeviceIntPoint& aPoint, const double& aPressure, + const uint32_t& aRotation, const int32_t& aTiltX, const int32_t& aTiltY, + const int32_t& aButton, const uint64_t& aObserverId); + + mozilla::ipc::IPCResult RecvSynthesizeNativeTouchpadDoubleTap( + const LayoutDeviceIntPoint& aPoint, const uint32_t& aModifierFlags); + + mozilla::ipc::IPCResult RecvSynthesizeNativeTouchpadPan( + const TouchpadGesturePhase& aEventPhase, + const LayoutDeviceIntPoint& aPoint, const double& aDeltaX, + const double& aDeltaY, const int32_t& aModifierFlags, + const uint64_t& aObserverId); + + mozilla::ipc::IPCResult RecvLockNativePointer(); + + mozilla::ipc::IPCResult RecvUnlockNativePointer(); + + /** + * The following Send*Event() marks aEvent as posted to remote process if + * it succeeded. So, you can check the result with + * aEvent.HasBeenPostedToRemoteProcess(). + */ + void SendRealMouseEvent(WidgetMouseEvent& aEvent); + + void SendRealDragEvent(WidgetDragEvent& aEvent, uint32_t aDragAction, + uint32_t aDropEffect, nsIPrincipal* aPrincipal, + nsIContentSecurityPolicy* aCsp); + + void SendMouseWheelEvent(WidgetWheelEvent& aEvent); + + /** + * Only when the event is synthesized, retrieving writing mode may flush + * the layout. + */ + MOZ_CAN_RUN_SCRIPT_BOUNDARY void SendRealKeyEvent( + WidgetKeyboardEvent& aEvent); + + void SendRealTouchEvent(WidgetTouchEvent& aEvent); + + /** + * Different from above Send*Event(), these methods return true if the + * event has been posted to the remote process or failed to do that but + * shouldn't be handled by following event listeners. + * If you need to check if it's actually posted to the remote process, + * you can refer aEvent.HasBeenPostedToRemoteProcess(). + */ + bool SendCompositionEvent(mozilla::WidgetCompositionEvent& aEvent, + uint32_t aCompositionId); + + bool SendSelectionEvent(mozilla::WidgetSelectionEvent& aEvent); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY bool SendHandleTap( + TapType aType, const LayoutDevicePoint& aPoint, Modifiers aModifiers, + const ScrollableLayerGuid& aGuid, uint64_t aInputBlockId, + const Maybe<DoubleTapToZoomMetrics>& aDoubleTapToZoomMetrics); + + already_AddRefed<PFilePickerParent> AllocPFilePickerParent( + const nsString& aTitle, const nsIFilePicker::Mode& aMode); + + bool GetGlobalJSObject(JSContext* cx, JSObject** globalp); + + void StartPersistence(CanonicalBrowsingContext* aContext, + nsIWebBrowserPersistDocumentReceiver* aRecv, + ErrorResult& aRv); + + bool HandleQueryContentEvent(mozilla::WidgetQueryContentEvent& aEvent); + + bool SendInsertText(const nsString& aStringToInsert); + + bool SendPasteTransferable(IPCTransferable&& aTransferable); + + // Helper for transforming a point + LayoutDeviceIntPoint TransformPoint( + const LayoutDeviceIntPoint& aPoint, + const LayoutDeviceToLayoutDeviceMatrix4x4& aMatrix); + LayoutDevicePoint TransformPoint( + const LayoutDevicePoint& aPoint, + const LayoutDeviceToLayoutDeviceMatrix4x4& aMatrix); + + // Transform a coordinate from the parent process coordinate space to the + // child process coordinate space. + LayoutDeviceIntPoint TransformParentToChild(const WidgetMouseEvent& aEvent); + LayoutDeviceIntPoint TransformParentToChild( + const LayoutDeviceIntPoint& aPoint); + LayoutDevicePoint TransformParentToChild(const LayoutDevicePoint& aPoint); + + // Transform a coordinate from the child process coordinate space to the + // parent process coordinate space. + LayoutDeviceIntPoint TransformChildToParent( + const LayoutDeviceIntPoint& aPoint); + LayoutDevicePoint TransformChildToParent(const LayoutDevicePoint& aPoint); + LayoutDeviceIntRect TransformChildToParent(const LayoutDeviceIntRect& aRect); + + // Returns the matrix that transforms event coordinates from the coordinate + // space of the child process to the coordinate space of the parent process. + LayoutDeviceToLayoutDeviceMatrix4x4 GetChildToParentConversionMatrix(); + + void SetChildToParentConversionMatrix( + const Maybe<LayoutDeviceToLayoutDeviceMatrix4x4>& aMatrix, + const ScreenRect& aRemoteDocumentRect); + + // Returns the offset from the origin of our frameloader's nearest widget to + // the origin of its layout frame. This offset is used to translate event + // coordinates relative to the PuppetWidget origin in the child process. + // + // GOING AWAY. PLEASE AVOID ADDING CALLERS. Use the above tranformation + // methods instead. + LayoutDeviceIntPoint GetChildProcessOffset(); + + // Returns the offset from the on-screen origin of our top-level window's + // widget (including window decorations) to the origin of our frameloader's + // nearest widget. This offset is used to translate coordinates from the + // PuppetWidget's origin to absolute screen coordinates in the child. + LayoutDeviceIntPoint GetClientOffset(); + + void StopIMEStateManagement(); + + PPaymentRequestParent* AllocPPaymentRequestParent(); + + bool DeallocPPaymentRequestParent(PPaymentRequestParent* aActor); + + bool SendLoadRemoteScript(const nsAString& aURL, + const bool& aRunInGlobalScope); + + void LayerTreeUpdate(bool aActive); + + mozilla::ipc::IPCResult RecvInvokeDragSession( + nsTArray<IPCTransferableData>&& aTransferables, const uint32_t& aAction, + Maybe<BigBuffer>&& aVisualDnDData, const uint32_t& aStride, + const gfx::SurfaceFormat& aFormat, const LayoutDeviceIntRect& aDragRect, + nsIPrincipal* aPrincipal, nsIContentSecurityPolicy* aCsp, + const CookieJarSettingsArgs& aCookieJarSettingsArgs, + const MaybeDiscarded<WindowContext>& aSourceWindowContext, + const MaybeDiscarded<WindowContext>& aSourceTopWindowContext); + + void AddInitialDnDDataTo(IPCTransferableData* aTransferableData, + nsIPrincipal** aPrincipal); + + bool TakeDragVisualization(RefPtr<mozilla::gfx::SourceSurface>& aSurface, + LayoutDeviceIntRect* aDragRect); + + mozilla::ipc::IPCResult RecvEnsureLayersConnected( + CompositorOptions* aCompositorOptions); + + // LiveResizeListener implementation + void LiveResizeStarted() override; + void LiveResizeStopped() override; + + void SetReadyToHandleInputEvents() { mIsReadyToHandleInputEvents = true; } + bool IsReadyToHandleInputEvents() { return mIsReadyToHandleInputEvents; } + + void NavigateByKey(bool aForward, bool aForDocumentNavigation); + + bool GetDocShellIsActive(); + void SetDocShellIsActive(bool aDocShellIsActive); + + bool GetHasPresented(); + bool GetHasLayers(); + bool GetRenderLayers(); + void SetRenderLayers(bool aRenderLayers); + bool GetPriorityHint(); + void SetPriorityHint(bool aPriorityHint); + void PreserveLayers(bool aPreserveLayers); + void NotifyResolutionChanged(); + + bool CanCancelContentJS(nsIRemoteTab::NavigationType aNavigationType, + int32_t aNavigationIndex, + nsIURI* aNavigationURI) const; + + // Called when the BrowserParent is being destroyed or entering bfcache. + void Deactivated(); + + protected: + friend BrowserBridgeParent; + friend BrowserHost; + + void SetBrowserBridgeParent(BrowserBridgeParent* aBrowser); + void SetBrowserHost(BrowserHost* aBrowser); + + bool ReceiveMessage( + const nsString& aMessage, bool aSync, ipc::StructuredCloneData* aData, + nsTArray<ipc::StructuredCloneData>* aJSONRetVal = nullptr); + + virtual void ActorDestroy(ActorDestroyReason why) override; + + mozilla::ipc::IPCResult RecvRemoteIsReadyToHandleInputEvents(); + + mozilla::ipc::IPCResult RecvSetDimensions(mozilla::DimensionRequest aRequest, + const double& aScale); + + mozilla::ipc::IPCResult RecvShowCanvasPermissionPrompt( + const nsCString& aOrigin, const bool& aHideDoorHanger); + + mozilla::ipc::IPCResult RecvSetSystemFont(const nsCString& aFontName); + mozilla::ipc::IPCResult RecvGetSystemFont(nsCString* aFontName); + + mozilla::ipc::IPCResult RecvVisitURI(nsIURI* aURI, nsIURI* aLastVisitedURI, + const uint32_t& aFlags, + const uint64_t& aBrowserId); + + mozilla::ipc::IPCResult RecvQueryVisitedState( + nsTArray<RefPtr<nsIURI>>&& aURIs); + + mozilla::ipc::IPCResult RecvMaybeFireEmbedderLoadEvents( + EmbedderElementEventType aFireEventAtEmbeddingElement); + + mozilla::ipc::IPCResult RecvRequestPointerLock( + RequestPointerLockResolver&& aResolve); + mozilla::ipc::IPCResult RecvReleasePointerLock(); + + mozilla::ipc::IPCResult RecvRequestPointerCapture( + const uint32_t& aPointerId, RequestPointerCaptureResolver&& aResolve); + mozilla::ipc::IPCResult RecvReleasePointerCapture(const uint32_t& aPointerId); + + mozilla::ipc::IPCResult RecvShowDynamicToolbar(); + + private: + void SuppressDisplayport(bool aEnabled); + + void SetRenderLayersInternal(bool aEnabled); + + already_AddRefed<nsFrameLoader> GetFrameLoader( + bool aUseCachedFrameLoaderAfterDestroy = false) const; + + void TryCacheDPIAndScale(); + + bool AsyncPanZoomEnabled() const; + + // Update state prior to routing an APZ-aware event to the child process. + // |aOutTargetGuid| will contain the identifier + // of the APZC instance that handled the event. aOutTargetGuid may be null. + // |aOutInputBlockId| will contain the identifier of the input block + // that this event was added to, if there was one. aOutInputBlockId may be + // null. |aOutApzResponse| will contain the response that the APZ gave when + // processing the input block; this is used for generating appropriate + // pointercancel events. + void ApzAwareEventRoutingToChild(ScrollableLayerGuid* aOutTargetGuid, + uint64_t* aOutInputBlockId, + nsEventStatus* aOutApzResponse); + + // When dropping links we perform a roundtrip from + // Parent (SendRealDragEvent) -> Child -> Parent (RecvDropLinks) + // and have to ensure that the child did not modify links to be loaded. + bool QueryDropLinksForVerification(); + + void UnlockNativePointer(); + + void UpdateNativePointerLockCenter(nsIWidget* aWidget); + + private: + // This is used when APZ needs to find the BrowserParent associated with a + // layer to dispatch events. + typedef nsTHashMap<nsUint64HashKey, BrowserParent*> LayerToBrowserParentTable; + static LayerToBrowserParentTable* sLayerToBrowserParentTable; + + static void AddBrowserParentToTable(layers::LayersId aLayersId, + BrowserParent* aBrowserParent); + + static void RemoveBrowserParentFromTable(layers::LayersId aLayersId); + + // Keeps track of which BrowserParent has keyboard focus. + // If nullptr, the parent process has focus. + // Use UpdateFocus() to manage. + static BrowserParent* sFocus; + + // Keeps track of which top-level BrowserParent the keyboard focus is under. + // If nullptr, the parent process has focus. + // Use SetTopLevelWebFocus and UnsetTopLevelWebFocus to manage. + static BrowserParent* sTopLevelWebFocus; + + // Setter for sTopLevelWebFocus + static void SetTopLevelWebFocus(BrowserParent* aBrowserParent); + + // Unsetter for sTopLevelWebFocus; only unsets if argument matches + // current sTopLevelWebFocus. Use UnsetTopLevelWebFocusAll() to + // unset regardless of current value. + static void UnsetTopLevelWebFocus(BrowserParent* aBrowserParent); + + // Recomputes sFocus and returns it. + static BrowserParent* UpdateFocus(); + + // Keeps track of which BrowserParent the real mouse event is sent to. + static BrowserParent* sLastMouseRemoteTarget; + + // Unsetter for LastMouseRemoteTarget; only unsets if argument matches + // current sLastMouseRemoteTarget. + static void UnsetLastMouseRemoteTarget(BrowserParent* aBrowserParent); + + struct APZData { + bool operator==(const APZData& aOther) const { + return aOther.guid == guid && aOther.blockId == blockId && + aOther.apzResponse == apzResponse; + } + + bool operator!=(const APZData& aOther) const { return !(*this == aOther); } + + ScrollableLayerGuid guid; + uint64_t blockId; + nsEventStatus apzResponse; + }; + void SendRealTouchMoveEvent(WidgetTouchEvent& aEvent, APZData& aAPZData, + uint32_t aConsecutiveTouchMoveCount); + + void UpdateVsyncParentVsyncDispatcher(); + + public: + // Unsets sTopLevelWebFocus regardless of its current value. + static void UnsetTopLevelWebFocusAll(); + + // Recomputes focus when the BrowsingContext tree changes in a + // way that potentially invalidates the sFocus. + static void UpdateFocusFromBrowsingContext(); + + private: + TabId mTabId; + + RefPtr<ContentParent> mManager; + // The root browsing context loaded in this BrowserParent. + RefPtr<CanonicalBrowsingContext> mBrowsingContext; + nsCOMPtr<nsILoadContext> mLoadContext; + RefPtr<Element> mFrameElement; + nsCOMPtr<nsIBrowserDOMWindow> mBrowserDOMWindow; + // We keep a strong reference to the frameloader after we've sent the + // Destroy message and before we've received __delete__. This allows us to + // dispatch message manager messages during this time. + RefPtr<nsFrameLoader> mFrameLoader; + uint32_t mChromeFlags; + + // Pointer back to BrowserBridgeParent if there is one associated with + // this BrowserParent. This is non-owning to avoid cycles and is managed + // by the BrowserBridgeParent instance, which has the strong reference + // to this BrowserParent. + BrowserBridgeParent* mBrowserBridgeParent; + // Pointer to the BrowserHost that owns us, if any. This is mutually + // exclusive with mBrowserBridgeParent, and one is guaranteed to be + // non-null. + BrowserHost* mBrowserHost; + + ContentCacheInParent mContentCache; + + layout::RemoteLayerTreeOwner mRemoteLayerTreeOwner; + + Maybe<LayoutDeviceToLayoutDeviceMatrix4x4> mChildToParentConversionMatrix; + Maybe<ScreenRect> mRemoteDocumentRect; + + // mWaitingReplyKeyboardEvents stores keyboard events which are sent from + // SendRealKeyEvent and the event will be back as a reply event. They are + // removed when RecvReplyKeyEvent receives corresponding event or newer event. + // Note that reply event will be used for handling non-reserved shortcut keys. + // Therefore, we need to store only important data for GlobalKeyHandler. + struct SentKeyEventData { + uint32_t mKeyCode; + uint32_t mCharCode; + uint32_t mPseudoCharCode; + KeyNameIndex mKeyNameIndex; + CodeNameIndex mCodeNameIndex; + Modifiers mModifiers; + nsID mUUID; + }; + nsTArray<SentKeyEventData> mWaitingReplyKeyboardEvents; + + nsIntRect mRect; + ScreenIntSize mDimensions; + float mDPI; + int32_t mRounding; + CSSToLayoutDeviceScale mDefaultScale; + bool mUpdatedDimensions; + nsSizeMode mSizeMode; + LayoutDeviceIntPoint mClientOffset; + LayoutDeviceIntPoint mChromeOffset; + + // When loading a new tab or window via window.open, the child is + // responsible for loading the URL it wants into the new BrowserChild. When + // the parent receives the CreateWindow message, though, it sends a LoadURL + // message, usually for about:blank. It's important for the about:blank load + // to get processed because the Firefox frontend expects every new window to + // immediately start loading something (see bug 1123090). However, we want + // the child to process the LoadURL message before it returns from + // ProvideWindow so that the URL sent from the parent doesn't override the + // child's URL. This is not possible using our IPC mechanisms. To solve the + // problem, we skip sending the LoadURL message in the parent and instead + // return the URL as a result from CreateWindow. The child simulates + // receiving a LoadURL message before returning from ProvideWindow. + // + // The mCreatingWindow flag is set while dispatching CreateWindow. During + // that time, any LoadURL calls are skipped. + bool mCreatingWindow; + + // When loading a new tab or window via window.open, we want to ensure that + // frame scripts for that tab are loaded before any scripts start to run in + // the window. We can't load the frame scripts the normal way, using + // separate IPC messages, since they won't be processed by the child until + // returning to the event loop, which is too late. Instead, we queue up + // frame scripts that we intend to load and send them as part of the + // CreateWindow response. Then BrowserChild loads them immediately. + nsTArray<FrameScriptInfo> mDelayedFrameScripts; + + // Cached cursor setting from BrowserChild. When the cursor is over the + // tab, it should take this appearance. + nsIWidget::Cursor mCursor; + + nsTArray<nsString> mVerifyDropLinks; + + RefPtr<VsyncParent> mVsyncParent; + +#ifdef DEBUG + int32_t mActiveSupressDisplayportCount = 0; +#endif + + // When true, we've initiated normal shutdown and notified our managing + // PContent. + bool mMarkedDestroying : 1; + // When true, the BrowserParent is invalid and we should not send IPC + // messages anymore. + bool mIsDestroyed : 1; + // True if the cursor changes from the BrowserChild should change the widget + // cursor. This happens whenever the cursor is in the remote target's + // region. + bool mRemoteTargetSetsCursor : 1; + + // If this flag is set, then the tab's layers will be preserved even when + // the tab's docshell is inactive. + bool mIsPreservingLayers : 1; + + // Holds the most recent value passed to the RenderLayers function. This + // does not necessarily mean that the layers have finished rendering + // and have uploaded - for that, use mHasLayers. + bool mRenderLayers : 1; + + // True if process should be set to a higher priority. + bool mPriorityHint : 1; + + // True if the compositor has reported that the BrowserChild has uploaded + // layers. + bool mHasLayers : 1; + + // True if this BrowserParent has had its layer tree sent to the compositor + // at least once. + bool mHasPresented : 1; + + // True when the remote browser is created and ready to handle input events. + bool mIsReadyToHandleInputEvents : 1; + + // True if we suppress the eMouseEnterIntoWidget event due to the + // BrowserChild was not ready to handle it. We will resend it when the next + // time we fire a mouse event and the BrowserChild is ready. + bool mIsMouseEnterIntoWidgetEventSuppressed : 1; + + // True after RecvLockNativePointer has been called and until + // UnlockNativePointer has been called. + bool mLockedNativePointer : 1; + + // True between ShowTooltip and HideTooltip messages. + bool mShowingTooltip : 1; +}; + +struct MOZ_STACK_CLASS BrowserParent::AutoUseNewTab final { + public: + explicit AutoUseNewTab(BrowserParent* aNewTab) : mNewTab(aNewTab) { + MOZ_ASSERT(!aNewTab->mCreatingWindow); + aNewTab->mCreatingWindow = true; + } + + ~AutoUseNewTab() { mNewTab->mCreatingWindow = false; } + + private: + RefPtr<BrowserParent> mNewTab; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_BrowserParent_h diff --git a/dom/ipc/CSPMessageUtils.cpp b/dom/ipc/CSPMessageUtils.cpp new file mode 100644 index 0000000000..b952d01b6f --- /dev/null +++ b/dom/ipc/CSPMessageUtils.cpp @@ -0,0 +1,50 @@ +/* -*- 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 "mozilla/dom/CSPMessageUtils.h" +#include "mozilla/ipc/PBackgroundSharedTypes.h" +#include "nsSerializationHelper.h" +#include "mozilla/ipc/BackgroundUtils.h" + +namespace IPC { + +using namespace mozilla::ipc; + +void ParamTraits<nsIContentSecurityPolicy*>::Write( + MessageWriter* aWriter, nsIContentSecurityPolicy* aParam) { + bool isNull = !aParam; + WriteParam(aWriter, isNull); + if (isNull) { + return; + } + + CSPInfo csp; + mozilla::Unused << NS_WARN_IF(NS_FAILED(CSPToCSPInfo(aParam, &csp))); + WriteParam(aWriter, csp); +} + +bool ParamTraits<nsIContentSecurityPolicy*>::Read( + MessageReader* aReader, RefPtr<nsIContentSecurityPolicy>* aResult) { + bool isNull; + if (!ReadParam(aReader, &isNull)) { + return false; + } + + if (isNull) { + *aResult = nullptr; + return true; + } + + CSPInfo csp; + if (!ReadParam(aReader, &csp)) { + return false; + } + + *aResult = CSPInfoToCSP(csp, nullptr, nullptr); + return *aResult; +} + +} // namespace IPC diff --git a/dom/ipc/CSPMessageUtils.h b/dom/ipc/CSPMessageUtils.h new file mode 100644 index 0000000000..286a3a9778 --- /dev/null +++ b/dom/ipc/CSPMessageUtils.h @@ -0,0 +1,25 @@ +/* -*- 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_dom_csp_message_utils_h__ +#define mozilla_dom_csp_message_utils_h__ + +#include "ipc/IPCMessageUtils.h" +#include "nsCOMPtr.h" +#include "nsIContentSecurityPolicy.h" + +namespace IPC { + +template <> +struct ParamTraits<nsIContentSecurityPolicy*> { + static void Write(MessageWriter* aWriter, nsIContentSecurityPolicy* aParam); + static bool Read(MessageReader* aReader, + RefPtr<nsIContentSecurityPolicy>* aResult); +}; + +} // namespace IPC + +#endif // mozilla_dom_csp_message_utils_h__ diff --git a/dom/ipc/ClonedErrorHolder.cpp b/dom/ipc/ClonedErrorHolder.cpp new file mode 100644 index 0000000000..8db9bd064e --- /dev/null +++ b/dom/ipc/ClonedErrorHolder.cpp @@ -0,0 +1,373 @@ +/* -*- 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 "mozilla/dom/ClonedErrorHolder.h" + +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/ClonedErrorHolderBinding.h" +#include "mozilla/dom/DOMException.h" +#include "mozilla/dom/DOMExceptionBinding.h" +#include "mozilla/dom/Exceptions.h" +#include "mozilla/dom/StructuredCloneTags.h" +#include "mozilla/dom/ToJSValue.h" +#include "jsapi.h" +#include "jsfriendapi.h" +#include "js/StructuredClone.h" +#include "nsReadableUtils.h" +#include "xpcpublic.h" + +using namespace mozilla; +using namespace mozilla::dom; + +// static +UniquePtr<ClonedErrorHolder> ClonedErrorHolder::Constructor( + const GlobalObject& aGlobal, JS::Handle<JSObject*> aError, + ErrorResult& aRv) { + return Create(aGlobal.Context(), aError, aRv); +} + +// static +UniquePtr<ClonedErrorHolder> ClonedErrorHolder::Create( + JSContext* aCx, JS::Handle<JSObject*> aError, ErrorResult& aRv) { + UniquePtr<ClonedErrorHolder> ceh(new ClonedErrorHolder()); + ceh->Init(aCx, aError, aRv); + if (aRv.Failed()) { + return nullptr; + } + return ceh; +} + +ClonedErrorHolder::ClonedErrorHolder() + : mName(VoidCString()), + mMessage(VoidCString()), + mFilename(VoidCString()), + mSourceLine(VoidCString()) {} + +void ClonedErrorHolder::Init(JSContext* aCx, JS::Handle<JSObject*> aError, + ErrorResult& aRv) { + JS::Rooted<JSObject*> stack(aCx); + + if (JSErrorReport* err = JS_ErrorFromException(aCx, aError)) { + mType = Type::JSError; + if (err->message()) { + mMessage = err->message().c_str(); + } + if (err->filename) { + mFilename = err->filename.c_str(); + } + if (err->linebuf()) { + AppendUTF16toUTF8( + nsDependentSubstring(err->linebuf(), err->linebufLength()), + mSourceLine); + mTokenOffset = err->tokenOffset(); + } + mLineNumber = err->lineno; + mColumn = err->column; + mErrorNumber = err->errorNumber; + mExnType = JSExnType(err->exnType); + + // Note: We don't save the souce ID here, since this object is cross-process + // clonable, and the source ID won't be valid in other processes. + // We don't store the source notes either, though for no other reason that + // it isn't clear that it's worth the complexity. + + stack = JS::ExceptionStackOrNull(aError); + } else { + RefPtr<DOMException> domExn; + RefPtr<Exception> exn; + if (NS_SUCCEEDED(UNWRAP_OBJECT(DOMException, aError, domExn))) { + mType = Type::DOMException; + mCode = domExn->Code(); + exn = std::move(domExn); + } else if (NS_SUCCEEDED(UNWRAP_OBJECT(Exception, aError, exn))) { + mType = Type::Exception; + } else { + aRv.ThrowNotSupportedError( + "We can only clone DOM Exceptions and native JS Error objects"); + return; + } + + nsAutoString str; + + exn->GetName(str); + CopyUTF16toUTF8(str, mName); + + exn->GetMessageMoz(str); + CopyUTF16toUTF8(str, mMessage); + + // Note: In DOM exceptions, filename, line number, and column number come + // from the stack frame, and don't need to be stored separately. mFilename, + // mLineNumber, and mColumn are only used for JS exceptions. + // + // We also don't serialized Exception's mThrownJSVal or mData fields, since + // they generally won't be serializable. + + mResult = nsresult(exn->Result()); + + if (nsCOMPtr<nsIStackFrame> frame = exn->GetLocation()) { + JS::Rooted<JS::Value> value(aCx); + frame->GetNativeSavedFrame(&value); + if (value.isObject()) { + stack = &value.toObject(); + } + } + } + + Maybe<JSAutoRealm> ar; + if (stack) { + ar.emplace(aCx, stack); + } + JS::Rooted<JS::Value> stackValue(aCx, JS::ObjectOrNullValue(stack)); + mStack.Write(aCx, stackValue, aRv); +} + +bool ClonedErrorHolder::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto, + JS::MutableHandle<JSObject*> aReflector) { + return ClonedErrorHolder_Binding::Wrap(aCx, this, aGivenProto, aReflector); +} + +static constexpr uint32_t kVoidStringLength = ~0; + +static bool WriteStringPair(JSStructuredCloneWriter* aWriter, + const nsACString& aString1, + const nsACString& aString2) { + auto StringLength = [](const nsACString& aStr) -> uint32_t { + auto length = uint32_t(aStr.Length()); + MOZ_DIAGNOSTIC_ASSERT(length != kVoidStringLength, + "We should not be serializing a 4GiB string"); + if (aStr.IsVoid()) { + return kVoidStringLength; + } + return length; + }; + + return JS_WriteUint32Pair(aWriter, StringLength(aString1), + StringLength(aString2)) && + JS_WriteBytes(aWriter, aString1.BeginReading(), aString1.Length()) && + JS_WriteBytes(aWriter, aString2.BeginReading(), aString2.Length()); +} + +static bool ReadStringPair(JSStructuredCloneReader* aReader, + nsACString& aString1, nsACString& aString2) { + auto ReadString = [&](nsACString& aStr, uint32_t aLength) { + if (aLength == kVoidStringLength) { + aStr.SetIsVoid(true); + return true; + } + char* data = nullptr; + return aLength == 0 || (aStr.GetMutableData(&data, aLength, fallible) && + JS_ReadBytes(aReader, data, aLength)); + }; + + aString1.Truncate(0); + aString2.Truncate(0); + + uint32_t length1, length2; + return JS_ReadUint32Pair(aReader, &length1, &length2) && + ReadString(aString1, length1) && ReadString(aString2, length2); +} + +bool ClonedErrorHolder::WriteStructuredClone(JSContext* aCx, + JSStructuredCloneWriter* aWriter, + StructuredCloneHolder* aHolder) { + auto& data = mStack.BufferData(); + return JS_WriteUint32Pair(aWriter, SCTAG_DOM_CLONED_ERROR_OBJECT, 0) && + WriteStringPair(aWriter, mName, mMessage) && + WriteStringPair(aWriter, mFilename, mSourceLine) && + JS_WriteUint32Pair(aWriter, mLineNumber, + *mColumn.addressOfValueForTranscode()) && + JS_WriteUint32Pair(aWriter, mTokenOffset, mErrorNumber) && + JS_WriteUint32Pair(aWriter, uint32_t(mType), uint32_t(mExnType)) && + JS_WriteUint32Pair(aWriter, mCode, uint32_t(mResult)) && + JS_WriteUint32Pair(aWriter, data.Size(), + JS_STRUCTURED_CLONE_VERSION) && + data.ForEachDataChunk([&](const char* aData, size_t aSize) { + return JS_WriteBytes(aWriter, aData, aSize); + }); +} + +bool ClonedErrorHolder::Init(JSContext* aCx, JSStructuredCloneReader* aReader) { + uint32_t type, exnType, result, code; + if (!(ReadStringPair(aReader, mName, mMessage) && + ReadStringPair(aReader, mFilename, mSourceLine) && + JS_ReadUint32Pair(aReader, &mLineNumber, + mColumn.addressOfValueForTranscode()) && + JS_ReadUint32Pair(aReader, &mTokenOffset, &mErrorNumber) && + JS_ReadUint32Pair(aReader, &type, &exnType) && + JS_ReadUint32Pair(aReader, &code, &result) && + mStack.ReadStructuredCloneInternal(aCx, aReader))) { + return false; + } + + if (type == uint32_t(Type::Uninitialized) || type >= uint32_t(Type::Max_) || + exnType >= uint32_t(JSEXN_ERROR_LIMIT)) { + return false; + } + + mType = Type(type); + mExnType = JSExnType(exnType); + mResult = nsresult(result); + mCode = code; + + return true; +} + +/* static */ +JSObject* ClonedErrorHolder::ReadStructuredClone( + JSContext* aCx, JSStructuredCloneReader* aReader, + StructuredCloneHolder* aHolder) { + // Keep the result object rooted across the call to ClonedErrorHolder::Release + // to avoid a potential rooting hazard. + JS::Rooted<JS::Value> errorVal(aCx); + { + UniquePtr<ClonedErrorHolder> ceh(new ClonedErrorHolder()); + if (!ceh->Init(aCx, aReader) || !ceh->ToErrorValue(aCx, &errorVal)) { + return nullptr; + } + } + return &errorVal.toObject(); +} + +static JS::UniqueTwoByteChars ToNullTerminatedJSStringBuffer( + JSContext* aCx, const nsString& aStr) { + // Since nsString is null terminated, we can simply copy + 1 characters. + size_t nbytes = (aStr.Length() + 1) * sizeof(char16_t); + JS::UniqueTwoByteChars buffer(static_cast<char16_t*>(JS_malloc(aCx, nbytes))); + if (buffer) { + memcpy(buffer.get(), aStr.get(), nbytes); + } + return buffer; +} + +static bool ToJSString(JSContext* aCx, const nsACString& aStr, + JS::MutableHandle<JSString*> aJSString) { + if (aStr.IsVoid()) { + aJSString.set(nullptr); + return true; + } + JS::Rooted<JS::Value> res(aCx); + if (xpc::NonVoidStringToJsval(aCx, NS_ConvertUTF8toUTF16(aStr), &res)) { + aJSString.set(res.toString()); + return true; + } + return false; +} + +bool ClonedErrorHolder::ToErrorValue(JSContext* aCx, + JS::MutableHandle<JS::Value> aResult) { + JS::Rooted<JS::Value> stackVal(aCx); + JS::Rooted<JSObject*> stack(aCx); + + IgnoredErrorResult rv; + mStack.Read(xpc::CurrentNativeGlobal(aCx), aCx, &stackVal, rv); + // Note: We continue even if reading the stack fails, since we can still + // produce a useful error object even without a stack. That said, if decoding + // the stack fails, there's a pretty good chance that the rest of the message + // is corrupt, and there's no telling how useful the final result will + // actually be. + if (!rv.Failed() && stackVal.isObject()) { + stack = &stackVal.toObject(); + // Make sure that this is really a saved frame. This mainly ensures that + // malicious code on the child side can't trigger a memory exploit by + // sending an incompatible data type, but also protects against potential + // issues like a cross-compartment wrapper being unexpectedly cut. + if (!js::IsSavedFrame(stack)) { + stack = nullptr; + } + } + + if (mType == Type::JSError) { + JS::Rooted<JSString*> filename(aCx); + JS::Rooted<JSString*> message(aCx); + + // For some unknown reason, we can end up with a void string in mFilename, + // which will cause filename to be null, which causes JS::CreateError() to + // crash. Make this code against robust against this by treating void + // strings as the empty string. + if (mFilename.IsVoid()) { + mFilename.Assign(""_ns); + } + + // When fuzzing, we can also end up with the message to be null, + // so we should handle that case as well. + if (mMessage.IsVoid()) { + mMessage.Assign(""_ns); + } + + if (!ToJSString(aCx, mFilename, &filename) || + !ToJSString(aCx, mMessage, &message)) { + return false; + } + if (!JS::CreateError(aCx, mExnType, stack, filename, mLineNumber, mColumn, + nullptr, message, JS::NothingHandleValue, aResult)) { + return false; + } + + if (!mSourceLine.IsVoid()) { + JS::Rooted<JSObject*> errObj(aCx, &aResult.toObject()); + if (JSErrorReport* err = JS_ErrorFromException(aCx, errObj)) { + NS_ConvertUTF8toUTF16 sourceLine(mSourceLine); + // Because this string ends up being consumed as an nsDependentString + // in nsXPCComponents_Utils::ReportError, this needs to be a null + // terminated string. + // + // See Bug 1699569. + if (mTokenOffset >= sourceLine.Length()) { + // Corrupt data, leave linebuf unset. + } else if (JS::UniqueTwoByteChars buffer = + ToNullTerminatedJSStringBuffer(aCx, sourceLine)) { + err->initOwnedLinebuf(buffer.release(), sourceLine.Length(), + mTokenOffset); + } else { + // Just ignore OOM and continue if the string copy failed. + JS_ClearPendingException(aCx); + } + } + } + + return true; + } + + nsCOMPtr<nsIStackFrame> frame(exceptions::CreateStack(aCx, stack)); + + RefPtr<Exception> exn; + if (mType == Type::Exception) { + exn = new Exception(mMessage, mResult, mName, frame, nullptr); + } else { + MOZ_ASSERT(mType == Type::DOMException); + exn = new DOMException(mResult, mMessage, mName, mCode, frame); + } + + return ToJSValue(aCx, exn, aResult); +} + +bool ClonedErrorHolder::Holder::ReadStructuredCloneInternal( + JSContext* aCx, JSStructuredCloneReader* aReader) { + uint32_t length; + uint32_t version; + if (!JS_ReadUint32Pair(aReader, &length, &version)) { + return false; + } + if (length % 8 != 0) { + return false; + } + + JSStructuredCloneData data(mStructuredCloneScope); + while (length) { + size_t size; + char* buffer = data.AllocateBytes(length, &size); + if (!buffer || !JS_ReadBytes(aReader, buffer, size)) { + return false; + } + length -= size; + } + + mBuffer = MakeUnique<JSAutoStructuredCloneBuffer>( + mStructuredCloneScope, &StructuredCloneHolder::sCallbacks, this); + mBuffer->adopt(std::move(data), version, &StructuredCloneHolder::sCallbacks); + return true; +} diff --git a/dom/ipc/ClonedErrorHolder.h b/dom/ipc/ClonedErrorHolder.h new file mode 100644 index 0000000000..862b43c78b --- /dev/null +++ b/dom/ipc/ClonedErrorHolder.h @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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_dom_ClonedErrorHolder_h +#define mozilla_dom_ClonedErrorHolder_h + +#include "mozilla/dom/NonRefcountedDOMObject.h" +#include "nsISupportsImpl.h" +#include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin +#include "js/ErrorReport.h" +#include "js/TypeDecls.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/StructuredCloneHolder.h" +#include "mozilla/Attributes.h" + +class nsIGlobalObject; +class nsQueryActorChild; + +namespace mozilla { +class ErrorResult; + +namespace dom { + +class ClonedErrorHolder final : public NonRefcountedDOMObject { + public: + static UniquePtr<ClonedErrorHolder> Constructor(const GlobalObject& aGlobal, + JS::Handle<JSObject*> aError, + ErrorResult& aRv); + + static UniquePtr<ClonedErrorHolder> Create(JSContext* aCx, + JS::Handle<JSObject*> aError, + ErrorResult& aRv); + + enum class Type : uint8_t { + Uninitialized, + JSError, + Exception, + DOMException, + Max_, + }; + + bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, + JS::MutableHandle<JSObject*> aReflector); + + bool WriteStructuredClone(JSContext* aCx, JSStructuredCloneWriter* aWriter, + StructuredCloneHolder* aHolder); + + // Reads the structured clone data for the ClonedErrorHolder and returns the + // wrapped object (either a JS Error or an Exception/DOMException object) + // directly. Never returns an actual ClonedErrorHolder object. + static JSObject* ReadStructuredClone(JSContext* aCx, + JSStructuredCloneReader* aReader, + StructuredCloneHolder* aHolder); + + private: + ClonedErrorHolder(); + + void Init(JSContext* aCx, JS::Handle<JSObject*> aError, ErrorResult& aRv); + + bool Init(JSContext* aCx, JSStructuredCloneReader* aReader); + + // Creates a new JS Error or Exception/DOMException object based on the + // values stored in the holder. Returns false and sets an exception on aCx + // if it fails. + bool ToErrorValue(JSContext* aCx, JS::MutableHandle<JS::Value> aResult); + + class Holder final : public StructuredCloneHolder { + public: + using StructuredCloneHolder::StructuredCloneHolder; + + bool ReadStructuredCloneInternal(JSContext* aCx, + JSStructuredCloneReader* aReader); + }; + + // Only a subset of the following fields are used, depending on the mType of + // the error stored: + nsCString mName; // Exception, DOMException + nsCString mMessage; // JSError, Exception, DOMException + nsCString mFilename; // JSError only + nsCString mSourceLine; // JSError only + + uint32_t mLineNumber = 0; // JSError only + JS::ColumnNumberOneOrigin mColumn; // JSError only + uint32_t mTokenOffset = 0; // JSError only + uint32_t mErrorNumber = 0; // JSError only + + Type mType = Type::Uninitialized; + + uint16_t mCode = 0; // DOMException only + JSExnType mExnType = JSExnType(0); // JSError only + nsresult mResult = NS_OK; // Exception, DOMException + + // JSError, Exception, DOMException + Holder mStack{Holder::CloningSupported, Holder::TransferringNotSupported, + Holder::StructuredCloneScope::DifferentProcess}; +}; + +} // namespace dom +} // namespace mozilla + +#endif // !defined(mozilla_dom_ClonedErrorHolder_h) diff --git a/dom/ipc/CoalescedInputData.cpp b/dom/ipc/CoalescedInputData.cpp new file mode 100644 index 0000000000..6336f86928 --- /dev/null +++ b/dom/ipc/CoalescedInputData.cpp @@ -0,0 +1,47 @@ +/* -*- 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 "nsRefreshDriver.h" +#include "BrowserChild.h" +#include "CoalescedInputData.h" +#include "mozilla/PresShell.h" + +using namespace mozilla; +using namespace mozilla::dom; + +void CoalescedInputFlusher::StartObserver() { + nsRefreshDriver* refreshDriver = GetRefreshDriver(); + if (mRefreshDriver && mRefreshDriver == refreshDriver) { + // Nothing to do if we already added an observer and it's same refresh + // driver. + return; + } + RemoveObserver(); + if (refreshDriver) { + mRefreshDriver = refreshDriver; + mRefreshDriver->AddRefreshObserver(this, FlushType::Event, + "Coalesced input move flusher"); + } +} + +CoalescedInputFlusher::CoalescedInputFlusher(BrowserChild* aBrowserChild) + : mBrowserChild(aBrowserChild) {} + +void CoalescedInputFlusher::RemoveObserver() { + if (mRefreshDriver) { + mRefreshDriver->RemoveRefreshObserver(this, FlushType::Event); + mRefreshDriver = nullptr; + } +} + +CoalescedInputFlusher::~CoalescedInputFlusher() = default; + +nsRefreshDriver* CoalescedInputFlusher::GetRefreshDriver() { + if (PresShell* presShell = mBrowserChild->GetTopLevelPresShell()) { + return presShell->GetRefreshDriver(); + } + return nullptr; +} diff --git a/dom/ipc/CoalescedInputData.h b/dom/ipc/CoalescedInputData.h new file mode 100644 index 0000000000..aeac9d99a0 --- /dev/null +++ b/dom/ipc/CoalescedInputData.h @@ -0,0 +1,74 @@ +/* -*- 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_dom_CoalescedInputData_h +#define mozilla_dom_CoalescedInputData_h + +#include "mozilla/UniquePtr.h" +#include "mozilla/layers/ScrollableLayerGuid.h" +#include "nsRefreshObservers.h" + +class nsRefreshDriver; + +namespace mozilla::dom { + +class BrowserChild; + +template <class InputEventType> +class CoalescedInputData { + protected: + using ScrollableLayerGuid = mozilla::layers::ScrollableLayerGuid; + + UniquePtr<InputEventType> mCoalescedInputEvent; + ScrollableLayerGuid mGuid; + uint64_t mInputBlockId; + + public: + CoalescedInputData() : mInputBlockId(0) {} + + void RetrieveDataFrom(CoalescedInputData& aSource) { + mCoalescedInputEvent = std::move(aSource.mCoalescedInputEvent); + mGuid = aSource.mGuid; + mInputBlockId = aSource.mInputBlockId; + } + + bool IsEmpty() { return !mCoalescedInputEvent; } + + bool CanCoalesce(const InputEventType& aEvent, + const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId); + + UniquePtr<InputEventType> TakeCoalescedEvent() { + return std::move(mCoalescedInputEvent); + } + + ScrollableLayerGuid GetScrollableLayerGuid() { return mGuid; } + + uint64_t GetInputBlockId() { return mInputBlockId; } +}; + +class CoalescedInputFlusher : public nsARefreshObserver { + public: + explicit CoalescedInputFlusher(BrowserChild* aBrowserChild); + + virtual void WillRefresh(mozilla::TimeStamp aTime) override = 0; + + NS_INLINE_DECL_REFCOUNTING(CoalescedInputFlusher, override) + + void StartObserver(); + void RemoveObserver(); + + protected: + virtual ~CoalescedInputFlusher(); + + nsRefreshDriver* GetRefreshDriver(); + + BrowserChild* mBrowserChild; + RefPtr<nsRefreshDriver> mRefreshDriver; +}; +} // namespace mozilla::dom + +#endif // mozilla_dom_CoalescedInputData_h diff --git a/dom/ipc/CoalescedMouseData.cpp b/dom/ipc/CoalescedMouseData.cpp new file mode 100644 index 0000000000..5aa445d2e0 --- /dev/null +++ b/dom/ipc/CoalescedMouseData.cpp @@ -0,0 +1,83 @@ +/* -*- 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 "base/basictypes.h" + +#include "CoalescedMouseData.h" +#include "BrowserChild.h" + +#include "mozilla/PresShell.h" +#include "mozilla/StaticPrefs_dom.h" +#include "nsRefreshDriver.h" + +using namespace mozilla; +using namespace mozilla::dom; + +void CoalescedMouseData::Coalesce(const WidgetMouseEvent& aEvent, + const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId) { + if (IsEmpty()) { + mCoalescedInputEvent = MakeUnique<WidgetMouseEvent>(aEvent); + mGuid = aGuid; + mInputBlockId = aInputBlockId; + MOZ_ASSERT(!mCoalescedInputEvent->mCoalescedWidgetEvents); + } else { + MOZ_ASSERT(mGuid == aGuid); + MOZ_ASSERT(mInputBlockId == aInputBlockId); + MOZ_ASSERT(mCoalescedInputEvent->mModifiers == aEvent.mModifiers); + MOZ_ASSERT(mCoalescedInputEvent->mReason == aEvent.mReason); + MOZ_ASSERT(mCoalescedInputEvent->mInputSource == aEvent.mInputSource); + MOZ_ASSERT(mCoalescedInputEvent->mButton == aEvent.mButton); + MOZ_ASSERT(mCoalescedInputEvent->mButtons == aEvent.mButtons); + mCoalescedInputEvent->mTimeStamp = aEvent.mTimeStamp; + mCoalescedInputEvent->mRefPoint = aEvent.mRefPoint; + mCoalescedInputEvent->mPressure = aEvent.mPressure; + mCoalescedInputEvent->AssignPointerHelperData(aEvent); + } + + if (aEvent.mMessage == eMouseMove) { + // PointerEvent::getCoalescedEvents is only applied to pointermove events. + if (!mCoalescedInputEvent->mCoalescedWidgetEvents) { + mCoalescedInputEvent->mCoalescedWidgetEvents = + new WidgetPointerEventHolder(); + } + // Append current event in mCoalescedWidgetEvents. We use them to generate + // DOM events when content calls PointerEvent::getCoalescedEvents. + WidgetPointerEvent* event = + mCoalescedInputEvent->mCoalescedWidgetEvents->mEvents.AppendElement( + aEvent); + + event->mMessage = ePointerMove; + event->mFlags.mBubbles = false; + event->mFlags.mCancelable = false; + } +} + +bool CoalescedMouseData::CanCoalesce(const WidgetMouseEvent& aEvent, + const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId) { + MOZ_ASSERT(aEvent.mMessage == eMouseMove); + return !mCoalescedInputEvent || + (!mCoalescedInputEvent->mFlags.mIsSynthesizedForTests && + !aEvent.mFlags.mIsSynthesizedForTests && + mCoalescedInputEvent->mModifiers == aEvent.mModifiers && + mCoalescedInputEvent->mInputSource == aEvent.mInputSource && + mCoalescedInputEvent->pointerId == aEvent.pointerId && + mCoalescedInputEvent->mButton == aEvent.mButton && + mCoalescedInputEvent->mButtons == aEvent.mButtons && mGuid == aGuid && + mInputBlockId == aInputBlockId); +} + +CoalescedMouseMoveFlusher::CoalescedMouseMoveFlusher( + BrowserChild* aBrowserChild) + : CoalescedInputFlusher(aBrowserChild) {} + +void CoalescedMouseMoveFlusher::WillRefresh(mozilla::TimeStamp aTime) { + MOZ_ASSERT(mRefreshDriver); + mBrowserChild->FlushAllCoalescedMouseData(); + mBrowserChild->ProcessPendingCoalescedMouseDataAndDispatchEvents(); +} + +CoalescedMouseMoveFlusher::~CoalescedMouseMoveFlusher() { RemoveObserver(); } diff --git a/dom/ipc/CoalescedMouseData.h b/dom/ipc/CoalescedMouseData.h new file mode 100644 index 0000000000..4d09783ec4 --- /dev/null +++ b/dom/ipc/CoalescedMouseData.h @@ -0,0 +1,45 @@ +/* -*- 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_dom_CoalescedMouseData_h +#define mozilla_dom_CoalescedMouseData_h + +#include "CoalescedInputData.h" +#include "mozilla/MouseEvents.h" +#include "nsRefreshObservers.h" + +class nsRefreshDriver; + +namespace mozilla::dom { + +class CoalescedMouseData final : public CoalescedInputData<WidgetMouseEvent> { + public: + CoalescedMouseData() { MOZ_COUNT_CTOR(mozilla::dom::CoalescedMouseData); } + + ~CoalescedMouseData() { MOZ_COUNT_DTOR(mozilla::dom::CoalescedMouseData); } + + void Coalesce(const WidgetMouseEvent& aEvent, + const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId); + + bool CanCoalesce(const WidgetMouseEvent& aEvent, + const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId); +}; + +class CoalescedMouseMoveFlusher final : public CoalescedInputFlusher { + public: + explicit CoalescedMouseMoveFlusher(BrowserChild* aBrowserChild); + + void WillRefresh(mozilla::TimeStamp aTime) override; + + private: + ~CoalescedMouseMoveFlusher() override; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_CoalescedMouseData_h diff --git a/dom/ipc/CoalescedTouchData.cpp b/dom/ipc/CoalescedTouchData.cpp new file mode 100644 index 0000000000..3c3bc71a0c --- /dev/null +++ b/dom/ipc/CoalescedTouchData.cpp @@ -0,0 +1,132 @@ +/* -*- 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 "CoalescedTouchData.h" +#include "BrowserChild.h" +#include "mozilla/dom/PointerEventHandler.h" + +using namespace mozilla; +using namespace mozilla::dom; + +static const uint32_t sMaxTouchMoveIdentifiers = 10; + +void CoalescedTouchData::CreateCoalescedTouchEvent( + const WidgetTouchEvent& aEvent) { + MOZ_ASSERT(IsEmpty()); + mCoalescedInputEvent = MakeUnique<WidgetTouchEvent>(aEvent); + for (size_t i = 0; i < mCoalescedInputEvent->mTouches.Length(); i++) { + const RefPtr<Touch>& touch = mCoalescedInputEvent->mTouches[i]; + touch->mCoalescedWidgetEvents = MakeAndAddRef<WidgetPointerEventHolder>(); + // Add an initial event into coalesced events, so + // the relevant pointer event would at least contain one coalesced event. + WidgetPointerEvent* event = + touch->mCoalescedWidgetEvents->mEvents.AppendElement(WidgetPointerEvent( + aEvent.IsTrusted(), ePointerMove, aEvent.mWidget)); + PointerEventHandler::InitPointerEventFromTouch(*event, aEvent, *touch, + i == 0); + event->mFlags.mBubbles = false; + event->mFlags.mCancelable = false; + } +} + +void CoalescedTouchData::Coalesce(const WidgetTouchEvent& aEvent, + const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId, + const nsEventStatus& aApzResponse) { + MOZ_ASSERT(aEvent.mMessage == eTouchMove); + if (IsEmpty()) { + CreateCoalescedTouchEvent(aEvent); + mGuid = aGuid; + mInputBlockId = aInputBlockId; + mApzResponse = aApzResponse; + } else { + MOZ_ASSERT(mGuid == aGuid); + MOZ_ASSERT(mInputBlockId == aInputBlockId); + MOZ_ASSERT(mCoalescedInputEvent->mModifiers == aEvent.mModifiers); + MOZ_ASSERT(mCoalescedInputEvent->mInputSource == aEvent.mInputSource); + + for (size_t i = 0; i < aEvent.mTouches.Length(); i++) { + const RefPtr<Touch>& touch = aEvent.mTouches[i]; + // Get the same touch in the original event + RefPtr<Touch> sameTouch = GetTouch(touch->Identifier()); + // The checks in CoalescedTouchData::CanCoalesce ensure it should never + // be null. + MOZ_ASSERT(sameTouch); + MOZ_ASSERT(sameTouch->mCoalescedWidgetEvents); + MOZ_ASSERT(!sameTouch->mCoalescedWidgetEvents->mEvents.IsEmpty()); + if (!sameTouch->Equals(touch)) { + sameTouch->SetSameAs(touch); + WidgetPointerEvent* event = + sameTouch->mCoalescedWidgetEvents->mEvents.AppendElement( + WidgetPointerEvent(aEvent.IsTrusted(), ePointerMove, + aEvent.mWidget)); + PointerEventHandler::InitPointerEventFromTouch(*event, aEvent, *touch, + i == 0); + event->mFlags.mBubbles = false; + event->mFlags.mCancelable = false; + } + } + + mCoalescedInputEvent->mTimeStamp = aEvent.mTimeStamp; + } +} + +bool CoalescedTouchData::CanCoalesce(const WidgetTouchEvent& aEvent, + const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId, + const nsEventStatus& aApzResponse) { + MOZ_ASSERT(!IsEmpty()); + if (mGuid != aGuid || mInputBlockId != aInputBlockId || + mCoalescedInputEvent->mModifiers != aEvent.mModifiers || + mCoalescedInputEvent->mInputSource != aEvent.mInputSource || + aEvent.mTouches.Length() > sMaxTouchMoveIdentifiers) { + return false; + } + + // Ensures both touchmove events have the same touches + if (aEvent.mTouches.Length() != mCoalescedInputEvent->mTouches.Length()) { + return false; + } + for (const RefPtr<Touch>& touch : aEvent.mTouches) { + if (!GetTouch(touch->Identifier())) { + return false; + } + } + + // If one of them is eIgnore and the other one is eConsumeDoDefault, + // we always coalesce them to eConsumeDoDefault. + if (mApzResponse != aApzResponse) { + if (mApzResponse == nsEventStatus::nsEventStatus_eIgnore && + aApzResponse == nsEventStatus::nsEventStatus_eConsumeDoDefault) { + mApzResponse = aApzResponse; + } else if (mApzResponse != nsEventStatus::nsEventStatus_eConsumeDoDefault || + aApzResponse != nsEventStatus::nsEventStatus_eIgnore) { + return false; + } + } + + return true; +} + +Touch* CoalescedTouchData::GetTouch(int32_t aIdentifier) { + for (const RefPtr<Touch>& touch : mCoalescedInputEvent->mTouches) { + if (touch->Identifier() == aIdentifier) { + return touch; + } + } + return nullptr; +} + +void CoalescedTouchMoveFlusher::WillRefresh(mozilla::TimeStamp aTime) { + MOZ_ASSERT(mRefreshDriver); + mBrowserChild->ProcessPendingCoalescedTouchData(); +} + +CoalescedTouchMoveFlusher::CoalescedTouchMoveFlusher( + BrowserChild* aBrowserChild) + : CoalescedInputFlusher(aBrowserChild) {} + +CoalescedTouchMoveFlusher::~CoalescedTouchMoveFlusher() { RemoveObserver(); } diff --git a/dom/ipc/CoalescedTouchData.h b/dom/ipc/CoalescedTouchData.h new file mode 100644 index 0000000000..1fc0efa262 --- /dev/null +++ b/dom/ipc/CoalescedTouchData.h @@ -0,0 +1,46 @@ +/* -*- 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_dom__CoalescedTouchData_h +#define mozilla_dom__CoalescedTouchData_h + +#include "CoalescedInputData.h" +#include "mozilla/TouchEvents.h" + +namespace mozilla::dom { + +class CoalescedTouchData final : public CoalescedInputData<WidgetTouchEvent> { + public: + void CreateCoalescedTouchEvent(const WidgetTouchEvent& aEvent); + + void Coalesce(const WidgetTouchEvent& aEvent, + const ScrollableLayerGuid& aGuid, const uint64_t& aInputBlockId, + const nsEventStatus& aApzResponse); + + bool CanCoalesce(const WidgetTouchEvent& aEvent, + const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId, + const nsEventStatus& aApzResponse); + + nsEventStatus GetApzResponse() { return mApzResponse; } + + private: + Touch* GetTouch(int32_t aIdentifier); + + nsEventStatus mApzResponse = nsEventStatus_eIgnore; +}; + +class CoalescedTouchMoveFlusher final : public CoalescedInputFlusher { + public: + explicit CoalescedTouchMoveFlusher(BrowserChild* aBrowserChild); + void WillRefresh(mozilla::TimeStamp aTime) override; + + private: + ~CoalescedTouchMoveFlusher() override; +}; +}; // namespace mozilla::dom + +#endif // mozilla_dom_CoalescedTouchData_h diff --git a/dom/ipc/CoalescedWheelData.cpp b/dom/ipc/CoalescedWheelData.cpp new file mode 100644 index 0000000000..c8e23ac86d --- /dev/null +++ b/dom/ipc/CoalescedWheelData.cpp @@ -0,0 +1,46 @@ +/* -*- 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 "base/basictypes.h" + +#include "CoalescedWheelData.h" + +using namespace mozilla; +using namespace mozilla::dom; + +void CoalescedWheelData::Coalesce(const WidgetWheelEvent& aEvent, + const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId) { + if (IsEmpty()) { + mCoalescedInputEvent = MakeUnique<WidgetWheelEvent>(aEvent); + mGuid = aGuid; + mInputBlockId = aInputBlockId; + } else { + MOZ_ASSERT(mGuid == aGuid); + MOZ_ASSERT(mInputBlockId == aInputBlockId); + MOZ_ASSERT(mCoalescedInputEvent->mModifiers == aEvent.mModifiers); + MOZ_ASSERT(mCoalescedInputEvent->mDeltaMode == aEvent.mDeltaMode); + MOZ_ASSERT(mCoalescedInputEvent->mCanTriggerSwipe == + aEvent.mCanTriggerSwipe); + mCoalescedInputEvent->mDeltaX += aEvent.mDeltaX; + mCoalescedInputEvent->mDeltaY += aEvent.mDeltaY; + mCoalescedInputEvent->mDeltaZ += aEvent.mDeltaZ; + mCoalescedInputEvent->mLineOrPageDeltaX += aEvent.mLineOrPageDeltaX; + mCoalescedInputEvent->mLineOrPageDeltaY += aEvent.mLineOrPageDeltaY; + mCoalescedInputEvent->mTimeStamp = aEvent.mTimeStamp; + } +} + +bool CoalescedWheelData::CanCoalesce(const WidgetWheelEvent& aEvent, + const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId) { + MOZ_ASSERT(!IsEmpty()); + return !mCoalescedInputEvent || + (mCoalescedInputEvent->mRefPoint == aEvent.mRefPoint && + mCoalescedInputEvent->mModifiers == aEvent.mModifiers && + mCoalescedInputEvent->mDeltaMode == aEvent.mDeltaMode && + mCoalescedInputEvent->mCanTriggerSwipe == aEvent.mCanTriggerSwipe && + mGuid == aGuid && mInputBlockId == aInputBlockId); +} diff --git a/dom/ipc/CoalescedWheelData.h b/dom/ipc/CoalescedWheelData.h new file mode 100644 index 0000000000..cd322d8874 --- /dev/null +++ b/dom/ipc/CoalescedWheelData.h @@ -0,0 +1,28 @@ +/* -*- 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_dom_CoalescedWheelData_h +#define mozilla_dom_CoalescedWheelData_h + +#include "CoalescedInputData.h" +#include "mozilla/MouseEvents.h" + +namespace mozilla::dom { + +class CoalescedWheelData final : public CoalescedInputData<WidgetWheelEvent> { + public: + void Coalesce(const WidgetWheelEvent& aEvent, + const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId); + + bool CanCoalesce(const WidgetWheelEvent& aEvent, + const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId); +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_CoalescedWheelData_h diff --git a/dom/ipc/ColorPickerParent.cpp b/dom/ipc/ColorPickerParent.cpp new file mode 100644 index 0000000000..18cf197330 --- /dev/null +++ b/dom/ipc/ColorPickerParent.cpp @@ -0,0 +1,77 @@ +/* -*- 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 "ColorPickerParent.h" +#include "nsComponentManagerUtils.h" +#include "mozilla/dom/Document.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/BrowserParent.h" + +using mozilla::Unused; +using namespace mozilla::dom; + +NS_IMPL_ISUPPORTS(ColorPickerParent::ColorPickerShownCallback, + nsIColorPickerShownCallback); + +NS_IMETHODIMP +ColorPickerParent::ColorPickerShownCallback::Update(const nsAString& aColor) { + if (mColorPickerParent) { + Unused << mColorPickerParent->SendUpdate(aColor); + } + return NS_OK; +} + +NS_IMETHODIMP +ColorPickerParent::ColorPickerShownCallback::Done(const nsAString& aColor) { + if (mColorPickerParent) { + Unused << ColorPickerParent::Send__delete__(mColorPickerParent, aColor); + } + return NS_OK; +} + +void ColorPickerParent::ColorPickerShownCallback::Destroy() { + mColorPickerParent = nullptr; +} + +bool ColorPickerParent::CreateColorPicker() { + mPicker = do_CreateInstance("@mozilla.org/colorpicker;1"); + if (!mPicker) { + return false; + } + + Element* ownerElement = BrowserParent::GetFrom(Manager())->GetOwnerElement(); + if (!ownerElement) { + return false; + } + + nsCOMPtr<nsPIDOMWindowOuter> window = ownerElement->OwnerDoc()->GetWindow(); + if (!window) { + return false; + } + + return NS_SUCCEEDED( + mPicker->Init(window, mTitle, mInitialColor, mDefaultColors)); +} + +mozilla::ipc::IPCResult ColorPickerParent::RecvOpen() { + if (!CreateColorPicker()) { + Unused << Send__delete__(this, mInitialColor); + return IPC_OK(); + } + + MOZ_ASSERT(!mCallback); + mCallback = new ColorPickerShownCallback(this); + + mPicker->Open(mCallback); + return IPC_OK(); +}; + +void ColorPickerParent::ActorDestroy(ActorDestroyReason aWhy) { + if (mCallback) { + mCallback->Destroy(); + } +} diff --git a/dom/ipc/ColorPickerParent.h b/dom/ipc/ColorPickerParent.h new file mode 100644 index 0000000000..00b0828034 --- /dev/null +++ b/dom/ipc/ColorPickerParent.h @@ -0,0 +1,58 @@ +/* -*- 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_dom_ColorPickerParent_h +#define mozilla_dom_ColorPickerParent_h + +#include "mozilla/dom/PColorPickerParent.h" +#include "nsIColorPicker.h" + +namespace mozilla::dom { + +class ColorPickerParent : public PColorPickerParent { + public: + ColorPickerParent(const nsString& aTitle, const nsString& aInitialColor, + const nsTArray<nsString>& aDefaultColors) + : mTitle(aTitle), + mInitialColor(aInitialColor), + mDefaultColors(aDefaultColors.Clone()) {} + + NS_INLINE_DECL_REFCOUNTING(ColorPickerParent, final) + + virtual mozilla::ipc::IPCResult RecvOpen() override; + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + class ColorPickerShownCallback final : public nsIColorPickerShownCallback { + public: + explicit ColorPickerShownCallback(ColorPickerParent* aColorPickerParnet) + : mColorPickerParent(aColorPickerParnet) {} + + NS_DECL_ISUPPORTS + NS_DECL_NSICOLORPICKERSHOWNCALLBACK + + void Destroy(); + + private: + ~ColorPickerShownCallback() = default; + RefPtr<ColorPickerParent> mColorPickerParent; + }; + + private: + virtual ~ColorPickerParent() = default; + + bool CreateColorPicker(); + + RefPtr<ColorPickerShownCallback> mCallback; + nsCOMPtr<nsIColorPicker> mPicker; + + nsString mTitle; + nsString mInitialColor; + nsTArray<nsString> mDefaultColors; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_ColorPickerParent_h diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp new file mode 100644 index 0000000000..e3b0ebd795 --- /dev/null +++ b/dom/ipc/ContentChild.cpp @@ -0,0 +1,5081 @@ +/* -*- Mode: C++; tab-width: 2; 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/. */ + +#ifdef MOZ_WIDGET_ANDROID +# include "AndroidDecoderModule.h" +#endif + +#include "BrowserChild.h" +#include "nsNSSComponent.h" +#include "ContentChild.h" +#include "GeckoProfiler.h" +#include "HandlerServiceChild.h" +#include "nsXPLookAndFeel.h" +#include "mozilla/AppShutdown.h" +#include "mozilla/Attributes.h" +#include "mozilla/BackgroundHangMonitor.h" +#include "mozilla/BenchmarkStorageChild.h" +#include "mozilla/FOGIPC.h" +#include "GMPServiceChild.h" +#include "Geolocation.h" +#include "imgLoader.h" +#include "ScrollingMetrics.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/ClipboardReadRequestChild.h" +#include "mozilla/Components.h" +#include "mozilla/HangDetails.h" +#include "mozilla/LoadInfo.h" +#include "mozilla/Logging.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/MemoryTelemetry.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/PerfStats.h" +#include "mozilla/Preferences.h" +#include "mozilla/ProcessHangMonitorIPC.h" +#include "mozilla/RemoteDecoderManagerChild.h" +#include "mozilla/RemoteLazyInputStreamChild.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/SharedStyleSheetCache.h" +#include "mozilla/SimpleEnumerator.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/StaticPrefs_browser.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StaticPrefs_fission.h" +#include "mozilla/StaticPrefs_javascript.h" +#include "mozilla/StaticPrefs_media.h" +#include "mozilla/StaticPrefs_threads.h" +#include "mozilla/StorageAccessAPIHelper.h" +#include "mozilla/TelemetryIPC.h" +#include "mozilla/Unused.h" +#include "mozilla/WebBrowserPersistDocumentChild.h" +#include "mozilla/devtools/HeapSnapshotTempFileHelperChild.h" +#include "mozilla/dom/AutoSuppressEventHandlingAndSuspend.h" +#include "mozilla/dom/BlobImpl.h" +#include "mozilla/dom/BrowserBridgeHost.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/BrowsingContextGroup.h" +#include "mozilla/dom/ChildProcessChannelListener.h" +#include "mozilla/dom/ChildProcessMessageManager.h" +#include "mozilla/dom/ClientManager.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ContentProcessManager.h" +#include "mozilla/dom/ContentPlaybackController.h" +#include "mozilla/dom/ContentProcessMessageManager.h" +#include "mozilla/dom/DataTransfer.h" +#include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/ExternalHelperAppChild.h" +#include "mozilla/dom/GetFilesHelper.h" +#include "mozilla/dom/IPCBlobUtils.h" +#include "mozilla/dom/InProcessChild.h" +#include "mozilla/dom/JSActorService.h" +#include "mozilla/dom/JSProcessActorBinding.h" +#include "mozilla/dom/JSProcessActorChild.h" +#include "mozilla/dom/LSObject.h" +#include "mozilla/dom/MemoryReportRequest.h" +#include "mozilla/dom/PSessionStorageObserverChild.h" +#include "mozilla/dom/PostMessageEvent.h" +#include "mozilla/dom/PushNotifier.h" +#include "mozilla/dom/RemoteWorkerService.h" +#include "mozilla/dom/ScreenOrientation.h" +#include "mozilla/dom/ServiceWorkerManager.h" +#include "mozilla/dom/SessionStorageManager.h" +#include "mozilla/dom/URLClassifierChild.h" +#include "mozilla/dom/UserActivation.h" +#include "mozilla/dom/WindowGlobalChild.h" +#include "mozilla/dom/WorkerDebugger.h" +#include "mozilla/dom/WorkerDebuggerManager.h" +#include "mozilla/dom/ipc/SharedMap.h" +#include "mozilla/extensions/ExtensionsChild.h" +#include "mozilla/extensions/StreamFilterParent.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/hal_sandbox/PHalChild.h" +#include "mozilla/intl/L10nRegistry.h" +#include "mozilla/intl/LocaleService.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/FileDescriptorUtils.h" +#include "mozilla/ipc/GeckoChildProcessHost.h" +#include "mozilla/ipc/ProcessChild.h" +#include "mozilla/ipc/TestShellChild.h" +#include "mozilla/layers/APZChild.h" +#include "mozilla/layers/CompositorManagerChild.h" +#include "mozilla/layers/ContentProcessController.h" +#include "mozilla/layers/ImageBridgeChild.h" +#ifdef NS_PRINTING +# include "mozilla/layout/RemotePrintJobChild.h" +#endif +#include "mozilla/loader/ScriptCacheActors.h" +#include "mozilla/media/MediaChild.h" +#include "mozilla/net/CaptivePortalService.h" +#include "mozilla/net/ChildDNSService.h" +#include "mozilla/net/CookieServiceChild.h" +#include "mozilla/net/DocumentChannelChild.h" +#include "mozilla/net/HttpChannelChild.h" +#include "mozilla/widget/RemoteLookAndFeel.h" +#include "mozilla/widget/ScreenManager.h" +#include "mozilla/widget/WidgetMessageUtils.h" +#include "nsBaseDragService.h" +#include "nsDocShellLoadTypes.h" +#include "nsFocusManager.h" +#include "nsHttpHandler.h" +#include "nsIConsoleService.h" +#include "nsIInputStreamChannel.h" +#include "nsILayoutHistoryState.h" +#include "nsILoadGroup.h" +#include "nsIOpenWindowInfo.h" +#include "nsISimpleEnumerator.h" +#include "nsIStringBundle.h" +#include "nsIURIMutator.h" +#include "nsQueryObject.h" +#include "nsRefreshDriver.h" +#include "nsSandboxFlags.h" +#include "mozmemory.h" + +#include "ChildProfilerController.h" + +#if defined(MOZ_SANDBOX) +# include "mozilla/SandboxSettings.h" +# if defined(XP_WIN) +# include "mozilla/sandboxTarget.h" +# elif defined(XP_LINUX) +# include "CubebUtils.h" +# include "mozilla/Sandbox.h" +# include "mozilla/SandboxInfo.h" +# elif defined(XP_MACOSX) +# include <CoreGraphics/CGError.h> +# include "mozilla/Sandbox.h" +# elif defined(__OpenBSD__) +# include <err.h> +# include <sys/stat.h> +# include <unistd.h> + +# include <fstream> + +# include "BinaryPath.h" +# include "SpecialSystemDirectory.h" +# include "nsILineInputStream.h" +# include "mozilla/ipc/UtilityProcessSandboxing.h" +# endif +# if defined(MOZ_DEBUG) && defined(ENABLE_TESTS) +# include "mozilla/SandboxTestingChild.h" +# endif +#endif + +#include "SandboxHal.h" +#include "mozInlineSpellChecker.h" +#include "mozilla/GlobalStyleSheetCache.h" +#include "nsAnonymousTemporaryFile.h" +#include "nsCategoryManagerUtils.h" +#include "nsClipboardProxy.h" +#include "nsContentPermissionHelper.h" +#include "nsDebugImpl.h" +#include "nsDirectoryService.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsDocShell.h" +#include "nsDocShellLoadState.h" +#include "nsHashPropertyBag.h" +#include "nsIConsoleListener.h" +#include "nsICycleCollectorListener.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIDocumentViewer.h" +#include "nsIDragService.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIMemoryInfoDumper.h" +#include "nsIMemoryReporter.h" +#include "nsIObserverService.h" +#include "nsIOService.h" +#include "nsIScriptError.h" +#include "nsIScriptSecurityManager.h" +#include "nsJSEnvironment.h" +#include "nsJSUtils.h" +#include "nsMemoryInfoDumper.h" +#include "nsServiceManagerUtils.h" +#include "nsStyleSheetService.h" +#include "nsThreadManager.h" +#include "nsVariant.h" +#include "nsXULAppAPI.h" +#include "IHistory.h" +#include "ReferrerInfo.h" +#include "base/message_loop.h" +#include "base/process_util.h" +#include "base/task.h" +#include "mozilla/dom/BlobURLProtocolHandler.h" +#include "mozilla/dom/PCycleCollectWithLogsChild.h" +#include "mozilla/dom/PerformanceStorage.h" +#include "nsChromeRegistryContent.h" +#include "nsFrameMessageManager.h" +#include "nsNetUtil.h" +#include "nsWindowMemoryReporter.h" + +#ifdef MOZ_WEBRTC +# include "jsapi/WebrtcGlobalChild.h" +#endif + +#include "PermissionMessageUtils.h" +#include "mozilla/Permission.h" +#include "mozilla/PermissionManager.h" + +#if defined(MOZ_WIDGET_ANDROID) +# include "APKOpen.h" +# include <sched.h> +#endif + +#ifdef XP_WIN +# include <process.h> +# define getpid _getpid +# include "mozilla/WinDllServices.h" +#endif + +#if defined(XP_MACOSX) +# include "nsMacUtilsImpl.h" +# include <sys/qos.h> +#endif /* XP_MACOSX */ + +#ifdef MOZ_X11 +# include "mozilla/X11Util.h" +#endif + +#ifdef ACCESSIBILITY +# include "nsAccessibilityService.h" +# ifdef XP_WIN +# include "mozilla/a11y/AccessibleWrap.h" +# endif +# include "mozilla/a11y/DocAccessible.h" +# include "mozilla/a11y/DocManager.h" +# include "mozilla/a11y/OuterDocAccessible.h" +#endif + +#include "mozilla/dom/File.h" +#include "mozilla/dom/MediaControllerBinding.h" + +#ifdef MOZ_WEBSPEECH +# include "mozilla/dom/PSpeechSynthesisChild.h" +#endif + +#include "ClearOnShutdown.h" +#include "DomainPolicy.h" +#include "GfxInfoBase.h" +#include "MMPrinter.h" +#include "mozilla/ipc/ProcessUtils.h" +#include "mozilla/ipc/URIUtils.h" +#include "VRManagerChild.h" +#include "gfxPlatform.h" +#include "gfxPlatformFontList.h" +#include "mozilla/RemoteSpellCheckEngineChild.h" +#include "mozilla/dom/TabContext.h" +#include "mozilla/dom/ipc/StructuredCloneData.h" +#include "mozilla/ipc/CrashReporterClient.h" +#include "mozilla/net/NeckoMessageUtils.h" +#include "mozilla/widget/PuppetBidiKeyboard.h" +#include "nsContentUtils.h" +#include "nsIPrincipal.h" +#include "nsString.h" +#include "nscore.h" // for NS_FREE_PERMANENT_DATA +#include "private/pprio.h" + +#ifdef MOZ_WIDGET_GTK +# include "mozilla/WidgetUtilsGtk.h" +# include "nsAppRunner.h" +# include <gtk/gtk.h> +#endif + +#ifdef MOZ_CODE_COVERAGE +# include "mozilla/CodeCoverageHandler.h" +#endif + +extern mozilla::LazyLogModule gSHIPBFCacheLog; + +using namespace mozilla; +using namespace mozilla::dom::ipc; +using namespace mozilla::media; +using namespace mozilla::embedding; +using namespace mozilla::gmp; +using namespace mozilla::hal_sandbox; +using namespace mozilla::ipc; +using namespace mozilla::intl; +using namespace mozilla::layers; +using namespace mozilla::layout; +using namespace mozilla::net; +using namespace mozilla::widget; +using mozilla::loader::PScriptCacheChild; + +namespace geckoprofiler::markers { +struct ProcessPriorityChange { + static constexpr Span<const char> MarkerTypeName() { + return MakeStringSpan("ProcessPriorityChange"); + } + static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter, + const ProfilerString8View& aPreviousPriority, + const ProfilerString8View& aNewPriority) { + aWriter.StringProperty("Before", aPreviousPriority); + aWriter.StringProperty("After", aNewPriority); + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; + schema.AddKeyFormat("Before", MS::Format::String); + schema.AddKeyFormat("After", MS::Format::String); + schema.AddStaticLabelValue("Note", + "This is a notification of the priority change " + "that was done by the parent process"); + schema.SetAllLabels( + "priority: {marker.data.Before} -> {marker.data.After}"); + return schema; + } +}; + +struct ProcessPriority { + static constexpr Span<const char> MarkerTypeName() { + return MakeStringSpan("ProcessPriority"); + } + static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter, + const ProfilerString8View& aPriority, + const ProfilingState& aProfilingState) { + aWriter.StringProperty("Priority", aPriority); + aWriter.StringProperty("Marker cause", + ProfilerString8View::WrapNullTerminatedString( + ProfilingStateToString(aProfilingState))); + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; + schema.AddKeyFormat("Priority", MS::Format::String); + schema.AddKeyFormat("Marker cause", MS::Format::String); + schema.SetAllLabels("priority: {marker.data.Priority}"); + return schema; + } +}; +} // namespace geckoprofiler::markers + +namespace mozilla { +namespace dom { + +// IPC sender for remote GC/CC logging. +class CycleCollectWithLogsChild final : public PCycleCollectWithLogsChild { + public: + NS_INLINE_DECL_REFCOUNTING(CycleCollectWithLogsChild) + + class Sink final : public nsICycleCollectorLogSink { + NS_DECL_ISUPPORTS + + Sink(CycleCollectWithLogsChild* aActor, const FileDescriptor& aGCLog, + const FileDescriptor& aCCLog) { + mActor = aActor; + mGCLog = FileDescriptorToFILE(aGCLog, "w"); + mCCLog = FileDescriptorToFILE(aCCLog, "w"); + } + + NS_IMETHOD Open(FILE** aGCLog, FILE** aCCLog) override { + if (NS_WARN_IF(!mGCLog) || NS_WARN_IF(!mCCLog)) { + return NS_ERROR_FAILURE; + } + *aGCLog = mGCLog; + *aCCLog = mCCLog; + return NS_OK; + } + + NS_IMETHOD CloseGCLog() override { + MOZ_ASSERT(mGCLog); + fclose(mGCLog); + mGCLog = nullptr; + mActor->SendCloseGCLog(); + return NS_OK; + } + + NS_IMETHOD CloseCCLog() override { + MOZ_ASSERT(mCCLog); + fclose(mCCLog); + mCCLog = nullptr; + mActor->SendCloseCCLog(); + return NS_OK; + } + + NS_IMETHOD GetFilenameIdentifier(nsAString& aIdentifier) override { + return UnimplementedProperty(); + } + + NS_IMETHOD SetFilenameIdentifier(const nsAString& aIdentifier) override { + return UnimplementedProperty(); + } + + NS_IMETHOD GetProcessIdentifier(int32_t* aIdentifier) override { + return UnimplementedProperty(); + } + + NS_IMETHOD SetProcessIdentifier(int32_t aIdentifier) override { + return UnimplementedProperty(); + } + + NS_IMETHOD GetGcLog(nsIFile** aPath) override { + return UnimplementedProperty(); + } + + NS_IMETHOD GetCcLog(nsIFile** aPath) override { + return UnimplementedProperty(); + } + + private: + ~Sink() { + if (mGCLog) { + fclose(mGCLog); + mGCLog = nullptr; + } + if (mCCLog) { + fclose(mCCLog); + mCCLog = nullptr; + } + // The XPCOM refcount drives the IPC lifecycle; + Unused << mActor->Send__delete__(mActor); + } + + nsresult UnimplementedProperty() { + MOZ_ASSERT(false, + "This object is a remote GC/CC logger;" + " this property isn't meaningful."); + return NS_ERROR_UNEXPECTED; + } + + RefPtr<CycleCollectWithLogsChild> mActor; + FILE* mGCLog; + FILE* mCCLog; + }; + + private: + ~CycleCollectWithLogsChild() = default; +}; + +NS_IMPL_ISUPPORTS(CycleCollectWithLogsChild::Sink, nsICycleCollectorLogSink); + +class AlertObserver { + public: + AlertObserver(nsIObserver* aObserver, const nsString& aData) + : mObserver(aObserver), mData(aData) {} + + ~AlertObserver() = default; + + nsCOMPtr<nsIObserver> mObserver; + nsString mData; +}; + +class ConsoleListener final : public nsIConsoleListener { + public: + explicit ConsoleListener(ContentChild* aChild) : mChild(aChild) {} + + NS_DECL_ISUPPORTS + NS_DECL_NSICONSOLELISTENER + + private: + ~ConsoleListener() = default; + + ContentChild* mChild; + friend class ContentChild; +}; + +NS_IMPL_ISUPPORTS(ConsoleListener, nsIConsoleListener) + +// Before we send the error to the parent process (which +// involves copying the memory), truncate any long lines. CSS +// errors in particular share the memory for long lines with +// repeated errors, but the IPC communication we're about to do +// will break that sharing, so we better truncate now. +static void TruncateString(nsAString& aString) { + if (aString.Length() > 1000) { + aString.Truncate(1000); + } +} + +NS_IMETHODIMP +ConsoleListener::Observe(nsIConsoleMessage* aMessage) { + if (!mChild) { + return NS_OK; + } + + nsCOMPtr<nsIScriptError> scriptError = do_QueryInterface(aMessage); + if (scriptError) { + nsAutoString msg, sourceName, sourceLine; + nsCString category; + uint32_t lineNum, colNum, flags; + bool fromPrivateWindow, fromChromeContext; + + nsresult rv = scriptError->GetErrorMessage(msg); + NS_ENSURE_SUCCESS(rv, rv); + TruncateString(msg); + rv = scriptError->GetSourceName(sourceName); + NS_ENSURE_SUCCESS(rv, rv); + TruncateString(sourceName); + rv = scriptError->GetSourceLine(sourceLine); + NS_ENSURE_SUCCESS(rv, rv); + TruncateString(sourceLine); + + rv = scriptError->GetCategory(getter_Copies(category)); + NS_ENSURE_SUCCESS(rv, rv); + rv = scriptError->GetLineNumber(&lineNum); + NS_ENSURE_SUCCESS(rv, rv); + rv = scriptError->GetColumnNumber(&colNum); + NS_ENSURE_SUCCESS(rv, rv); + rv = scriptError->GetFlags(&flags); + NS_ENSURE_SUCCESS(rv, rv); + rv = scriptError->GetIsFromPrivateWindow(&fromPrivateWindow); + NS_ENSURE_SUCCESS(rv, rv); + rv = scriptError->GetIsFromChromeContext(&fromChromeContext); + NS_ENSURE_SUCCESS(rv, rv); + + { + AutoJSAPI jsapi; + jsapi.Init(); + JSContext* cx = jsapi.cx(); + + JS::Rooted<JS::Value> stack(cx); + rv = scriptError->GetStack(&stack); + NS_ENSURE_SUCCESS(rv, rv); + + if (stack.isObject()) { + // Because |stack| might be a cross-compartment wrapper, we can't use it + // with JSAutoRealm. Use the stackGlobal for that. + JS::Rooted<JS::Value> stackGlobal(cx); + rv = scriptError->GetStackGlobal(&stackGlobal); + NS_ENSURE_SUCCESS(rv, rv); + + JSAutoRealm ar(cx, &stackGlobal.toObject()); + + StructuredCloneData data; + ErrorResult err; + data.Write(cx, stack, err); + if (err.Failed()) { + return err.StealNSResult(); + } + + ClonedMessageData cloned; + if (!data.BuildClonedMessageData(cloned)) { + return NS_ERROR_FAILURE; + } + + mChild->SendScriptErrorWithStack( + msg, sourceName, sourceLine, lineNum, colNum, flags, category, + fromPrivateWindow, fromChromeContext, cloned); + return NS_OK; + } + } + + mChild->SendScriptError(msg, sourceName, sourceLine, lineNum, colNum, flags, + category, fromPrivateWindow, 0, fromChromeContext); + return NS_OK; + } + + nsString msg; + nsresult rv = aMessage->GetMessageMoz(msg); + NS_ENSURE_SUCCESS(rv, rv); + mChild->SendConsoleMessage(msg); + return NS_OK; +} + +#ifdef NIGHTLY_BUILD +/** + * The singleton of this class is registered with the BackgroundHangMonitor as + * an annotator, so that the hang monitor can record whether or not there were + * pending input events when the thread hung. + */ +class PendingInputEventHangAnnotator final : public BackgroundHangAnnotator { + public: + virtual void AnnotateHang(BackgroundHangAnnotations& aAnnotations) override { + int32_t pending = ContentChild::GetSingleton()->GetPendingInputEvents(); + if (pending > 0) { + aAnnotations.AddAnnotation(u"PendingInput"_ns, pending); + } + } + + static PendingInputEventHangAnnotator sSingleton; +}; +PendingInputEventHangAnnotator PendingInputEventHangAnnotator::sSingleton; +#endif + +class ContentChild::ShutdownCanary final {}; + +ContentChild* ContentChild::sSingleton; +StaticAutoPtr<ContentChild::ShutdownCanary> ContentChild::sShutdownCanary; + +ContentChild::ContentChild() + : mID(uint64_t(-1)), + mIsForBrowser(false), + mIsAlive(true), + mShuttingDown(false) { + // This process is a content process, so it's clearly running in + // multiprocess mode! + nsDebugImpl::SetMultiprocessMode("Child"); + + // Our static analysis doesn't allow capturing ref-counted pointers in + // lambdas, so we need to hide it in a uintptr_t. This is safe because this + // lambda will be destroyed in ~ContentChild(). + uintptr_t self = reinterpret_cast<uintptr_t>(this); + profiler_add_state_change_callback( + AllProfilingStates(), + [self](ProfilingState aProfilingState) { + const ContentChild* selfPtr = + reinterpret_cast<const ContentChild*>(self); + PROFILER_MARKER("Process Priority", OTHER, + mozilla::MarkerThreadId::MainThread(), ProcessPriority, + ProfilerString8View::WrapNullTerminatedString( + ProcessPriorityToString(selfPtr->mProcessPriority)), + aProfilingState); + }, + self); + + // When ContentChild is created, the observer service does not even exist. + // When ContentChild::RecvSetXPCOMProcessAttributes is called (the first + // IPDL call made on this object), shutdown may have already happened. Thus + // we create a canary here that relies upon getting cleared if shutdown + // happens without requiring the observer service at this time. + if (!sShutdownCanary) { + sShutdownCanary = new ShutdownCanary(); + ClearOnShutdown(&sShutdownCanary, ShutdownPhase::XPCOMShutdown); + } +} + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning( \ + disable : 4722) /* Silence "destructor never returns" warning \ + */ +#endif + +ContentChild::~ContentChild() { + profiler_remove_state_change_callback(reinterpret_cast<uintptr_t>(this)); + +#ifndef NS_FREE_PERMANENT_DATA + MOZ_CRASH("Content Child shouldn't be destroyed."); +#endif +} + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +NS_INTERFACE_MAP_BEGIN(ContentChild) + NS_INTERFACE_MAP_ENTRY(nsIDOMProcessChild) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMProcessChild) +NS_INTERFACE_MAP_END + +mozilla::ipc::IPCResult ContentChild::RecvSetXPCOMProcessAttributes( + XPCOMInitData&& aXPCOMInit, const StructuredCloneData& aInitialData, + FullLookAndFeel&& aLookAndFeelData, dom::SystemFontList&& aFontList, + Maybe<SharedMemoryHandle>&& aSharedUASheetHandle, + const uintptr_t& aSharedUASheetAddress, + nsTArray<SharedMemoryHandle>&& aSharedFontListBlocks, + const bool& aIsReadyForBackgroundProcessing) { + if (!sShutdownCanary) { + return IPC_OK(); + } + + mLookAndFeelData = std::move(aLookAndFeelData); + mFontList = std::move(aFontList); + mSharedFontListBlocks = std::move(aSharedFontListBlocks); + + gfx::gfxVars::SetValuesForInitialize(aXPCOMInit.gfxNonDefaultVarUpdates()); + PerfStats::SetCollectionMask(aXPCOMInit.perfStatsMask()); + InitSharedUASheets(std::move(aSharedUASheetHandle), aSharedUASheetAddress); + InitXPCOM(std::move(aXPCOMInit), aInitialData, + aIsReadyForBackgroundProcessing); + InitGraphicsDeviceData(aXPCOMInit.contentDeviceData()); + RefPtr<net::ChildDNSService> dnsServiceChild = + dont_AddRef(net::ChildDNSService::GetSingleton()); + if (dnsServiceChild) { + dnsServiceChild->SetTRRDomain(aXPCOMInit.trrDomain()); + dnsServiceChild->SetTRRModeInChild(aXPCOMInit.trrMode(), + aXPCOMInit.trrModeFromPref()); + } + return IPC_OK(); +} + +class nsGtkNativeInitRunnable : public Runnable { + public: + nsGtkNativeInitRunnable() : Runnable("nsGtkNativeInitRunnable") {} + + NS_IMETHOD Run() override { + LookAndFeel::NativeInit(); + return NS_OK; + } +}; + +void ContentChild::Init(mozilla::ipc::UntypedEndpoint&& aEndpoint, + const char* aParentBuildID, uint64_t aChildID, + bool aIsForBrowser) { +#ifdef MOZ_WIDGET_GTK + // When running X11 only build we need to pass a display down + // to gtk_init because it's not going to use the one from the environment + // on its own when deciding which backend to use, and when starting under + // XWayland, it may choose to start with the wayland backend + // instead of the x11 backend. + // The DISPLAY environment variable is normally set by the parent process. + // The MOZ_GDK_DISPLAY environment variable is set from nsAppRunner.cpp + // when --display is set by the command line. + if (!gfxPlatform::IsHeadless()) { + const char* display_name = PR_GetEnv("MOZ_GDK_DISPLAY"); + if (!display_name) { + bool waylandEnabled = false; +# ifdef MOZ_WAYLAND + waylandEnabled = IsWaylandEnabled(); +# endif + if (!waylandEnabled) { + display_name = PR_GetEnv("DISPLAY"); + } + } + if (display_name) { + int argc = 3; + char option_name[] = "--display"; + char* argv[] = { + // argv0 is unused because g_set_prgname() was called in + // XRE_InitChildProcess(). + nullptr, option_name, const_cast<char*>(display_name), nullptr}; + char** argvp = argv; + gtk_init(&argc, &argvp); + } else { + gtk_init(nullptr, nullptr); + } + } +#endif + +#ifdef MOZ_X11 + if (!gfxPlatform::IsHeadless()) { + // Do this after initializing GDK, or GDK will install its own handler. + XRE_InstallX11ErrorHandler(); + } +#endif + + MOZ_ASSERT(!sSingleton, "only one ContentChild per child"); + + // Once we start sending IPC messages, we need the thread manager to be + // initialized so we can deal with the responses. Do that here before we + // try to construct the crash reporter. + nsresult rv = nsThreadManager::get().Init(); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_CRASH("Failed to initialize the thread manager in ContentChild::Init"); + } + + if (!aEndpoint.Bind(this)) { + MOZ_CRASH("Bind failed in ContentChild::Init"); + } + sSingleton = this; + + // If communications with the parent have broken down, take the process + // down so it's not hanging around. + GetIPCChannel()->SetAbortOnError(true); + + // This must be checked 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(); + } + +#if defined(__OpenBSD__) && defined(MOZ_SANDBOX) + StartOpenBSDSandbox(GeckoProcessType_Content); +#endif + +#ifdef MOZ_X11 +# ifdef MOZ_WIDGET_GTK + if (GdkIsX11Display() && !gfxPlatform::IsHeadless()) { + // Send the parent our X socket to act as a proxy reference for our X + // resources. + int xSocketFd = ConnectionNumber(DefaultXDisplay()); + SendBackUpXResources(FileDescriptor(xSocketFd)); + } +# endif +#endif + + CrashReporterClient::InitSingleton(this); + + mID = aChildID; + mIsForBrowser = aIsForBrowser; + + SetProcessName("Web Content"_ns); + +#ifdef NIGHTLY_BUILD + // NOTE: We have to register the annotator on the main thread, as annotators + // only affect a single thread. + SchedulerGroup::Dispatch( + NS_NewRunnableFunction("RegisterPendingInputEventHangAnnotator", [] { + BackgroundHangMonitor::RegisterAnnotator( + PendingInputEventHangAnnotator::sSingleton); + })); +#endif +} + +void ContentChild::AddProfileToProcessName(const nsACString& aProfile) { + nsCOMPtr<nsIPrincipal> isolationPrincipal = + ContentParent::CreateRemoteTypeIsolationPrincipal(mRemoteType); + if (isolationPrincipal) { + // DEFAULT_PRIVATE_BROWSING_ID is the value when it's not private + if (isolationPrincipal->OriginAttributesRef().mPrivateBrowsingId != + nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID) { + return; + } + } + + mProcessName = aProfile + ":"_ns + mProcessName; //<profile_name>:example.com +} + +void ContentChild::SetProcessName(const nsACString& aName, + const nsACString* aSite, + const nsACString* aCurrentProfile) { + char* name; + if ((name = PR_GetEnv("MOZ_DEBUG_APP_PROCESS")) && aName.EqualsASCII(name)) { +#ifdef XP_UNIX + printf_stderr("\n\nCHILDCHILDCHILDCHILD\n [%s] debug me @%d\n\n", name, + getpid()); + sleep(30); +#elif defined(XP_WIN) + // Windows has a decent JIT debugging story, so NS_DebugBreak does the + // right thing. + NS_DebugBreak(NS_DEBUG_BREAK, + "Invoking NS_DebugBreak() to debug child process", nullptr, + __FILE__, __LINE__); +#endif + } + + if (aSite) { + profiler_set_process_name(aName, aSite); + } else { + profiler_set_process_name(aName); + } + + mProcessName = aName; + + // Requires pref flip + if (aSite && StaticPrefs::fission_processSiteNames()) { + nsCOMPtr<nsIPrincipal> isolationPrincipal = + ContentParent::CreateRemoteTypeIsolationPrincipal(mRemoteType); + if (isolationPrincipal) { + // DEFAULT_PRIVATE_BROWSING_ID is the value when it's not private + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("private = %d, pref = %d", + isolationPrincipal->OriginAttributesRef().mPrivateBrowsingId != + nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID, + StaticPrefs::fission_processPrivateWindowSiteNames())); + if (isolationPrincipal->OriginAttributesRef().mPrivateBrowsingId == + nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID +#ifdef NIGHTLY_BUILD + // Nightly can show site names for private windows, with a second pref + || StaticPrefs::fission_processPrivateWindowSiteNames() +#endif + ) { +#if !defined(XP_MACOSX) + // Mac doesn't have the 15-character limit Linux does + // Sets profiler process name + if (isolationPrincipal->SchemeIs("https")) { + nsAutoCString schemeless; + isolationPrincipal->GetHostPort(schemeless); + nsAutoCString originSuffix; + isolationPrincipal->GetOriginSuffix(originSuffix); + schemeless.Append(originSuffix); + mProcessName = schemeless; + } else +#endif + { + mProcessName = *aSite; + } + } + } + } + + if (StaticPrefs::fission_processProfileName() && aCurrentProfile && + !aCurrentProfile->IsEmpty()) { + AddProfileToProcessName(*aCurrentProfile); + } + + // else private window, don't change process name, or the pref isn't set + // mProcessName is always flat (mProcessName == aName) + + mozilla::ipc::SetThisProcessName(mProcessName.get()); + + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("Changed name of process %d to %s", getpid(), + PromiseFlatCString(mProcessName).get())); +} + +static nsresult GetCreateWindowParams(nsIOpenWindowInfo* aOpenWindowInfo, + nsDocShellLoadState* aLoadState, + bool aForceNoReferrer, + nsIReferrerInfo** aReferrerInfo, + nsIPrincipal** aTriggeringPrincipal, + nsIContentSecurityPolicy** aCsp) { + if (!aTriggeringPrincipal || !aCsp) { + NS_ERROR("aTriggeringPrincipal || aCsp is null"); + return NS_ERROR_FAILURE; + } + + if (!aReferrerInfo) { + NS_ERROR("aReferrerInfo is null"); + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIReferrerInfo> referrerInfo; + if (aForceNoReferrer) { + referrerInfo = new ReferrerInfo(nullptr, ReferrerPolicy::_empty, false); + } + if (aLoadState && !referrerInfo) { + referrerInfo = aLoadState->GetReferrerInfo(); + } + + RefPtr<BrowsingContext> parent = aOpenWindowInfo->GetParent(); + nsCOMPtr<nsPIDOMWindowOuter> opener = + parent ? parent->GetDOMWindow() : nullptr; + if (!opener) { + nsCOMPtr<nsIPrincipal> nullPrincipal = + NullPrincipal::Create(aOpenWindowInfo->GetOriginAttributes()); + if (!referrerInfo) { + referrerInfo = new ReferrerInfo(nullptr, ReferrerPolicy::_empty); + } + + referrerInfo.swap(*aReferrerInfo); + NS_ADDREF(*aTriggeringPrincipal = nullPrincipal); + return NS_OK; + } + + nsCOMPtr<Document> doc = opener->GetDoc(); + NS_ADDREF(*aTriggeringPrincipal = doc->NodePrincipal()); + + nsCOMPtr<nsIContentSecurityPolicy> csp = doc->GetCsp(); + if (csp) { + csp.forget(aCsp); + } + + nsCOMPtr<nsIURI> baseURI = doc->GetDocBaseURI(); + if (!baseURI) { + NS_ERROR("Document didn't return a base URI"); + return NS_ERROR_FAILURE; + } + + if (!referrerInfo) { + referrerInfo = new ReferrerInfo(*doc); + } + + referrerInfo.swap(*aReferrerInfo); + return NS_OK; +} + +nsresult ContentChild::ProvideWindowCommon( + NotNull<BrowserChild*> aTabOpener, nsIOpenWindowInfo* aOpenWindowInfo, + uint32_t aChromeFlags, bool aCalledFromJS, nsIURI* aURI, + const nsAString& aName, const nsACString& aFeatures, + const UserActivation::Modifiers& aModifiers, bool aForceNoOpener, + bool aForceNoReferrer, bool aIsPopupRequested, + nsDocShellLoadState* aLoadState, bool* aWindowIsNew, + BrowsingContext** aReturn) { + *aReturn = nullptr; + + nsAutoCString features(aFeatures); + nsAutoString name(aName); + + nsresult rv; + + RefPtr<BrowsingContext> parent = aOpenWindowInfo->GetParent(); + MOZ_DIAGNOSTIC_ASSERT(parent, "We must have a parent BC"); + + // Block the attempt to open a new window if the opening BrowsingContext is + // not marked to use remote tabs. This ensures that the newly opened window is + // correctly remote. + if (NS_WARN_IF(!parent->UseRemoteTabs())) { + return NS_ERROR_ABORT; + } + + bool useRemoteSubframes = + aChromeFlags & nsIWebBrowserChrome::CHROME_FISSION_WINDOW; + + uint32_t parentSandboxFlags = parent->SandboxFlags(); + if (Document* doc = parent->GetDocument()) { + parentSandboxFlags = doc->GetSandboxFlags(); + } + + // Certain conditions complicate the process of creating the new + // BrowsingContext, and prevent us from using the + // "CreateWindowInDifferentProcess" codepath. + // * With Fission enabled, process selection will happen during the load, so + // switching processes eagerly will not provide a benefit. + // * Windows created for printing must be created within the current process + // so that a static clone of the source document can be created. + // * Sandboxed popups require the full window creation codepath. + // * Loads with form or POST data require the full window creation codepath. + bool cannotLoadInDifferentProcess = + useRemoteSubframes || aOpenWindowInfo->GetIsForPrinting() || + (parentSandboxFlags & + SANDBOX_PROPAGATES_TO_AUXILIARY_BROWSING_CONTEXTS) || + (aLoadState && + (aLoadState->IsFormSubmission() || aLoadState->PostDataStream())); + if (!cannotLoadInDifferentProcess) { + // If we're in a content process and we have noopener set, there's no reason + // to load in our process, so let's load it elsewhere! + bool loadInDifferentProcess = + aForceNoOpener && StaticPrefs::dom_noopener_newprocess_enabled(); + if (loadInDifferentProcess) { + nsCOMPtr<nsIPrincipal> triggeringPrincipal; + nsCOMPtr<nsIContentSecurityPolicy> csp; + nsCOMPtr<nsIReferrerInfo> referrerInfo; + rv = GetCreateWindowParams(aOpenWindowInfo, aLoadState, aForceNoReferrer, + getter_AddRefs(referrerInfo), + getter_AddRefs(triggeringPrincipal), + getter_AddRefs(csp)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (name.LowerCaseEqualsLiteral("_blank")) { + name.Truncate(); + } + + MOZ_DIAGNOSTIC_ASSERT(!nsContentUtils::IsSpecialName(name)); + + Unused << SendCreateWindowInDifferentProcess( + aTabOpener, parent, aChromeFlags, aCalledFromJS, aURI, features, + aModifiers, name, triggeringPrincipal, csp, referrerInfo, + aOpenWindowInfo->GetOriginAttributes()); + + // We return NS_ERROR_ABORT, so that the caller knows that we've abandoned + // the window open as far as it is concerned. + return NS_ERROR_ABORT; + } + } + + TabId tabId(nsContentUtils::GenerateTabId()); + + // We need to assign a TabGroup to the PBrowser actor before we send it to the + // parent. Otherwise, the parent could send messages to us before we have a + // proper TabGroup for that actor. + RefPtr<BrowsingContext> openerBC; + if (!aForceNoOpener) { + openerBC = parent; + } + + RefPtr<BrowsingContext> browsingContext = BrowsingContext::CreateDetached( + nullptr, openerBC, nullptr, aName, BrowsingContext::Type::Content, + aIsPopupRequested); + MOZ_ALWAYS_SUCCEEDS(browsingContext->SetRemoteTabs(true)); + MOZ_ALWAYS_SUCCEEDS(browsingContext->SetRemoteSubframes(useRemoteSubframes)); + MOZ_ALWAYS_SUCCEEDS(browsingContext->SetOriginAttributes( + aOpenWindowInfo->GetOriginAttributes())); + + browsingContext->InitPendingInitialization(true); + auto unsetPending = MakeScopeExit([browsingContext]() { + Unused << browsingContext->SetPendingInitialization(false); + }); + + browsingContext->EnsureAttached(); + + // The initial about:blank document we generate within the nsDocShell will + // almost certainly be replaced at some point. Unfortunately, getting the + // principal right here causes bugs due to frame scripts not getting events + // they expect, due to the real initial about:blank not being created yet. + // + // For this reason, we intentionally mispredict the initial principal here, so + // that we can act the same as we did before when not predicting a result + // principal. This `PWindowGlobal` will almost immediately be destroyed. + nsCOMPtr<nsIPrincipal> initialPrincipal = + NullPrincipal::Create(browsingContext->OriginAttributesRef()); + WindowGlobalInit windowInit = WindowGlobalActor::AboutBlankInitializer( + browsingContext, initialPrincipal); + + RefPtr<WindowGlobalChild> windowChild = + WindowGlobalChild::CreateDisconnected(windowInit); + if (NS_WARN_IF(!windowChild)) { + return NS_ERROR_ABORT; + } + + auto newChild = MakeNotNull<RefPtr<BrowserChild>>( + this, tabId, *aTabOpener, browsingContext, aChromeFlags, + /* aIsTopLevel */ true); + + if (IsShuttingDown()) { + return NS_ERROR_ABORT; + } + + // Open a remote endpoint for our PBrowser actor. + ManagedEndpoint<PBrowserParent> parentEp = OpenPBrowserEndpoint(newChild); + if (NS_WARN_IF(!parentEp.IsValid())) { + return NS_ERROR_ABORT; + } + + // Open a remote endpoint for our PWindowGlobal actor. + ManagedEndpoint<PWindowGlobalParent> windowParentEp = + newChild->OpenPWindowGlobalEndpoint(windowChild); + if (NS_WARN_IF(!windowParentEp.IsValid())) { + return NS_ERROR_ABORT; + } + + // Tell the parent process to set up its PBrowserParent. + PopupIPCTabContext ipcContext(aTabOpener, 0); + if (NS_WARN_IF(!SendConstructPopupBrowser( + std::move(parentEp), std::move(windowParentEp), tabId, ipcContext, + windowInit, aChromeFlags))) { + return NS_ERROR_ABORT; + } + + windowChild->Init(); + auto guardNullWindowGlobal = MakeScopeExit([&] { + if (!windowChild->GetWindowGlobal()) { + windowChild->Destroy(); + } + }); + + // Now that |newChild| has had its IPC link established, call |Init| to set it + // up. + // XXX: This MOZ_KnownLive is only necessary because the static analysis can't + // tell that NotNull<RefPtr<BrowserChild>> is a strong pointer. + RefPtr<nsPIDOMWindowOuter> parentWindow = + parent ? parent->GetDOMWindow() : nullptr; + if (NS_FAILED(MOZ_KnownLive(newChild)->Init(parentWindow, windowChild))) { + return NS_ERROR_ABORT; + } + + // Set to true when we're ready to return from this function. + bool ready = false; + + // NOTE: Capturing by reference here is safe, as this function won't return + // until one of these callbacks is called. + auto resolve = [&](CreatedWindowInfo&& info) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + rv = info.rv(); + *aWindowIsNew = info.windowOpened(); + nsTArray<FrameScriptInfo> frameScripts(std::move(info.frameScripts())); + uint32_t maxTouchPoints = info.maxTouchPoints(); + DimensionInfo dimensionInfo = std::move(info.dimensions()); + + // Once this function exits, we should try to exit the nested event loop. + ready = true; + + // NOTE: We have to handle this immediately in the resolve callback in order + // to make sure that we don't process any more IPC messages before returning + // from ProvideWindowCommon. + + // Handle the error which we got back from the parent process, if we got + // one. + if (NS_FAILED(rv)) { + return; + } + + if (!*aWindowIsNew) { + rv = NS_ERROR_ABORT; + return; + } + + // If the BrowserChild has been torn down, we don't need to do this anymore. + if (NS_WARN_IF(!newChild->IPCOpen() || newChild->IsDestroyed())) { + rv = NS_ERROR_ABORT; + return; + } + + ParentShowInfo showInfo(u""_ns, /* fakeShowInfo = */ true, + /* isTransparent = */ false, + newChild->WebWidget()->GetDPI(), + newChild->WebWidget()->RoundsWidgetCoordinatesTo(), + newChild->WebWidget()->GetDefaultScale().scale); + + newChild->SetMaxTouchPoints(maxTouchPoints); + + if (aForceNoOpener || !parent) { + MOZ_DIAGNOSTIC_ASSERT(!browsingContext->HadOriginalOpener()); + MOZ_DIAGNOSTIC_ASSERT(browsingContext->GetOpenerId() == 0); + } else { + MOZ_DIAGNOSTIC_ASSERT(browsingContext->HadOriginalOpener()); + MOZ_DIAGNOSTIC_ASSERT(browsingContext->GetOpenerId() == parent->Id()); + } + + // Unfortunately we don't get a window unless we've shown the frame. That's + // pretty bogus; see bug 763602. + newChild->DoFakeShow(showInfo); + + newChild->RecvUpdateDimensions(dimensionInfo); + + for (size_t i = 0; i < frameScripts.Length(); i++) { + FrameScriptInfo& info = frameScripts[i]; + if (!newChild->RecvLoadRemoteScript(info.url(), + info.runInGlobalScope())) { + MOZ_CRASH(); + } + } + + if (xpc::IsInAutomation()) { + if (nsCOMPtr<nsPIDOMWindowOuter> outer = + do_GetInterface(newChild->WebNavigation())) { + nsCOMPtr<nsIObserverService> obs(services::GetObserverService()); + obs->NotifyObservers( + outer, "dangerous:test-only:new-browser-child-ready", nullptr); + } + } + + browsingContext.forget(aReturn); + }; + + // NOTE: Capturing by reference here is safe, as this function won't return + // until one of these callbacks is called. + auto reject = [&](ResponseRejectReason) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + NS_WARNING("windowCreated promise rejected"); + rv = NS_ERROR_NOT_AVAILABLE; + ready = true; + }; + + // Send down the request to open the window. + nsCOMPtr<nsIPrincipal> triggeringPrincipal; + nsCOMPtr<nsIContentSecurityPolicy> csp; + nsCOMPtr<nsIReferrerInfo> referrerInfo; + rv = GetCreateWindowParams(aOpenWindowInfo, aLoadState, aForceNoReferrer, + getter_AddRefs(referrerInfo), + getter_AddRefs(triggeringPrincipal), + getter_AddRefs(csp)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + SendCreateWindow(aTabOpener, parent, newChild, aChromeFlags, aCalledFromJS, + aOpenWindowInfo->GetIsForPrinting(), + aOpenWindowInfo->GetIsForWindowDotPrint(), aURI, features, + aModifiers, triggeringPrincipal, csp, referrerInfo, + aOpenWindowInfo->GetOriginAttributes(), std::move(resolve), + std::move(reject)); + + // ======================= + // Begin Nested Event Loop + // ======================= + + // We have to wait for a response from SendCreateWindow or with information + // we're going to need to return from this function, So we spin a nested event + // loop until they get back to us. + + { + // Suppress event handling for all contexts in our BrowsingContextGroup so + // that event handlers cannot target our new window while it's still being + // opened. Note that pending events that were suppressed while our blocker + // was active will be dispatched asynchronously from a runnable dispatched + // to the main event loop after this function returns, not immediately when + // we leave this scope. + AutoSuppressEventHandlingAndSuspend seh(browsingContext->Group()); + + AutoNoJSAPI nojsapi; + + // Spin the event loop until we get a response. Callers of this function + // already have to guard against an inner event loop spinning in the + // non-e10s case because of the need to spin one to create a new chrome + // window. + SpinEventLoopUntil("ContentChild::ProvideWindowCommon"_ns, + [&]() { return ready; }); + MOZ_RELEASE_ASSERT(ready, + "We are on the main thread, so we should not exit this " + "loop without ready being true."); + } + + // ===================== + // End Nested Event Loop + // ===================== + + // It's possible for our new BrowsingContext to become discarded during the + // nested event loop, in which case we shouldn't return it, since our callers + // will generally not be prepared to deal with that. + if (*aReturn && (*aReturn)->IsDiscarded()) { + NS_RELEASE(*aReturn); + return NS_ERROR_ABORT; + } + + // We should have the results already set by the callbacks. + MOZ_ASSERT_IF(NS_SUCCEEDED(rv), *aReturn); + return rv; +} + +bool ContentChild::IsAlive() const { return mIsAlive; } + +bool ContentChild::IsShuttingDown() const { return mShuttingDown; } + +void ContentChild::GetProcessName(nsACString& aName) const { + aName = mProcessName; +} + +/* static */ +void ContentChild::AppendProcessId(nsACString& aName) { + if (!aName.IsEmpty()) { + aName.Append(' '); + } + unsigned pid = getpid(); + aName.Append(nsPrintfCString("(pid %u)", pid)); +} + +void ContentChild::InitGraphicsDeviceData(const ContentDeviceData& aData) { + gfxPlatform::InitChild(aData); +} + +void ContentChild::InitSharedUASheets(Maybe<SharedMemoryHandle>&& aHandle, + uintptr_t aAddress) { + MOZ_ASSERT_IF(!aHandle, !aAddress); + + if (!aAddress) { + return; + } + + // Map the shared memory storing the user agent style sheets. Do this as + // early as possible to maximize the chance of being able to map at the + // address we want. + GlobalStyleSheetCache::SetSharedMemory(std::move(*aHandle), aAddress); +} + +void ContentChild::InitXPCOM( + XPCOMInitData&& aXPCOMInit, + const mozilla::dom::ipc::StructuredCloneData& aInitialData, + bool aIsReadyForBackgroundProcessing) { +#ifdef MOZ_WIDGET_GTK + // LookAndFeel::NativeInit takes a long time to run on Linux, here we schedule + // it as soon as possible after BackgroundChild::Startup to give + // it chance to run ahead of ConstructBrowser + nsCOMPtr<nsIRunnable> event = new nsGtkNativeInitRunnable(); + NS_DispatchToMainThreadQueue(event.forget(), EventQueuePriority::Idle); +#endif + +#if defined(XP_WIN) + // DLL services untrusted modules processing depends on + // BackgroundChild::Startup having been called + RefPtr<DllServices> dllSvc(DllServices::Get()); + dllSvc->StartUntrustedModulesProcessor(aIsReadyForBackgroundProcessing); +#endif // defined(XP_WIN) + + PBackgroundChild* actorChild = BackgroundChild::GetOrCreateForCurrentThread(); + if (NS_WARN_IF(!actorChild)) { + MOZ_ASSERT_UNREACHABLE("PBackground init can't fail at this point"); + return; + } + + ClientManager::Startup(); + + // RemoteWorkerService will be initialized in RecvRemoteType, to avoid to + // register it to the RemoteWorkerManager while it is still a prealloc + // remoteType and defer it to the point the child process is assigned a. + // actual remoteType. + + nsCOMPtr<nsIConsoleService> svc(do_GetService(NS_CONSOLESERVICE_CONTRACTID)); + if (!svc) { + NS_WARNING("Couldn't acquire console service"); + return; + } + + mConsoleListener = new ConsoleListener(this); + if (NS_FAILED(svc->RegisterListener(mConsoleListener))) + NS_WARNING("Couldn't register console listener for child process"); + + mAvailableDictionaries = std::move(aXPCOMInit.dictionaries()); + + RecvSetOffline(aXPCOMInit.isOffline()); + RecvSetConnectivity(aXPCOMInit.isConnected()); + + LocaleService::GetInstance()->AssignAppLocales(aXPCOMInit.appLocales()); + LocaleService::GetInstance()->AssignRequestedLocales( + aXPCOMInit.requestedLocales()); + + L10nRegistry::RegisterFileSourcesFromParentProcess( + aXPCOMInit.l10nFileSources()); + + RecvSetCaptivePortalState(aXPCOMInit.captivePortalState()); + RecvBidiKeyboardNotify(aXPCOMInit.isLangRTL(), + aXPCOMInit.haveBidiKeyboards()); + + if (aXPCOMInit.domainPolicy().active()) { + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + MOZ_ASSERT(ssm); + ssm->ActivateDomainPolicyInternal(getter_AddRefs(mPolicy)); + if (!mPolicy) { + MOZ_CRASH("Failed to activate domain policy."); + } + mPolicy->ApplyClone(&aXPCOMInit.domainPolicy()); + } + + nsCOMPtr<nsIClipboard> clipboard( + do_GetService("@mozilla.org/widget/clipboard;1")); + if (nsCOMPtr<nsIClipboardProxy> clipboardProxy = + do_QueryInterface(clipboard)) { + clipboardProxy->SetCapabilities(aXPCOMInit.clipboardCaps()); + } + + { + AutoJSAPI jsapi; + if (NS_WARN_IF(!jsapi.Init(xpc::PrivilegedJunkScope()))) { + MOZ_CRASH(); + } + ErrorResult rv; + JS::Rooted<JS::Value> data(jsapi.cx()); + mozilla::dom::ipc::StructuredCloneData id; + id.Copy(aInitialData); + id.Read(jsapi.cx(), &data, rv); + if (NS_WARN_IF(rv.Failed())) { + MOZ_CRASH(); + } + auto* global = ContentProcessMessageManager::Get(); + global->SetInitialProcessData(data); + } + + // The stylesheet cache is not ready yet. Store this URL for future use. + nsCOMPtr<nsIURI> ucsURL = std::move(aXPCOMInit.userContentSheetURL()); + GlobalStyleSheetCache::SetUserContentCSSURL(ucsURL); + + GfxInfoBase::SetFeatureStatus(std::move(aXPCOMInit.gfxFeatureStatus())); + + // Initialize the RemoteDecoderManager thread and its associated PBackground + // channel. + RemoteDecoderManagerChild::Init(); + + Preferences::RegisterCallbackAndCall(&OnFissionBlocklistPrefChange, + kFissionEnforceBlockList); + Preferences::RegisterCallbackAndCall(&OnFissionBlocklistPrefChange, + kFissionOmitBlockListValues); + + // Set the dynamic scalar definitions for this process. + TelemetryIPC::AddDynamicScalarDefinitions(aXPCOMInit.dynamicScalarDefs()); +} + +mozilla::ipc::IPCResult ContentChild::RecvRequestMemoryReport( + const uint32_t& aGeneration, const bool& aAnonymize, + const bool& aMinimizeMemoryUsage, + const Maybe<mozilla::ipc::FileDescriptor>& aDMDFile, + const RequestMemoryReportResolver& aResolver) { + nsCString process; + if (aAnonymize || mRemoteType.IsEmpty()) { + GetProcessName(process); + } else { + process = mRemoteType; + } + AppendProcessId(process); + MOZ_ASSERT(!process.IsEmpty()); + + MemoryReportRequestClient::Start( + aGeneration, aAnonymize, aMinimizeMemoryUsage, aDMDFile, process, + [&](const MemoryReport& aReport) { + Unused << GetSingleton()->SendAddMemoryReport(aReport); + }, + aResolver); + return IPC_OK(); +} + +#if defined(XP_WIN) +mozilla::ipc::IPCResult ContentChild::RecvGetUntrustedModulesData( + GetUntrustedModulesDataResolver&& aResolver) { + RefPtr<DllServices> dllSvc(DllServices::Get()); + dllSvc->GetUntrustedModulesData()->Then( + GetMainThreadSerialEventTarget(), __func__, + [aResolver](Maybe<UntrustedModulesData>&& aData) { + aResolver(std::move(aData)); + }, + [aResolver](nsresult aReason) { aResolver(Nothing()); }); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvUnblockUntrustedModulesThread() { + if (nsCOMPtr<nsIObserverService> obs = + mozilla::services::GetObserverService()) { + obs->NotifyObservers(nullptr, "unblock-untrusted-modules-thread", nullptr); + } + return IPC_OK(); +} +#endif // defined(XP_WIN) + +PCycleCollectWithLogsChild* ContentChild::AllocPCycleCollectWithLogsChild( + const bool& aDumpAllTraces, const FileDescriptor& aGCLog, + const FileDescriptor& aCCLog) { + return do_AddRef(new CycleCollectWithLogsChild()).take(); +} + +mozilla::ipc::IPCResult ContentChild::RecvPCycleCollectWithLogsConstructor( + PCycleCollectWithLogsChild* aActor, const bool& aDumpAllTraces, + const FileDescriptor& aGCLog, const FileDescriptor& aCCLog) { + // The sink's destructor is called when the last reference goes away, which + // will cause the actor to be closed down. + auto* actor = static_cast<CycleCollectWithLogsChild*>(aActor); + RefPtr<CycleCollectWithLogsChild::Sink> sink = + new CycleCollectWithLogsChild::Sink(actor, aGCLog, aCCLog); + + // Invoke the dumper, which will take a reference to the sink. + nsCOMPtr<nsIMemoryInfoDumper> dumper = + do_GetService("@mozilla.org/memory-info-dumper;1"); + dumper->DumpGCAndCCLogsToSink(aDumpAllTraces, sink); + return IPC_OK(); +} + +bool ContentChild::DeallocPCycleCollectWithLogsChild( + PCycleCollectWithLogsChild* aActor) { + RefPtr<CycleCollectWithLogsChild> actor = + dont_AddRef(static_cast<CycleCollectWithLogsChild*>(aActor)); + return true; +} + +mozilla::ipc::IPCResult ContentChild::RecvInitGMPService( + Endpoint<PGMPServiceChild>&& aGMPService) { + if (!GMPServiceChild::Create(std::move(aGMPService))) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvInitProfiler( + Endpoint<PProfilerChild>&& aEndpoint) { + mProfilerController = ChildProfilerController::Create(std::move(aEndpoint)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvGMPsChanged( + nsTArray<GMPCapabilityData>&& capabilities) { + GeckoMediaPluginServiceChild::UpdateGMPCapabilities(std::move(capabilities)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvInitProcessHangMonitor( + Endpoint<PProcessHangMonitorChild>&& aHangMonitor) { + CreateHangMonitorChild(std::move(aHangMonitor)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::GetResultForRenderingInitFailure( + base::ProcessId aOtherPid) { + if (aOtherPid == base::GetCurrentProcId() || aOtherPid == OtherPid()) { + // If we are talking to ourselves, or the UI process, then that is a fatal + // protocol error. + return IPC_FAIL_NO_REASON(this); + } + + // If we are talking to the GPU process, then we should recover from this on + // the next ContentChild::RecvReinitRendering call. + gfxCriticalNote << "Could not initialize rendering with GPU process"; + return IPC_OK(); +} + +#if defined(XP_MACOSX) +extern "C" { +void CGSShutdownServerConnections(); +}; +#endif + +mozilla::ipc::IPCResult ContentChild::RecvInitRendering( + Endpoint<PCompositorManagerChild>&& aCompositor, + Endpoint<PImageBridgeChild>&& aImageBridge, + Endpoint<PVRManagerChild>&& aVRBridge, + Endpoint<PRemoteDecoderManagerChild>&& aVideoManager, + nsTArray<uint32_t>&& namespaces) { + MOZ_ASSERT(namespaces.Length() == 3); + + // Note that for all of the methods below, if it can fail, it should only + // return false if the failure is an IPDL error. In such situations, + // ContentChild can reason about whether or not to wait for + // RecvReinitRendering (because we surmised the GPU process crashed), or if it + // should crash itself (because we are actually talking to the UI process). If + // there are localized failures (e.g. failed to spawn a thread), then it + // should MOZ_RELEASE_ASSERT or MOZ_CRASH as necessary instead. + if (!CompositorManagerChild::Init(std::move(aCompositor), namespaces[0])) { + return GetResultForRenderingInitFailure(aCompositor.OtherPid()); + } + if (!CompositorManagerChild::CreateContentCompositorBridge(namespaces[1])) { + return GetResultForRenderingInitFailure(aCompositor.OtherPid()); + } + if (!ImageBridgeChild::InitForContent(std::move(aImageBridge), + namespaces[2])) { + return GetResultForRenderingInitFailure(aImageBridge.OtherPid()); + } + if (!gfx::VRManagerChild::InitForContent(std::move(aVRBridge))) { + return GetResultForRenderingInitFailure(aVRBridge.OtherPid()); + } + RemoteDecoderManagerChild::InitForGPUProcess(std::move(aVideoManager)); + +#if defined(XP_MACOSX) && !defined(MOZ_SANDBOX) + // Close all current connections to the WindowServer. This ensures that the + // Activity Monitor will not label the content process as "Not responding" + // because it's not running a native event loop. See bug 1384336. When the + // build is configured with sandbox support, this is called during sandbox + // setup. + CGSShutdownServerConnections(); +#endif + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvReinitRendering( + Endpoint<PCompositorManagerChild>&& aCompositor, + Endpoint<PImageBridgeChild>&& aImageBridge, + Endpoint<PVRManagerChild>&& aVRBridge, + Endpoint<PRemoteDecoderManagerChild>&& aVideoManager, + nsTArray<uint32_t>&& namespaces) { + MOZ_ASSERT(namespaces.Length() == 3); + nsTArray<RefPtr<BrowserChild>> tabs = BrowserChild::GetAll(); + + // Re-establish singleton bridges to the compositor. + if (!CompositorManagerChild::Init(std::move(aCompositor), namespaces[0])) { + return GetResultForRenderingInitFailure(aCompositor.OtherPid()); + } + if (!CompositorManagerChild::CreateContentCompositorBridge(namespaces[1])) { + return GetResultForRenderingInitFailure(aCompositor.OtherPid()); + } + if (!ImageBridgeChild::ReinitForContent(std::move(aImageBridge), + namespaces[2])) { + return GetResultForRenderingInitFailure(aImageBridge.OtherPid()); + } + if (!gfx::VRManagerChild::InitForContent(std::move(aVRBridge))) { + return GetResultForRenderingInitFailure(aVRBridge.OtherPid()); + } + gfxPlatform::GetPlatform()->CompositorUpdated(); + + // Establish new PLayerTransactions. + for (const auto& browserChild : tabs) { + if (browserChild->GetLayersId().IsValid()) { + browserChild->ReinitRendering(); + } + } + + // Notify any observers that the compositor has been reinitialized, + // eg the ZoomConstraintsClients for documents in this process. + // This must occur after the ReinitRendering call above so that the + // APZCTreeManagers have been connected. + nsCOMPtr<nsIObserverService> observerService = services::GetObserverService(); + if (observerService) { + observerService->NotifyObservers(nullptr, "compositor-reinitialized", + nullptr); + } + + RemoteDecoderManagerChild::InitForGPUProcess(std::move(aVideoManager)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvReinitRenderingForDeviceReset() { + gfxPlatform::GetPlatform()->CompositorUpdated(); + + nsTArray<RefPtr<BrowserChild>> tabs = BrowserChild::GetAll(); + for (const auto& browserChild : tabs) { + if (browserChild->GetLayersId().IsValid()) { + browserChild->ReinitRenderingForDeviceReset(); + } + } + return IPC_OK(); +} + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +extern "C" { +CGError CGSSetDenyWindowServerConnections(bool); +}; + +static void DisconnectWindowServer(bool aIsSandboxEnabled) { + // Close all current connections to the WindowServer. This ensures that the + // Activity Monitor will not label the content process as "Not responding" + // because it's not running a native event loop. See bug 1384336. + // This is required with or without the sandbox enabled. Until the + // window server is blocked as the policy level, this should be called + // just before CGSSetDenyWindowServerConnections() so there are no + // windowserver connections active when CGSSetDenyWindowServerConnections() + // is called. + CGSShutdownServerConnections(); + + // Actual security benefits are only achieved when we additionally deny + // future connections using the sandbox policy. WebGL must be remoted if + // the windowserver connections are blocked. WebGL remoting is disabled + // for some tests. + if (aIsSandboxEnabled && + Preferences::GetBool( + "security.sandbox.content.mac.disconnect-windowserver") && + Preferences::GetBool("webgl.out-of-process")) { + CGError result = CGSSetDenyWindowServerConnections(true); + MOZ_DIAGNOSTIC_ASSERT(result == kCGErrorSuccess); +# if !MOZ_DIAGNOSTIC_ASSERT_ENABLED + Unused << result; +# endif + } +} +#endif + +mozilla::ipc::IPCResult ContentChild::RecvSetProcessSandbox( + const Maybe<mozilla::ipc::FileDescriptor>& aBroker) { + // We may want to move the sandbox initialization somewhere else + // at some point; see bug 880808. +#if defined(MOZ_SANDBOX) + + bool sandboxEnabled = true; +# if defined(XP_LINUX) + // On Linux, we have to support systems that can't use any sandboxing. + sandboxEnabled = SandboxInfo::Get().CanSandboxContent(); + + if (sandboxEnabled && !StaticPrefs::media_cubeb_sandbox()) { + // Pre-start audio before sandboxing; see bug 1443612. + Unused << CubebUtils::GetCubeb(); + } + + if (sandboxEnabled) { + sandboxEnabled = SetContentProcessSandbox( + ContentProcessSandboxParams::ForThisProcess(aBroker)); + } +# elif defined(XP_WIN) + mozilla::SandboxTarget::Instance()->StartSandbox(); +# elif defined(XP_MACOSX) + sandboxEnabled = (GetEffectiveContentSandboxLevel() >= 1); + DisconnectWindowServer(sandboxEnabled); +# endif + + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::ContentSandboxEnabled, sandboxEnabled); +# if defined(XP_LINUX) && !defined(ANDROID) + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::ContentSandboxCapabilities, + static_cast<int>(SandboxInfo::Get().AsInteger())); +# endif /* XP_LINUX && !ANDROID */ +#endif /* MOZ_SANDBOX */ + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvBidiKeyboardNotify( + const bool& aIsLangRTL, const bool& aHaveBidiKeyboards) { + // bidi is always of type PuppetBidiKeyboard* (because in the child, the only + // possible implementation of nsIBidiKeyboard is PuppetBidiKeyboard). + PuppetBidiKeyboard* bidi = + static_cast<PuppetBidiKeyboard*>(nsContentUtils::GetBidiKeyboard()); + if (bidi) { + bidi->SetBidiKeyboardInfo(aIsLangRTL, aHaveBidiKeyboards); + } + return IPC_OK(); +} + +static StaticRefPtr<CancelableRunnable> gFirstIdleTask; + +static void FirstIdle(void) { + MOZ_ASSERT(gFirstIdleTask); + gFirstIdleTask = nullptr; + + ContentChild::GetSingleton()->SendFirstIdle(); +} + +mozilla::ipc::IPCResult ContentChild::RecvConstructBrowser( + ManagedEndpoint<PBrowserChild>&& aBrowserEp, + ManagedEndpoint<PWindowGlobalChild>&& aWindowEp, const TabId& aTabId, + const IPCTabContext& aContext, const WindowGlobalInit& aWindowInit, + const uint32_t& aChromeFlags, const ContentParentId& aCpID, + const bool& aIsForBrowser, const bool& aIsTopLevel) { + MOZ_DIAGNOSTIC_ASSERT(!IsShuttingDown()); + + static bool hasRunOnce = false; + if (!hasRunOnce) { + hasRunOnce = true; + MOZ_ASSERT(!gFirstIdleTask); + RefPtr<CancelableRunnable> firstIdleTask = + NewCancelableRunnableFunction("FirstIdleRunnable", FirstIdle); + gFirstIdleTask = firstIdleTask; + if (NS_FAILED(NS_DispatchToCurrentThreadQueue(firstIdleTask.forget(), + EventQueuePriority::Idle))) { + gFirstIdleTask = nullptr; + hasRunOnce = false; + } + } + + RefPtr<BrowsingContext> browsingContext = + BrowsingContext::Get(aWindowInit.context().mBrowsingContextId); + if (!browsingContext || browsingContext->IsDiscarded()) { + nsPrintfCString reason("%s initial %s BrowsingContext", + browsingContext ? "discarded" : "missing", + aIsTopLevel ? "top" : "frame"); + MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Warning, ("%s", reason.get())); + if (!aIsTopLevel) { + // Recover if the BrowsingContext is missing for a new subframe. The + // `ManagedEndpoint` instances will be automatically destroyed. + NS_WARNING(reason.get()); + return IPC_OK(); + } + + // (these are the only possible values of `reason` at this point) + return browsingContext + ? IPC_FAIL(this, "discarded initial top BrowsingContext") + : IPC_FAIL(this, "missing initial top BrowsingContext"); + } + + if (xpc::IsInAutomation() && + StaticPrefs:: + browser_tabs_remote_testOnly_failPBrowserCreation_enabled()) { + nsAutoCString idString; + if (NS_SUCCEEDED(Preferences::GetCString( + "browser.tabs.remote.testOnly.failPBrowserCreation.browsingContext", + idString))) { + nsresult rv = NS_OK; + uint64_t bcid = idString.ToInteger64(&rv); + if (NS_SUCCEEDED(rv) && bcid == browsingContext->Id()) { + NS_WARNING("Injecting artificial PBrowser creation failure"); + return IPC_OK(); + } + } + } + + if (!aWindowInit.isInitialDocument() || + !NS_IsAboutBlank(aWindowInit.documentURI())) { + return IPC_FAIL(this, + "Logic in CreateDocumentViewerForActor currently requires " + "actors to be initial about:blank documents"); + } + + // We'll happily accept any kind of IPCTabContext here; we don't need to + // check that it's of a certain type for security purposes, because we + // believe whatever the parent process tells us. + MaybeInvalidTabContext tc(aContext); + if (!tc.IsValid()) { + NS_ERROR(nsPrintfCString("Received an invalid TabContext from " + "the parent process. (%s) Crashing...", + tc.GetInvalidReason()) + .get()); + MOZ_CRASH("Invalid TabContext received from the parent process."); + } + + RefPtr<WindowGlobalChild> windowChild = + WindowGlobalChild::CreateDisconnected(aWindowInit); + if (!windowChild) { + return IPC_FAIL(this, "Failed to create initial WindowGlobalChild"); + } + + RefPtr<BrowserChild> browserChild = + BrowserChild::Create(this, aTabId, tc.GetTabContext(), browsingContext, + aChromeFlags, aIsTopLevel); + + // Bind the created BrowserChild to IPC to actually link the actor. + if (NS_WARN_IF(!BindPBrowserEndpoint(std::move(aBrowserEp), browserChild))) { + return IPC_FAIL(this, "BindPBrowserEndpoint failed"); + } + + if (NS_WARN_IF(!browserChild->BindPWindowGlobalEndpoint(std::move(aWindowEp), + windowChild))) { + return IPC_FAIL(this, "BindPWindowGlobalEndpoint failed"); + } + windowChild->Init(); + auto guardNullWindowGlobal = MakeScopeExit([&] { + if (!windowChild->GetWindowGlobal()) { + windowChild->Destroy(); + } + }); + + // Ensure that a BrowsingContext is set for our BrowserChild before + // running `Init`. + MOZ_RELEASE_ASSERT(browserChild->mBrowsingContext->Id() == + aWindowInit.context().mBrowsingContextId); + + if (NS_WARN_IF( + NS_FAILED(browserChild->Init(/* aOpener */ nullptr, windowChild)))) { + return IPC_FAIL(browserChild, "BrowserChild::Init failed"); + } + + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + if (os) { + os->NotifyObservers(static_cast<nsIBrowserChild*>(browserChild), + "tab-child-created", nullptr); + } + // Notify parent that we are ready to handle input events. + browserChild->SendRemoteIsReadyToHandleInputEvents(); + return IPC_OK(); +} + +void ContentChild::GetAvailableDictionaries( + nsTArray<nsCString>& aDictionaries) { + aDictionaries = mAvailableDictionaries.Clone(); +} + +mozilla::PRemoteSpellcheckEngineChild* +ContentChild::AllocPRemoteSpellcheckEngineChild() { + MOZ_CRASH( + "Default Constructor for PRemoteSpellcheckEngineChild should never be " + "called"); + return nullptr; +} + +bool ContentChild::DeallocPRemoteSpellcheckEngineChild( + PRemoteSpellcheckEngineChild* child) { + delete child; + return true; +} + +mozilla::ipc::IPCResult ContentChild::RecvNotifyEmptyHTTPCache() { + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + obs->NotifyObservers(nullptr, "cacheservice:empty-cache", nullptr); + return IPC_OK(); +} + +PHalChild* ContentChild::AllocPHalChild() { return CreateHalChild(); } + +bool ContentChild::DeallocPHalChild(PHalChild* aHal) { + delete aHal; + return true; +} + +devtools::PHeapSnapshotTempFileHelperChild* +ContentChild::AllocPHeapSnapshotTempFileHelperChild() { + return devtools::HeapSnapshotTempFileHelperChild::Create(); +} + +bool ContentChild::DeallocPHeapSnapshotTempFileHelperChild( + devtools::PHeapSnapshotTempFileHelperChild* aHeapSnapshotHelper) { + delete aHeapSnapshotHelper; + return true; +} + +already_AddRefed<PTestShellChild> ContentChild::AllocPTestShellChild() { + return MakeAndAddRef<TestShellChild>(); +} + +mozilla::ipc::IPCResult ContentChild::RecvPTestShellConstructor( + PTestShellChild* actor) { + return IPC_OK(); +} + +RefPtr<GenericPromise> ContentChild::UpdateCookieStatus(nsIChannel* aChannel) { + RefPtr<CookieServiceChild> csChild = CookieServiceChild::GetSingleton(); + NS_ASSERTION(csChild, "Couldn't get CookieServiceChild"); + + return csChild->TrackCookieLoad(aChannel); +} + +PScriptCacheChild* ContentChild::AllocPScriptCacheChild( + const FileDescOrError& cacheFile, const bool& wantCacheData) { + return new loader::ScriptCacheChild(); +} + +bool ContentChild::DeallocPScriptCacheChild(PScriptCacheChild* cache) { + delete static_cast<loader::ScriptCacheChild*>(cache); + return true; +} + +mozilla::ipc::IPCResult ContentChild::RecvPScriptCacheConstructor( + PScriptCacheChild* actor, const FileDescOrError& cacheFile, + const bool& wantCacheData) { + Maybe<FileDescriptor> fd; + if (cacheFile.type() == cacheFile.TFileDescriptor) { + fd.emplace(cacheFile.get_FileDescriptor()); + } + + static_cast<loader::ScriptCacheChild*>(actor)->Init(fd, wantCacheData); + + // Some scripts listen for "app-startup" to start. However, in the content + // process, this category runs before the ScriptPreloader is initialized so + // these scripts wouldn't be added to the cache. Instead, if a script needs to + // run on start up in the content process, it should listen for this category. + NS_CreateServicesFromCategory("content-process-ready-for-script", nullptr, + "content-process-ready-for-script", nullptr); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvNetworkLinkTypeChange( + const uint32_t& aType) { + mNetworkLinkType = aType; + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, "contentchild:network-link-type-changed", + nullptr); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvSocketProcessCrashed() { + nsIOService::IncreaseSocketProcessCrashCount(); + return IPC_OK(); +} + +PRemotePrintJobChild* ContentChild::AllocPRemotePrintJobChild() { +#ifdef NS_PRINTING + return new RemotePrintJobChild(); +#else + return nullptr; +#endif +} + +already_AddRefed<PClipboardReadRequestChild> +ContentChild::AllocPClipboardReadRequestChild( + const nsTArray<nsCString>& aTypes) { + return MakeAndAddRef<ClipboardReadRequestChild>(aTypes); +} + +media::PMediaChild* ContentChild::AllocPMediaChild() { + return media::AllocPMediaChild(); +} + +bool ContentChild::DeallocPMediaChild(media::PMediaChild* aActor) { + return media::DeallocPMediaChild(aActor); +} + +PBenchmarkStorageChild* ContentChild::AllocPBenchmarkStorageChild() { + return BenchmarkStorageChild::Instance(); +} + +bool ContentChild::DeallocPBenchmarkStorageChild( + PBenchmarkStorageChild* aActor) { + delete aActor; + return true; +} + +#ifdef MOZ_WEBRTC +PWebrtcGlobalChild* ContentChild::AllocPWebrtcGlobalChild() { + auto* child = new WebrtcGlobalChild(); + return child; +} + +bool ContentChild::DeallocPWebrtcGlobalChild(PWebrtcGlobalChild* aActor) { + delete static_cast<WebrtcGlobalChild*>(aActor); + return true; +} +#endif + +mozilla::ipc::IPCResult ContentChild::RecvRegisterChrome( + nsTArray<ChromePackage>&& packages, + nsTArray<SubstitutionMapping>&& resources, + nsTArray<OverrideMapping>&& overrides, const nsCString& locale, + const bool& reset) { + nsCOMPtr<nsIChromeRegistry> registrySvc = nsChromeRegistry::GetService(); + nsChromeRegistryContent* chromeRegistry = + static_cast<nsChromeRegistryContent*>(registrySvc.get()); + if (!chromeRegistry) { + return IPC_FAIL(this, "ChromeRegistryContent is null!"); + } + chromeRegistry->RegisterRemoteChrome(packages, resources, overrides, locale, + reset); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvRegisterChromeItem( + const ChromeRegistryItem& item) { + nsCOMPtr<nsIChromeRegistry> registrySvc = nsChromeRegistry::GetService(); + nsChromeRegistryContent* chromeRegistry = + static_cast<nsChromeRegistryContent*>(registrySvc.get()); + if (!chromeRegistry) { + return IPC_FAIL(this, "ChromeRegistryContent is null!"); + } + switch (item.type()) { + case ChromeRegistryItem::TChromePackage: + chromeRegistry->RegisterPackage(item.get_ChromePackage()); + break; + + case ChromeRegistryItem::TOverrideMapping: + chromeRegistry->RegisterOverride(item.get_OverrideMapping()); + break; + + case ChromeRegistryItem::TSubstitutionMapping: + chromeRegistry->RegisterSubstitution(item.get_SubstitutionMapping()); + break; + + default: + MOZ_ASSERT(false, "bad chrome item"); + return IPC_FAIL_NO_REASON(this); + } + + return IPC_OK(); +} +mozilla::ipc::IPCResult ContentChild::RecvClearStyleSheetCache( + const Maybe<RefPtr<nsIPrincipal>>& aForPrincipal, + const Maybe<nsCString>& aBaseDomain) { + nsIPrincipal* principal = + aForPrincipal ? aForPrincipal.value().get() : nullptr; + const nsCString* baseDomain = aBaseDomain ? aBaseDomain.ptr() : nullptr; + SharedStyleSheetCache::Clear(principal, baseDomain); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvClearImageCacheFromPrincipal( + nsIPrincipal* aPrincipal) { + imgLoader* loader; + if (aPrincipal->OriginAttributesRef().mPrivateBrowsingId == + nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID) { + loader = imgLoader::NormalLoader(); + } else { + loader = imgLoader::PrivateBrowsingLoader(); + } + + loader->RemoveEntriesInternal(aPrincipal, nullptr); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvClearImageCacheFromBaseDomain( + const nsCString& aBaseDomain) { + imgLoader::NormalLoader()->RemoveEntriesInternal(nullptr, &aBaseDomain); + imgLoader::PrivateBrowsingLoader()->RemoveEntriesInternal(nullptr, + &aBaseDomain); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvClearImageCache( + const bool& privateLoader, const bool& chrome) { + imgLoader* loader = privateLoader ? imgLoader::PrivateBrowsingLoader() + : imgLoader::NormalLoader(); + + loader->ClearCache(chrome); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvSetOffline(const bool& offline) { + nsCOMPtr<nsIIOService> io(do_GetIOService()); + NS_ASSERTION(io, "IO Service can not be null"); + + io->SetOffline(offline); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvSetConnectivity( + const bool& connectivity) { + nsCOMPtr<nsIIOService> io(do_GetIOService()); + nsCOMPtr<nsIIOServiceInternal> ioInternal(do_QueryInterface(io)); + NS_ASSERTION(ioInternal, "IO Service can not be null"); + + ioInternal->SetConnectivity(connectivity); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvSetCaptivePortalState( + const int32_t& aState) { + nsCOMPtr<nsICaptivePortalService> cps = do_GetService(NS_CAPTIVEPORTAL_CID); + if (!cps) { + return IPC_OK(); + } + + mozilla::net::CaptivePortalService* portal = + static_cast<mozilla::net::CaptivePortalService*>(cps.get()); + portal->SetStateInChild(aState); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvSetTRRMode( + const nsIDNSService::ResolverMode& mode, + const nsIDNSService::ResolverMode& modeFromPref) { + RefPtr<net::ChildDNSService> dnsServiceChild = + dont_AddRef(net::ChildDNSService::GetSingleton()); + if (dnsServiceChild) { + dnsServiceChild->SetTRRModeInChild(mode, modeFromPref); + } + return IPC_OK(); +} + +void ContentChild::ActorDestroy(ActorDestroyReason why) { + if (mForceKillTimer) { + mForceKillTimer->Cancel(); + mForceKillTimer = nullptr; + } + + if (AbnormalShutdown == why) { + NS_WARNING("shutting down early because of crash!"); + ProcessChild::QuickExit(); + } + +#ifndef NS_FREE_PERMANENT_DATA + // In release builds, there's no point in the content process + // going through the full XPCOM shutdown path, because it doesn't + // keep persistent state. + ProcessChild::QuickExit(); +#else + // Destroy our JSProcessActors, and reject any pending queries. + JSActorDidDestroy(); + +# if defined(XP_WIN) + RefPtr<DllServices> dllSvc(DllServices::Get()); + dllSvc->DisableFull(); +# endif // defined(XP_WIN) + + if (gFirstIdleTask) { + gFirstIdleTask->Cancel(); + gFirstIdleTask = nullptr; + } + + BlobURLProtocolHandler::RemoveDataEntries(); + + mSharedData = nullptr; + + mAlertObservers.Clear(); + + mIdleObservers.Clear(); + + if (mConsoleListener) { + nsCOMPtr<nsIConsoleService> svc( + do_GetService(NS_CONSOLESERVICE_CONTRACTID)); + if (svc) { + svc->UnregisterListener(mConsoleListener); + mConsoleListener->mChild = nullptr; + } + } + mIsAlive = false; + + CrashReporterClient::DestroySingleton(); + + XRE_ShutdownChildProcess(); +#endif // NS_FREE_PERMANENT_DATA +} + +void ContentChild::ProcessingError(Result aCode, const char* aReason) { + switch (aCode) { + case MsgDropped: + NS_WARNING("MsgDropped in ContentChild"); + return; + + case MsgNotKnown: + case MsgNotAllowed: + case MsgPayloadError: + case MsgProcessingError: + case MsgRouteError: + case MsgValueError: + break; + + default: + MOZ_CRASH("not reached"); + } + + nsDependentCString reason(aReason); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::ipc_channel_error, reason); + + MOZ_CRASH("Content child abort due to IPC error"); +} + +nsresult ContentChild::AddRemoteAlertObserver(const nsString& aData, + nsIObserver* aObserver) { + NS_ASSERTION(aObserver, "Adding a null observer?"); + mAlertObservers.AppendElement(new AlertObserver(aObserver, aData)); + return NS_OK; +} + +mozilla::ipc::IPCResult ContentChild::RecvPreferenceUpdate(const Pref& aPref) { + Preferences::SetPreference(aPref); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvVarUpdate(const GfxVarUpdate& aVar) { + gfx::gfxVars::ApplyUpdate(aVar); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvUpdatePerfStatsCollectionMask( + const uint64_t& aMask) { + PerfStats::SetCollectionMask(static_cast<PerfStats::MetricMask>(aMask)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvCollectPerfStatsJSON( + CollectPerfStatsJSONResolver&& aResolver) { + aResolver(PerfStats::CollectLocalPerfStatsJSON()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvCollectScrollingMetrics( + CollectScrollingMetricsResolver&& aResolver) { + auto metrics = ScrollingMetrics::CollectLocalScrollingMetrics(); + using ResolverArgs = std::tuple<const uint32_t&, const uint32_t&>; + aResolver(ResolverArgs(std::get<0>(metrics), std::get<1>(metrics))); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvNotifyAlertsObserver( + const nsCString& aType, const nsString& aData) { + nsTArray<nsCOMPtr<nsIObserver>> observersToNotify; + + mAlertObservers.RemoveElementsBy([&](UniquePtr<AlertObserver>& observer) { + if (!observer->mData.Equals(aData)) { + return false; + } + + // aType == alertfinished, this alert is done and we can remove the + // observer. + observersToNotify.AppendElement(observer->mObserver); + return aType.EqualsLiteral("alertfinished"); + }); + + for (auto& observer : observersToNotify) { + observer->Observe(nullptr, aType.get(), aData.get()); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvNotifyVisited( + nsTArray<VisitedQueryResult>&& aURIs) { + nsCOMPtr<IHistory> history = components::History::Service(); + if (!history) { + return IPC_OK(); + } + for (const VisitedQueryResult& result : aURIs) { + nsCOMPtr<nsIURI> newURI = result.uri(); + if (!newURI) { + return IPC_FAIL_NO_REASON(this); + } + auto status = result.visited() ? IHistory::VisitedStatus::Visited + : IHistory::VisitedStatus::Unvisited; + history->NotifyVisited(newURI, status); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvThemeChanged( + FullLookAndFeel&& aLookAndFeelData, widget::ThemeChangeKind aKind) { + LookAndFeel::SetData(std::move(aLookAndFeelData)); + LookAndFeel::NotifyChangedAllWindows(aKind); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvLoadProcessScript( + const nsString& aURL) { + auto* global = ContentProcessMessageManager::Get(); + global->LoadScript(aURL); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvAsyncMessage( + const nsString& aMsg, const ClonedMessageData& aData) { + AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING("ContentChild::RecvAsyncMessage", + OTHER, aMsg); + MMPrinter::Print("ContentChild::RecvAsyncMessage", aMsg, aData); + + RefPtr<nsFrameMessageManager> cpm = + nsFrameMessageManager::GetChildProcessManager(); + if (cpm) { + StructuredCloneData data; + ipc::UnpackClonedMessageData(aData, data); + cpm->ReceiveMessage(cpm, nullptr, aMsg, false, &data, nullptr, + IgnoreErrors()); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvRegisterStringBundles( + nsTArray<mozilla::dom::StringBundleDescriptor>&& aDescriptors) { + nsCOMPtr<nsIStringBundleService> stringBundleService = + components::StringBundle::Service(); + + for (auto& descriptor : aDescriptors) { + stringBundleService->RegisterContentBundle( + descriptor.bundleURL(), descriptor.mapFile(), descriptor.mapSize()); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvUpdateL10nFileSources( + nsTArray<mozilla::dom::L10nFileSourceDescriptor>&& aDescriptors) { + L10nRegistry::RegisterFileSourcesFromParentProcess(aDescriptors); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvUpdateSharedData( + const FileDescriptor& aMapFile, const uint32_t& aMapSize, + nsTArray<IPCBlob>&& aBlobs, nsTArray<nsCString>&& aChangedKeys) { + nsTArray<RefPtr<BlobImpl>> blobImpls(aBlobs.Length()); + for (auto& ipcBlob : aBlobs) { + blobImpls.AppendElement(IPCBlobUtils::Deserialize(ipcBlob)); + } + + if (mSharedData) { + mSharedData->Update(aMapFile, aMapSize, std::move(blobImpls), + std::move(aChangedKeys)); + } else { + mSharedData = + new SharedMap(ContentProcessMessageManager::Get()->GetParentObject(), + aMapFile, aMapSize, std::move(blobImpls)); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvFontListChanged() { + gfxPlatformFontList::PlatformFontList()->FontListChanged(); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvForceGlobalReflow( + bool aNeedsReframe) { + gfxPlatform::ForceGlobalReflow(aNeedsReframe ? gfxPlatform::NeedsReframe::Yes + : gfxPlatform::NeedsReframe::No); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvGeolocationUpdate( + nsIDOMGeoPosition* aPosition) { + RefPtr<nsGeolocationService> gs = + nsGeolocationService::GetGeolocationService(); + if (!gs) { + return IPC_OK(); + } + gs->Update(aPosition); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvGeolocationError( + const uint16_t& errorCode) { + RefPtr<nsGeolocationService> gs = + nsGeolocationService::GetGeolocationService(); + if (!gs) { + return IPC_OK(); + } + gs->NotifyError(errorCode); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvUpdateDictionaryList( + nsTArray<nsCString>&& aDictionaries) { + mAvailableDictionaries = std::move(aDictionaries); + mozInlineSpellChecker::UpdateCanEnableInlineSpellChecking(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvUpdateFontList( + dom::SystemFontList&& aFontList) { + mFontList = std::move(aFontList); + if (gfxPlatform::Initialized()) { + gfxPlatform::GetPlatform()->UpdateFontList(true); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvRebuildFontList( + const bool& aFullRebuild) { + if (gfxPlatform::Initialized()) { + gfxPlatform::GetPlatform()->UpdateFontList(aFullRebuild); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvFontListShmBlockAdded( + const uint32_t& aGeneration, const uint32_t& aIndex, + base::SharedMemoryHandle&& aHandle) { + if (gfxPlatform::Initialized()) { + gfxPlatformFontList::PlatformFontList()->ShmBlockAdded(aGeneration, aIndex, + std::move(aHandle)); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvUpdateAppLocales( + nsTArray<nsCString>&& aAppLocales) { + LocaleService::GetInstance()->AssignAppLocales(aAppLocales); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvUpdateRequestedLocales( + nsTArray<nsCString>&& aRequestedLocales) { + LocaleService::GetInstance()->AssignRequestedLocales(aRequestedLocales); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvSystemTimezoneChanged() { + nsJSUtils::ResetTimeZone(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvAddPermission( + const IPC::Permission& permission) { + nsCOMPtr<nsIPermissionManager> permissionManagerIface = + components::PermissionManager::Service(); + PermissionManager* permissionManager = + static_cast<PermissionManager*>(permissionManagerIface.get()); + MOZ_ASSERT(permissionManager, + "We have no permissionManager in the Content process !"); + + // note we do not need to force mUserContextId to the default here because + // the permission manager does that internally. + nsAutoCString originNoSuffix; + OriginAttributes attrs; + bool success = attrs.PopulateFromOrigin(permission.origin, originNoSuffix); + NS_ENSURE_TRUE(success, IPC_FAIL_NO_REASON(this)); + + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), originNoSuffix); + NS_ENSURE_SUCCESS(rv, IPC_OK()); + + nsCOMPtr<nsIPrincipal> principal = + mozilla::BasePrincipal::CreateContentPrincipal(uri, attrs); + + // child processes don't care about modification time. + int64_t modificationTime = 0; + + permissionManager->AddInternal( + principal, nsCString(permission.type), permission.capability, 0, + permission.expireType, permission.expireTime, modificationTime, + PermissionManager::eNotify, PermissionManager::eNoDBOperation); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvRemoveAllPermissions() { + nsCOMPtr<nsIPermissionManager> permissionManagerIface = + components::PermissionManager::Service(); + PermissionManager* permissionManager = + static_cast<PermissionManager*>(permissionManagerIface.get()); + MOZ_ASSERT(permissionManager, + "We have no permissionManager in the Content process !"); + + permissionManager->RemoveAllFromIPC(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvFlushMemory(const nsString& reason) { + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (!mShuttingDown && os) { + os->NotifyObservers(nullptr, "memory-pressure", reason.get()); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvActivateA11y() { +#ifdef ACCESSIBILITY + // Start accessibility in content process if it's running in chrome + // process. + GetOrCreateAccService(nsAccessibilityService::eMainProcess); +#endif // ACCESSIBILITY + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvShutdownA11y() { +#ifdef ACCESSIBILITY + // Try to shutdown accessibility in content process if it's shutting down in + // chrome process. + MaybeShutdownAccService(nsAccessibilityService::eMainProcess); +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvApplicationForeground() { + // Rebroadcast the "application-foreground" + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, "application-foreground", nullptr); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvApplicationBackground() { + // Rebroadcast the "application-background" + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, "application-background", nullptr); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvGarbageCollect() { + // Rebroadcast the "child-gc-request" so that workers will GC. + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, "child-gc-request", nullptr); + } + nsJSContext::GarbageCollectNow(JS::GCReason::DOM_IPC); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvCycleCollect() { + // Rebroadcast the "child-cc-request" so that workers will CC. + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, "child-cc-request", nullptr); + } + nsJSContext::CycleCollectNow(CCReason::IPC_MESSAGE); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvUnlinkGhosts() { +#ifdef DEBUG + nsWindowMemoryReporter::UnlinkGhostWindows(); +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvAppInfo( + const nsCString& version, const nsCString& buildID, const nsCString& name, + const nsCString& UAName, const nsCString& ID, const nsCString& vendor, + const nsCString& sourceURL, const nsCString& updateURL) { + mAppInfo.version.Assign(version); + mAppInfo.buildID.Assign(buildID); + mAppInfo.name.Assign(name); + mAppInfo.UAName.Assign(UAName); + mAppInfo.ID.Assign(ID); + mAppInfo.vendor.Assign(vendor); + mAppInfo.sourceURL.Assign(sourceURL); + mAppInfo.updateURL.Assign(updateURL); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvRemoteType( + const nsCString& aRemoteType, const nsCString& aProfile) { + if (aRemoteType == mRemoteType) { + // Allocation of preallocated processes that are still launching can + // cause this + return IPC_OK(); + } + + if (!mRemoteType.IsVoid()) { + // Preallocated processes are type PREALLOC_REMOTE_TYPE; they may not + // become a File: process, or Privileged About Content Process + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("Changing remoteType of process %d from %s to %s", getpid(), + mRemoteType.get(), aRemoteType.get())); + // prealloc->anything (but file) or web->web allowed, and no-change + MOZ_RELEASE_ASSERT(mRemoteType == PREALLOC_REMOTE_TYPE && + aRemoteType != FILE_REMOTE_TYPE && + aRemoteType != PRIVILEGEDABOUT_REMOTE_TYPE); + } else { + // Initial setting of remote type. Either to 'prealloc' or the actual + // final type (if we didn't use a preallocated process) + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("Setting remoteType of process %d to %s", getpid(), + aRemoteType.get())); + + if (aRemoteType == PREALLOC_REMOTE_TYPE) { + PreallocInit(); + } + } + + auto remoteTypePrefix = RemoteTypePrefix(aRemoteType); + + // Must do before SetProcessName + mRemoteType.Assign(aRemoteType); + + // Update the process name so about:memory's process names are more obvious. + if (aRemoteType == FILE_REMOTE_TYPE) { + SetProcessName("file:// Content"_ns, nullptr, &aProfile); + } else if (aRemoteType == EXTENSION_REMOTE_TYPE) { + SetProcessName("WebExtensions"_ns, nullptr, &aProfile); + } else if (aRemoteType == PRIVILEGEDABOUT_REMOTE_TYPE) { + SetProcessName("Privileged Content"_ns, nullptr, &aProfile); + } else if (aRemoteType == PRIVILEGEDMOZILLA_REMOTE_TYPE) { + SetProcessName("Privileged Mozilla"_ns, nullptr, &aProfile); + } else if (remoteTypePrefix == WITH_COOP_COEP_REMOTE_TYPE) { + // The profiler can sanitize out the eTLD+1 + nsDependentCSubstring etld = + Substring(aRemoteType, WITH_COOP_COEP_REMOTE_TYPE.Length() + 1); +#ifdef NIGHTLY_BUILD + SetProcessName("WebCOOP+COEP Content"_ns, &etld, &aProfile); +#else + SetProcessName("Isolated Web Content"_ns, &etld, + &aProfile); // to avoid confusing people +#endif + } else if (remoteTypePrefix == FISSION_WEB_REMOTE_TYPE) { + // The profiler can sanitize out the eTLD+1 + nsDependentCSubstring etld = + Substring(aRemoteType, FISSION_WEB_REMOTE_TYPE.Length() + 1); + SetProcessName("Isolated Web Content"_ns, &etld, &aProfile); + } else if (remoteTypePrefix == SERVICEWORKER_REMOTE_TYPE) { + // The profiler can sanitize out the eTLD+1 + nsDependentCSubstring etld = + Substring(aRemoteType, SERVICEWORKER_REMOTE_TYPE.Length() + 1); + SetProcessName("Isolated Service Worker"_ns, &etld, &aProfile); + } else { + // else "prealloc" or "web" type -> "Web Content" + SetProcessName("Web Content"_ns, nullptr, &aProfile); + } + + // Turn off Spectre mitigations in isolated web content processes. + if (StaticPrefs::javascript_options_spectre_disable_for_isolated_content() && + (remoteTypePrefix == FISSION_WEB_REMOTE_TYPE || + remoteTypePrefix == SERVICEWORKER_REMOTE_TYPE || + remoteTypePrefix == WITH_COOP_COEP_REMOTE_TYPE || + aRemoteType == PRIVILEGEDABOUT_REMOTE_TYPE || + aRemoteType == PRIVILEGEDMOZILLA_REMOTE_TYPE)) { + JS::DisableSpectreMitigationsAfterInit(); + } + + // Use the prefix to avoid URIs from Fission isolated processes. + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::RemoteType, + remoteTypePrefix); + + // Defer RemoteWorkerService initialization until the child process does + // receive its specific remoteType and can become actionable for the + // RemoteWorkerManager in the parent process. + if (mRemoteType != PREALLOC_REMOTE_TYPE) { + RemoteWorkerService::Initialize(); + } + + return IPC_OK(); +} + +// A method to initialize anything we need during the preallocation phase +void ContentChild::PreallocInit() { + EnsureNSSInitializedChromeOrContent(); + + // SetAcceptLanguages() needs to read localized strings (file access), + // which is slow, so do this in prealloc + nsHttpHandler::PresetAcceptLanguages(); +} + +// Call RemoteTypePrefix() on the result to remove URIs if you want to use this +// for telemetry. +const nsACString& ContentChild::GetRemoteType() const { return mRemoteType; } + +mozilla::ipc::IPCResult ContentChild::RecvInitBlobURLs( + nsTArray<BlobURLRegistrationData>&& aRegistrations) { + for (uint32_t i = 0; i < aRegistrations.Length(); ++i) { + BlobURLRegistrationData& registration = aRegistrations[i]; + RefPtr<BlobImpl> blobImpl = IPCBlobUtils::Deserialize(registration.blob()); + MOZ_ASSERT(blobImpl); + + BlobURLProtocolHandler::AddDataEntry(registration.url(), + registration.principal(), + registration.partitionKey(), blobImpl); + // If we have received an already-revoked blobURL, we have to keep it alive + // for a while (see BlobURLProtocolHandler) in order to support pending + // operations such as navigation, download and so on. + if (registration.revoked()) { + BlobURLProtocolHandler::RemoveDataEntry(registration.url(), false); + } + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvInitJSActorInfos( + nsTArray<JSProcessActorInfo>&& aContentInfos, + nsTArray<JSWindowActorInfo>&& aWindowInfos) { + RefPtr<JSActorService> actSvc = JSActorService::GetSingleton(); + actSvc->LoadJSActorInfos(aContentInfos, aWindowInfos); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvUnregisterJSWindowActor( + const nsCString& aName) { + RefPtr<JSActorService> actSvc = JSActorService::GetSingleton(); + actSvc->UnregisterWindowActor(aName); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvUnregisterJSProcessActor( + const nsCString& aName) { + RefPtr<JSActorService> actSvc = JSActorService::GetSingleton(); + actSvc->UnregisterProcessActor(aName); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvLastPrivateDocShellDestroyed() { + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + obs->NotifyObservers(nullptr, "last-pb-context-exited", nullptr); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvNotifyProcessPriorityChanged( + const hal::ProcessPriority& aPriority) { + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + NS_ENSURE_TRUE(os, IPC_OK()); + + RefPtr<nsHashPropertyBag> props = new nsHashPropertyBag(); + props->SetPropertyAsInt32(u"priority"_ns, static_cast<int32_t>(aPriority)); + + PROFILER_MARKER("Process Priority", OTHER, + mozilla::MarkerThreadId::MainThread(), ProcessPriorityChange, + ProfilerString8View::WrapNullTerminatedString( + ProcessPriorityToString(mProcessPriority)), + ProfilerString8View::WrapNullTerminatedString( + ProcessPriorityToString(aPriority))); + + // Record FOG data before the priority change. + // Ignore the change if it's the first time we set the process priority. + if (mProcessPriority != hal::PROCESS_PRIORITY_UNKNOWN) { + glean::RecordPowerMetrics(); + } + + ConfigureThreadPerformanceHints(aPriority); + + mProcessPriority = aPriority; + + os->NotifyObservers(static_cast<nsIPropertyBag2*>(props), + "ipc:process-priority-changed", nullptr); + if (StaticPrefs:: + dom_memory_foreground_content_processes_have_larger_page_cache()) { + if (mProcessPriority >= hal::PROCESS_PRIORITY_FOREGROUND) { + // Note: keep this in sync with the JS shell (js/src/shell/js.cpp). + moz_set_max_dirty_page_modifier(4); + } else if (mProcessPriority == hal::PROCESS_PRIORITY_BACKGROUND) { + moz_set_max_dirty_page_modifier(-2); + } else { + moz_set_max_dirty_page_modifier(0); + } + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvMinimizeMemoryUsage() { + nsCOMPtr<nsIMemoryReporterManager> mgr = + do_GetService("@mozilla.org/memory-reporter-manager;1"); + NS_ENSURE_TRUE(mgr, IPC_OK()); + + Unused << mgr->MinimizeMemoryUsage(/* callback = */ nullptr); + return IPC_OK(); +} + +void ContentChild::AddIdleObserver(nsIObserver* aObserver, + uint32_t aIdleTimeInS) { + MOZ_ASSERT(aObserver, "null idle observer"); + // Make sure aObserver isn't released while we wait for the parent + aObserver->AddRef(); + SendAddIdleObserver(reinterpret_cast<uint64_t>(aObserver), aIdleTimeInS); + mIdleObservers.Insert(aObserver); +} + +void ContentChild::RemoveIdleObserver(nsIObserver* aObserver, + uint32_t aIdleTimeInS) { + MOZ_ASSERT(aObserver, "null idle observer"); + SendRemoveIdleObserver(reinterpret_cast<uint64_t>(aObserver), aIdleTimeInS); + aObserver->Release(); + mIdleObservers.Remove(aObserver); +} + +mozilla::ipc::IPCResult ContentChild::RecvNotifyIdleObserver( + const uint64_t& aObserver, const nsCString& aTopic, + const nsString& aTimeStr) { + nsIObserver* observer = reinterpret_cast<nsIObserver*>(aObserver); + if (mIdleObservers.Contains(observer)) { + observer->Observe(nullptr, aTopic.get(), aTimeStr.get()); + } else { + NS_WARNING("Received notification for an idle observer that was removed."); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvLoadAndRegisterSheet( + nsIURI* aURI, const uint32_t& aType) { + if (!aURI) { + return IPC_OK(); + } + + nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance(); + if (sheetService) { + sheetService->LoadAndRegisterSheet(aURI, aType); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvUnregisterSheet( + nsIURI* aURI, const uint32_t& aType) { + if (!aURI) { + return IPC_OK(); + } + + nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance(); + if (sheetService) { + sheetService->UnregisterSheet(aURI, aType); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvDomainSetChanged( + const uint32_t& aSetType, const uint32_t& aChangeType, nsIURI* aDomain) { + if (aChangeType == ACTIVATE_POLICY) { + if (mPolicy) { + return IPC_OK(); + } + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + MOZ_ASSERT(ssm); + ssm->ActivateDomainPolicyInternal(getter_AddRefs(mPolicy)); + if (!mPolicy) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); + } + if (!mPolicy) { + MOZ_ASSERT_UNREACHABLE( + "If the domain policy is not active yet," + " the first message should be ACTIVATE_POLICY"); + return IPC_FAIL_NO_REASON(this); + } + + NS_ENSURE_TRUE(mPolicy, IPC_FAIL_NO_REASON(this)); + + if (aChangeType == DEACTIVATE_POLICY) { + mPolicy->Deactivate(); + mPolicy = nullptr; + return IPC_OK(); + } + + nsCOMPtr<nsIDomainSet> set; + switch (aSetType) { + case BLOCKLIST: + mPolicy->GetBlocklist(getter_AddRefs(set)); + break; + case SUPER_BLOCKLIST: + mPolicy->GetSuperBlocklist(getter_AddRefs(set)); + break; + case ALLOWLIST: + mPolicy->GetAllowlist(getter_AddRefs(set)); + break; + case SUPER_ALLOWLIST: + mPolicy->GetSuperAllowlist(getter_AddRefs(set)); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unexpected setType"); + return IPC_FAIL_NO_REASON(this); + } + + MOZ_ASSERT(set); + + switch (aChangeType) { + case ADD_DOMAIN: + NS_ENSURE_TRUE(aDomain, IPC_FAIL_NO_REASON(this)); + set->Add(aDomain); + break; + case REMOVE_DOMAIN: + NS_ENSURE_TRUE(aDomain, IPC_FAIL_NO_REASON(this)); + set->Remove(aDomain); + break; + case CLEAR_DOMAINS: + set->Clear(); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unexpected changeType"); + return IPC_FAIL_NO_REASON(this); + } + + return IPC_OK(); +} + +void ContentChild::StartForceKillTimer() { + if (mForceKillTimer) { + return; + } + + int32_t timeoutSecs = StaticPrefs::dom_ipc_tabs_shutdownTimeoutSecs(); + if (timeoutSecs > 0) { + NS_NewTimerWithFuncCallback(getter_AddRefs(mForceKillTimer), + ContentChild::ForceKillTimerCallback, this, + timeoutSecs * 1000, nsITimer::TYPE_ONE_SHOT, + "dom::ContentChild::StartForceKillTimer"); + MOZ_ASSERT(mForceKillTimer); + } +} + +/* static */ +void ContentChild::ForceKillTimerCallback(nsITimer* aTimer, void* aClosure) { + ProcessChild::QuickExit(); +} + +mozilla::ipc::IPCResult ContentChild::RecvShutdownConfirmedHP() { + CrashReporter::AppendToCrashReportAnnotation( + CrashReporter::Annotation::IPCShutdownState, + "RecvShutdownConfirmedHP entry"_ns); + + // Bug 1755376: If we see "RecvShutdownConfirmedHP entry" often in + // bug IPCError_ShutDownKill we might want to anticipate + // ShutdownPhase::AppShutdownConfirmed to start here. + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvShutdown() { + CrashReporter::AppendToCrashReportAnnotation( + CrashReporter::Annotation::IPCShutdownState, "RecvShutdown entry"_ns); + + // Signal the ongoing shutdown to AppShutdown, this + // will make abort nested SpinEventLoopUntilOrQuit loops + AppShutdown::AdvanceShutdownPhaseWithoutNotify( + ShutdownPhase::AppShutdownConfirmed); + + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + if (os) { + CrashReporter::AppendToCrashReportAnnotation( + CrashReporter::Annotation::IPCShutdownState, + "content-child-will-shutdown started"_ns); + + os->NotifyObservers(ToSupports(this), "content-child-will-shutdown", + nullptr); + } + + ShutdownInternal(); + return IPC_OK(); +} + +void ContentChild::ShutdownInternal() { + CrashReporter::AppendToCrashReportAnnotation( + CrashReporter::Annotation::IPCShutdownState, "ShutdownInternal entry"_ns); + + // If we receive the shutdown message from within a nested event loop, we want + // to wait for that event loop to finish. Otherwise we could prematurely + // terminate an "unload" or "pagehide" event handler (which might be doing a + // sync XHR, for example). + + MOZ_ASSERT(NS_IsMainThread()); + RefPtr<nsThread> mainThread = nsThreadManager::get().GetCurrentThread(); + // Note that we only have to check the recursion count for the current + // cooperative thread. Since the Shutdown message is not labeled with a + // SchedulerGroup, there can be no other cooperative threads doing work while + // we're running. + if (mainThread && mainThread->RecursionDepth() > 1) { + // We're in a nested event loop. Let's delay for an arbitrary period of + // time (100ms) in the hopes that the event loop will have finished by + // then. + GetCurrentSerialEventTarget()->DelayedDispatch( + NewRunnableMethod("dom::ContentChild::RecvShutdown", this, + &ContentChild::ShutdownInternal), + 100); + return; + } + + mShuttingDown = true; + +#ifdef NIGHTLY_BUILD + BackgroundHangMonitor::UnregisterAnnotator( + PendingInputEventHangAnnotator::sSingleton); +#endif + + if (mPolicy) { + mPolicy->Deactivate(); + mPolicy = nullptr; + } + + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + if (os) { + CrashReporter::AppendToCrashReportAnnotation( + CrashReporter::Annotation::IPCShutdownState, + "content-child-shutdown started"_ns); + os->NotifyObservers(ToSupports(this), "content-child-shutdown", nullptr); + } + + GetIPCChannel()->SetAbortOnError(false); + + if (mProfilerController) { + const bool isProfiling = profiler_is_active(); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::ProfilerChildShutdownPhase, + isProfiling ? "Profiling - GrabShutdownProfileAndShutdown"_ns + : "Not profiling - GrabShutdownProfileAndShutdown"_ns); + ProfileAndAdditionalInformation shutdownProfileAndAdditionalInformation = + mProfilerController->GrabShutdownProfileAndShutdown(); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::ProfilerChildShutdownPhase, + isProfiling ? "Profiling - Destroying ChildProfilerController"_ns + : "Not profiling - Destroying ChildProfilerController"_ns); + mProfilerController = nullptr; + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::ProfilerChildShutdownPhase, + isProfiling ? "Profiling - SendShutdownProfile (sending)"_ns + : "Not profiling - SendShutdownProfile (sending)"_ns); + if (const size_t len = shutdownProfileAndAdditionalInformation.SizeOf(); + len >= size_t(IPC::Channel::kMaximumMessageSize)) { + shutdownProfileAndAdditionalInformation.mProfile = nsPrintfCString( + "*Profile from pid %u bigger (%zu) than IPC max (%zu)", + unsigned(profiler_current_process_id().ToNumber()), len, + size_t(IPC::Channel::kMaximumMessageSize)); + } + // Send the shutdown profile to the parent process through our own + // message channel, which we know will survive for long enough. + bool sent = + SendShutdownProfile(shutdownProfileAndAdditionalInformation.mProfile); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::ProfilerChildShutdownPhase, + sent ? (isProfiling ? "Profiling - SendShutdownProfile (sent)"_ns + : "Not profiling - SendShutdownProfile (sent)"_ns) + : (isProfiling + ? "Profiling - SendShutdownProfile (failed)"_ns + : "Not profiling - SendShutdownProfile (failed)"_ns)); + } + + if (PerfStats::GetCollectionMask() != 0) { + SendShutdownPerfStats(PerfStats::CollectLocalPerfStatsJSON()); + } + + // Start a timer that will ensure we quickly exit after a reasonable period + // of time. Prevents shutdown hangs after our connection to the parent + // closes or when the parent is too busy to ever kill us. + CrashReporter::AppendToCrashReportAnnotation( + CrashReporter::Annotation::IPCShutdownState, "StartForceKillTimer"_ns); + StartForceKillTimer(); + + CrashReporter::AppendToCrashReportAnnotation( + CrashReporter::Annotation::IPCShutdownState, + "SendFinishShutdown (sending)"_ns); + + // Notify the parent that we are done with shutdown. This is sent with high + // priority and will just flag we are done. + Unused << SendNotifyShutdownSuccess(); + + // Now tell the parent to actually destroy our channel which will make end + // our process. This is expected to be the last event the parent will + // ever process for this ContentChild. + bool sent = SendFinishShutdown(); + + CrashReporter::AppendToCrashReportAnnotation( + CrashReporter::Annotation::IPCShutdownState, + sent ? "SendFinishShutdown (sent)"_ns : "SendFinishShutdown (failed)"_ns); +} + +mozilla::ipc::IPCResult ContentChild::RecvUpdateWindow( + const uintptr_t& aChildId) { + MOZ_ASSERT( + false, + "ContentChild::RecvUpdateWindow calls unexpected on this platform."); + return IPC_FAIL_NO_REASON(this); +} + +PContentPermissionRequestChild* +ContentChild::AllocPContentPermissionRequestChild( + Span<const PermissionRequest> aRequests, nsIPrincipal* aPrincipal, + nsIPrincipal* aTopLevelPrincipal, const bool& aIsHandlingUserInput, + const bool& aMaybeUnsafePermissionDelegate, const TabId& aTabId) { + MOZ_CRASH("unused"); + return nullptr; +} + +bool ContentChild::DeallocPContentPermissionRequestChild( + PContentPermissionRequestChild* actor) { + nsContentPermissionUtils::NotifyRemoveContentPermissionRequestChild(actor); + auto child = static_cast<RemotePermissionRequest*>(actor); + child->IPDLRelease(); + return true; +} + +already_AddRefed<PWebBrowserPersistDocumentChild> +ContentChild::AllocPWebBrowserPersistDocumentChild( + PBrowserChild* aBrowser, const MaybeDiscarded<BrowsingContext>& aContext) { + return MakeAndAddRef<WebBrowserPersistDocumentChild>(); +} + +mozilla::ipc::IPCResult ContentChild::RecvPWebBrowserPersistDocumentConstructor( + PWebBrowserPersistDocumentChild* aActor, PBrowserChild* aBrowser, + const MaybeDiscarded<BrowsingContext>& aContext) { + if (NS_WARN_IF(!aBrowser)) { + return IPC_FAIL_NO_REASON(this); + } + + if (aContext.IsNullOrDiscarded()) { + aActor->SendInitFailure(NS_ERROR_NO_CONTENT); + return IPC_OK(); + } + + nsCOMPtr<Document> foundDoc = aContext.get()->GetDocument(); + + if (!foundDoc) { + aActor->SendInitFailure(NS_ERROR_NO_CONTENT); + } else { + static_cast<WebBrowserPersistDocumentChild*>(aActor)->Start(foundDoc); + } + return IPC_OK(); +} + +static already_AddRefed<DataTransfer> ConvertToDataTransfer( + nsTArray<IPCTransferableData>&& aTransferables, EventMessage aMessage) { + // Check if we are receiving any file objects. If we are we will want + // to hide any of the other objects coming in from content. + bool hasFiles = false; + for (uint32_t i = 0; i < aTransferables.Length() && !hasFiles; ++i) { + auto& items = aTransferables[i].items(); + for (uint32_t j = 0; j < items.Length() && !hasFiles; ++j) { + if (items[j].data().type() == + IPCTransferableDataType::TIPCTransferableDataBlob) { + hasFiles = true; + } + } + } + // Add the entries from the IPC to the new DataTransfer + RefPtr<DataTransfer> dataTransfer = + new DataTransfer(nullptr, aMessage, false, -1); + for (uint32_t i = 0; i < aTransferables.Length(); ++i) { + auto& items = aTransferables[i].items(); + for (uint32_t j = 0; j < items.Length(); ++j) { + const IPCTransferableDataItem& item = items[j]; + RefPtr<nsVariantCC> variant = new nsVariantCC(); + nsresult rv = + nsContentUtils::IPCTransferableDataItemToVariant(item, variant); + if (NS_FAILED(rv)) { + continue; + } + + // We should hide this data from content if we have a file, and we + // aren't a file. + bool hidden = + hasFiles && item.data().type() != + IPCTransferableDataType::TIPCTransferableDataBlob; + dataTransfer->SetDataWithPrincipalFromOtherProcess( + NS_ConvertUTF8toUTF16(item.flavor()), variant, i, + nsContentUtils::GetSystemPrincipal(), hidden); + } + } + return dataTransfer.forget(); +} + +mozilla::ipc::IPCResult ContentChild::RecvInvokeDragSession( + const MaybeDiscarded<WindowContext>& aSourceWindowContext, + const MaybeDiscarded<WindowContext>& aSourceTopWindowContext, + nsTArray<IPCTransferableData>&& aTransferables, const uint32_t& aAction) { + if (nsCOMPtr<nsIDragService> dragService = + do_GetService("@mozilla.org/widget/dragservice;1")) { + dragService->StartDragSession(); + nsCOMPtr<nsIDragSession> session; + dragService->GetCurrentSession(getter_AddRefs(session)); + if (session) { + session->SetSourceWindowContext(aSourceWindowContext.GetMaybeDiscarded()); + session->SetSourceTopWindowContext( + aSourceTopWindowContext.GetMaybeDiscarded()); + session->SetDragAction(aAction); + + RefPtr<DataTransfer> dataTransfer = + ConvertToDataTransfer(std::move(aTransferables), eDragStart); + session->SetDataTransfer(dataTransfer); + } + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvUpdateDragSession( + nsTArray<IPCTransferableData>&& aTransferables, + EventMessage aEventMessage) { + if (nsCOMPtr<nsIDragService> dragService = + do_GetService("@mozilla.org/widget/dragservice;1")) { + nsCOMPtr<nsIDragSession> session; + dragService->GetCurrentSession(getter_AddRefs(session)); + if (session) { + nsCOMPtr<DataTransfer> dataTransfer = + ConvertToDataTransfer(std::move(aTransferables), aEventMessage); + session->SetDataTransfer(dataTransfer); + } + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvEndDragSession( + const bool& aDoneDrag, const bool& aUserCancelled, + const LayoutDeviceIntPoint& aDragEndPoint, const uint32_t& aKeyModifiers, + const uint32_t& aDropEffect) { + nsCOMPtr<nsIDragService> dragService = + do_GetService("@mozilla.org/widget/dragservice;1"); + if (dragService) { + nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession(); + if (dragSession) { + if (aUserCancelled) { + dragSession->UserCancelled(); + } + + RefPtr<DataTransfer> dataTransfer = dragSession->GetDataTransfer(); + if (dataTransfer) { + dataTransfer->SetDropEffectInt(aDropEffect); + } + } + + static_cast<nsBaseDragService*>(dragService.get()) + ->SetDragEndPoint(aDragEndPoint); + dragService->EndDragSession(aDoneDrag, aKeyModifiers); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvPush(const nsCString& aScope, + nsIPrincipal* aPrincipal, + const nsString& aMessageId) { + PushMessageDispatcher dispatcher(aScope, aPrincipal, aMessageId, Nothing()); + Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObserversAndWorkers())); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvPushWithData( + const nsCString& aScope, nsIPrincipal* aPrincipal, + const nsString& aMessageId, nsTArray<uint8_t>&& aData) { + PushMessageDispatcher dispatcher(aScope, aPrincipal, aMessageId, + Some(std::move(aData))); + Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObserversAndWorkers())); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvPushSubscriptionChange( + const nsCString& aScope, nsIPrincipal* aPrincipal) { + PushSubscriptionChangeDispatcher dispatcher(aScope, aPrincipal); + Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObserversAndWorkers())); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvPushError(const nsCString& aScope, + nsIPrincipal* aPrincipal, + const nsString& aMessage, + const uint32_t& aFlags) { + PushErrorDispatcher dispatcher(aScope, aPrincipal, aMessage, aFlags); + Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObserversAndWorkers())); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +ContentChild::RecvNotifyPushSubscriptionModifiedObservers( + const nsCString& aScope, nsIPrincipal* aPrincipal) { + PushSubscriptionModifiedDispatcher dispatcher(aScope, aPrincipal); + Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObservers())); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvBlobURLRegistration( + const nsCString& aURI, const IPCBlob& aBlob, nsIPrincipal* aPrincipal, + const nsCString& aPartitionKey) { + RefPtr<BlobImpl> blobImpl = IPCBlobUtils::Deserialize(aBlob); + MOZ_ASSERT(blobImpl); + + BlobURLProtocolHandler::AddDataEntry(aURI, aPrincipal, aPartitionKey, + blobImpl); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvBlobURLUnregistration( + const nsCString& aURI) { + BlobURLProtocolHandler::RemoveDataEntry( + aURI, + /* aBroadcastToOtherProcesses = */ false); + return IPC_OK(); +} + +void ContentChild::CreateGetFilesRequest(const nsAString& aDirectoryPath, + bool aRecursiveFlag, nsID& aUUID, + GetFilesHelperChild* aChild) { + MOZ_ASSERT(aChild); + MOZ_ASSERT(!mGetFilesPendingRequests.Contains(aUUID)); + + Unused << SendGetFilesRequest(aUUID, aDirectoryPath, aRecursiveFlag); + mGetFilesPendingRequests.InsertOrUpdate(aUUID, RefPtr{aChild}); +} + +void ContentChild::DeleteGetFilesRequest(nsID& aUUID, + GetFilesHelperChild* aChild) { + MOZ_ASSERT(aChild); + MOZ_ASSERT(mGetFilesPendingRequests.Contains(aUUID)); + + Unused << SendDeleteGetFilesRequest(aUUID); + mGetFilesPendingRequests.Remove(aUUID); +} + +mozilla::ipc::IPCResult ContentChild::RecvGetFilesResponse( + const nsID& aUUID, const GetFilesResponseResult& aResult) { + RefPtr<GetFilesHelperChild> child; + + // This object can already been deleted in case DeleteGetFilesRequest has + // been called when the response was sending by the parent. + if (!mGetFilesPendingRequests.Remove(aUUID, getter_AddRefs(child))) { + return IPC_OK(); + } + + if (aResult.type() == GetFilesResponseResult::TGetFilesResponseFailure) { + child->Finished(aResult.get_GetFilesResponseFailure().errorCode()); + } else { + MOZ_ASSERT(aResult.type() == + GetFilesResponseResult::TGetFilesResponseSuccess); + + const nsTArray<IPCBlob>& ipcBlobs = + aResult.get_GetFilesResponseSuccess().blobs(); + + bool succeeded = true; + for (uint32_t i = 0; succeeded && i < ipcBlobs.Length(); ++i) { + RefPtr<BlobImpl> impl = IPCBlobUtils::Deserialize(ipcBlobs[i]); + succeeded = child->AppendBlobImpl(impl); + } + + child->Finished(succeeded ? NS_OK : NS_ERROR_OUT_OF_MEMORY); + } + return IPC_OK(); +} + +/* static */ +void ContentChild::FatalErrorIfNotUsingGPUProcess(const char* const aErrorMsg, + base::ProcessId aOtherPid) { + // If we're communicating with the same process or the UI process then we + // want to crash normally. Otherwise we want to just warn as the other end + // must be the GPU process and it crashing shouldn't be fatal for us. + if (aOtherPid == base::GetCurrentProcId() || + (GetSingleton() && GetSingleton()->OtherPid() == aOtherPid)) { + mozilla::ipc::FatalError(aErrorMsg, false); + } else { + nsAutoCString formattedMessage("IPDL error: \""); + formattedMessage.AppendASCII(aErrorMsg); + formattedMessage.AppendLiteral(R"(".)"); + NS_WARNING(formattedMessage.get()); + } +} + +PURLClassifierChild* ContentChild::AllocPURLClassifierChild( + nsIPrincipal* aPrincipal, bool* aSuccess) { + *aSuccess = true; + return new URLClassifierChild(); +} + +bool ContentChild::DeallocPURLClassifierChild(PURLClassifierChild* aActor) { + MOZ_ASSERT(aActor); + delete aActor; + return true; +} + +PURLClassifierLocalChild* ContentChild::AllocPURLClassifierLocalChild( + nsIURI* aUri, Span<const IPCURLClassifierFeature> aFeatures) { + return new URLClassifierLocalChild(); +} + +bool ContentChild::DeallocPURLClassifierLocalChild( + PURLClassifierLocalChild* aActor) { + MOZ_ASSERT(aActor); + delete aActor; + return true; +} + +PSessionStorageObserverChild* +ContentChild::AllocPSessionStorageObserverChild() { + MOZ_CRASH( + "PSessionStorageObserverChild actors should be manually constructed!"); +} + +bool ContentChild::DeallocPSessionStorageObserverChild( + PSessionStorageObserverChild* aActor) { + MOZ_ASSERT(aActor); + + delete aActor; + return true; +} + +mozilla::ipc::IPCResult ContentChild::RecvProvideAnonymousTemporaryFile( + const uint64_t& aID, const FileDescOrError& aFDOrError) { + mozilla::UniquePtr<AnonymousTemporaryFileCallback> callback; + mPendingAnonymousTemporaryFiles.Remove(aID, &callback); + MOZ_ASSERT(callback); + + PRFileDesc* prfile = nullptr; + if (aFDOrError.type() == FileDescOrError::Tnsresult) { + DebugOnly<nsresult> rv = aFDOrError.get_nsresult(); + MOZ_ASSERT(NS_FAILED(rv)); + } else { + auto rawFD = aFDOrError.get_FileDescriptor().ClonePlatformHandle(); + prfile = PR_ImportFile(PROsfd(rawFD.release())); + } + (*callback)(prfile); + return IPC_OK(); +} + +nsresult ContentChild::AsyncOpenAnonymousTemporaryFile( + const AnonymousTemporaryFileCallback& aCallback) { + MOZ_ASSERT(NS_IsMainThread()); + + static uint64_t id = 0; + auto newID = id++; + if (!SendRequestAnonymousTemporaryFile(newID)) { + return NS_ERROR_FAILURE; + } + + // Remember the association with the callback. + MOZ_ASSERT(!mPendingAnonymousTemporaryFiles.Get(newID)); + mPendingAnonymousTemporaryFiles.GetOrInsertNew(newID, aCallback); + return NS_OK; +} + +mozilla::ipc::IPCResult ContentChild::RecvSetPermissionsWithKey( + const nsCString& aPermissionKey, nsTArray<IPC::Permission>&& aPerms) { + RefPtr<PermissionManager> permManager = PermissionManager::GetInstance(); + if (permManager) { + permManager->SetPermissionsWithKey(aPermissionKey, aPerms); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvRefreshScreens( + nsTArray<ScreenDetails>&& aScreens) { + ScreenManager& screenManager = ScreenManager::GetSingleton(); + screenManager.Refresh(std::move(aScreens)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvShareCodeCoverageMutex( + CrossProcessMutexHandle aHandle) { +#ifdef MOZ_CODE_COVERAGE + CodeCoverageHandler::Init(std::move(aHandle)); + return IPC_OK(); +#else + MOZ_CRASH("Shouldn't receive this message in non-code coverage builds!"); +#endif +} + +mozilla::ipc::IPCResult ContentChild::RecvFlushCodeCoverageCounters( + FlushCodeCoverageCountersResolver&& aResolver) { +#ifdef MOZ_CODE_COVERAGE + CodeCoverageHandler::FlushCounters(); + aResolver(/* unused */ true); + return IPC_OK(); +#else + MOZ_CRASH("Shouldn't receive this message in non-code coverage builds!"); +#endif +} + +mozilla::ipc::IPCResult ContentChild::RecvSetInputEventQueueEnabled() { + nsThreadManager::get().EnableMainThreadEventPrioritization(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvFlushInputEventQueue() { + nsThreadManager::get().FlushInputEventPrioritization(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvSuspendInputEventQueue() { + nsThreadManager::get().SuspendInputEventPrioritization(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvResumeInputEventQueue() { + nsThreadManager::get().ResumeInputEventPrioritization(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvAddDynamicScalars( + nsTArray<DynamicScalarDefinition>&& aDefs) { + TelemetryIPC::AddDynamicScalarDefinitions(aDefs); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvCrossProcessRedirect( + RedirectToRealChannelArgs&& aArgs, + nsTArray<Endpoint<extensions::PStreamFilterParent>>&& aEndpoints, + CrossProcessRedirectResolver&& aResolve) { + nsCOMPtr<nsILoadInfo> loadInfo; + nsresult rv = mozilla::ipc::LoadInfoArgsToLoadInfo( + aArgs.loadInfo(), NOT_REMOTE_TYPE, getter_AddRefs(loadInfo)); + if (NS_FAILED(rv)) { + MOZ_DIAGNOSTIC_ASSERT(false, "LoadInfoArgsToLoadInfo failed"); + return IPC_OK(); + } + + nsCOMPtr<nsIChannel> newChannel; + MOZ_ASSERT((aArgs.loadStateInternalLoadFlags() & + nsDocShell::InternalLoad::INTERNAL_LOAD_FLAGS_IS_SRCDOC) || + aArgs.srcdocData().IsVoid()); + rv = nsDocShell::CreateRealChannelForDocument( + getter_AddRefs(newChannel), aArgs.uri(), loadInfo, nullptr, + aArgs.newLoadFlags(), aArgs.srcdocData(), aArgs.baseUri()); + + if (RefPtr<HttpBaseChannel> httpChannel = do_QueryObject(newChannel)) { + httpChannel->SetEarlyHints(std::move(aArgs.earlyHints())); + httpChannel->SetEarlyHintLinkType(aArgs.earlyHintLinkType()); + } + + // This is used to report any errors back to the parent by calling + // CrossProcessRedirectFinished. + RefPtr<HttpChannelChild> httpChild = do_QueryObject(newChannel); + auto resolve = [=](const nsresult& aRv) { + nsresult rv = aRv; + if (httpChild) { + rv = httpChild->CrossProcessRedirectFinished(rv); + } + aResolve(rv); + }; + auto scopeExit = MakeScopeExit([&]() { resolve(rv); }); + + 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(); + } + + if (aArgs.init()) { + HttpBaseChannel::ReplacementChannelConfig config(std::move(*aArgs.init())); + HttpBaseChannel::ConfigureReplacementChannel( + newChannel, config, + HttpBaseChannel::ReplacementReason::DocumentChannel); + } + + if (aArgs.contentDisposition()) { + newChannel->SetContentDisposition(*aArgs.contentDisposition()); + } + + if (aArgs.contentDispositionFilename()) { + newChannel->SetContentDispositionFilename( + *aArgs.contentDispositionFilename()); + } + + if (nsCOMPtr<nsIChildChannel> childChannel = do_QueryInterface(newChannel)) { + // Connect to the parent if this is a remote channel. If it's entirely + // handled locally, then we'll call AsyncOpen from the docshell when + // we complete the setup + rv = childChannel->ConnectParent( + aArgs.registrarId()); // creates parent channel + if (NS_FAILED(rv)) { + return IPC_OK(); + } + } + + // We need to copy the property bag before signaling that the channel + // is ready so that the nsDocShell can retrieve the history data when called. + if (nsCOMPtr<nsIWritablePropertyBag> bag = do_QueryInterface(newChannel)) { + nsHashPropertyBag::CopyFrom(bag, aArgs.properties()); + } + + RefPtr<nsDocShellLoadState> loadState; + rv = nsDocShellLoadState::CreateFromPendingChannel( + newChannel, aArgs.loadIdentifier(), aArgs.registrarId(), + getter_AddRefs(loadState)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return IPC_OK(); + } + loadState->SetLoadFlags(aArgs.loadStateExternalLoadFlags()); + loadState->SetInternalLoadFlags(aArgs.loadStateInternalLoadFlags()); + if (IsValidLoadType(aArgs.loadStateLoadType())) { + loadState->SetLoadType(aArgs.loadStateLoadType()); + } + + if (aArgs.loadingSessionHistoryInfo().isSome()) { + loadState->SetLoadingSessionHistoryInfo( + aArgs.loadingSessionHistoryInfo().ref()); + } + if (aArgs.originalUriString().isSome()) { + loadState->SetOriginalURIString(aArgs.originalUriString().ref()); + } + + RefPtr<ChildProcessChannelListener> processListener = + ChildProcessChannelListener::GetSingleton(); + // The listener will call completeRedirectSetup or asyncOpen on the channel. + processListener->OnChannelReady(loadState, aArgs.loadIdentifier(), + std::move(aEndpoints), aArgs.timing(), + std::move(resolve)); + scopeExit.release(); + + // scopeExit will call CrossProcessRedirectFinished(rv) here + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvStartDelayedAutoplayMediaComponents( + const MaybeDiscarded<BrowsingContext>& aContext) { + if (NS_WARN_IF(aContext.IsNullOrDiscarded())) { + return IPC_OK(); + } + + aContext.get()->StartDelayedAutoplayMediaComponents(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvUpdateMediaControlAction( + const MaybeDiscarded<BrowsingContext>& aContext, + const MediaControlAction& aAction) { + if (NS_WARN_IF(aContext.IsNullOrDiscarded())) { + return IPC_OK(); + } + + ContentMediaControlKeyHandler::HandleMediaControlAction(aContext.get(), + aAction); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvOnAllowAccessFor( + const MaybeDiscarded<BrowsingContext>& aContext, + const nsCString& aTrackingOrigin, uint32_t aCookieBehavior, + const ContentBlockingNotifier::StorageAccessPermissionGrantedReason& + aReason) { + MOZ_ASSERT(!aContext.IsNull(), "Browsing context cannot be null"); + + StorageAccessAPIHelper::OnAllowAccessFor( + aContext.GetMaybeDiscarded(), aTrackingOrigin, aCookieBehavior, aReason); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvOnContentBlockingDecision( + const MaybeDiscarded<BrowsingContext>& aContext, + const ContentBlockingNotifier::BlockingDecision& aDecision, + uint32_t aRejectedReason) { + MOZ_ASSERT(!aContext.IsNull(), "Browsing context cannot be null"); + + nsCOMPtr<nsPIDOMWindowOuter> outer = aContext.get()->GetDOMWindow(); + if (!outer) { + MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to a context without a outer " + "window")); + return IPC_OK(); + } + + nsCOMPtr<nsPIDOMWindowInner> inner = outer->GetCurrentInnerWindow(); + if (!inner) { + MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to a context without a inner " + "window")); + return IPC_OK(); + } + + ContentBlockingNotifier::OnDecision(inner, aDecision, aRejectedReason); + return IPC_OK(); +} + +#ifdef NIGHTLY_BUILD +void ContentChild::OnChannelReceivedMessage(const Message& aMsg) { + if (nsContentUtils::IsMessageInputEvent(aMsg)) { + mPendingInputEvents++; + } +} + +PContentChild::Result ContentChild::OnMessageReceived(const Message& aMsg) { + if (nsContentUtils::IsMessageInputEvent(aMsg)) { + DebugOnly<uint32_t> prevEvts = mPendingInputEvents--; + MOZ_ASSERT(prevEvts > 0); + } + + return PContentChild::OnMessageReceived(aMsg); +} + +PContentChild::Result ContentChild::OnMessageReceived( + const Message& aMsg, UniquePtr<Message>& aReply) { + return PContentChild::OnMessageReceived(aMsg, aReply); +} +#endif + +mozilla::ipc::IPCResult ContentChild::RecvCreateBrowsingContext( + uint64_t aGroupId, BrowsingContext::IPCInitializer&& aInit) { + // We can't already have a BrowsingContext with this ID. + if (RefPtr<BrowsingContext> existing = BrowsingContext::Get(aInit.mId)) { + return IPC_FAIL(this, "Browsing context already exists"); + } + + RefPtr<WindowContext> parent = WindowContext::GetById(aInit.mParentId); + if (!parent && aInit.mParentId != 0) { + // Handle this case by ignoring the request, as parent must be in the + // process of being discarded. + // In the future it would be nice to avoid sending this message to the child + // at all. + NS_WARNING("Attempt to attach BrowsingContext to discarded parent"); + return IPC_OK(); + } + + RefPtr<BrowsingContextGroup> group = + BrowsingContextGroup::GetOrCreate(aGroupId); + return BrowsingContext::CreateFromIPC(std::move(aInit), group, nullptr); +} + +mozilla::ipc::IPCResult ContentChild::RecvDiscardBrowsingContext( + const MaybeDiscarded<BrowsingContext>& aContext, bool aDoDiscard, + DiscardBrowsingContextResolver&& aResolve) { + if (BrowsingContext* context = aContext.GetMaybeDiscarded()) { + if (aDoDiscard && !context->IsDiscarded()) { + context->Detach(/* aFromIPC */ true); + } + context->AddDiscardListener(aResolve); + return IPC_OK(); + } + + // Immediately resolve the promise, as we've received the message. This will + // allow the parent process to discard references to this BC. + aResolve(true); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvRegisterBrowsingContextGroup( + uint64_t aGroupId, nsTArray<SyncedContextInitializer>&& aInits) { + RefPtr<BrowsingContextGroup> group = + BrowsingContextGroup::GetOrCreate(aGroupId); + + // Each of the initializers in aInits is sorted in pre-order, so our parent + // should always be available before the element itself. + for (auto& initUnion : aInits) { + switch (initUnion.type()) { + case SyncedContextInitializer::TBrowsingContextInitializer: { + auto& init = initUnion.get_BrowsingContextInitializer(); +#ifdef DEBUG + RefPtr<BrowsingContext> existing = BrowsingContext::Get(init.mId); + MOZ_ASSERT(!existing, "BrowsingContext must not exist yet!"); + + RefPtr<WindowContext> parent = init.GetParent(); + MOZ_ASSERT_IF(parent, parent->Group() == group); +#endif + + BrowsingContext::CreateFromIPC(std::move(init), group, nullptr); + break; + } + case SyncedContextInitializer::TWindowContextInitializer: { + auto& init = initUnion.get_WindowContextInitializer(); +#ifdef DEBUG + RefPtr<WindowContext> existing = + WindowContext::GetById(init.mInnerWindowId); + MOZ_ASSERT(!existing, "WindowContext must not exist yet!"); + RefPtr<BrowsingContext> parent = + BrowsingContext::Get(init.mBrowsingContextId); + MOZ_ASSERT(parent && parent->Group() == group); +#endif + + WindowContext::CreateFromIPC(std::move(init)); + break; + }; + default: + MOZ_ASSERT_UNREACHABLE(); + } + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvDestroyBrowsingContextGroup( + uint64_t aGroupId) { + if (RefPtr<BrowsingContextGroup> group = + BrowsingContextGroup::GetExisting(aGroupId)) { + group->ChildDestroy(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvWindowClose( + const MaybeDiscarded<BrowsingContext>& aContext, bool aTrustedCaller) { + if (aContext.IsNullOrDiscarded()) { + MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + + nsCOMPtr<nsPIDOMWindowOuter> window = aContext.get()->GetDOMWindow(); + if (!window) { + MOZ_LOG( + BrowsingContext::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to a context without a window")); + return IPC_OK(); + } + + // Call `GetDocument()` to force the document and its inner window to be + // created, as it would be forced to be created if this call was being + // performed in-process. + if (NS_WARN_IF(!aContext.get()->GetDocument())) { + MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to a context but document " + "creation failed")); + return IPC_OK(); + } + + nsGlobalWindowOuter::Cast(window)->CloseOuter(aTrustedCaller); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvWindowFocus( + const MaybeDiscarded<BrowsingContext>& aContext, CallerType aCallerType, + uint64_t aActionId) { + if (aContext.IsNullOrDiscarded()) { + MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + + nsCOMPtr<nsPIDOMWindowOuter> window = aContext.get()->GetDOMWindow(); + if (!window) { + MOZ_LOG( + BrowsingContext::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to a context without a window")); + return IPC_OK(); + } + + // Call `GetDocument()` to force the document and its inner window to be + // created, as it would be forced to be created if this call was being + // performed in-process. + if (NS_WARN_IF(!aContext.get()->GetDocument())) { + MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to a context but document " + "creation failed")); + return IPC_OK(); + } + + nsGlobalWindowOuter::Cast(window)->FocusOuter( + aCallerType, /* aFromOtherProcess */ true, aActionId); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvWindowBlur( + const MaybeDiscarded<BrowsingContext>& aContext, CallerType aCallerType) { + if (aContext.IsNullOrDiscarded()) { + MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + + nsCOMPtr<nsPIDOMWindowOuter> window = aContext.get()->GetDOMWindow(); + if (!window) { + MOZ_LOG( + BrowsingContext::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to a context without a window")); + return IPC_OK(); + } + + // Call `GetDocument()` to force the document and its inner window to be + // created, as it would be forced to be created if this call was being + // performed in-process. + if (NS_WARN_IF(!aContext.get()->GetDocument())) { + MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to a context but document " + "creation failed")); + return IPC_OK(); + } + + nsGlobalWindowOuter::Cast(window)->BlurOuter(aCallerType); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvRaiseWindow( + const MaybeDiscarded<BrowsingContext>& aContext, CallerType aCallerType, + uint64_t aActionId) { + if (aContext.IsNullOrDiscarded()) { + MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + + nsCOMPtr<nsPIDOMWindowOuter> window = aContext.get()->GetDOMWindow(); + if (!window) { + MOZ_LOG( + BrowsingContext::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to a context without a window")); + return IPC_OK(); + } + + if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) { + fm->RaiseWindow(window, aCallerType, aActionId); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvAdjustWindowFocus( + const MaybeDiscarded<BrowsingContext>& aContext, bool aIsVisible, + uint64_t aActionId) { + if (aContext.IsNullOrDiscarded()) { + MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + + if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) { + RefPtr<BrowsingContext> bc = aContext.get(); + fm->AdjustInProcessWindowFocus(bc, false, aIsVisible, aActionId); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvClearFocus( + const MaybeDiscarded<BrowsingContext>& aContext) { + if (aContext.IsNullOrDiscarded()) { + MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + + nsCOMPtr<nsPIDOMWindowOuter> window = aContext.get()->GetDOMWindow(); + if (!window) { + MOZ_LOG( + BrowsingContext::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to a context without a window")); + return IPC_OK(); + } + + if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) { + fm->ClearFocus(window); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvSetFocusedBrowsingContext( + const MaybeDiscarded<BrowsingContext>& aContext, uint64_t aActionId) { + if (aContext.IsNullOrDiscarded()) { + MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + fm->SetFocusedBrowsingContextFromOtherProcess(aContext.get(), aActionId); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvSetActiveBrowsingContext( + const MaybeDiscarded<BrowsingContext>& aContext, uint64_t aActionId) { + if (aContext.IsNullOrDiscarded()) { + MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + fm->SetActiveBrowsingContextFromOtherProcess(aContext.get(), aActionId); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvAbortOrientationPendingPromises( + const MaybeDiscarded<BrowsingContext>& aContext) { + if (aContext.IsNullOrDiscarded()) { + MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + + dom::ScreenOrientation::AbortInProcessOrientationPromises(aContext.get()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvUnsetActiveBrowsingContext( + const MaybeDiscarded<BrowsingContext>& aContext, uint64_t aActionId) { + if (aContext.IsNullOrDiscarded()) { + MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + fm->UnsetActiveBrowsingContextFromOtherProcess(aContext.get(), aActionId); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvSetFocusedElement( + const MaybeDiscarded<BrowsingContext>& aContext, bool aNeedsFocus) { + if (aContext.IsNullOrDiscarded()) { + MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + + nsCOMPtr<nsPIDOMWindowOuter> window = aContext.get()->GetDOMWindow(); + if (!window) { + MOZ_LOG( + BrowsingContext::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to a context without a window")); + return IPC_OK(); + } + + window->SetFocusedElement(nullptr, 0, aNeedsFocus); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvFinalizeFocusOuter( + const MaybeDiscarded<BrowsingContext>& aContext, bool aCanFocus, + CallerType aCallerType) { + if (aContext.IsNullOrDiscarded()) { + MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + + if (Element* frame = aContext.get()->GetEmbedderElement()) { + nsContentUtils::RequestFrameFocus(*frame, aCanFocus, aCallerType); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvBlurToChild( + const MaybeDiscarded<BrowsingContext>& aFocusedBrowsingContext, + const MaybeDiscarded<BrowsingContext>& aBrowsingContextToClear, + const MaybeDiscarded<BrowsingContext>& aAncestorBrowsingContextToFocus, + bool aIsLeavingDocument, bool aAdjustWidget, uint64_t aActionId) { + if (aFocusedBrowsingContext.IsNullOrDiscarded()) { + MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + + RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager(); + if (MOZ_UNLIKELY(!fm)) { + return IPC_OK(); + } + + RefPtr<BrowsingContext> toClear = aBrowsingContextToClear.IsDiscarded() + ? nullptr + : aBrowsingContextToClear.get(); + RefPtr<BrowsingContext> toFocus = + aAncestorBrowsingContextToFocus.IsDiscarded() + ? nullptr + : aAncestorBrowsingContextToFocus.get(); + + RefPtr<BrowsingContext> focusedBrowsingContext = + aFocusedBrowsingContext.get(); + + fm->BlurFromOtherProcess(focusedBrowsingContext, toClear, toFocus, + aIsLeavingDocument, aAdjustWidget, aActionId); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvSetupFocusedAndActive( + const MaybeDiscarded<BrowsingContext>& aFocusedBrowsingContext, + uint64_t aActionIdForFocused, + const MaybeDiscarded<BrowsingContext>& aActiveBrowsingContext, + uint64_t aActionIdForActive) { + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + if (!aActiveBrowsingContext.IsNullOrDiscarded()) { + fm->SetActiveBrowsingContextFromOtherProcess(aActiveBrowsingContext.get(), + aActionIdForActive); + } + if (!aFocusedBrowsingContext.IsNullOrDiscarded()) { + fm->SetFocusedBrowsingContextFromOtherProcess( + aFocusedBrowsingContext.get(), aActionIdForFocused); + } + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvReviseActiveBrowsingContext( + uint64_t aOldActionId, + const MaybeDiscarded<BrowsingContext>& aActiveBrowsingContext, + uint64_t aNewActionId) { + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm && !aActiveBrowsingContext.IsNullOrDiscarded()) { + fm->ReviseActiveBrowsingContext(aOldActionId, aActiveBrowsingContext.get(), + aNewActionId); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvReviseFocusedBrowsingContext( + uint64_t aOldActionId, + const MaybeDiscarded<BrowsingContext>& aFocusedBrowsingContext, + uint64_t aNewActionId) { + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm && !aFocusedBrowsingContext.IsNullOrDiscarded()) { + fm->ReviseFocusedBrowsingContext( + aOldActionId, aFocusedBrowsingContext.get(), aNewActionId); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvMaybeExitFullscreen( + const MaybeDiscarded<BrowsingContext>& aContext) { + if (aContext.IsNullOrDiscarded()) { + MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + + nsIDocShell* shell = aContext.get()->GetDocShell(); + if (!shell) { + return IPC_OK(); + } + + Document* doc = shell->GetDocument(); + if (doc && doc->GetFullscreenElement()) { + Document::AsyncExitFullscreen(doc); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvWindowPostMessage( + const MaybeDiscarded<BrowsingContext>& aContext, + const ClonedOrErrorMessageData& aMessage, const PostMessageData& aData) { + if (aContext.IsNullOrDiscarded()) { + MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + + RefPtr<nsGlobalWindowOuter> window = + nsGlobalWindowOuter::Cast(aContext.get()->GetDOMWindow()); + if (!window) { + MOZ_LOG( + BrowsingContext::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to a context without a window")); + return IPC_OK(); + } + + nsCOMPtr<nsIPrincipal> providedPrincipal; + if (!window->GetPrincipalForPostMessage( + aData.targetOrigin(), aData.targetOriginURI(), + aData.callerPrincipal(), *aData.subjectPrincipal(), + getter_AddRefs(providedPrincipal))) { + return IPC_OK(); + } + + // Call `GetDocument()` to force the document and its inner window to be + // created, as it would be forced to be created if this call was being + // performed in-process. + if (NS_WARN_IF(!aContext.get()->GetDocument())) { + MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug, + ("ChildIPC: Trying to send a message to a context but document " + "creation failed")); + return IPC_OK(); + } + + // It's OK if `sourceBc` has already been discarded, so long as we can + // continue to wrap it. + RefPtr<BrowsingContext> sourceBc = aData.source().GetMaybeDiscarded(); + + // Create and asynchronously dispatch a runnable which will handle actual DOM + // event creation and dispatch. + RefPtr<PostMessageEvent> event = + new PostMessageEvent(sourceBc, aData.origin(), window, providedPrincipal, + aData.innerWindowId(), aData.callerURI(), + aData.scriptLocation(), aData.isFromPrivateWindow()); + event->UnpackFrom(aMessage); + + event->DispatchToTargetThread(IgnoredErrorResult()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvCommitBrowsingContextTransaction( + const MaybeDiscarded<BrowsingContext>& aContext, + BrowsingContext::BaseTransaction&& aTransaction, uint64_t aEpoch) { + return aTransaction.CommitFromIPC(aContext, aEpoch, this); +} + +mozilla::ipc::IPCResult ContentChild::RecvCommitWindowContextTransaction( + const MaybeDiscarded<WindowContext>& aContext, + WindowContext::BaseTransaction&& aTransaction, uint64_t aEpoch) { + return aTransaction.CommitFromIPC(aContext, aEpoch, this); +} + +mozilla::ipc::IPCResult ContentChild::RecvCreateWindowContext( + WindowContext::IPCInitializer&& aInit) { + RefPtr<BrowsingContext> bc = BrowsingContext::Get(aInit.mBrowsingContextId); + if (!bc) { + // Handle this case by ignoring the request, as bc must be in the process of + // being discarded. + // In the future it would be nice to avoid sending this message to the child + // at all. + NS_WARNING("Attempt to attach WindowContext to discarded parent"); + return IPC_OK(); + } + + WindowContext::CreateFromIPC(std::move(aInit)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvDiscardWindowContext( + uint64_t aContextId, DiscardWindowContextResolver&& aResolve) { + // Resolve immediately to acknowledge call + aResolve(true); + + RefPtr<WindowContext> window = WindowContext::GetById(aContextId); + if (NS_WARN_IF(!window) || NS_WARN_IF(window->IsDiscarded())) { + return IPC_OK(); + } + + window->Discard(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvScriptError( + const nsString& aMessage, const nsString& aSourceName, + const nsString& aSourceLine, const uint32_t& aLineNumber, + const uint32_t& aColNumber, const uint32_t& aFlags, + const nsCString& aCategory, const bool& aFromPrivateWindow, + const uint64_t& aInnerWindowId, const bool& aFromChromeContext) { + nsresult rv = NS_OK; + nsCOMPtr<nsIConsoleService> consoleService = + do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, IPC_FAIL(this, "Failed to get console service")); + + nsCOMPtr<nsIScriptError> scriptError( + do_CreateInstance(NS_SCRIPTERROR_CONTRACTID)); + NS_ENSURE_TRUE(scriptError, + IPC_FAIL(this, "Failed to construct nsIScriptError")); + + scriptError->InitWithWindowID(aMessage, aSourceName, aSourceLine, aLineNumber, + aColNumber, aFlags, aCategory, aInnerWindowId, + aFromChromeContext); + rv = consoleService->LogMessage(scriptError); + NS_ENSURE_SUCCESS(rv, IPC_FAIL(this, "Failed to log script error")); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvReportFrameTimingData( + const LoadInfoArgs& loadInfoArgs, const nsString& entryName, + const nsString& initiatorType, UniquePtr<PerformanceTimingData>&& aData) { + if (!aData) { + return IPC_FAIL(this, "aData should not be null"); + } + + nsCOMPtr<nsILoadInfo> loadInfo; + nsresult rv = mozilla::ipc::LoadInfoArgsToLoadInfo( + loadInfoArgs, NOT_REMOTE_TYPE, getter_AddRefs(loadInfo)); + if (NS_FAILED(rv)) { + MOZ_DIAGNOSTIC_ASSERT(false, "LoadInfoArgsToLoadInfo failed"); + return IPC_OK(); + } + + // It is important to call LoadInfo::GetPerformanceStorage instead of simply + // getting the performance object via the innerWindowID in order to perform + // necessary cross origin checks. + if (PerformanceStorage* storage = loadInfo->GetPerformanceStorage()) { + storage->AddEntry(entryName, initiatorType, std::move(aData)); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvLoadURI( + const MaybeDiscarded<BrowsingContext>& aContext, + nsDocShellLoadState* aLoadState, bool aSetNavigating, + LoadURIResolver&& aResolve) { + auto resolveOnExit = MakeScopeExit([&] { aResolve(true); }); + + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + RefPtr<BrowsingContext> context = aContext.get(); + if (!context->IsInProcess()) { + // The DocShell has been torn down or the BrowsingContext has changed + // process in the middle of the load request. There's not much we can do at + // this point, so just give up. + return IPC_OK(); + } + + context->LoadURI(aLoadState, aSetNavigating); + + nsCOMPtr<nsPIDOMWindowOuter> window = context->GetDOMWindow(); + BrowserChild* bc = BrowserChild::GetFrom(window); + if (bc) { + bc->NotifyNavigationFinished(); + } + +#ifdef MOZ_CRASHREPORTER + if (CrashReporter::GetEnabled()) { + nsCOMPtr<nsIURI> annotationURI; + + nsresult rv = NS_MutateURI(aLoadState->URI()) + .SetUserPass(""_ns) + .Finalize(annotationURI); + + if (NS_FAILED(rv)) { + // Ignore failures on about: URIs. + annotationURI = aLoadState->URI(); + } + + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::URL, + annotationURI->GetSpecOrDefault()); + } +#endif + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvInternalLoad( + nsDocShellLoadState* aLoadState) { + if (!aLoadState->Target().IsEmpty() || + aLoadState->TargetBrowsingContext().IsNull()) { + return IPC_FAIL(this, "must already be retargeted"); + } + if (aLoadState->TargetBrowsingContext().IsDiscarded()) { + return IPC_OK(); + } + RefPtr<BrowsingContext> context = aLoadState->TargetBrowsingContext().get(); + + context->InternalLoad(aLoadState); + +#ifdef MOZ_CRASHREPORTER + if (CrashReporter::GetEnabled()) { + nsCOMPtr<nsIURI> annotationURI; + + nsresult rv = NS_MutateURI(aLoadState->URI()) + .SetUserPass(""_ns) + .Finalize(annotationURI); + + if (NS_FAILED(rv)) { + // Ignore failures on about: URIs. + annotationURI = aLoadState->URI(); + } + + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::URL, + annotationURI->GetSpecOrDefault()); + } +#endif + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvDisplayLoadError( + const MaybeDiscarded<BrowsingContext>& aContext, const nsAString& aURI) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + RefPtr<BrowsingContext> context = aContext.get(); + + context->DisplayLoadError(aURI); + + nsCOMPtr<nsPIDOMWindowOuter> window = context->GetDOMWindow(); + BrowserChild* bc = BrowserChild::GetFrom(window); + if (bc) { + bc->NotifyNavigationFinished(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvHistoryCommitIndexAndLength( + const MaybeDiscarded<BrowsingContext>& aContext, const uint32_t& aIndex, + const uint32_t& aLength, const nsID& aChangeID) { + if (!aContext.IsNullOrDiscarded()) { + ChildSHistory* shistory = aContext.get()->GetChildSessionHistory(); + if (shistory) { + shistory->SetIndexAndLength(aIndex, aLength, aChangeID); + } + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvGetLayoutHistoryState( + const MaybeDiscarded<BrowsingContext>& aContext, + GetLayoutHistoryStateResolver&& aResolver) { + nsCOMPtr<nsILayoutHistoryState> state; + nsIDocShell* docShell; + mozilla::Maybe<mozilla::dom::Wireframe> wireframe; + if (!aContext.IsNullOrDiscarded() && + (docShell = aContext.get()->GetDocShell())) { + docShell->PersistLayoutHistoryState(); + docShell->GetLayoutHistoryState(getter_AddRefs(state)); + wireframe = static_cast<nsDocShell*>(docShell)->GetWireframe(); + } + aResolver( + std::tuple<nsILayoutHistoryState*, const mozilla::Maybe<Wireframe>&>( + state, wireframe)); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvDispatchLocationChangeEvent( + const MaybeDiscarded<BrowsingContext>& aContext) { + if (!aContext.IsNullOrDiscarded() && aContext.get()->GetDocShell()) { + aContext.get()->GetDocShell()->DispatchLocationChangeEvent(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvDispatchBeforeUnloadToSubtree( + const MaybeDiscarded<BrowsingContext>& aStartingAt, + DispatchBeforeUnloadToSubtreeResolver&& aResolver) { + if (aStartingAt.IsNullOrDiscarded()) { + aResolver(nsIDocumentViewer::eAllowNavigation); + } else { + DispatchBeforeUnloadToSubtree(aStartingAt.get(), std::move(aResolver)); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvDecoderSupportedMimeTypes( + nsTArray<nsCString>&& aSupportedTypes) { +#ifdef MOZ_WIDGET_ANDROID + AndroidDecoderModule::SetSupportedMimeTypes(std::move(aSupportedTypes)); +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvInitNextGenLocalStorageEnabled( + const bool& aEnabled) { + mozilla::dom::RecvInitNextGenLocalStorageEnabled(aEnabled); + + return IPC_OK(); +} + +/* static */ void ContentChild::DispatchBeforeUnloadToSubtree( + BrowsingContext* aStartingAt, + const DispatchBeforeUnloadToSubtreeResolver& aResolver) { + bool resolved = false; + + aStartingAt->PreOrderWalk([&](dom::BrowsingContext* aBC) { + if (aBC->GetDocShell()) { + nsCOMPtr<nsIDocumentViewer> viewer; + aBC->GetDocShell()->GetDocViewer(getter_AddRefs(viewer)); + if (viewer && + viewer->DispatchBeforeUnload() == + nsIDocumentViewer::eRequestBlockNavigation && + !resolved) { + // Send our response as soon as we find any blocker, so that we can show + // the permit unload prompt as soon as possible, without giving + // subsequent handlers a chance to delay it. + aResolver(nsIDocumentViewer::eRequestBlockNavigation); + resolved = true; + } + } + }); + + if (!resolved) { + aResolver(nsIDocumentViewer::eAllowNavigation); + } +} + +mozilla::ipc::IPCResult ContentChild::RecvGoBack( + const MaybeDiscarded<BrowsingContext>& aContext, + const Maybe<int32_t>& aCancelContentJSEpoch, bool aRequireUserInteraction, + bool aUserActivation) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + BrowsingContext* bc = aContext.get(); + + if (RefPtr<nsDocShell> docShell = nsDocShell::Cast(bc->GetDocShell())) { + if (aCancelContentJSEpoch) { + docShell->SetCancelContentJSEpoch(*aCancelContentJSEpoch); + } + docShell->GoBack(aRequireUserInteraction, aUserActivation); + + if (BrowserChild* browserChild = BrowserChild::GetFrom(docShell)) { + browserChild->NotifyNavigationFinished(); + } + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvGoForward( + const MaybeDiscarded<BrowsingContext>& aContext, + const Maybe<int32_t>& aCancelContentJSEpoch, bool aRequireUserInteraction, + bool aUserActivation) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + BrowsingContext* bc = aContext.get(); + + if (RefPtr<nsDocShell> docShell = nsDocShell::Cast(bc->GetDocShell())) { + if (aCancelContentJSEpoch) { + docShell->SetCancelContentJSEpoch(*aCancelContentJSEpoch); + } + docShell->GoForward(aRequireUserInteraction, aUserActivation); + + if (BrowserChild* browserChild = BrowserChild::GetFrom(docShell)) { + browserChild->NotifyNavigationFinished(); + } + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvGoToIndex( + const MaybeDiscarded<BrowsingContext>& aContext, const int32_t& aIndex, + const Maybe<int32_t>& aCancelContentJSEpoch, bool aUserActivation) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + BrowsingContext* bc = aContext.get(); + + if (RefPtr<nsDocShell> docShell = nsDocShell::Cast(bc->GetDocShell())) { + if (aCancelContentJSEpoch) { + docShell->SetCancelContentJSEpoch(*aCancelContentJSEpoch); + } + docShell->GotoIndex(aIndex, aUserActivation); + + if (BrowserChild* browserChild = BrowserChild::GetFrom(docShell)) { + browserChild->NotifyNavigationFinished(); + } + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvReload( + const MaybeDiscarded<BrowsingContext>& aContext, + const uint32_t aReloadFlags) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + BrowsingContext* bc = aContext.get(); + + if (RefPtr<nsDocShell> docShell = nsDocShell::Cast(bc->GetDocShell())) { + docShell->Reload(aReloadFlags); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvStopLoad( + const MaybeDiscarded<BrowsingContext>& aContext, + const uint32_t aStopFlags) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + BrowsingContext* bc = aContext.get(); + + if (auto* docShell = nsDocShell::Cast(bc->GetDocShell())) { + docShell->Stop(aStopFlags); + } + + return IPC_OK(); +} + +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) +mozilla::ipc::IPCResult ContentChild::RecvInitSandboxTesting( + Endpoint<PSandboxTestingChild>&& aEndpoint) { + if (!SandboxTestingChild::Initialize(std::move(aEndpoint))) { + return IPC_FAIL( + this, "InitSandboxTesting failed to initialise the child process."); + } + return IPC_OK(); +} +#endif + +NS_IMETHODIMP ContentChild::GetChildID(uint64_t* aOut) { + *aOut = mID; + return NS_OK; +} + +NS_IMETHODIMP ContentChild::GetActor(const nsACString& aName, JSContext* aCx, + JSProcessActorChild** retval) { + ErrorResult error; + RefPtr<JSProcessActorChild> actor = + JSActorManager::GetActor(aCx, aName, error) + .downcast<JSProcessActorChild>(); + if (error.MaybeSetPendingException(aCx)) { + return NS_ERROR_FAILURE; + } + actor.forget(retval); + return NS_OK; +} + +NS_IMETHODIMP ContentChild::GetExistingActor(const nsACString& aName, + JSProcessActorChild** retval) { + RefPtr<JSProcessActorChild> actor = + JSActorManager::GetExistingActor(aName).downcast<JSProcessActorChild>(); + actor.forget(retval); + return NS_OK; +} + +already_AddRefed<JSActor> ContentChild::InitJSActor( + JS::Handle<JSObject*> aMaybeActor, const nsACString& aName, + ErrorResult& aRv) { + RefPtr<JSProcessActorChild> actor; + if (aMaybeActor.get()) { + aRv = UNWRAP_OBJECT(JSProcessActorChild, aMaybeActor.get(), actor); + if (aRv.Failed()) { + return nullptr; + } + } else { + actor = new JSProcessActorChild(); + } + + MOZ_RELEASE_ASSERT(!actor->Manager(), + "mManager was already initialized once!"); + actor->Init(aName, this); + return actor.forget(); +} + +IPCResult ContentChild::RecvRawMessage(const JSActorMessageMeta& aMeta, + const Maybe<ClonedMessageData>& aData, + const Maybe<ClonedMessageData>& aStack) { + Maybe<StructuredCloneData> data; + if (aData) { + data.emplace(); + data->BorrowFromClonedMessageData(*aData); + } + Maybe<StructuredCloneData> stack; + if (aStack) { + stack.emplace(); + stack->BorrowFromClonedMessageData(*aStack); + } + ReceiveRawMessage(aMeta, std::move(data), std::move(stack)); + return IPC_OK(); +} + +NS_IMETHODIMP ContentChild::GetCanSend(bool* aCanSend) { + *aCanSend = CanSend(); + return NS_OK; +} + +ContentChild* ContentChild::AsContentChild() { return this; } + +JSActorManager* ContentChild::AsJSActorManager() { return this; } + +IPCResult ContentChild::RecvFlushFOGData(FlushFOGDataResolver&& aResolver) { + glean::FlushFOGData(std::move(aResolver)); + return IPC_OK(); +} + +IPCResult ContentChild::RecvUpdateMediaCodecsSupported( + RemoteDecodeIn aLocation, const media::MediaCodecsSupported& aSupported) { + RemoteDecoderManagerChild::SetSupported(aLocation, aSupported); + + return IPC_OK(); +} + +void ContentChild::ConfigureThreadPerformanceHints( + const hal::ProcessPriority& aPriority) { + if (aPriority >= hal::PROCESS_PRIORITY_FOREGROUND) { + static bool canUsePerformanceHintSession = true; + if (!mPerformanceHintSession && canUsePerformanceHintSession) { + nsTArray<PlatformThreadHandle> threads; + Servo_ThreadPool_GetThreadHandles(&threads); +#ifdef XP_WIN + threads.AppendElement(GetCurrentThread()); +#else + threads.AppendElement(pthread_self()); +#endif + + mPerformanceHintSession = hal::CreatePerformanceHintSession( + threads, GetPerformanceHintTarget(TimeDuration::FromMilliseconds( + nsRefreshDriver::DefaultInterval()))); + + // Avoid repeatedly attempting to create a session if it is not + // supported. + canUsePerformanceHintSession = mPerformanceHintSession != nullptr; + } + +#ifdef MOZ_WIDGET_ANDROID + // On Android if we are unable to use PerformanceHintManager then fall back + // to setting the stylo threads' affinities to the performant cores. Android + // automatically sets each thread's affinity to all cores when a process is + // foregrounded, and to a subset of cores when the process is backgrounded. + // We must therefore override this each time the process is foregrounded, + // but we don't have to do anything when backgrounded. + if (!mPerformanceHintSession) { + if (const auto& cpuInfo = hal::GetHeterogeneousCpuInfo()) { + // If CPUs are homogeneous there is no point setting affinity. + if (cpuInfo->mBigCpus.Count() != cpuInfo->mTotalNumCpus) { + BitSet<hal::HeterogeneousCpuInfo::MAX_CPUS> cpus = cpuInfo->mBigCpus; + if (cpus.Count() < 2) { + // From testing on a variety of devices it appears using only the + // big cores gives best performance when there are 2 or more big + // cores. If there are fewer than 2 big cores then additionally + // using the medium cores performs better. + cpus |= cpuInfo->mMediumCpus; + } + + static_assert( + hal::HeterogeneousCpuInfo::MAX_CPUS <= CPU_SETSIZE, + "HeterogeneousCpuInfo::MAX_CPUS is too large for CPU_SETSIZE"); + + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + for (size_t i = 0; i < cpuInfo->mTotalNumCpus; i++) { + if (cpus.Test(i)) { + CPU_SET(i, &cpuset); + } + } + + // Only set the affinity for the stylo threads, not the main thread. + // Testing showed no difference in performance between the two + // options, and as newly spawned threads automatically inherit their + // parent's affinity mask there may be unintended consequences to + // setting the main thread affinity. + nsTArray<PlatformThreadHandle> threads; + Servo_ThreadPool_GetThreadHandles(&threads); + for (pthread_t thread : threads) { + int ret = sched_setaffinity(pthread_gettid_np(thread), + sizeof(cpu_set_t), &cpuset); + // Occasionally sched_setaffinity fails, presumably due to a race + // between us receiving the process foreground signal and whatever + // is responsible for restricting which processes can use certain + // cores. Trying again in a runnable seems to do the trick, but if + // that still fails it's not the end of the world. + if (ret < 0) { + NS_DispatchToCurrentThread(NS_NewRunnableFunction( + "ContentChild::ConfigureThreadPerformanceHints", + [threads = std::move(threads), cpuset] { + for (pthread_t thread : threads) { + sched_setaffinity(pthread_gettid_np(thread), + sizeof(cpu_set_t), &cpuset); + } + })); + break; + } + } + } + } + } +#endif + } else { + mPerformanceHintSession = nullptr; + } +} + +} // namespace dom + +#if defined(__OpenBSD__) && defined(MOZ_SANDBOX) + +static LazyLogModule sPledgeLog("OpenBSDSandbox"); + +NS_IMETHODIMP +OpenBSDFindPledgeUnveilFilePath(const char* file, nsACString& result) { + struct stat st; + + // Allow overriding files in /etc/$MOZ_APP_NAME + result.Assign(nsPrintfCString("/etc/%s/%s", MOZ_APP_NAME, file)); + if (stat(PromiseFlatCString(result).get(), &st) == 0) { + return NS_OK; + } + + // Or look in the system default directory + result.Assign(nsPrintfCString( + "/usr/local/lib/%s/browser/defaults/preferences/%s", MOZ_APP_NAME, file)); + if (stat(PromiseFlatCString(result).get(), &st) == 0) { + return NS_OK; + } + + errx(1, "can't locate %s", file); +} + +NS_IMETHODIMP +OpenBSDPledgePromises(const nsACString& aPath) { + // Using NS_LOCAL_FILE_CONTRACTID/NS_LOCALFILEINPUTSTREAM_CONTRACTID requires + // a lot of setup before they are supported and we want to pledge early on + // before all of that, so read the file directly + std::ifstream input(PromiseFlatCString(aPath).get()); + + // Build up one line of pledge promises without comments + nsAutoCString promises; + bool disabled = false; + int linenum = 0; + for (std::string tLine; std::getline(input, tLine);) { + nsAutoCString line(tLine.c_str()); + linenum++; + + // Cut off any comments at the end of the line, also catches lines + // that are entirely a comment + int32_t hash = line.FindChar('#'); + if (hash >= 0) { + line = Substring(line, 0, hash); + } + line.CompressWhitespace(true, true); + if (line.IsEmpty()) { + continue; + } + + if (linenum == 1 && line.EqualsLiteral("disable")) { + disabled = true; + break; + } + + if (!promises.IsEmpty()) { + promises.Append(" "); + } + promises.Append(line); + } + input.close(); + + if (disabled) { + warnx("%s: disabled", PromiseFlatCString(aPath).get()); + } else { + MOZ_LOG( + sPledgeLog, LogLevel::Debug, + ("%s: pledge(%s)\n", PromiseFlatCString(aPath).get(), promises.get())); + if (pledge(promises.get(), nullptr) != 0) { + err(1, "%s: pledge(%s) failed", PromiseFlatCString(aPath).get(), + promises.get()); + } + } + + return NS_OK; +} + +void ExpandUnveilPath(nsAutoCString& path) { + // Expand $XDG_RUNTIME_DIR to the environment variable, or ~/.cache + nsCString xdgRuntimeDir(PR_GetEnv("XDG_RUNTIME_DIR")); + if (xdgRuntimeDir.IsEmpty()) { + xdgRuntimeDir = "~/.cache"; + } + path.ReplaceSubstring("$XDG_RUNTIME_DIR", xdgRuntimeDir.get()); + + // Expand $XDG_CONFIG_HOME to the environment variable, or ~/.config + nsCString xdgConfigHome(PR_GetEnv("XDG_CONFIG_HOME")); + if (xdgConfigHome.IsEmpty()) { + xdgConfigHome = "~/.config"; + } + path.ReplaceSubstring("$XDG_CONFIG_HOME", xdgConfigHome.get()); + + // Expand $XDG_CACHE_HOME to the environment variable, or ~/.cache + nsCString xdgCacheHome(PR_GetEnv("XDG_CACHE_HOME")); + if (xdgCacheHome.IsEmpty()) { + xdgCacheHome = "~/.cache"; + } + path.ReplaceSubstring("$XDG_CACHE_HOME", xdgCacheHome.get()); + + // Expand $XDG_DATA_HOME to the environment variable, or ~/.local/share + nsCString xdgDataHome(PR_GetEnv("XDG_DATA_HOME")); + if (xdgDataHome.IsEmpty()) { + xdgDataHome = "~/.local/share"; + } + path.ReplaceSubstring("$XDG_DATA_HOME", xdgDataHome.get()); + + // Expand leading ~ to the user's home directory + nsCOMPtr<nsIFile> homeDir; + nsresult rv = + GetSpecialSystemDirectory(Unix_HomeDirectory, getter_AddRefs(homeDir)); + if (NS_FAILED(rv)) { + errx(1, "failed getting home directory"); + } + if (path.FindChar('~') == 0) { + nsCString tHome(homeDir->NativePath()); + tHome.Append(Substring(path, 1, path.Length() - 1)); + path = tHome.get(); + } +} + +void MkdirP(nsAutoCString& path) { + // nsLocalFile::CreateAllAncestors would be nice to use + + nsAutoCString tPath(""); + for (const nsACString& dir : path.Split('/')) { + struct stat st; + + if (dir.IsEmpty()) { + continue; + } + + tPath.Append("/"); + tPath.Append(dir); + + if (stat(tPath.get(), &st) == -1) { + if (mkdir(tPath.get(), 0700) == -1) { + err(1, "failed mkdir(%s) while MkdirP(%s)", + PromiseFlatCString(tPath).get(), PromiseFlatCString(path).get()); + } + } + } +} + +NS_IMETHODIMP +OpenBSDUnveilPaths(const nsACString& uPath, const nsACString& pledgePath) { + // Using NS_LOCAL_FILE_CONTRACTID/NS_LOCALFILEINPUTSTREAM_CONTRACTID requires + // a lot of setup before they are allowed/supported and we want to pledge and + // unveil early on before all of that is setup + std::ifstream input(PromiseFlatCString(uPath).get()); + + bool disabled = false; + int linenum = 0; + for (std::string tLine; std::getline(input, tLine);) { + nsAutoCString line(tLine.c_str()); + linenum++; + + // Cut off any comments at the end of the line, also catches lines + // that are entirely a comment + int32_t hash = line.FindChar('#'); + if (hash >= 0) { + line = Substring(line, 0, hash); + } + line.CompressWhitespace(true, true); + if (line.IsEmpty()) { + continue; + } + + if (linenum == 1 && line.EqualsLiteral("disable")) { + disabled = true; + break; + } + + int32_t space = line.FindChar(' '); + if (space <= 0) { + errx(1, "%s: line %d: invalid format", PromiseFlatCString(uPath).get(), + linenum); + } + + nsAutoCString uPath(Substring(line, 0, space)); + ExpandUnveilPath(uPath); + + nsAutoCString perms(Substring(line, space + 1, line.Length() - space - 1)); + + MOZ_LOG(sPledgeLog, LogLevel::Debug, + ("%s: unveil(%s, %s)\n", PromiseFlatCString(uPath).get(), + uPath.get(), perms.get())); + if (unveil(uPath.get(), perms.get()) == -1 && errno != ENOENT) { + err(1, "%s: unveil(%s, %s) failed", PromiseFlatCString(uPath).get(), + uPath.get(), perms.get()); + } + } + input.close(); + + if (disabled) { + warnx("%s: disabled", PromiseFlatCString(uPath).get()); + } else { + struct stat st; + + // Only unveil the pledgePath file if it's not already unveiled, otherwise + // some containing directory will lose visibility. + if (stat(PromiseFlatCString(pledgePath).get(), &st) == -1) { + if (errno == ENOENT) { + if (unveil(PromiseFlatCString(pledgePath).get(), "r") == -1) { + err(1, "unveil(%s, r) failed", PromiseFlatCString(pledgePath).get()); + } + } else { + err(1, "stat(%s)", PromiseFlatCString(pledgePath).get()); + } + } + } + + return NS_OK; +} + +bool StartOpenBSDSandbox(GeckoProcessType type, ipc::SandboxingKind kind) { + nsAutoCString pledgeFile; + nsAutoCString unveilFile; + char binaryPath[MAXPATHLEN]; + + switch (type) { + case GeckoProcessType_Default: { + OpenBSDFindPledgeUnveilFilePath("pledge.main", pledgeFile); + OpenBSDFindPledgeUnveilFilePath("unveil.main", unveilFile); + + // Ensure dconf dir exists before we veil the filesystem + nsAutoCString dConf("$XDG_RUNTIME_DIR/dconf"); + ExpandUnveilPath(dConf); + MkdirP(dConf); + break; + } + + case GeckoProcessType_Content: + OpenBSDFindPledgeUnveilFilePath("pledge.content", pledgeFile); + OpenBSDFindPledgeUnveilFilePath("unveil.content", unveilFile); + break; + + case GeckoProcessType_GPU: + OpenBSDFindPledgeUnveilFilePath("pledge.gpu", pledgeFile); + OpenBSDFindPledgeUnveilFilePath("unveil.gpu", unveilFile); + break; + + case GeckoProcessType_Socket: + OpenBSDFindPledgeUnveilFilePath("pledge.socket", pledgeFile); + OpenBSDFindPledgeUnveilFilePath("unveil.socket", unveilFile); + break; + + case GeckoProcessType_RDD: + OpenBSDFindPledgeUnveilFilePath("pledge.rdd", pledgeFile); + OpenBSDFindPledgeUnveilFilePath("unveil.rdd", unveilFile); + break; + + case GeckoProcessType_Utility: { + MOZ_RELEASE_ASSERT(kind <= SandboxingKind::COUNT, + "Should define a sandbox"); + switch (kind) { + case ipc::SandboxingKind::GENERIC_UTILITY: + default: + OpenBSDFindPledgeUnveilFilePath("pledge.utility", pledgeFile); + OpenBSDFindPledgeUnveilFilePath("unveil.utility", unveilFile); + break; + } + } break; + + default: + MOZ_ASSERT(false, "unknown process type"); + return false; + } + + nsresult rv = mozilla::BinaryPath::Get(binaryPath); + if (NS_FAILED(rv)) { + errx(1, "failed to cache binary path ?"); + } + + if (NS_WARN_IF(NS_FAILED(OpenBSDUnveilPaths(unveilFile, pledgeFile)))) { + errx(1, "failed reading/parsing %s", unveilFile.get()); + } + + if (NS_WARN_IF(NS_FAILED(OpenBSDPledgePromises(pledgeFile)))) { + errx(1, "failed reading/parsing %s", pledgeFile.get()); + } + + // Don't overwrite an existing session dbus address, but ensure it is set + if (!PR_GetEnv("DBUS_SESSION_BUS_ADDRESS")) { + PR_SetEnv("DBUS_SESSION_BUS_ADDRESS="); + } + + return true; +} +#endif + +} // namespace mozilla + +/* static */ +nsIDOMProcessChild* nsIDOMProcessChild::GetSingleton() { + if (XRE_IsContentProcess()) { + return mozilla::dom::ContentChild::GetSingleton(); + } + return mozilla::dom::InProcessChild::Singleton(); +} diff --git a/dom/ipc/ContentChild.h b/dom/ipc/ContentChild.h new file mode 100644 index 0000000000..4cd1e0771c --- /dev/null +++ b/dom/ipc/ContentChild.h @@ -0,0 +1,911 @@ +/* -*- Mode: C++; tab-width: 2; 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/. */ + +#ifndef mozilla_dom_ContentChild_h +#define mozilla_dom_ContentChild_h + +#include "mozilla/Atomics.h" +#include "mozilla/dom/BlobImpl.h" +#include "mozilla/dom/GetFilesHelper.h" +#include "mozilla/dom/UserActivation.h" +#include "mozilla/dom/PContentChild.h" +#include "mozilla/dom/ProcessActor.h" +#include "mozilla/dom/RemoteType.h" +#include "mozilla/Hal.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/UniquePtr.h" +#include "nsClassHashtable.h" +#include "nscore.h" +#include "nsHashKeys.h" +#include "nsIDOMProcessChild.h" +#include "nsRefPtrHashtable.h" +#include "nsString.h" +#include "nsTArrayForwardDeclare.h" +#include "nsTHashSet.h" + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +# include "nsIFile.h" +#endif + +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) +# include "mozilla/PSandboxTestingChild.h" +#endif + +struct ChromePackage; +class nsIObserver; +struct SubstitutionMapping; +struct OverrideMapping; +class nsIDomainPolicy; +class nsIURIClassifierCallback; +class nsDocShellLoadState; +class nsFrameLoader; +class nsIOpenWindowInfo; + +namespace mozilla { +class RemoteSpellcheckEngineChild; +class ChildProfilerController; +class BenchmarkStorageChild; + +namespace ipc { +class UntypedEndpoint; +} + +namespace loader { +class PScriptCacheChild; +} + +namespace widget { +enum class ThemeChangeKind : uint8_t; +} +namespace dom { + +namespace ipc { +class SharedMap; +} + +class AlertObserver; +class ConsoleListener; +class ClonedMessageData; +class BrowserChild; +class TabContext; +enum class CallerType : uint32_t; + +class ContentChild final : public PContentChild, + public nsIDOMProcessChild, + public mozilla::ipc::IShmemAllocator, + public ProcessActor { + using ClonedMessageData = mozilla::dom::ClonedMessageData; + using FileDescriptor = mozilla::ipc::FileDescriptor; + + friend class PContentChild; + + public: + NS_DECL_NSIDOMPROCESSCHILD + + ContentChild(); + virtual ~ContentChild(); + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override { return 1; } + NS_IMETHOD_(MozExternalRefCountType) Release(void) override { return 1; } + + struct AppInfo { + nsCString version; + nsCString buildID; + nsCString name; + nsCString UAName; + nsCString ID; + nsCString vendor; + nsCString sourceURL; + nsCString updateURL; + }; + + MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult ProvideWindowCommon( + NotNull<BrowserChild*> aTabOpener, nsIOpenWindowInfo* aOpenWindowInfo, + uint32_t aChromeFlags, bool aCalledFromJS, nsIURI* aURI, + const nsAString& aName, const nsACString& aFeatures, + const UserActivation::Modifiers& aModifiers, bool aForceNoOpener, + bool aForceNoReferrer, bool aIsPopupRequested, + nsDocShellLoadState* aLoadState, bool* aWindowIsNew, + BrowsingContext** aReturn); + + void Init(mozilla::ipc::UntypedEndpoint&& aEndpoint, + const char* aParentBuildID, uint64_t aChildID, bool aIsForBrowser); + + void InitXPCOM(XPCOMInitData&& aXPCOMInit, + const mozilla::dom::ipc::StructuredCloneData& aInitialData, + bool aIsReadyForBackgroundProcessing); + + void InitSharedUASheets(Maybe<base::SharedMemoryHandle>&& aHandle, + uintptr_t aAddress); + + void InitGraphicsDeviceData(const ContentDeviceData& aData); + + static ContentChild* GetSingleton() { return sSingleton; } + + const AppInfo& GetAppInfo() { return mAppInfo; } + + void SetProcessName(const nsACString& aName, + const nsACString* aETLDplus1 = nullptr, + const nsACString* aCurrentProfile = nullptr); + + void GetProcessName(nsACString& aName) const; + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + void GetProfileDir(nsIFile** aProfileDir) const { + *aProfileDir = mProfileDir; + NS_IF_ADDREF(*aProfileDir); + } + + void SetProfileDir(nsIFile* aProfileDir) { mProfileDir = aProfileDir; } +#endif + + bool IsAlive() const; + + bool IsShuttingDown() const; + + ipc::SharedMap* SharedData() { return mSharedData; }; + + static void AppendProcessId(nsACString& aName); + + static RefPtr<GenericPromise> UpdateCookieStatus(nsIChannel* aChannel); + + mozilla::ipc::IPCResult RecvInitGMPService( + Endpoint<PGMPServiceChild>&& aGMPService); + + mozilla::ipc::IPCResult RecvInitProfiler( + Endpoint<PProfilerChild>&& aEndpoint); + + mozilla::ipc::IPCResult RecvGMPsChanged( + nsTArray<GMPCapabilityData>&& capabilities); + + mozilla::ipc::IPCResult RecvInitProcessHangMonitor( + Endpoint<PProcessHangMonitorChild>&& aHangMonitor); + + mozilla::ipc::IPCResult RecvInitRendering( + Endpoint<PCompositorManagerChild>&& aCompositor, + Endpoint<PImageBridgeChild>&& aImageBridge, + Endpoint<PVRManagerChild>&& aVRBridge, + Endpoint<PRemoteDecoderManagerChild>&& aVideoManager, + nsTArray<uint32_t>&& namespaces); + + mozilla::ipc::IPCResult RecvReinitRendering( + Endpoint<PCompositorManagerChild>&& aCompositor, + Endpoint<PImageBridgeChild>&& aImageBridge, + Endpoint<PVRManagerChild>&& aVRBridge, + Endpoint<PRemoteDecoderManagerChild>&& aVideoManager, + nsTArray<uint32_t>&& namespaces); + + mozilla::ipc::IPCResult RecvReinitRenderingForDeviceReset(); + + mozilla::ipc::IPCResult RecvSetProcessSandbox( + const Maybe<FileDescriptor>& aBroker); + + PHalChild* AllocPHalChild(); + bool DeallocPHalChild(PHalChild*); + + PHeapSnapshotTempFileHelperChild* AllocPHeapSnapshotTempFileHelperChild(); + + bool DeallocPHeapSnapshotTempFileHelperChild( + PHeapSnapshotTempFileHelperChild*); + + PCycleCollectWithLogsChild* AllocPCycleCollectWithLogsChild( + const bool& aDumpAllTraces, const FileDescriptor& aGCLog, + const FileDescriptor& aCCLog); + + bool DeallocPCycleCollectWithLogsChild(PCycleCollectWithLogsChild* aActor); + + virtual mozilla::ipc::IPCResult RecvPCycleCollectWithLogsConstructor( + PCycleCollectWithLogsChild* aChild, const bool& aDumpAllTraces, + const FileDescriptor& aGCLog, const FileDescriptor& aCCLog) override; + + already_AddRefed<PWebBrowserPersistDocumentChild> + AllocPWebBrowserPersistDocumentChild( + PBrowserChild* aBrowser, const MaybeDiscarded<BrowsingContext>& aContext); + + virtual mozilla::ipc::IPCResult RecvPWebBrowserPersistDocumentConstructor( + PWebBrowserPersistDocumentChild* aActor, PBrowserChild* aBrowser, + const MaybeDiscarded<BrowsingContext>& aContext) override; + + already_AddRefed<PTestShellChild> AllocPTestShellChild(); + + virtual mozilla::ipc::IPCResult RecvPTestShellConstructor( + PTestShellChild*) override; + + PScriptCacheChild* AllocPScriptCacheChild(const FileDescOrError& cacheFile, + const bool& wantCacheData); + + bool DeallocPScriptCacheChild(PScriptCacheChild*); + + virtual mozilla::ipc::IPCResult RecvPScriptCacheConstructor( + PScriptCacheChild*, const FileDescOrError& cacheFile, + const bool& wantCacheData) override; + + PRemotePrintJobChild* AllocPRemotePrintJobChild(); + + already_AddRefed<PClipboardReadRequestChild> AllocPClipboardReadRequestChild( + const nsTArray<nsCString>& aTypes); + + PMediaChild* AllocPMediaChild(); + + bool DeallocPMediaChild(PMediaChild* aActor); + + PBenchmarkStorageChild* AllocPBenchmarkStorageChild(); + + bool DeallocPBenchmarkStorageChild(PBenchmarkStorageChild* aActor); + + mozilla::ipc::IPCResult RecvNotifyEmptyHTTPCache(); + + mozilla::ipc::IPCResult RecvRegisterChrome( + nsTArray<ChromePackage>&& packages, + nsTArray<SubstitutionMapping>&& resources, + nsTArray<OverrideMapping>&& overrides, const nsCString& locale, + const bool& reset); + mozilla::ipc::IPCResult RecvRegisterChromeItem( + const ChromeRegistryItem& item); + + mozilla::ipc::IPCResult RecvClearStyleSheetCache( + const Maybe<RefPtr<nsIPrincipal>>& aForPrincipal, + const Maybe<nsCString>& aBaseDomain); + + mozilla::ipc::IPCResult RecvClearImageCacheFromPrincipal( + nsIPrincipal* aPrincipal); + mozilla::ipc::IPCResult RecvClearImageCacheFromBaseDomain( + const nsCString& aBaseDomain); + mozilla::ipc::IPCResult RecvClearImageCache(const bool& privateLoader, + const bool& chrome); + + PRemoteSpellcheckEngineChild* AllocPRemoteSpellcheckEngineChild(); + + bool DeallocPRemoteSpellcheckEngineChild(PRemoteSpellcheckEngineChild*); + + mozilla::ipc::IPCResult RecvSetOffline(const bool& offline); + + mozilla::ipc::IPCResult RecvSetConnectivity(const bool& connectivity); + mozilla::ipc::IPCResult RecvSetCaptivePortalState(const int32_t& state); + mozilla::ipc::IPCResult RecvSetTRRMode( + const nsIDNSService::ResolverMode& mode, + const nsIDNSService::ResolverMode& modeFromPref); + + mozilla::ipc::IPCResult RecvBidiKeyboardNotify(const bool& isLangRTL, + const bool& haveBidiKeyboards); + + mozilla::ipc::IPCResult RecvNotifyVisited(nsTArray<VisitedQueryResult>&&); + + mozilla::ipc::IPCResult RecvThemeChanged(FullLookAndFeel&&, + widget::ThemeChangeKind); + + // auto remove when alertfinished is received. + nsresult AddRemoteAlertObserver(const nsString& aData, + nsIObserver* aObserver); + + mozilla::ipc::IPCResult RecvPreferenceUpdate(const Pref& aPref); + mozilla::ipc::IPCResult RecvVarUpdate(const GfxVarUpdate& pref); + + mozilla::ipc::IPCResult RecvUpdatePerfStatsCollectionMask( + const uint64_t& aMask); + + mozilla::ipc::IPCResult RecvCollectPerfStatsJSON( + CollectPerfStatsJSONResolver&& aResolver); + + mozilla::ipc::IPCResult RecvCollectScrollingMetrics( + CollectScrollingMetricsResolver&& aResolver); + + mozilla::ipc::IPCResult RecvNotifyAlertsObserver(const nsCString& aType, + const nsString& aData); + + mozilla::ipc::IPCResult RecvLoadProcessScript(const nsString& aURL); + + mozilla::ipc::IPCResult RecvAsyncMessage(const nsString& aMsg, + const ClonedMessageData& aData); + + mozilla::ipc::IPCResult RecvRegisterStringBundles( + nsTArray<StringBundleDescriptor>&& stringBundles); + + mozilla::ipc::IPCResult RecvUpdateL10nFileSources( + nsTArray<L10nFileSourceDescriptor>&& aDescriptors); + + mozilla::ipc::IPCResult RecvUpdateSharedData( + const FileDescriptor& aMapFile, const uint32_t& aMapSize, + nsTArray<IPCBlob>&& aBlobs, nsTArray<nsCString>&& aChangedKeys); + + mozilla::ipc::IPCResult RecvFontListChanged(); + mozilla::ipc::IPCResult RecvForceGlobalReflow(bool aNeedsReframe); + + mozilla::ipc::IPCResult RecvGeolocationUpdate(nsIDOMGeoPosition* aPosition); + + // MOZ_CAN_RUN_SCRIPT_BOUNDARY because we don't have MOZ_CAN_RUN_SCRIPT bits + // in IPC code yet. + MOZ_CAN_RUN_SCRIPT_BOUNDARY + mozilla::ipc::IPCResult RecvGeolocationError(const uint16_t& errorCode); + + mozilla::ipc::IPCResult RecvUpdateDictionaryList( + nsTArray<nsCString>&& aDictionaries); + + mozilla::ipc::IPCResult RecvUpdateFontList(SystemFontList&&); + mozilla::ipc::IPCResult RecvRebuildFontList(const bool& aFullRebuild); + mozilla::ipc::IPCResult RecvFontListShmBlockAdded( + const uint32_t& aGeneration, const uint32_t& aIndex, + base::SharedMemoryHandle&& aHandle); + + mozilla::ipc::IPCResult RecvUpdateAppLocales( + nsTArray<nsCString>&& aAppLocales); + mozilla::ipc::IPCResult RecvUpdateRequestedLocales( + nsTArray<nsCString>&& aRequestedLocales); + + mozilla::ipc::IPCResult RecvSystemTimezoneChanged(); + + mozilla::ipc::IPCResult RecvAddPermission(const IPC::Permission& permission); + + mozilla::ipc::IPCResult RecvRemoveAllPermissions(); + + mozilla::ipc::IPCResult RecvFlushMemory(const nsString& reason); + + mozilla::ipc::IPCResult RecvActivateA11y(); + mozilla::ipc::IPCResult RecvShutdownA11y(); + + mozilla::ipc::IPCResult RecvApplicationForeground(); + mozilla::ipc::IPCResult RecvApplicationBackground(); + mozilla::ipc::IPCResult RecvGarbageCollect(); + mozilla::ipc::IPCResult RecvCycleCollect(); + mozilla::ipc::IPCResult RecvUnlinkGhosts(); + + mozilla::ipc::IPCResult RecvAppInfo( + const nsCString& version, const nsCString& buildID, const nsCString& name, + const nsCString& UAName, const nsCString& ID, const nsCString& vendor, + const nsCString& sourceURL, const nsCString& updateURL); + + mozilla::ipc::IPCResult RecvRemoteType(const nsCString& aRemoteType, + const nsCString& aProfile); + + void PreallocInit(); + + // Call RemoteTypePrefix() on the result to remove URIs if you want to use + // this for telemetry. + const nsACString& GetRemoteType() const override; + + mozilla::ipc::IPCResult RecvInitBlobURLs( + nsTArray<BlobURLRegistrationData>&& aRegistations); + + mozilla::ipc::IPCResult RecvInitJSActorInfos( + nsTArray<JSProcessActorInfo>&& aContentInfos, + nsTArray<JSWindowActorInfo>&& aWindowInfos); + + mozilla::ipc::IPCResult RecvUnregisterJSWindowActor(const nsCString& aName); + + mozilla::ipc::IPCResult RecvUnregisterJSProcessActor(const nsCString& aName); + + mozilla::ipc::IPCResult RecvLastPrivateDocShellDestroyed(); + + mozilla::ipc::IPCResult RecvNotifyProcessPriorityChanged( + const hal::ProcessPriority& aPriority); + + mozilla::ipc::IPCResult RecvMinimizeMemoryUsage(); + + mozilla::ipc::IPCResult RecvLoadAndRegisterSheet(nsIURI* aURI, + const uint32_t& aType); + + mozilla::ipc::IPCResult RecvUnregisterSheet(nsIURI* aURI, + const uint32_t& aType); + + void AddIdleObserver(nsIObserver* aObserver, uint32_t aIdleTimeInS); + + void RemoveIdleObserver(nsIObserver* aObserver, uint32_t aIdleTimeInS); + + mozilla::ipc::IPCResult RecvNotifyIdleObserver(const uint64_t& aObserver, + const nsCString& aTopic, + const nsString& aData); + + mozilla::ipc::IPCResult RecvUpdateWindow(const uintptr_t& aChildId); + + mozilla::ipc::IPCResult RecvDomainSetChanged(const uint32_t& aSetType, + const uint32_t& aChangeType, + nsIURI* aDomain); + + mozilla::ipc::IPCResult RecvShutdownConfirmedHP(); + + mozilla::ipc::IPCResult RecvShutdown(); + + mozilla::ipc::IPCResult RecvInvokeDragSession( + const MaybeDiscarded<WindowContext>& aSourceWindowContext, + const MaybeDiscarded<WindowContext>& aSourceTopWindowContext, + nsTArray<IPCTransferableData>&& aTransferables, const uint32_t& aAction); + + mozilla::ipc::IPCResult RecvUpdateDragSession( + nsTArray<IPCTransferableData>&& aTransferables, + EventMessage aEventMessage); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY + mozilla::ipc::IPCResult RecvEndDragSession( + const bool& aDoneDrag, const bool& aUserCancelled, + const mozilla::LayoutDeviceIntPoint& aEndDragPoint, + const uint32_t& aKeyModifiers, const uint32_t& aDropEffect); + + mozilla::ipc::IPCResult RecvPush(const nsCString& aScope, + nsIPrincipal* aPrincipal, + const nsString& aMessageId); + + mozilla::ipc::IPCResult RecvPushWithData(const nsCString& aScope, + nsIPrincipal* aPrincipal, + const nsString& aMessageId, + nsTArray<uint8_t>&& aData); + + mozilla::ipc::IPCResult RecvPushSubscriptionChange(const nsCString& aScope, + nsIPrincipal* aPrincipal); + + mozilla::ipc::IPCResult RecvPushError(const nsCString& aScope, + nsIPrincipal* aPrincipal, + const nsString& aMessage, + const uint32_t& aFlags); + + mozilla::ipc::IPCResult RecvNotifyPushSubscriptionModifiedObservers( + const nsCString& aScope, nsIPrincipal* aPrincipal); + + mozilla::ipc::IPCResult RecvRefreshScreens( + nsTArray<ScreenDetails>&& aScreens); + + mozilla::ipc::IPCResult RecvNetworkLinkTypeChange(const uint32_t& aType); + uint32_t NetworkLinkType() const { return mNetworkLinkType; } + + mozilla::ipc::IPCResult RecvSocketProcessCrashed(); + + // Get the directory for IndexedDB files. We query the parent for this and + // cache the value + nsString& GetIndexedDBPath(); + + ContentParentId GetID() const { return mID; } + + bool IsForBrowser() const { return mIsForBrowser; } + + MOZ_CAN_RUN_SCRIPT_BOUNDARY mozilla::ipc::IPCResult RecvConstructBrowser( + ManagedEndpoint<PBrowserChild>&& aBrowserEp, + ManagedEndpoint<PWindowGlobalChild>&& aWindowEp, const TabId& aTabId, + const IPCTabContext& aContext, const WindowGlobalInit& aWindowInit, + const uint32_t& aChromeFlags, const ContentParentId& aCpID, + const bool& aIsForBrowser, const bool& aIsTopLevel); + + FORWARD_SHMEM_ALLOCATOR_TO(PContentChild) + + void GetAvailableDictionaries(nsTArray<nsCString>& aDictionaries); + +#ifdef MOZ_WEBRTC + PWebrtcGlobalChild* AllocPWebrtcGlobalChild(); + bool DeallocPWebrtcGlobalChild(PWebrtcGlobalChild* aActor); +#endif + + PContentPermissionRequestChild* AllocPContentPermissionRequestChild( + Span<const PermissionRequest> aRequests, nsIPrincipal* aPrincipal, + nsIPrincipal* aTopLevelPrincipal, const bool& aIsHandlingUserInput, + const bool& aMaybeUnsafePermissionDelegate, const TabId& aTabId); + bool DeallocPContentPermissionRequestChild( + PContentPermissionRequestChild* actor); + + // GetFiles for WebKit/Blink FileSystem API and Directory API must run on the + // parent process. + void CreateGetFilesRequest(const nsAString& aDirectoryPath, + bool aRecursiveFlag, nsID& aUUID, + GetFilesHelperChild* aChild); + + void DeleteGetFilesRequest(nsID& aUUID, GetFilesHelperChild* aChild); + + mozilla::ipc::IPCResult RecvGetFilesResponse( + const nsID& aUUID, const GetFilesResponseResult& aResult); + + mozilla::ipc::IPCResult RecvBlobURLRegistration( + const nsCString& aURI, const IPCBlob& aBlob, nsIPrincipal* aPrincipal, + const nsCString& aPartitionKey); + + mozilla::ipc::IPCResult RecvBlobURLUnregistration(const nsCString& aURI); + + mozilla::ipc::IPCResult RecvRequestMemoryReport( + const uint32_t& generation, const bool& anonymize, + const bool& minimizeMemoryUsage, const Maybe<FileDescriptor>& DMDFile, + const RequestMemoryReportResolver& aResolver); + +#if defined(XP_WIN) + mozilla::ipc::IPCResult RecvGetUntrustedModulesData( + GetUntrustedModulesDataResolver&& aResolver); + mozilla::ipc::IPCResult RecvUnblockUntrustedModulesThread(); +#endif // defined(XP_WIN) + + mozilla::ipc::IPCResult RecvSetXPCOMProcessAttributes( + XPCOMInitData&& aXPCOMInit, const StructuredCloneData& aInitialData, + FullLookAndFeel&& aLookAndFeelData, SystemFontList&& aFontList, + Maybe<base::SharedMemoryHandle>&& aSharedUASheetHandle, + const uintptr_t& aSharedUASheetAddress, + nsTArray<base::SharedMemoryHandle>&& aSharedFontListBlocks, + const bool& aIsReadyForBackgroundProcessing); + + mozilla::ipc::IPCResult RecvProvideAnonymousTemporaryFile( + const uint64_t& aID, const FileDescOrError& aFD); + + mozilla::ipc::IPCResult RecvSetPermissionsWithKey( + const nsCString& aPermissionKey, nsTArray<IPC::Permission>&& aPerms); + + mozilla::ipc::IPCResult RecvShareCodeCoverageMutex( + CrossProcessMutexHandle aHandle); + + mozilla::ipc::IPCResult RecvFlushCodeCoverageCounters( + FlushCodeCoverageCountersResolver&& aResolver); + + mozilla::ipc::IPCResult RecvSetInputEventQueueEnabled(); + + mozilla::ipc::IPCResult RecvFlushInputEventQueue(); + + mozilla::ipc::IPCResult RecvSuspendInputEventQueue(); + + mozilla::ipc::IPCResult RecvResumeInputEventQueue(); + + mozilla::ipc::IPCResult RecvAddDynamicScalars( + nsTArray<DynamicScalarDefinition>&& aDefs); + + // Get a reference to the font list passed from the chrome process, + // for use during gfx initialization. + SystemFontList& SystemFontList() { return mFontList; } + + nsTArray<base::SharedMemoryHandle>& SharedFontListBlocks() { + return mSharedFontListBlocks; + } + + // PURLClassifierChild + PURLClassifierChild* AllocPURLClassifierChild(nsIPrincipal* aPrincipal, + bool* aSuccess); + bool DeallocPURLClassifierChild(PURLClassifierChild* aActor); + + // PURLClassifierLocalChild + PURLClassifierLocalChild* AllocPURLClassifierLocalChild( + nsIURI* aUri, Span<const IPCURLClassifierFeature> aFeatures); + bool DeallocPURLClassifierLocalChild(PURLClassifierLocalChild* aActor); + + PSessionStorageObserverChild* AllocPSessionStorageObserverChild(); + + bool DeallocPSessionStorageObserverChild( + PSessionStorageObserverChild* aActor); + + FullLookAndFeel& BorrowLookAndFeelData() { return mLookAndFeelData; } + + /** + * Helper function for protocols that use the GPU process when available. + * Overrides FatalError to just be a warning when communicating with the + * GPU process since we don't want to crash the content process when the + * GPU process crashes. + */ + static void FatalErrorIfNotUsingGPUProcess(const char* const aErrorMsg, + base::ProcessId aOtherPid); + + using AnonymousTemporaryFileCallback = std::function<void(PRFileDesc*)>; + nsresult AsyncOpenAnonymousTemporaryFile( + const AnonymousTemporaryFileCallback& aCallback); + + mozilla::ipc::IPCResult RecvSaveRecording(const FileDescriptor& aFile); + + mozilla::ipc::IPCResult RecvCrossProcessRedirect( + RedirectToRealChannelArgs&& aArgs, + nsTArray<Endpoint<extensions::PStreamFilterParent>>&& aEndpoints, + CrossProcessRedirectResolver&& aResolve); + + mozilla::ipc::IPCResult RecvStartDelayedAutoplayMediaComponents( + const MaybeDiscarded<BrowsingContext>& aContext); + + mozilla::ipc::IPCResult RecvUpdateMediaControlAction( + const MaybeDiscarded<BrowsingContext>& aContext, + const MediaControlAction& aAction); + + // See `BrowsingContext::mEpochs` for an explanation of this field. + uint64_t GetBrowsingContextFieldEpoch() const { + return mBrowsingContextFieldEpoch; + } + uint64_t NextBrowsingContextFieldEpoch() { + mBrowsingContextFieldEpoch++; + return mBrowsingContextFieldEpoch; + } + + mozilla::ipc::IPCResult RecvOnAllowAccessFor( + const MaybeDiscarded<BrowsingContext>& aContext, + const nsCString& aTrackingOrigin, uint32_t aCookieBehavior, + const ContentBlockingNotifier::StorageAccessPermissionGrantedReason& + aReason); + + mozilla::ipc::IPCResult RecvOnContentBlockingDecision( + const MaybeDiscarded<BrowsingContext>& aContext, + const ContentBlockingNotifier::BlockingDecision& aDecision, + uint32_t aRejectedReason); + +#ifdef NIGHTLY_BUILD + // Fetch the current number of pending input events. + // + // NOTE: This method performs an atomic read, and is safe to call from all + // threads. + uint32_t GetPendingInputEvents() { return mPendingInputEvents; } +#endif + +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) + mozilla::ipc::IPCResult RecvInitSandboxTesting( + Endpoint<PSandboxTestingChild>&& aEndpoint); +#endif + + private: + static void ForceKillTimerCallback(nsITimer* aTimer, void* aClosure); + void StartForceKillTimer(); + + void ShutdownInternal(); + + mozilla::ipc::IPCResult GetResultForRenderingInitFailure( + base::ProcessId aOtherPid); + + virtual void ActorDestroy(ActorDestroyReason why) override; + + virtual void ProcessingError(Result aCode, const char* aReason) override; + + mozilla::ipc::IPCResult RecvCreateBrowsingContext( + uint64_t aGroupId, BrowsingContext::IPCInitializer&& aInit); + + mozilla::ipc::IPCResult RecvDiscardBrowsingContext( + const MaybeDiscarded<BrowsingContext>& aContext, bool aDoDiscard, + DiscardBrowsingContextResolver&& aResolve); + + mozilla::ipc::IPCResult RecvRegisterBrowsingContextGroup( + uint64_t aGroupId, nsTArray<SyncedContextInitializer>&& aInits); + mozilla::ipc::IPCResult RecvDestroyBrowsingContextGroup(uint64_t aGroupId); + + mozilla::ipc::IPCResult RecvWindowClose( + const MaybeDiscarded<BrowsingContext>& aContext, bool aTrustedCaller); + mozilla::ipc::IPCResult RecvWindowFocus( + const MaybeDiscarded<BrowsingContext>& aContext, CallerType aCallerType, + uint64_t aActionId); + mozilla::ipc::IPCResult RecvWindowBlur( + const MaybeDiscarded<BrowsingContext>& aContext, CallerType aCallerType); + MOZ_CAN_RUN_SCRIPT_BOUNDARY mozilla::ipc::IPCResult RecvRaiseWindow( + const MaybeDiscarded<BrowsingContext>& aContext, CallerType aCallerType, + uint64_t aActionId); + MOZ_CAN_RUN_SCRIPT_BOUNDARY mozilla::ipc::IPCResult RecvAdjustWindowFocus( + const MaybeDiscarded<BrowsingContext>& aContext, bool aIsVisible, + uint64_t aActionId); + MOZ_CAN_RUN_SCRIPT_BOUNDARY mozilla::ipc::IPCResult RecvClearFocus( + const MaybeDiscarded<BrowsingContext>& aContext); + mozilla::ipc::IPCResult RecvSetFocusedBrowsingContext( + const MaybeDiscarded<BrowsingContext>& aContext, uint64_t aActionId); + mozilla::ipc::IPCResult RecvSetActiveBrowsingContext( + const MaybeDiscarded<BrowsingContext>& aContext, uint64_t aActionId); + mozilla::ipc::IPCResult RecvAbortOrientationPendingPromises( + const MaybeDiscarded<BrowsingContext>& aContext); + mozilla::ipc::IPCResult RecvUnsetActiveBrowsingContext( + const MaybeDiscarded<BrowsingContext>& aContext, uint64_t aActionId); + mozilla::ipc::IPCResult RecvSetFocusedElement( + const MaybeDiscarded<BrowsingContext>& aContext, bool aNeedsFocus); + mozilla::ipc::IPCResult RecvFinalizeFocusOuter( + const MaybeDiscarded<BrowsingContext>& aContext, bool aCanFocus, + CallerType aCallerType); + MOZ_CAN_RUN_SCRIPT_BOUNDARY mozilla::ipc::IPCResult RecvBlurToChild( + const MaybeDiscarded<BrowsingContext>& aFocusedBrowsingContext, + const MaybeDiscarded<BrowsingContext>& aBrowsingContextToClear, + const MaybeDiscarded<BrowsingContext>& aAncestorBrowsingContextToFocus, + bool aIsLeavingDocument, bool aAdjustWidget, uint64_t aActionId); + mozilla::ipc::IPCResult RecvSetupFocusedAndActive( + const MaybeDiscarded<BrowsingContext>& aFocusedBrowsingContext, + uint64_t aActionIdForFocused, + const MaybeDiscarded<BrowsingContext>& aActiveBrowsingContext, + uint64_t aActionIdForActive); + mozilla::ipc::IPCResult RecvReviseActiveBrowsingContext( + uint64_t aOldActionId, + const MaybeDiscarded<BrowsingContext>& aActiveBrowsingContext, + uint64_t aNewActionId); + mozilla::ipc::IPCResult RecvReviseFocusedBrowsingContext( + uint64_t aOldActionId, + const MaybeDiscarded<BrowsingContext>& aFocusedBrowsingContext, + uint64_t aNewActionId); + mozilla::ipc::IPCResult RecvMaybeExitFullscreen( + const MaybeDiscarded<BrowsingContext>& aContext); + + mozilla::ipc::IPCResult RecvWindowPostMessage( + const MaybeDiscarded<BrowsingContext>& aContext, + const ClonedOrErrorMessageData& aMessage, const PostMessageData& aData); + + mozilla::ipc::IPCResult RecvCommitBrowsingContextTransaction( + const MaybeDiscarded<BrowsingContext>& aContext, + BrowsingContext::BaseTransaction&& aTransaction, uint64_t aEpoch); + + mozilla::ipc::IPCResult RecvCommitWindowContextTransaction( + const MaybeDiscarded<WindowContext>& aContext, + WindowContext::BaseTransaction&& aTransaction, uint64_t aEpoch); + + mozilla::ipc::IPCResult RecvCreateWindowContext( + WindowContext::IPCInitializer&& aInit); + mozilla::ipc::IPCResult RecvDiscardWindowContext( + uint64_t aContextId, DiscardWindowContextResolver&& aResolve); + + mozilla::ipc::IPCResult RecvScriptError( + const nsString& aMessage, const nsString& aSourceName, + const nsString& aSourceLine, const uint32_t& aLineNumber, + const uint32_t& aColNumber, const uint32_t& aFlags, + const nsCString& aCategory, const bool& aFromPrivateWindow, + const uint64_t& aInnerWindowId, const bool& aFromChromeContext); + + mozilla::ipc::IPCResult RecvReportFrameTimingData( + const LoadInfoArgs& loadInfoArgs, const nsString& entryName, + const nsString& initiatorType, UniquePtr<PerformanceTimingData>&& aData); + + mozilla::ipc::IPCResult RecvLoadURI( + const MaybeDiscarded<BrowsingContext>& aContext, + nsDocShellLoadState* aLoadState, bool aSetNavigating, + LoadURIResolver&& aResolve); + + mozilla::ipc::IPCResult RecvInternalLoad(nsDocShellLoadState* aLoadState); + + mozilla::ipc::IPCResult RecvDisplayLoadError( + const MaybeDiscarded<BrowsingContext>& aContext, const nsAString& aURI); + + mozilla::ipc::IPCResult RecvGoBack( + const MaybeDiscarded<BrowsingContext>& aContext, + const Maybe<int32_t>& aCancelContentJSEpoch, bool aRequireUserInteraction, + bool aUserActivation); + mozilla::ipc::IPCResult RecvGoForward( + const MaybeDiscarded<BrowsingContext>& aContext, + const Maybe<int32_t>& aCancelContentJSEpoch, bool aRequireUserInteraction, + bool aUserActivation); + mozilla::ipc::IPCResult RecvGoToIndex( + const MaybeDiscarded<BrowsingContext>& aContext, const int32_t& aIndex, + const Maybe<int32_t>& aCancelContentJSEpoch, bool aUserActivation); + mozilla::ipc::IPCResult RecvReload( + const MaybeDiscarded<BrowsingContext>& aContext, + const uint32_t aReloadFlags); + mozilla::ipc::IPCResult RecvStopLoad( + const MaybeDiscarded<BrowsingContext>& aContext, + const uint32_t aStopFlags); + + mozilla::ipc::IPCResult RecvRawMessage( + const JSActorMessageMeta& aMeta, const Maybe<ClonedMessageData>& aData, + const Maybe<ClonedMessageData>& aStack); + + already_AddRefed<JSActor> InitJSActor(JS::Handle<JSObject*> aMaybeActor, + const nsACString& aName, + ErrorResult& aRv) override; + mozilla::ipc::IProtocol* AsNativeActor() override { return this; } + + mozilla::ipc::IPCResult RecvHistoryCommitIndexAndLength( + const MaybeDiscarded<BrowsingContext>& aContext, const uint32_t& aIndex, + const uint32_t& aLength, const nsID& aChangeID); + + mozilla::ipc::IPCResult RecvGetLayoutHistoryState( + const MaybeDiscarded<BrowsingContext>& aContext, + GetLayoutHistoryStateResolver&& aResolver); + + mozilla::ipc::IPCResult RecvDispatchLocationChangeEvent( + const MaybeDiscarded<BrowsingContext>& aContext); + + mozilla::ipc::IPCResult RecvDispatchBeforeUnloadToSubtree( + const MaybeDiscarded<BrowsingContext>& aStartingAt, + DispatchBeforeUnloadToSubtreeResolver&& aResolver); + + mozilla::ipc::IPCResult RecvDecoderSupportedMimeTypes( + nsTArray<nsCString>&& aSupportedTypes); + + mozilla::ipc::IPCResult RecvInitNextGenLocalStorageEnabled( + const bool& aEnabled); + + public: + static void DispatchBeforeUnloadToSubtree( + BrowsingContext* aStartingAt, + const DispatchBeforeUnloadToSubtreeResolver& aResolver); + + hal::ProcessPriority GetProcessPriority() const { return mProcessPriority; } + + hal::PerformanceHintSession* PerformanceHintSession() const { + return mPerformanceHintSession.get(); + } + + // Returns the target work duration for the PerformanceHintSession, based on + // the refresh interval. Estimate that we want the tick to complete in at most + // half of the refresh period. This is fairly arbitrary and can be tweaked + // later. + static TimeDuration GetPerformanceHintTarget(TimeDuration aRefreshInterval) { + return aRefreshInterval / int64_t(2); + } + + private: + void AddProfileToProcessName(const nsACString& aProfile); + mozilla::ipc::IPCResult RecvFlushFOGData(FlushFOGDataResolver&& aResolver); + + mozilla::ipc::IPCResult RecvUpdateMediaCodecsSupported( + RemoteDecodeIn aLocation, const media::MediaCodecsSupported& aSupported); + +#ifdef NIGHTLY_BUILD + virtual void OnChannelReceivedMessage(const Message& aMsg) override; + + virtual PContentChild::Result OnMessageReceived(const Message& aMsg) override; + + virtual PContentChild::Result OnMessageReceived( + const Message& aMsg, UniquePtr<Message>& aReply) override; +#endif + + void ConfigureThreadPerformanceHints(const hal::ProcessPriority& aPriority); + + nsTArray<mozilla::UniquePtr<AlertObserver>> mAlertObservers; + RefPtr<ConsoleListener> mConsoleListener; + + nsTHashSet<nsIObserver*> mIdleObservers; + + nsTArray<nsCString> mAvailableDictionaries; + + // Temporary storage for a list of available fonts, passed from the + // parent process and used to initialize gfx in the child. Currently used + // only on MacOSX and Linux. + dom::SystemFontList mFontList; + // Temporary storage for look and feel data. + FullLookAndFeel mLookAndFeelData; + // Temporary storage for list of shared-fontlist memory blocks. + nsTArray<base::SharedMemoryHandle> mSharedFontListBlocks; + + /** + * An ID unique to the process containing our corresponding + * content parent. + * + * We expect our content parent to set this ID immediately after opening a + * channel to us. + */ + ContentParentId mID; + + AppInfo mAppInfo; + + bool mIsForBrowser; + nsCString mRemoteType = NOT_REMOTE_TYPE; + bool mIsAlive; + nsCString mProcessName; + + static ContentChild* sSingleton; + + class ShutdownCanary; + static StaticAutoPtr<ShutdownCanary> sShutdownCanary; + + nsCOMPtr<nsIDomainPolicy> mPolicy; + nsCOMPtr<nsITimer> mForceKillTimer; + + RefPtr<ipc::SharedMap> mSharedData; + + RefPtr<ChildProfilerController> mProfilerController; + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + nsCOMPtr<nsIFile> mProfileDir; +#endif + + // Hashtable to keep track of the pending GetFilesHelper objects. + // This GetFilesHelperChild objects are removed when RecvGetFilesResponse is + // received. + nsRefPtrHashtable<nsIDHashKey, GetFilesHelperChild> mGetFilesPendingRequests; + + nsClassHashtable<nsUint64HashKey, AnonymousTemporaryFileCallback> + mPendingAnonymousTemporaryFiles; + + mozilla::Atomic<bool> mShuttingDown; + +#ifdef NIGHTLY_BUILD + // NOTE: This member is atomic because it can be accessed from + // off-main-thread. + mozilla::Atomic<uint32_t> mPendingInputEvents; +#endif + + uint32_t mNetworkLinkType = 0; + + // See `BrowsingContext::mEpochs` for an explanation of this field. + uint64_t mBrowsingContextFieldEpoch = 0; + + hal::ProcessPriority mProcessPriority = hal::PROCESS_PRIORITY_UNKNOWN; + + // Session created when the process priority is FOREGROUND to ensure high + // priority scheduling of important threads. (Currently main thread and style + // threads.) The work duration is reported by the RefreshDriverTimer. + UniquePtr<hal::PerformanceHintSession> mPerformanceHintSession; +}; + +inline nsISupports* ToSupports(mozilla::dom::ContentChild* aContentChild) { + return static_cast<nsIDOMProcessChild*>(aContentChild); +} + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_ContentChild_h diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp new file mode 100644 index 0000000000..13206216f3 --- /dev/null +++ b/dom/ipc/ContentParent.cpp @@ -0,0 +1,8184 @@ +/* -*- Mode: C++; tab-width: 2; 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/. */ + +#ifdef MOZ_WIDGET_ANDROID +# include "AndroidDecoderModule.h" +#endif + +#include "mozilla/AppShutdown.h" +#include "mozilla/DebugOnly.h" + +#include "base/basictypes.h" +#include "base/shared_memory.h" + +#include "ContentParent.h" +#include "mozilla/ipc/ProcessUtils.h" +#include "mozilla/CmdLineAndEnvUtils.h" +#include "BrowserParent.h" + +#include "chrome/common/process_watcher.h" +#include "mozilla/Result.h" +#include "mozilla/XREAppData.h" +#include "nsComponentManagerUtils.h" +#include "nsIBrowserDOMWindow.h" + +#include "GMPServiceParent.h" +#include "HandlerServiceParent.h" +#include "IHistory.h" +#include <map> +#include <utility> + +#include "ContentProcessManager.h" +#include "GeckoProfiler.h" +#include "Geolocation.h" +#include "GfxInfoBase.h" +#include "MMPrinter.h" +#include "PreallocatedProcessManager.h" +#include "ProcessPriorityManager.h" +#include "ProfilerParent.h" +#include "SandboxHal.h" +#include "SourceSurfaceRawData.h" +#include "mozilla/ipc/URIUtils.h" +#include "gfxPlatform.h" +#include "gfxPlatformFontList.h" +#include "nsDNSService2.h" +#include "nsPIDNSService.h" +#include "mozilla/AntiTrackingUtils.h" +#include "mozilla/AppShutdown.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/BenchmarkStorageParent.h" +#include "mozilla/Casting.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/ClipboardReadRequestParent.h" +#include "mozilla/ClipboardWriteRequestParent.h" +#include "mozilla/ContentBlockingUserInteraction.h" +#include "mozilla/FOGIPC.h" +#include "mozilla/GlobalStyleSheetCache.h" +#include "mozilla/GeckoArgs.h" +#include "mozilla/HangDetails.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/Maybe.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/Preferences.h" +#include "mozilla/PresShell.h" +#include "mozilla/ProcessHangMonitor.h" +#include "mozilla/ProcessHangMonitorIPC.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/RecursiveMutex.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/ScriptPreloader.h" +#include "mozilla/Components.h" +#include "mozilla/Sprintf.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StaticPrefs_fission.h" +#include "mozilla/StaticPrefs_media.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/StaticPrefs_widget.h" +#include "mozilla/StorageAccessAPIHelper.h" +#include "mozilla/StyleSheet.h" +#include "mozilla/StyleSheetInlines.h" +#include "mozilla/TaskController.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TelemetryIPC.h" +#include "mozilla/ThreadSafety.h" +#include "mozilla/Unused.h" +#include "mozilla/WebBrowserPersistDocumentParent.h" +#include "mozilla/devtools/HeapSnapshotTempFileHelperParent.h" +#include "mozilla/dom/BlobURLProtocolHandler.h" +#include "mozilla/dom/BrowserHost.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/BrowsingContextGroup.h" +#include "mozilla/dom/CancelContentJSOptionsBinding.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/ClientManager.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/DataTransfer.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/ExternalHelperAppParent.h" +#include "mozilla/dom/File.h" +#include "mozilla/dom/FileSystemSecurity.h" +#include "mozilla/dom/GeolocationBinding.h" +#include "mozilla/dom/GeolocationPositionError.h" +#include "mozilla/dom/GetFilesHelper.h" +#include "mozilla/dom/IPCBlobUtils.h" +#include "mozilla/dom/JSActorService.h" +#include "mozilla/dom/JSProcessActorBinding.h" +#include "mozilla/dom/LocalStorageCommon.h" +#include "mozilla/dom/MediaController.h" +#include "mozilla/dom/MemoryReportRequest.h" +#include "mozilla/dom/MediaStatusManager.h" +#include "mozilla/dom/Notification.h" +#include "mozilla/dom/PContentPermissionRequestParent.h" +#include "mozilla/dom/PCycleCollectWithLogsParent.h" +#include "mozilla/dom/ParentProcessMessageManager.h" +#include "mozilla/dom/Permissions.h" +#include "mozilla/dom/ProcessMessageManager.h" +#include "mozilla/dom/PushNotifier.h" +#include "mozilla/dom/ServiceWorkerManager.h" +#include "mozilla/dom/ServiceWorkerRegistrar.h" +#include "mozilla/dom/ServiceWorkerUtils.h" +#include "mozilla/dom/SessionHistoryEntry.h" +#include "mozilla/dom/SessionStorageManager.h" +#include "mozilla/dom/StorageIPC.h" +#include "mozilla/dom/UserActivation.h" +#include "mozilla/dom/URLClassifierParent.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/dom/ipc/SharedMap.h" +#include "mozilla/dom/ipc/StructuredCloneData.h" +#include "mozilla/dom/nsMixedContentBlocker.h" +#include "mozilla/dom/power/PowerManagerService.h" +#include "mozilla/dom/quota/QuotaManagerService.h" +#include "mozilla/extensions/ExtensionsParent.h" +#include "mozilla/extensions/StreamFilterParent.h" +#include "mozilla/gfx/GPUProcessManager.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/glean/GleanPings.h" +#include "mozilla/hal_sandbox/PHalParent.h" +#include "mozilla/intl/L10nRegistry.h" +#include "mozilla/intl/LocaleService.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/ipc/ByteBuf.h" +#include "mozilla/ipc/CrashReporterHost.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/FileDescriptorUtils.h" +#include "mozilla/ipc/IPCStreamUtils.h" +#include "mozilla/ipc/TestShellParent.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/ImageBridgeParent.h" +#include "mozilla/layers/LayerTreeOwnerTracker.h" +#include "mozilla/layers/PAPZParent.h" +#include "mozilla/loader/ScriptCacheActors.h" +#include "mozilla/media/MediaParent.h" +#include "mozilla/mozSpellChecker.h" +#include "mozilla/net/CookieServiceParent.h" +#include "mozilla/net/NeckoMessageUtils.h" +#include "mozilla/net/NeckoParent.h" +#include "mozilla/net/PCookieServiceParent.h" +#include "mozilla/net/CookieKey.h" +#include "mozilla/net/TRRService.h" +#include "mozilla/TelemetryComms.h" +#include "mozilla/TelemetryEventEnums.h" +#include "mozilla/RemoteLazyInputStreamParent.h" +#include "mozilla/widget/RemoteLookAndFeel.h" +#include "mozilla/widget/ScreenManager.h" +#include "mozilla/widget/TextRecognition.h" +#include "nsAnonymousTemporaryFile.h" +#include "nsAppRunner.h" +#include "nsCExternalHandlerService.h" +#include "nsCOMPtr.h" +#include "nsChromeRegistryChrome.h" +#include "nsConsoleMessage.h" +#include "nsConsoleService.h" +#include "nsContentPermissionHelper.h" +#include "nsContentUtils.h" +#include "nsCRT.h" +#include "nsDebugImpl.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDocShell.h" +#include "nsEmbedCID.h" +#include "nsFocusManager.h" +#include "nsFrameLoader.h" +#include "nsFrameMessageManager.h" +#include "nsGlobalWindowOuter.h" +#include "nsHashPropertyBag.h" +#include "nsHyphenationManager.h" +#include "nsIAlertsService.h" +#include "nsIAppShell.h" +#include "nsIAppWindow.h" +#include "nsIAsyncInputStream.h" +#include "nsIBidiKeyboard.h" +#include "nsICaptivePortalService.h" +#include "nsICertOverrideService.h" +#include "nsIClipboard.h" +#include "nsIContentAnalysis.h" +#include "nsIContentProcess.h" +#include "nsIContentSecurityPolicy.h" +#include "nsICookie.h" +#include "nsICrashService.h" +#include "nsICycleCollectorListener.h" +#include "nsIDocShell.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIDragService.h" +#include "nsIExternalProtocolService.h" +#include "nsIGfxInfo.h" +#include "nsIUserIdleService.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsILocalStorageManager.h" +#include "nsIMemoryInfoDumper.h" +#include "nsIMemoryReporter.h" +#include "nsIMozBrowserFrame.h" +#include "nsINetworkLinkService.h" +#include "nsIObserverService.h" +#include "nsIParentChannel.h" +#include "nsIScriptError.h" +#include "nsIScriptSecurityManager.h" +#include "nsIServiceWorkerManager.h" +#include "nsISiteSecurityService.h" +#include "nsIStringBundle.h" +#include "nsITimer.h" +#include "nsIURL.h" +#include "nsIWebBrowserChrome.h" +#include "nsIX509Cert.h" +#include "nsIXULRuntime.h" +#include "nsICookieNotification.h" +#if defined(MOZ_WIDGET_GTK) || defined(XP_WIN) +# include "nsIconChannel.h" +#endif +#include "nsMemoryInfoDumper.h" +#include "nsMemoryReporterManager.h" +#include "nsOpenURIInFrameParams.h" +#include "nsPIWindowWatcher.h" +#include "nsQueryObject.h" +#include "nsReadableUtils.h" +#include "nsSHistory.h" +#include "nsScriptError.h" +#include "nsServiceManagerUtils.h" +#include "nsStreamUtils.h" +#include "nsStyleSheetService.h" +#include "nsThread.h" +#include "nsThreadUtils.h" +#include "nsWidgetsCID.h" +#include "nsWindowWatcher.h" +#include "prenv.h" +#include "prio.h" +#include "private/pprio.h" +#include "xpcpublic.h" +#include "nsOpenWindowInfo.h" +#include "nsFrameLoaderOwner.h" + +#ifdef MOZ_WEBRTC +# include "jsapi/WebrtcGlobalParent.h" +#endif + +#if defined(XP_MACOSX) +# include "nsMacUtilsImpl.h" +# include "mozilla/AvailableMemoryWatcher.h" +#endif + +#if defined(ANDROID) || defined(LINUX) +# include "nsSystemInfo.h" +#endif + +#if defined(XP_LINUX) +# include "mozilla/Hal.h" +#endif + +#ifdef ANDROID +# include "gfxAndroidPlatform.h" +#endif + +#include "mozilla/PermissionManager.h" + +#ifdef MOZ_WIDGET_ANDROID +# include "AndroidBridge.h" +# include "mozilla/java/GeckoProcessManagerWrappers.h" +# include "mozilla/java/GeckoProcessTypeWrappers.h" +#endif + +#ifdef MOZ_WIDGET_GTK +# include <gdk/gdk.h> +# include "mozilla/WidgetUtilsGtk.h" +#endif + +#include "mozilla/RemoteSpellCheckEngineParent.h" + +#include "Crypto.h" + +#ifdef MOZ_WEBSPEECH +# include "mozilla/dom/SpeechSynthesisParent.h" +#endif + +#if defined(MOZ_SANDBOX) +# include "mozilla/SandboxSettings.h" +# if defined(XP_LINUX) +# include "mozilla/SandboxInfo.h" +# include "mozilla/SandboxBroker.h" +# include "mozilla/SandboxBrokerPolicyFactory.h" +# endif +# if defined(XP_MACOSX) +# include "mozilla/Sandbox.h" +# endif +#endif + +#ifdef XP_WIN +# include "mozilla/widget/AudioSession.h" +# include "mozilla/WinDllServices.h" +#endif + +#ifdef MOZ_CODE_COVERAGE +# include "mozilla/CodeCoverageHandler.h" +#endif + +#ifdef FUZZING_SNAPSHOT +# include "mozilla/fuzzing/IPCFuzzController.h" +#endif + +// For VP9Benchmark::sBenchmarkFpsPref +#include "Benchmark.h" + +#include "mozilla/RemoteDecodeUtils.h" +#include "nsIToolkitProfileService.h" +#include "nsIToolkitProfile.h" + +static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID); + +using base::KillProcess; + +using namespace CrashReporter; +using namespace mozilla::dom::power; +using namespace mozilla::media; +using namespace mozilla::embedding; +using namespace mozilla::gfx; +using namespace mozilla::gmp; +using namespace mozilla::hal; +using namespace mozilla::ipc; +using namespace mozilla::intl; +using namespace mozilla::layers; +using namespace mozilla::layout; +using namespace mozilla::net; +using namespace mozilla::psm; +using namespace mozilla::widget; +using namespace mozilla::Telemetry; +using mozilla::loader::PScriptCacheParent; +using mozilla::Telemetry::ProcessID; + +extern mozilla::LazyLogModule gFocusLog; + +#define LOGFOCUS(args) MOZ_LOG(gFocusLog, mozilla::LogLevel::Debug, args) + +extern mozilla::LazyLogModule sPDMLog; +#define LOGPDM(...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) + +namespace mozilla { +namespace CubebUtils { +extern FileDescriptor CreateAudioIPCConnection(); +} + +namespace dom { + +LazyLogModule gProcessLog("Process"); + +static std::map<RemoteDecodeIn, media::MediaCodecsSupported> sCodecsSupported; + +/* static */ +uint32_t ContentParent::sMaxContentProcesses = 0; + +/* static */ +LogModule* ContentParent::GetLog() { return gProcessLog; } + +/* static */ +uint32_t ContentParent::sPageLoadEventCounter = 0; + +#define NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC "ipc:network:set-offline" +#define NS_IPC_IOSERVICE_SET_CONNECTIVITY_TOPIC "ipc:network:set-connectivity" + +// IPC receiver for remote GC/CC logging. +class CycleCollectWithLogsParent final : public PCycleCollectWithLogsParent { + public: + MOZ_COUNTED_DTOR(CycleCollectWithLogsParent) + + static bool AllocAndSendConstructor(ContentParent* aManager, + bool aDumpAllTraces, + nsICycleCollectorLogSink* aSink, + nsIDumpGCAndCCLogsCallback* aCallback) { + CycleCollectWithLogsParent* actor; + FILE* gcLog; + FILE* ccLog; + nsresult rv; + + actor = new CycleCollectWithLogsParent(aSink, aCallback); + rv = actor->mSink->Open(&gcLog, &ccLog); + if (NS_WARN_IF(NS_FAILED(rv))) { + delete actor; + return false; + } + + return aManager->SendPCycleCollectWithLogsConstructor( + actor, aDumpAllTraces, FILEToFileDescriptor(gcLog), + FILEToFileDescriptor(ccLog)); + } + + private: + virtual mozilla::ipc::IPCResult RecvCloseGCLog() override { + Unused << mSink->CloseGCLog(); + return IPC_OK(); + } + + virtual mozilla::ipc::IPCResult RecvCloseCCLog() override { + Unused << mSink->CloseCCLog(); + return IPC_OK(); + } + + virtual mozilla::ipc::IPCResult Recv__delete__() override { + // Report completion to mCallback only on successful + // completion of the protocol. + nsCOMPtr<nsIFile> gcLog, ccLog; + mSink->GetGcLog(getter_AddRefs(gcLog)); + mSink->GetCcLog(getter_AddRefs(ccLog)); + Unused << mCallback->OnDump(gcLog, ccLog, /* parent = */ false); + return IPC_OK(); + } + + virtual void ActorDestroy(ActorDestroyReason aReason) override { + // If the actor is unexpectedly destroyed, we deliberately + // don't call Close[GC]CLog on the sink, because the logs may + // be incomplete. See also the nsCycleCollectorLogSinkToFile + // implementaiton of those methods, and its destructor. + } + + CycleCollectWithLogsParent(nsICycleCollectorLogSink* aSink, + nsIDumpGCAndCCLogsCallback* aCallback) + : mSink(aSink), mCallback(aCallback) { + MOZ_COUNT_CTOR(CycleCollectWithLogsParent); + } + + nsCOMPtr<nsICycleCollectorLogSink> mSink; + nsCOMPtr<nsIDumpGCAndCCLogsCallback> mCallback; +}; + +// A memory reporter for ContentParent objects themselves. +class ContentParentsMemoryReporter final : public nsIMemoryReporter { + ~ContentParentsMemoryReporter() = default; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMEMORYREPORTER +}; + +NS_IMPL_ISUPPORTS(ContentParentsMemoryReporter, nsIMemoryReporter) + +NS_IMETHODIMP +ContentParentsMemoryReporter::CollectReports( + nsIHandleReportCallback* aHandleReport, nsISupports* aData, + bool aAnonymize) { + AutoTArray<ContentParent*, 16> cps; + ContentParent::GetAllEvenIfDead(cps); + + for (uint32_t i = 0; i < cps.Length(); i++) { + ContentParent* cp = cps[i]; + MessageChannel* channel = cp->GetIPCChannel(); + + nsString friendlyName; + cp->FriendlyName(friendlyName, aAnonymize); + + cp->AddRef(); + nsrefcnt refcnt = cp->Release(); + + const char* channelStr = "no channel"; + uint32_t numQueuedMessages = 0; + if (channel) { + if (channel->IsClosed()) { + channelStr = "closed channel"; + } else { + channelStr = "open channel"; + } + numQueuedMessages = + 0; // XXX was channel->Unsound_NumQueuedMessages(); Bug 1754876 + } + + nsPrintfCString path( + "queued-ipc-messages/content-parent" + "(%s, pid=%d, %s, 0x%p, refcnt=%" PRIuPTR ")", + NS_ConvertUTF16toUTF8(friendlyName).get(), cp->Pid(), channelStr, + static_cast<nsIObserver*>(cp), refcnt); + + constexpr auto desc = + "The number of unset IPC messages held in this ContentParent's " + "channel. A large value here might indicate that we're leaking " + "messages. Similarly, a ContentParent object for a process that's no " + "longer running could indicate that we're leaking ContentParents."_ns; + + aHandleReport->Callback(/* process */ ""_ns, path, KIND_OTHER, UNITS_COUNT, + numQueuedMessages, desc, aData); + } + + return NS_OK; +} + +// A hashtable (by type) of processes/ContentParents. This includes +// processes that are in the Preallocator cache (which would be type +// 'prealloc'), and recycled processes ('web' and in the future +// eTLD+1-locked) processes). +nsClassHashtable<nsCStringHashKey, nsTArray<ContentParent*>>* + ContentParent::sBrowserContentParents; + +namespace { + +uint64_t ComputeLoadedOriginHash(nsIPrincipal* aPrincipal) { + uint32_t originNoSuffix = + BasePrincipal::Cast(aPrincipal)->GetOriginNoSuffixHash(); + uint32_t originSuffix = + BasePrincipal::Cast(aPrincipal)->GetOriginSuffixHash(); + + return ((uint64_t)originNoSuffix) << 32 | originSuffix; +} + +class ScriptableCPInfo final : public nsIContentProcessInfo { + public: + explicit ScriptableCPInfo(ContentParent* aParent) : mContentParent(aParent) { + MOZ_ASSERT(mContentParent); + } + + NS_DECL_ISUPPORTS + NS_DECL_NSICONTENTPROCESSINFO + + void ProcessDied() { mContentParent = nullptr; } + + private: + ~ScriptableCPInfo() { MOZ_ASSERT(!mContentParent, "must call ProcessDied"); } + + ContentParent* mContentParent; +}; + +NS_IMPL_ISUPPORTS(ScriptableCPInfo, nsIContentProcessInfo) + +NS_IMETHODIMP +ScriptableCPInfo::GetIsAlive(bool* aIsAlive) { + *aIsAlive = mContentParent != nullptr; + return NS_OK; +} + +NS_IMETHODIMP +ScriptableCPInfo::GetProcessId(int32_t* aPID) { + if (!mContentParent) { + *aPID = -1; + return NS_ERROR_NOT_INITIALIZED; + } + + *aPID = mContentParent->Pid(); + if (*aPID == -1) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +ScriptableCPInfo::GetTabCount(int32_t* aTabCount) { + if (!mContentParent) { + return NS_ERROR_NOT_INITIALIZED; + } + + ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); + *aTabCount = cpm->GetBrowserParentCountByProcessId(mContentParent->ChildID()); + + return NS_OK; +} + +NS_IMETHODIMP +ScriptableCPInfo::GetMessageManager(nsISupports** aMessenger) { + *aMessenger = nullptr; + if (!mContentParent) { + return NS_ERROR_NOT_INITIALIZED; + } + + RefPtr<ProcessMessageManager> manager = mContentParent->GetMessageManager(); + manager.forget(aMessenger); + return NS_OK; +} + +ProcessID GetTelemetryProcessID(const nsACString& remoteType) { + // OOP WebExtensions run in a content process. + // For Telemetry though we want to break out collected data from the + // WebExtensions process into a separate bucket, to make sure we can analyze + // it separately and avoid skewing normal content process metrics. + return remoteType == EXTENSION_REMOTE_TYPE ? ProcessID::Extension + : ProcessID::Content; +} + +} // anonymous namespace + +StaticAutoPtr<LinkedList<ContentParent>> ContentParent::sContentParents; +StaticRefPtr<ContentParent> ContentParent::sRecycledE10SProcess; +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) +StaticAutoPtr<SandboxBrokerPolicyFactory> + ContentParent::sSandboxBrokerPolicyFactory; +#endif +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +StaticAutoPtr<std::vector<std::string>> ContentParent::sMacSandboxParams; +#endif + +// Set to true when the first content process gets created. +static bool sCreatedFirstContentProcess = false; + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED +// True when we're running the process selection code, and do not expect to +// enter code paths where processes may die. +static bool sInProcessSelector = false; +#endif + +// The first content child has ID 1, so the chrome process can have ID 0. +static uint64_t gContentChildID = 1; + +static const char* sObserverTopics[] = { + NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC, + NS_IPC_IOSERVICE_SET_CONNECTIVITY_TOPIC, + NS_IPC_CAPTIVE_PORTAL_SET_STATE, + "application-background", + "application-foreground", + "memory-pressure", + "child-gc-request", + "child-cc-request", + "child-mmu-request", + "child-ghost-request", + "last-pb-context-exited", + "file-watcher-update", +#ifdef ACCESSIBILITY + "a11y-init-or-shutdown", +#endif + "cacheservice:empty-cache", + "intl:app-locales-changed", + "intl:requested-locales-changed", + "cookie-changed", + "private-cookie-changed", + NS_NETWORK_LINK_TYPE_TOPIC, + NS_NETWORK_TRR_MODE_CHANGED_TOPIC, + "network:socket-process-crashed", + DEFAULT_TIMEZONE_CHANGED_OBSERVER_TOPIC, +}; + +// PreallocateProcess is called by the PreallocatedProcessManager. +// ContentParent then takes this process back within GetNewOrUsedBrowserProcess. +/*static*/ already_AddRefed<ContentParent> +ContentParent::MakePreallocProcess() { + RefPtr<ContentParent> process = new ContentParent(PREALLOC_REMOTE_TYPE); + return process.forget(); +} + +/*static*/ +void ContentParent::StartUp() { + // FIXME Bug 1023701 - Stop using ContentParent static methods in + // child process + if (!XRE_IsParentProcess()) { + return; + } + + // From this point on, NS_WARNING, NS_ASSERTION, etc. should print out the + // PID along with the warning. + nsDebugImpl::SetMultiprocessMode("Parent"); + + // Note: This reporter measures all ContentParents. + RegisterStrongMemoryReporter(new ContentParentsMemoryReporter()); + + BackgroundChild::Startup(); + ClientManager::Startup(); + + Preferences::RegisterCallbackAndCall(&OnFissionBlocklistPrefChange, + kFissionEnforceBlockList); + Preferences::RegisterCallbackAndCall(&OnFissionBlocklistPrefChange, + kFissionOmitBlockListValues); + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + sSandboxBrokerPolicyFactory = new SandboxBrokerPolicyFactory(); +#endif + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + sMacSandboxParams = new std::vector<std::string>(); +#endif +} + +/*static*/ +void ContentParent::ShutDown() { + // For the most, we rely on normal process shutdown and + // ClearOnShutdown() to clean up our state. + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + sSandboxBrokerPolicyFactory = nullptr; +#endif + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + sMacSandboxParams = nullptr; +#endif +} + +/*static*/ +uint32_t ContentParent::GetPoolSize(const nsACString& aContentProcessType) { + if (!sBrowserContentParents) { + return 0; + } + + nsTArray<ContentParent*>* parents = + sBrowserContentParents->Get(aContentProcessType); + + return parents ? parents->Length() : 0; +} + +/*static*/ nsTArray<ContentParent*>& ContentParent::GetOrCreatePool( + const nsACString& aContentProcessType) { + if (!sBrowserContentParents) { + sBrowserContentParents = + new nsClassHashtable<nsCStringHashKey, nsTArray<ContentParent*>>; + } + + return *sBrowserContentParents->GetOrInsertNew(aContentProcessType); +} + +const nsDependentCSubstring RemoteTypePrefix( + const nsACString& aContentProcessType) { + // The suffix after a `=` in a remoteType is dynamic, and used to control the + // process pool to use. + int32_t equalIdx = aContentProcessType.FindChar(L'='); + if (equalIdx == kNotFound) { + equalIdx = aContentProcessType.Length(); + } + return StringHead(aContentProcessType, equalIdx); +} + +bool IsWebRemoteType(const nsACString& aContentProcessType) { + // Note: matches webIsolated, web, and webCOOP+COEP types. + return StringBeginsWith(aContentProcessType, DEFAULT_REMOTE_TYPE); +} + +bool IsWebCoopCoepRemoteType(const nsACString& aContentProcessType) { + return StringBeginsWith(aContentProcessType, + WITH_COOP_COEP_REMOTE_TYPE_PREFIX); +} + +bool IsExtensionRemoteType(const nsACString& aContentProcessType) { + return aContentProcessType == EXTENSION_REMOTE_TYPE; +} + +/*static*/ +uint32_t ContentParent::GetMaxProcessCount( + const nsACString& aContentProcessType) { + // Max process count is based only on the prefix. + const nsDependentCSubstring processTypePrefix = + RemoteTypePrefix(aContentProcessType); + + // Check for the default remote type of "web", as it uses different prefs. + if (processTypePrefix == DEFAULT_REMOTE_TYPE) { + return GetMaxWebProcessCount(); + } + + // Read the pref controling this remote type. `dom.ipc.processCount` is not + // used as a fallback, as it is intended to control the number of "web" + // content processes, checked in `mozilla::GetMaxWebProcessCount()`. + nsAutoCString processCountPref("dom.ipc.processCount."); + processCountPref.Append(processTypePrefix); + + int32_t maxContentParents = Preferences::GetInt(processCountPref.get(), 1); + if (maxContentParents < 1) { + maxContentParents = 1; + } + + return static_cast<uint32_t>(maxContentParents); +} + +/*static*/ +bool ContentParent::IsMaxProcessCountReached( + const nsACString& aContentProcessType) { + return GetPoolSize(aContentProcessType) >= + GetMaxProcessCount(aContentProcessType); +} + +// Really more ReleaseUnneededProcesses() +/*static*/ +void ContentParent::ReleaseCachedProcesses() { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("ReleaseCachedProcesses:")); + if (!sBrowserContentParents) { + return; + } + +#ifdef DEBUG + for (const auto& cps : *sBrowserContentParents) { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("%s: %zu processes", PromiseFlatCString(cps.GetKey()).get(), + cps.GetData()->Length())); + } +#endif + + // First let's collect all processes and keep a grip. + AutoTArray<RefPtr<ContentParent>, 32> fixArray; + for (const auto& contentParents : sBrowserContentParents->Values()) { + for (auto* cp : *contentParents) { + fixArray.AppendElement(cp); + } + } + + for (const auto& cp : fixArray) { + // Ensure the process cannot be claimed between check and MarkAsDead. + RecursiveMutexAutoLock lock(cp->ThreadsafeHandleMutex()); + + if (cp->ManagedPBrowserParent().Count() == 0 && !cp->HasActiveWorker() && + cp->mRemoteType == DEFAULT_REMOTE_TYPE) { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + (" Shutdown %p (%s)", cp.get(), cp->mRemoteType.get())); + + PreallocatedProcessManager::Erase(cp); + // Make sure we don't select this process for new tabs or workers. + cp->MarkAsDead(); + // Start a soft shutdown. + cp->ShutDownProcess(SEND_SHUTDOWN_MESSAGE); + // Make sure that this process is no longer accessible from JS by its + // message manager. + cp->ShutDownMessageManager(); + } else { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + (" Skipping %p (%s), count %d, HasActiveWorker %d", cp.get(), + cp->mRemoteType.get(), cp->ManagedPBrowserParent().Count(), + cp->HasActiveWorker())); + } + } +} + +/*static*/ +already_AddRefed<ContentParent> ContentParent::MinTabSelect( + const nsTArray<ContentParent*>& aContentParents, + int32_t aMaxContentParents) { + uint32_t maxSelectable = + std::min(static_cast<uint32_t>(aContentParents.Length()), + static_cast<uint32_t>(aMaxContentParents)); + uint32_t min = INT_MAX; + RefPtr<ContentParent> candidate; + ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); + + for (uint32_t i = 0; i < maxSelectable; i++) { + ContentParent* p = aContentParents[i]; + MOZ_DIAGNOSTIC_ASSERT(!p->IsDead()); + + // Ignore processes that were slated for removal but not yet removed from + // the pool (see also GetUsedBrowserProcess and BlockShutdown). + if (!p->IsShuttingDown()) { + uint32_t tabCount = cpm->GetBrowserParentCountByProcessId(p->ChildID()); + if (tabCount < min) { + candidate = p; + min = tabCount; + } + } + } + + // If all current processes have at least one tab and we have not yet reached + // the maximum, use a new process. + if (min > 0 && + aContentParents.Length() < static_cast<uint32_t>(aMaxContentParents)) { + return nullptr; + } + + // Otherwise we return candidate. + return candidate.forget(); +} + +/* static */ +already_AddRefed<nsIPrincipal> +ContentParent::CreateRemoteTypeIsolationPrincipal( + const nsACString& aRemoteType) { + if ((RemoteTypePrefix(aRemoteType) != FISSION_WEB_REMOTE_TYPE) && + !StringBeginsWith(aRemoteType, WITH_COOP_COEP_REMOTE_TYPE_PREFIX)) { + return nullptr; + } + + int32_t offset = aRemoteType.FindChar('=') + 1; + MOZ_ASSERT(offset > 1, "can not extract origin from that remote type"); + nsAutoCString origin( + Substring(aRemoteType, offset, aRemoteType.Length() - offset)); + + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + nsCOMPtr<nsIPrincipal> principal; + ssm->CreateContentPrincipalFromOrigin(origin, getter_AddRefs(principal)); + return principal.forget(); +} + +/*static*/ +already_AddRefed<ContentParent> ContentParent::GetUsedBrowserProcess( + const nsACString& aRemoteType, nsTArray<ContentParent*>& aContentParents, + uint32_t aMaxContentParents, bool aPreferUsed, ProcessPriority aPriority) { +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + AutoRestore ar(sInProcessSelector); + sInProcessSelector = true; +#endif + + uint32_t numberOfParents = aContentParents.Length(); + nsTArray<RefPtr<nsIContentProcessInfo>> infos(numberOfParents); + for (auto* cp : aContentParents) { + infos.AppendElement(cp->mScriptableHelper); + } + + if (aPreferUsed && numberOfParents) { + // If we prefer re-using existing content processes, we don't want to create + // a new process, and instead re-use an existing one, so pretend the process + // limit is at the current number of processes. + aMaxContentParents = numberOfParents; + } + + nsCOMPtr<nsIContentProcessProvider> cpp = + do_GetService("@mozilla.org/ipc/processselector;1"); + int32_t index; + if (cpp && NS_SUCCEEDED(cpp->ProvideProcess(aRemoteType, infos, + aMaxContentParents, &index))) { + // If the provider returned an existing ContentParent, use that one. + if (0 <= index && static_cast<uint32_t>(index) <= aMaxContentParents) { + RefPtr<ContentParent> retval = aContentParents[index]; + // Ignore processes that were slated for removal but not yet removed from + // the pool. + if (!retval->IsShuttingDown()) { + if (profiler_thread_is_being_profiled_for_markers()) { + nsPrintfCString marker("Reused process %u", + (unsigned int)retval->ChildID()); + PROFILER_MARKER_TEXT("Process", DOM, {}, marker); + } + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("GetUsedProcess: Reused process %p (%u) for %s", retval.get(), + (unsigned int)retval->ChildID(), + PromiseFlatCString(aRemoteType).get())); + retval->AssertAlive(); + retval->StopRecyclingE10SOnly(true); + return retval.forget(); + } + } + } else { + // If there was a problem with the JS chooser, fall back to a random + // selection. + NS_WARNING("nsIContentProcessProvider failed to return a process"); + RefPtr<ContentParent> random; + if ((random = MinTabSelect(aContentParents, aMaxContentParents))) { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("GetUsedProcess: Reused random process %p (%d) for %s", + random.get(), (unsigned int)random->ChildID(), + PromiseFlatCString(aRemoteType).get())); + random->AssertAlive(); + random->StopRecyclingE10SOnly(true); + return random.forget(); + } + } + + // If we are loading into the "web" remote type, are choosing to launch a new + // tab, and have a recycled E10S process, we should launch into that process. + if (aRemoteType == DEFAULT_REMOTE_TYPE && sRecycledE10SProcess) { + RefPtr<ContentParent> recycled = sRecycledE10SProcess; + MOZ_DIAGNOSTIC_ASSERT(recycled->GetRemoteType() == DEFAULT_REMOTE_TYPE); + recycled->AssertAlive(); + recycled->StopRecyclingE10SOnly(true); + if (profiler_thread_is_being_profiled_for_markers()) { + nsPrintfCString marker("Recycled process %u (%p)", + (unsigned int)recycled->ChildID(), recycled.get()); + PROFILER_MARKER_TEXT("Process", DOM, {}, marker); + } + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("Recycled process %p", recycled.get())); + + return recycled.forget(); + } + + // Try to take a preallocated process except for certain remote types. + // Note: this process may not have finished launching yet + RefPtr<ContentParent> preallocated; + if (aRemoteType != FILE_REMOTE_TYPE && + aRemoteType != PRIVILEGEDABOUT_REMOTE_TYPE && + aRemoteType != EXTENSION_REMOTE_TYPE && // Bug 1638119 + (preallocated = PreallocatedProcessManager::Take(aRemoteType))) { + MOZ_DIAGNOSTIC_ASSERT(preallocated->GetRemoteType() == + PREALLOC_REMOTE_TYPE); + MOZ_DIAGNOSTIC_ASSERT(sRecycledE10SProcess != preallocated); + preallocated->AssertAlive(); + + if (profiler_thread_is_being_profiled_for_markers()) { + nsPrintfCString marker( + "Assigned preallocated process %u%s", + (unsigned int)preallocated->ChildID(), + preallocated->IsLaunching() ? " (still launching)" : ""); + PROFILER_MARKER_TEXT("Process", DOM, {}, marker); + } + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("Adopted preallocated process %p for type %s%s", + preallocated.get(), PromiseFlatCString(aRemoteType).get(), + preallocated->IsLaunching() ? " (still launching)" : "")); + + // This ensures that the preallocator won't shut down the process once + // it finishes starting + preallocated->mRemoteType.Assign(aRemoteType); + { + RecursiveMutexAutoLock lock(preallocated->mThreadsafeHandle->mMutex); + preallocated->mThreadsafeHandle->mRemoteType = preallocated->mRemoteType; + } + preallocated->mRemoteTypeIsolationPrincipal = + CreateRemoteTypeIsolationPrincipal(aRemoteType); + preallocated->mActivateTS = TimeStamp::Now(); + preallocated->AddToPool(aContentParents); + + // rare, but will happen + if (!preallocated->IsLaunching()) { + // Specialize this process for the appropriate remote type, and activate + // it. + + Unused << preallocated->SendRemoteType(preallocated->mRemoteType, + preallocated->mProfile); + + nsCOMPtr<nsIObserverService> obs = + mozilla::services::GetObserverService(); + if (obs) { + nsAutoString cpId; + cpId.AppendInt(static_cast<uint64_t>(preallocated->ChildID())); + obs->NotifyObservers(static_cast<nsIObserver*>(preallocated), + "process-type-set", cpId.get()); + preallocated->AssertAlive(); + } + } + return preallocated.forget(); + } + + return nullptr; +} + +/*static*/ +already_AddRefed<ContentParent> +ContentParent::GetNewOrUsedLaunchingBrowserProcess( + const nsACString& aRemoteType, BrowsingContextGroup* aGroup, + ProcessPriority aPriority, bool aPreferUsed) { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("GetNewOrUsedProcess for type %s", + PromiseFlatCString(aRemoteType).get())); + + // Fallback check (we really want our callers to avoid this). + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + MOZ_DIAGNOSTIC_ASSERT( + false, "Late attempt to GetNewOrUsedLaunchingBrowserProcess!"); + return nullptr; + } + + // If we have an existing host process attached to this BrowsingContextGroup, + // always return it, as we can never have multiple host processes within a + // single BrowsingContextGroup. + RefPtr<ContentParent> contentParent; + if (aGroup) { + contentParent = aGroup->GetHostProcess(aRemoteType); + Unused << NS_WARN_IF(contentParent && contentParent->IsShuttingDown()); + if (contentParent && !contentParent->IsShuttingDown()) { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("GetNewOrUsedProcess: Existing host process %p (launching %d)", + contentParent.get(), contentParent->IsLaunching())); + contentParent->AssertAlive(); + contentParent->StopRecyclingE10SOnly(true); + return contentParent.forget(); + } + } + + nsTArray<ContentParent*>& contentParents = GetOrCreatePool(aRemoteType); + uint32_t maxContentParents = GetMaxProcessCount(aRemoteType); + + // Let's try and reuse an existing process. + contentParent = GetUsedBrowserProcess( + aRemoteType, contentParents, maxContentParents, aPreferUsed, aPriority); + + if (!contentParent) { + // No reusable process. Let's create and launch one. + // The life cycle will be set to `LifecycleState::LAUNCHING`. + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("Launching new process immediately for type %s", + PromiseFlatCString(aRemoteType).get())); + + contentParent = new ContentParent(aRemoteType); + if (NS_WARN_IF(!contentParent->BeginSubprocessLaunch(aPriority))) { + // Launch aborted because of shutdown. Bailout. + contentParent->LaunchSubprocessReject(); + return nullptr; + } + // Until the new process is ready let's not allow to start up any + // preallocated processes. The blocker will be removed once we receive + // the first idle message. + contentParent->mIsAPreallocBlocker = true; + PreallocatedProcessManager::AddBlocker(aRemoteType, contentParent); + + // Store this process for future reuse. + contentParent->AddToPool(contentParents); + + MOZ_LOG( + ContentParent::GetLog(), LogLevel::Debug, + ("GetNewOrUsedProcess: new immediate process %p", contentParent.get())); + } + // else we have an existing or preallocated process (which may be + // still launching) + + contentParent->AssertAlive(); + contentParent->StopRecyclingE10SOnly(true); + if (aGroup) { + aGroup->EnsureHostProcess(contentParent); + } + return contentParent.forget(); +} + +/*static*/ +RefPtr<ContentParent::LaunchPromise> +ContentParent::GetNewOrUsedBrowserProcessAsync(const nsACString& aRemoteType, + BrowsingContextGroup* aGroup, + ProcessPriority aPriority, + bool aPreferUsed) { + // Obtain a `ContentParent` launched asynchronously. + RefPtr<ContentParent> contentParent = GetNewOrUsedLaunchingBrowserProcess( + aRemoteType, aGroup, aPriority, aPreferUsed); + if (!contentParent) { + // In case of launch error, stop here. + return LaunchPromise::CreateAndReject(NS_ERROR_ILLEGAL_DURING_SHUTDOWN, + __func__); + } + return contentParent->WaitForLaunchAsync(aPriority); +} + +/*static*/ +already_AddRefed<ContentParent> ContentParent::GetNewOrUsedBrowserProcess( + const nsACString& aRemoteType, BrowsingContextGroup* aGroup, + ProcessPriority aPriority, bool aPreferUsed) { + RefPtr<ContentParent> contentParent = GetNewOrUsedLaunchingBrowserProcess( + aRemoteType, aGroup, aPriority, aPreferUsed); + if (!contentParent || !contentParent->WaitForLaunchSync(aPriority)) { + // In case of launch error, stop here. + return nullptr; + } + return contentParent.forget(); +} + +RefPtr<ContentParent::LaunchPromise> ContentParent::WaitForLaunchAsync( + ProcessPriority aPriority) { + MOZ_DIAGNOSTIC_ASSERT(!IsDead()); + if (!IsLaunching()) { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("WaitForLaunchAsync: launched")); + return LaunchPromise::CreateAndResolve(this, __func__); + } + + // We've started an async content process launch. + Telemetry::Accumulate(Telemetry::CONTENT_PROCESS_LAUNCH_IS_SYNC, 0); + + // We have located a process that hasn't finished initializing, then attempt + // to finish initializing. Both `LaunchSubprocessResolve` and + // `LaunchSubprocessReject` are safe to call multiple times if we race with + // other `WaitForLaunchAsync` callbacks. + return mSubprocess->WhenProcessHandleReady()->Then( + GetCurrentSerialEventTarget(), __func__, + [self = RefPtr{this}, aPriority]() { + if (self->LaunchSubprocessResolve(/* aIsSync = */ false, aPriority)) { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("WaitForLaunchAsync: async, now launched")); + self->mActivateTS = TimeStamp::Now(); + return LaunchPromise::CreateAndResolve(self, __func__); + } + + self->LaunchSubprocessReject(); + return LaunchPromise::CreateAndReject(NS_ERROR_INVALID_ARG, __func__); + }, + [self = RefPtr{this}]() { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("WaitForLaunchAsync: async, rejected")); + self->LaunchSubprocessReject(); + return LaunchPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + }); +} + +bool ContentParent::WaitForLaunchSync(ProcessPriority aPriority) { + MOZ_DIAGNOSTIC_ASSERT(!IsDead()); + if (!IsLaunching()) { + return true; + } + + // We've started a sync content process launch. + Telemetry::Accumulate(Telemetry::CONTENT_PROCESS_LAUNCH_IS_SYNC, 1); + + // We're a process which hasn't finished initializing. We may be racing + // against whoever launched it (and whoever else is already racing). Since + // we're sync, we win the race and finish the initialization. + bool launchSuccess = mSubprocess->WaitForProcessHandle(); + if (launchSuccess && + LaunchSubprocessResolve(/* aIsSync = */ true, aPriority)) { + mActivateTS = TimeStamp::Now(); + return true; + } + // In case of failure. + LaunchSubprocessReject(); + return false; +} + +static nsIDocShell* GetOpenerDocShellHelper(Element* aFrameElement) { + // Propagate the private-browsing status of the element's parent + // docshell to the remote docshell, via the chrome flags. + MOZ_ASSERT(aFrameElement); + nsPIDOMWindowOuter* win = aFrameElement->OwnerDoc()->GetWindow(); + if (!win) { + NS_WARNING("Remote frame has no window"); + return nullptr; + } + nsIDocShell* docShell = win->GetDocShell(); + if (!docShell) { + NS_WARNING("Remote frame has no docshell"); + return nullptr; + } + + return docShell; +} + +mozilla::ipc::IPCResult ContentParent::RecvCreateGMPService() { + Endpoint<PGMPServiceParent> parent; + Endpoint<PGMPServiceChild> child; + + if (mGMPCreated) { + return IPC_FAIL(this, "GMP Service already created"); + } + + nsresult rv; + rv = PGMPService::CreateEndpoints(base::GetCurrentProcId(), OtherPid(), + &parent, &child); + if (NS_FAILED(rv)) { + return IPC_FAIL(this, "CreateEndpoints failed"); + } + + if (!GMPServiceParent::Create(std::move(parent))) { + return IPC_FAIL(this, "GMPServiceParent::Create failed"); + } + + if (!SendInitGMPService(std::move(child))) { + return IPC_FAIL(this, "SendInitGMPService failed"); + } + + mGMPCreated = true; + + return IPC_OK(); +} + +Atomic<bool, mozilla::Relaxed> sContentParentTelemetryEventEnabled(false); + +/*static*/ +void ContentParent::LogAndAssertFailedPrincipalValidationInfo( + nsIPrincipal* aPrincipal, const char* aMethod) { + // nsContentSecurityManager may also enable this same event, but that's okay + if (!sContentParentTelemetryEventEnabled.exchange(true)) { + sContentParentTelemetryEventEnabled = true; + Telemetry::SetEventRecordingEnabled("security"_ns, true); + } + + // Send Telemetry + nsAutoCString principalScheme, principalType, spec; + CopyableTArray<EventExtraEntry> extra(2); + + if (!aPrincipal) { + principalType.AssignLiteral("NullPtr"); + } else if (aPrincipal->IsSystemPrincipal()) { + principalType.AssignLiteral("SystemPrincipal"); + } else if (aPrincipal->GetIsExpandedPrincipal()) { + principalType.AssignLiteral("ExpandedPrincipal"); + } else if (aPrincipal->GetIsContentPrincipal()) { + principalType.AssignLiteral("ContentPrincipal"); + aPrincipal->GetSpec(spec); + aPrincipal->GetScheme(principalScheme); + + extra.AppendElement(EventExtraEntry{"scheme"_ns, principalScheme}); + } else { + principalType.AssignLiteral("Unknown"); + } + + extra.AppendElement(EventExtraEntry{"principalType"_ns, principalType}); + + // Do not send telemetry when chrome-debugging is enabled + bool isChromeDebuggingEnabled = + Preferences::GetBool("devtools.chrome.enabled", false); + if (!isChromeDebuggingEnabled) { + Telemetry::EventID eventType = + Telemetry::EventID::Security_Fissionprincipals_Contentparent; + Telemetry::RecordEvent(eventType, mozilla::Some(aMethod), + mozilla::Some(extra)); + } + + // And log it + MOZ_LOG( + ContentParent::GetLog(), LogLevel::Error, + (" Receiving unexpected Principal (%s) within %s", + aPrincipal && aPrincipal->GetIsContentPrincipal() ? spec.get() + : principalType.get(), + aMethod)); + +#ifdef DEBUG + // Not only log but also ensure we do not receive an unexpected + // principal when running in debug mode. + MOZ_ASSERT(false, "Receiving unexpected Principal"); +#endif +} + +bool ContentParent::ValidatePrincipal( + nsIPrincipal* aPrincipal, + const EnumSet<ValidatePrincipalOptions>& aOptions) { + // If the pref says we should not validate, then there is nothing to do + if (!StaticPrefs::dom_security_enforceIPCBasedPrincipalVetting()) { + return true; + } + + // If there is no principal, then there is nothing to validate! + if (!aPrincipal) { + return aOptions.contains(ValidatePrincipalOptions::AllowNullPtr); + } + + // We currently do not track relationships between specific null principals + // and content processes, so we can not validate much here - just allow all + // null principals we see because they are generally safe anyway! + if (aPrincipal->GetIsNullPrincipal()) { + return true; + } + + // Only allow the system principal if the passed in options flags + // request permitting the system principal. + if (aPrincipal->IsSystemPrincipal()) { + return aOptions.contains(ValidatePrincipalOptions::AllowSystem); + } + + // XXXckerschb: we should eliminate the resource carve-out here and always + // validate the Principal, see Bug 1686200: Investigate Principal for pdf.js + if (aPrincipal->SchemeIs("resource")) { + return true; + } + + // Validate each inner principal individually, allowing us to catch expanded + // principals containing the system principal, etc. + if (aPrincipal->GetIsExpandedPrincipal()) { + if (!aOptions.contains(ValidatePrincipalOptions::AllowExpanded)) { + return false; + } + // FIXME: There are more constraints on expanded principals in-practice, + // such as the structure of extension expanded principals. This may need + // to be investigated more in the future. + nsCOMPtr<nsIExpandedPrincipal> expandedPrincipal = + do_QueryInterface(aPrincipal); + const auto& allowList = expandedPrincipal->AllowList(); + for (const auto& innerPrincipal : allowList) { + if (!ValidatePrincipal(innerPrincipal, aOptions)) { + return false; + } + } + return true; + } + + // A URI with a file:// scheme can never load in a non-file content process + // due to sandboxing. + if (aPrincipal->SchemeIs("file")) { + // If we don't support a separate 'file' process, then we can return here. + if (!StaticPrefs::browser_tabs_remote_separateFileUriProcess()) { + return true; + } + return mRemoteType == FILE_REMOTE_TYPE; + } + + if (aPrincipal->SchemeIs("about")) { + uint32_t flags = 0; + if (NS_FAILED(aPrincipal->GetAboutModuleFlags(&flags))) { + return false; + } + + // Block principals for about: URIs which can't load in this process. + if (!(flags & (nsIAboutModule::URI_CAN_LOAD_IN_CHILD | + nsIAboutModule::URI_MUST_LOAD_IN_CHILD))) { + return false; + } + if (flags & nsIAboutModule::URI_MUST_LOAD_IN_EXTENSION_PROCESS) { + return mRemoteType == EXTENSION_REMOTE_TYPE; + } + return true; + } + + if (!mRemoteTypeIsolationPrincipal || + RemoteTypePrefix(mRemoteType) != FISSION_WEB_REMOTE_TYPE) { + return true; + } + + // Web content can contain extension content frames, so a content process may + // send us an extension's principal. + auto* addonPolicy = BasePrincipal::Cast(aPrincipal)->AddonPolicy(); + if (addonPolicy) { + return true; + } + + // Ensure that the expected site-origin matches the one specified by our + // mRemoteTypeIsolationPrincipal. + nsAutoCString siteOriginNoSuffix; + if (NS_FAILED(aPrincipal->GetSiteOriginNoSuffix(siteOriginNoSuffix))) { + return false; + } + nsAutoCString remoteTypeSiteOriginNoSuffix; + if (NS_FAILED(mRemoteTypeIsolationPrincipal->GetSiteOriginNoSuffix( + remoteTypeSiteOriginNoSuffix))) { + return false; + } + + return remoteTypeSiteOriginNoSuffix.Equals(siteOriginNoSuffix); +} + +/*static*/ +already_AddRefed<RemoteBrowser> ContentParent::CreateBrowser( + const TabContext& aContext, Element* aFrameElement, + const nsACString& aRemoteType, BrowsingContext* aBrowsingContext, + ContentParent* aOpenerContentParent) { + AUTO_PROFILER_LABEL("ContentParent::CreateBrowser", OTHER); + + MOZ_DIAGNOSTIC_ASSERT( + !aBrowsingContext->Canonical()->GetBrowserParent(), + "BrowsingContext must not have BrowserParent, or have previous " + "BrowserParent cleared"); + + // Don't bother creating new content browsers after entering shutdown. This + // could lead to starting a new content process, which may significantly delay + // shutdown, and the content is unlikely to be displayed. + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + NS_WARNING("Ignoring remote browser creation request during shutdown"); + return nullptr; + } + + nsAutoCString remoteType(aRemoteType); + if (remoteType.IsEmpty()) { + remoteType = DEFAULT_REMOTE_TYPE; + } + + TabId tabId(nsContentUtils::GenerateTabId()); + + nsIDocShell* docShell = GetOpenerDocShellHelper(aFrameElement); + TabId openerTabId; + if (docShell) { + openerTabId = BrowserParent::GetTabIdFrom(docShell); + } + + RefPtr<ContentParent> constructorSender; + MOZ_RELEASE_ASSERT(XRE_IsParentProcess(), + "Cannot allocate BrowserParent in content process"); + if (aOpenerContentParent && !aOpenerContentParent->IsShuttingDown()) { + constructorSender = aOpenerContentParent; + } else { + constructorSender = GetNewOrUsedBrowserProcess( + remoteType, aBrowsingContext->Group(), PROCESS_PRIORITY_FOREGROUND); + if (!constructorSender) { + return nullptr; + } + } + + aBrowsingContext->SetEmbedderElement(aFrameElement); + + // Ensure that the process which we're using to launch is set as the host + // process for this BrowsingContextGroup. + aBrowsingContext->Group()->EnsureHostProcess(constructorSender); + + nsCOMPtr<nsIDocShellTreeOwner> treeOwner; + docShell->GetTreeOwner(getter_AddRefs(treeOwner)); + if (!treeOwner) { + return nullptr; + } + + nsCOMPtr<nsIWebBrowserChrome> wbc = do_GetInterface(treeOwner); + if (!wbc) { + return nullptr; + } + uint32_t chromeFlags = 0; + wbc->GetChromeFlags(&chromeFlags); + + nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(docShell); + if (loadContext && loadContext->UsePrivateBrowsing()) { + chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW; + } + if (loadContext && loadContext->UseRemoteTabs()) { + chromeFlags |= nsIWebBrowserChrome::CHROME_REMOTE_WINDOW; + } + if (loadContext && loadContext->UseRemoteSubframes()) { + chromeFlags |= nsIWebBrowserChrome::CHROME_FISSION_WINDOW; + } + + if (tabId == 0) { + return nullptr; + } + + aBrowsingContext->Canonical()->SetOwnerProcessId( + constructorSender->ChildID()); + + RefPtr<BrowserParent> browserParent = + new BrowserParent(constructorSender, tabId, aContext, + aBrowsingContext->Canonical(), chromeFlags); + + // Open a remote endpoint for our PBrowser actor. + ManagedEndpoint<PBrowserChild> childEp = + constructorSender->OpenPBrowserEndpoint(browserParent); + if (NS_WARN_IF(!childEp.IsValid())) { + return nullptr; + } + + ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); + if (NS_WARN_IF(!cpm)) { + return nullptr; + } + cpm->RegisterRemoteFrame(browserParent); + + nsCOMPtr<nsIPrincipal> initialPrincipal = + NullPrincipal::Create(aBrowsingContext->OriginAttributesRef()); + WindowGlobalInit windowInit = WindowGlobalActor::AboutBlankInitializer( + aBrowsingContext, initialPrincipal); + + RefPtr<WindowGlobalParent> windowParent = + WindowGlobalParent::CreateDisconnected(windowInit); + if (NS_WARN_IF(!windowParent)) { + return nullptr; + } + + // Open a remote endpoint for the initial PWindowGlobal actor. + ManagedEndpoint<PWindowGlobalChild> windowEp = + browserParent->OpenPWindowGlobalEndpoint(windowParent); + if (NS_WARN_IF(!windowEp.IsValid())) { + return nullptr; + } + + // Tell the content process to set up its PBrowserChild. + bool ok = constructorSender->SendConstructBrowser( + std::move(childEp), std::move(windowEp), tabId, + aContext.AsIPCTabContext(), windowInit, chromeFlags, + constructorSender->ChildID(), constructorSender->IsForBrowser(), + /* aIsTopLevel */ true); + if (NS_WARN_IF(!ok)) { + return nullptr; + } + + // Ensure that we're marked as the current BrowserParent on our + // CanonicalBrowsingContext. + aBrowsingContext->Canonical()->SetCurrentBrowserParent(browserParent); + + windowParent->Init(); + + RefPtr<BrowserHost> browserHost = new BrowserHost(browserParent); + browserParent->SetOwnerElement(aFrameElement); + return browserHost.forget(); +} + +void ContentParent::GetAll(nsTArray<ContentParent*>& aArray) { + aArray.Clear(); + + for (auto* cp : AllProcesses(eLive)) { + aArray.AppendElement(cp); + } +} + +void ContentParent::GetAllEvenIfDead(nsTArray<ContentParent*>& aArray) { + aArray.Clear(); + + for (auto* cp : AllProcesses(eAll)) { + aArray.AppendElement(cp); + } +} + +void ContentParent::BroadcastStringBundle( + const StringBundleDescriptor& aBundle) { + AutoTArray<StringBundleDescriptor, 1> array; + array.AppendElement(aBundle); + + for (auto* cp : AllProcesses(eLive)) { + Unused << cp->SendRegisterStringBundles(array); + } +} + +void ContentParent::BroadcastFontListChanged() { + for (auto* cp : AllProcesses(eLive)) { + Unused << cp->SendFontListChanged(); + } +} + +void ContentParent::BroadcastShmBlockAdded(uint32_t aGeneration, + uint32_t aIndex) { + auto* pfl = gfxPlatformFontList::PlatformFontList(); + for (auto* cp : AllProcesses(eLive)) { + base::SharedMemoryHandle handle = + pfl->ShareShmBlockToProcess(aIndex, cp->Pid()); + if (handle == base::SharedMemory::NULLHandle()) { + // If something went wrong here, we just skip it; the child will need to + // request the block as needed, at some performance cost. + continue; + } + Unused << cp->SendFontListShmBlockAdded(aGeneration, aIndex, + std::move(handle)); + } +} + +void ContentParent::BroadcastThemeUpdate(widget::ThemeChangeKind aKind) { + const FullLookAndFeel& lnf = *RemoteLookAndFeel::ExtractData(); + for (auto* cp : AllProcesses(eLive)) { + Unused << cp->SendThemeChanged(lnf, aKind); + } +} + +/*static */ +void ContentParent::BroadcastMediaCodecsSupportedUpdate( + RemoteDecodeIn aLocation, const media::MediaCodecsSupported& aSupported) { + // Merge incoming codec support with existing support list + media::MCSInfo::AddSupport(aSupported); + auto support = media::MCSInfo::GetSupport(); + + // Update processes + sCodecsSupported[aLocation] = support; + for (auto* cp : AllProcesses(eAll)) { + Unused << cp->SendUpdateMediaCodecsSupported(aLocation, support); + } + + // Generate + save support string for display in about:support + nsCString supportString; + media::MCSInfo::GetMediaCodecsSupportedString(supportString, support); + gfx::gfxVars::SetCodecSupportInfo(supportString); + + // Print the support info only from the given location for debug purpose. + supportString.Truncate(); + media::MCSInfo::GetMediaCodecsSupportedString(supportString, aSupported); + supportString.ReplaceSubstring("\n"_ns, ", "_ns); + LOGPDM("Broadcast support from '%s', support=%s", + RemoteDecodeInToStr(aLocation), supportString.get()); +} + +const nsACString& ContentParent::GetRemoteType() const { return mRemoteType; } + +static StaticRefPtr<nsIAsyncShutdownClient> sXPCOMShutdownClient; +static StaticRefPtr<nsIAsyncShutdownClient> sProfileBeforeChangeClient; +static StaticRefPtr<nsIAsyncShutdownClient> sQuitApplicationGrantedClient; + +void ContentParent::Init() { + MOZ_ASSERT(sXPCOMShutdownClient); + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + size_t length = ArrayLength(sObserverTopics); + for (size_t i = 0; i < length; ++i) { + obs->AddObserver(this, sObserverTopics[i], false); + } + } + + if (obs) { + nsAutoString cpId; + cpId.AppendInt(static_cast<uint64_t>(this->ChildID())); + obs->NotifyObservers(static_cast<nsIObserver*>(this), "ipc:content-created", + cpId.get()); + } + +#ifdef ACCESSIBILITY + // If accessibility is running in chrome process then start it in content + // process. + if (GetAccService()) { + Unused << SendActivateA11y(); + } +#endif // #ifdef ACCESSIBILITY + + Unused << SendInitProfiler(ProfilerParent::CreateForProcess(OtherPid())); + + RefPtr<GeckoMediaPluginServiceParent> gmps( + GeckoMediaPluginServiceParent::GetSingleton()); + if (gmps) { + gmps->UpdateContentProcessGMPCapabilities(this); + } + + // Flush any pref updates that happened during launch and weren't + // included in the blobs set up in BeginSubprocessLaunch. + for (const Pref& pref : mQueuedPrefs) { + Unused << NS_WARN_IF(!SendPreferenceUpdate(pref)); + } + mQueuedPrefs.Clear(); + + Unused << SendInitNextGenLocalStorageEnabled(NextGenLocalStorageEnabled()); +} + +// Note that for E10S we can get a false here that will be overruled by +// TryToRecycleE10SOnly as late as MaybeBeginShutdown. We cannot really +// foresee its result here. +bool ContentParent::CheckTabDestroyWillKeepAlive( + uint32_t aExpectedBrowserCount) { + return ManagedPBrowserParent().Count() != aExpectedBrowserCount || + ShouldKeepProcessAlive(); +} + +RecursiveMutex& ContentParent::ThreadsafeHandleMutex() { + return mThreadsafeHandle->mMutex; +} + +void ContentParent::NotifyTabWillDestroy() { + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed) +#if !defined(MOZ_WIDGET_ANDROID) + /* on Android we keep processes alive more agressively, see + NotifyTabDestroying where we omit MaybeBeginShutdown */ + || (/* we cannot trust CheckTabDestroyWillKeepAlive in E10S mode */ + mozilla::FissionAutostart() && + !CheckTabDestroyWillKeepAlive(mNumDestroyingTabs + 1)) +#endif + ) { + // Once we notify the impending shutdown, the content process will stop + // to process content JS on interrupt (among other things), so we need to + // be sure that the process will not be re-used after this point. + // The inverse is harmless, that is if we decide later to shut it down + // but did not notify here, it will be just notified later (but in rare + // cases too late to avoid a hang). + NotifyImpendingShutdown(); +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + mNotifiedImpendingShutdownOnTabWillDestroy = true; +#endif + } +} + +void ContentParent::MaybeBeginShutDown(uint32_t aExpectedBrowserCount, + bool aSendShutDown) { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose, + ("MaybeBeginShutdown %p, %u vs %u", this, + ManagedPBrowserParent().Count(), aExpectedBrowserCount)); + MOZ_ASSERT(NS_IsMainThread()); + + // We need to lock our mutex here to ensure the state does not change + // between the check and the MarkAsDead. + // Note that if we come through BrowserParent::Destroy our mutex is + // already locked. + // TODO: We want to get rid of the ThreadsafeHandle, see bug 1683595. + RecursiveMutexAutoLock lock(mThreadsafeHandle->mMutex); + + // Both CheckTabDestroyWillKeepAlive and TryToRecycleE10SOnly will return + // false if IsInOrBeyond(AppShutdownConfirmed), so if the parent shuts + // down we will always shutdown the child. + if (CheckTabDestroyWillKeepAlive(aExpectedBrowserCount) || + TryToRecycleE10SOnly()) { + return; + } + + MOZ_LOG( + ContentParent::GetLog(), LogLevel::Debug, + ("Beginning ContentParent Shutdown %p (%s)", this, mRemoteType.get())); + + // We're dying now, prevent anything from re-using this process. + MarkAsDead(); + SignalImpendingShutdownToContentJS(); + + if (aSendShutDown) { + AsyncSendShutDownMessage(); + } else { + // aSendShutDown is false only when we get called from + // NotifyTabDestroying where we expect a subsequent call from + // NotifyTabDestroyed triggered by a Browser actor destroy + // roundtrip through the content process that might never arrive. + StartSendShutdownTimer(); + } +} + +void ContentParent::AsyncSendShutDownMessage() { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose, + ("AsyncSendShutDownMessage %p", this)); + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(sRecycledE10SProcess != this); + + // In the case of normal shutdown, send a shutdown message to child to + // allow it to perform shutdown tasks. + GetCurrentSerialEventTarget()->Dispatch(NewRunnableMethod<ShutDownMethod>( + "dom::ContentParent::ShutDownProcess", this, + &ContentParent::ShutDownProcess, SEND_SHUTDOWN_MESSAGE)); +} + +void MaybeLogBlockShutdownDiagnostics(ContentParent* aSelf, const char* aMsg, + const char* aFile, int32_t aLine) { +#if defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED) + if (aSelf->IsBlockingShutdown()) { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Info, + ("ContentParent: id=%p pid=%d - %s at %s(%d)", aSelf, aSelf->Pid(), + aMsg, aFile, aLine)); + } +#else + Unused << aSelf; + Unused << aMsg; + Unused << aFile; + Unused << aLine; +#endif +} + +bool ContentParent::ShutDownProcess(ShutDownMethod aMethod) { + bool result = false; + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("ShutDownProcess: %p", this)); + // NB: must MarkAsDead() here so that this isn't accidentally + // returned from Get*() while in the midst of shutdown. + MarkAsDead(); + + // Shutting down by sending a shutdown message works differently than the + // other methods. We first call Shutdown() in the child. After the child is + // ready, it calls FinishShutdown() on us. Then we close the channel. + if (aMethod == SEND_SHUTDOWN_MESSAGE) { + if (!mShutdownPending) { + if (CanSend()) { + // Stop sending input events with input priority when shutting down. + SetInputPriorityEventEnabled(false); + // If we did not earlier, let's signal the shutdown to JS now. + SignalImpendingShutdownToContentJS(); + // Send a high priority announcement first. If this fails, SendShutdown + // will also fail. + Unused << SendShutdownConfirmedHP(); + // Send the definite message with normal priority. + if (SendShutdown()) { + MaybeLogBlockShutdownDiagnostics( + this, "ShutDownProcess: Sent shutdown message.", __FILE__, + __LINE__); + mShutdownPending = true; + // We start the kill timer only after we asked our process to + // shutdown. + StartForceKillTimer(); + result = true; + } else { + MaybeLogBlockShutdownDiagnostics( + this, "ShutDownProcess: !!! Send shutdown message failed! !!!", + __FILE__, __LINE__); + } + } else { + MaybeLogBlockShutdownDiagnostics( + this, "ShutDownProcess: !!! !CanSend !!!", __FILE__, __LINE__); + } + } else { + MaybeLogBlockShutdownDiagnostics( + this, "ShutDownProcess: Shutdown already pending.", __FILE__, + __LINE__); + + result = true; + } + // If call was not successful, the channel must have been broken + // somehow, and we will clean up the error in ActorDestroy. + return result; + } + + using mozilla::dom::quota::QuotaManagerService; + + if (QuotaManagerService* qms = QuotaManagerService::GetOrCreate()) { + qms->AbortOperationsForProcess(mChildID); + } + + if (aMethod == CLOSE_CHANNEL) { + if (!mCalledClose) { + MaybeLogBlockShutdownDiagnostics( + this, "ShutDownProcess: Closing channel.", __FILE__, __LINE__); + // Close() can only be called once: It kicks off the destruction sequence. + mCalledClose = true; + Close(); + } + result = true; + } + + // A ContentParent object might not get freed until after XPCOM shutdown has + // shut down the cycle collector. But by then it's too late to release any + // CC'ed objects, so we need to null them out here, while we still can. See + // bug 899761. + ShutDownMessageManager(); + return result; +} + +mozilla::ipc::IPCResult ContentParent::RecvNotifyShutdownSuccess() { + if (!mShutdownPending) { + return IPC_FAIL(this, "RecvNotifyShutdownSuccess without mShutdownPending"); + } + + mIsNotifiedShutdownSuccess = true; + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvFinishShutdown() { + if (!mShutdownPending) { + return IPC_FAIL(this, "RecvFinishShutdown without mShutdownPending"); + } + + // At this point, we already called ShutDownProcess once with + // SEND_SHUTDOWN_MESSAGE. To actually close the channel, we call + // ShutDownProcess again with CLOSE_CHANNEL. + if (mCalledClose) { + MaybeLogBlockShutdownDiagnostics( + this, "RecvFinishShutdown: Channel already closed.", __FILE__, + __LINE__); + } + + ShutDownProcess(CLOSE_CHANNEL); + return IPC_OK(); +} + +void ContentParent::ShutDownMessageManager() { + if (!mMessageManager) { + return; + } + + mMessageManager->ReceiveMessage(mMessageManager, nullptr, + CHILD_PROCESS_SHUTDOWN_MESSAGE, false, + nullptr, nullptr, IgnoreErrors()); + + mMessageManager->SetOsPid(-1); + mMessageManager->Disconnect(); + mMessageManager = nullptr; +} + +void ContentParent::AddToPool(nsTArray<ContentParent*>& aPool) { + MOZ_DIAGNOSTIC_ASSERT(!mIsInPool); + AssertAlive(); + MOZ_DIAGNOSTIC_ASSERT(!mCalledKillHard); + aPool.AppendElement(this); + mIsInPool = true; +} + +void ContentParent::RemoveFromPool(nsTArray<ContentParent*>& aPool) { + MOZ_DIAGNOSTIC_ASSERT(mIsInPool); + aPool.RemoveElement(this); + mIsInPool = false; +} + +void ContentParent::AssertNotInPool() { + MOZ_RELEASE_ASSERT(!mIsInPool); + + MOZ_RELEASE_ASSERT(sRecycledE10SProcess != this); + MOZ_RELEASE_ASSERT(!sBrowserContentParents || + !sBrowserContentParents->Contains(mRemoteType) || + !sBrowserContentParents->Get(mRemoteType)->Contains(this)); + + for (const auto& group : mGroups) { + MOZ_RELEASE_ASSERT(group->GetHostProcess(mRemoteType) != this, + "still a host process for one of our groups?"); + } +} + +void ContentParent::AssertAlive() { + MOZ_DIAGNOSTIC_ASSERT(!mNotifiedImpendingShutdownOnTabWillDestroy); + MOZ_DIAGNOSTIC_ASSERT(!mIsSignaledImpendingShutdown); + MOZ_DIAGNOSTIC_ASSERT(!IsDead()); +} + +void ContentParent::RemoveFromList() { + if (!mIsInPool) { +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + AssertNotInPool(); +#endif + return; + } + + // Ensure that this BrowsingContextGroup is no longer used to host new + // documents from any associated BrowsingContextGroups. It may become a host + // again in the future, if it is restored to the pool. + for (const auto& group : mGroups) { + group->RemoveHostProcess(this); + } + + StopRecyclingE10SOnly(/* aForeground */ false); + + if (sBrowserContentParents) { + if (auto entry = sBrowserContentParents->Lookup(mRemoteType)) { + const auto& contentParents = entry.Data(); + RemoveFromPool(*contentParents); + if (contentParents->IsEmpty()) { + entry.Remove(); + } + } + if (sBrowserContentParents->IsEmpty()) { + delete sBrowserContentParents; + sBrowserContentParents = nullptr; + } + } +} + +void ContentParent::MarkAsDead() { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose, + ("Marking ContentProcess %p as dead", this)); + MOZ_DIAGNOSTIC_ASSERT(!sInProcessSelector); + RemoveFromList(); + + // Flag shutdown has started for us to our threadsafe handle. + { + // Depending on how we get here, the lock might or might not be set. + RecursiveMutexAutoLock lock(mThreadsafeHandle->mMutex); + + mThreadsafeHandle->mShutdownStarted = true; + } + + // Prevent this process from being re-used. + PreallocatedProcessManager::Erase(this); + StopRecyclingE10SOnly(false); + +#if defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_PROFILE_GENERATE) + if (IsAlive()) { + // We're intentionally killing the content process at this point to ensure + // that we never have a "dead" content process sitting around and occupying + // an Android Service. + // + // The exception is in MOZ_PROFILE_GENERATE builds where we must allow the + // process to shutdown cleanly so that profile data can be dumped. This is + // okay as we will not reach our process limit during the profile run. + nsCOMPtr<nsIEventTarget> launcherThread(GetIPCLauncher()); + MOZ_ASSERT(launcherThread); + + auto procType = java::GeckoProcessType::CONTENT(); + auto selector = + java::GeckoProcessManager::Selector::New(procType, OtherPid()); + + launcherThread->Dispatch(NS_NewRunnableFunction( + "ContentParent::MarkAsDead", + [selector = + java::GeckoProcessManager::Selector::GlobalRef(selector)]() { + java::GeckoProcessManager::ShutdownProcess(selector); + })); + } +#endif + + if (mScriptableHelper) { + static_cast<ScriptableCPInfo*>(mScriptableHelper.get())->ProcessDied(); + mScriptableHelper = nullptr; + } + + mLifecycleState = LifecycleState::DEAD; +} + +void ContentParent::OnChannelError() { + RefPtr<ContentParent> kungFuDeathGrip(this); + PContentParent::OnChannelError(); +} + +void ContentParent::ProcessingError(Result aCode, const char* aReason) { + if (MsgDropped == aCode) { + return; + } + // Other errors are big deals. +#ifndef FUZZING + KillHard(aReason); +#endif + if (CanSend()) { + GetIPCChannel()->InduceConnectionError(); + } +} + +void ContentParent::ActorDestroy(ActorDestroyReason why) { +#ifdef FUZZING_SNAPSHOT + MOZ_FUZZING_IPC_DROP_PEER("ContentParent::ActorDestroy"); +#endif + + if (mSendShutdownTimer) { + mSendShutdownTimer->Cancel(); + mSendShutdownTimer = nullptr; + } + if (mForceKillTimer) { + mForceKillTimer->Cancel(); + mForceKillTimer = nullptr; + } + + // Signal shutdown completion regardless of error state, so we can + // finish waiting in the xpcom-shutdown/profile-before-change observer. + RemoveShutdownBlockers(); + + if (mHangMonitorActor) { + ProcessHangMonitor::RemoveProcess(mHangMonitorActor); + mHangMonitorActor = nullptr; + } + + RefPtr<FileSystemSecurity> fss = FileSystemSecurity::Get(); + if (fss) { + fss->Forget(ChildID()); + } + + if (why == NormalShutdown && !mCalledClose) { + // If we shut down normally but haven't called Close, assume somebody + // else called Close on us. In that case, we still need to call + // ShutDownProcess below to perform other necessary clean up. + mCalledClose = true; + } + + // Make sure we always clean up. + ShutDownProcess(CLOSE_CHANNEL); + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + size_t length = ArrayLength(sObserverTopics); + for (size_t i = 0; i < length; ++i) { + obs->RemoveObserver(static_cast<nsIObserver*>(this), sObserverTopics[i]); + } + } + + // remove the global remote preferences observers + Preferences::RemoveObserver(this, ""); + gfxVars::RemoveReceiver(this); + + if (GPUProcessManager* gpu = GPUProcessManager::Get()) { + // Note: the manager could have shutdown already. + gpu->RemoveListener(this); + } + + RecvRemoveGeolocationListener(); + + // Destroy our JSProcessActors, and reject any pending queries. + JSActorDidDestroy(); + + if (obs) { + RefPtr<nsHashPropertyBag> props = new nsHashPropertyBag(); + + props->SetPropertyAsUint64(u"childID"_ns, mChildID); + + if (AbnormalShutdown == why) { + Telemetry::Accumulate(Telemetry::SUBPROCESS_ABNORMAL_ABORT, "content"_ns, + 1); + + props->SetPropertyAsBool(u"abnormal"_ns, true); + + nsAutoString dumpID; + // There's a window in which child processes can crash + // after IPC is established, but before a crash reporter + // is created. + if (mCrashReporter) { + // if mCreatedPairedMinidumps is true, we've already generated + // parent/child dumps for desktop crashes. + if (!mCreatedPairedMinidumps) { +#if defined(XP_MACOSX) + RefPtr<nsAvailableMemoryWatcherBase> memWatcher; + memWatcher = nsAvailableMemoryWatcherBase::GetSingleton(); + memWatcher->AddChildAnnotations(mCrashReporter); +#endif + + mCrashReporter->GenerateCrashReport(OtherPid()); + } + + if (mCrashReporter->HasMinidump()) { + dumpID = mCrashReporter->MinidumpID(); + } + } else { + HandleOrphanedMinidump(&dumpID); + } + + if (!dumpID.IsEmpty()) { + props->SetPropertyAsAString(u"dumpID"_ns, dumpID); + } + } + nsAutoString cpId; + cpId.AppendInt(static_cast<uint64_t>(this->ChildID())); + obs->NotifyObservers((nsIPropertyBag2*)props, "ipc:content-shutdown", + cpId.get()); + } + + // Remove any and all idle listeners. + if (mIdleListeners.Length() > 0) { + nsCOMPtr<nsIUserIdleService> idleService = + do_GetService("@mozilla.org/widget/useridleservice;1"); + if (idleService) { + RefPtr<ParentIdleListener> listener; + for (const auto& lentry : mIdleListeners) { + listener = static_cast<ParentIdleListener*>(lentry.get()); + idleService->RemoveIdleObserver(listener, listener->mTime); + } + } + mIdleListeners.Clear(); + } + + MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose, + ("destroying Subprocess in ActorDestroy: ContentParent %p " + "mSubprocess %p handle %" PRIuPTR, + this, mSubprocess, + mSubprocess ? (uintptr_t)mSubprocess->GetChildProcessHandle() : -1)); + // FIXME (bug 1520997): does this really need an additional dispatch? + if (GetCurrentSerialEventTarget()) { + GetCurrentSerialEventTarget()->Dispatch(NS_NewRunnableFunction( + "DelayedDeleteSubprocessRunnable", [subprocess = mSubprocess] { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("destroyed Subprocess in ActorDestroy: Subprocess %p handle " + "%" PRIuPTR, + subprocess, + subprocess ? (uintptr_t)subprocess->GetChildProcessHandle() + : -1)); + subprocess->Destroy(); + })); + } + mSubprocess = nullptr; + + ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); + if (cpm) { + cpm->RemoveContentProcess(this->ChildID()); + } + + if (mDriverCrashGuard) { + mDriverCrashGuard->NotifyCrashed(); + } + + // Unregister all the BlobURLs registered by the ContentChild. + for (uint32_t i = 0; i < mBlobURLs.Length(); ++i) { + BlobURLProtocolHandler::RemoveDataEntry(mBlobURLs[i]); + } + + mBlobURLs.Clear(); + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + AssertNotInPool(); +#endif + + // As this process is going away, ensure that every BrowsingContext hosted by + // it has been detached, and every BrowsingContextGroup has been fully + // unsubscribed. + BrowsingContext::DiscardFromContentParent(this); + + const nsTHashSet<RefPtr<BrowsingContextGroup>> groups = std::move(mGroups); + for (const auto& group : groups) { + group->Unsubscribe(this); + } + MOZ_DIAGNOSTIC_ASSERT(mGroups.IsEmpty()); + + mPendingLoadStates.Clear(); +} + +bool ContentParent::TryToRecycleE10SOnly() { + // Only try to recycle "web" content processes, as other remote types are + // generally more unique, and cannot be effectively re-used. This is disabled + // with Fission, as "web" content processes are no longer frequently used. + // + // Disabling the process pre-allocator will also disable process recycling, + // allowing for more consistent process counts under testing. + if (mRemoteType != DEFAULT_REMOTE_TYPE || mozilla::FissionAutostart() || + !PreallocatedProcessManager::Enabled()) { + return false; + } + + // This life time check should be replaced by a memory health check (memory + // usage + fragmentation). + + // Note that this is specifically to help with edge cases that rapidly + // create-and-destroy processes + const double kMaxLifeSpan = 5; + MOZ_LOG( + ContentParent::GetLog(), LogLevel::Debug, + ("TryToRecycle ContentProcess %p (%u) with lifespan %f seconds", this, + (unsigned int)ChildID(), (TimeStamp::Now() - mActivateTS).ToSeconds())); + + if (mCalledKillHard || !IsAlive() || + (TimeStamp::Now() - mActivateTS).ToSeconds() > kMaxLifeSpan) { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("TryToRecycle did not recycle %p", this)); + + // It's possible that the process was already cached, and we're being called + // from a different path, and we're now past kMaxLifeSpan (or some other). + // Ensure that if we're going to kill this process we don't recycle it. + StopRecyclingE10SOnly(/* aForeground */ false); + return false; + } + + if (!sRecycledE10SProcess) { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("TryToRecycle began recycling %p", this)); + sRecycledE10SProcess = this; + + ProcessPriorityManager::SetProcessPriority(this, + PROCESS_PRIORITY_BACKGROUND); + return true; + } + + if (sRecycledE10SProcess == this) { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("TryToRecycle continue recycling %p", this)); + return true; + } + + // Some other process is already being recycled, just shut this one down. + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("TryToRecycle did not recycle %p (already recycling %p)", this, + sRecycledE10SProcess.get())); + return false; +} + +void ContentParent::StopRecyclingE10SOnly(bool aForeground) { + if (sRecycledE10SProcess != this) { + return; + } + + sRecycledE10SProcess = nullptr; + if (aForeground) { + ProcessPriorityManager::SetProcessPriority(this, + PROCESS_PRIORITY_FOREGROUND); + } +} + +bool ContentParent::HasActiveWorker() { + // If we have active workers, we need to stay alive. + { + // Most of the times we'll get here with the mutex acquired, but still. + RecursiveMutexAutoLock lock(mThreadsafeHandle->mMutex); + if (mThreadsafeHandle->mRemoteWorkerActorCount) { + return true; + } + } + return false; +} + +bool ContentParent::ShouldKeepProcessAlive() { + if (HasActiveWorker()) { + return true; + } + + if (mNumKeepaliveCalls > 0) { + return true; + } + + if (IsLaunching()) { + return true; + } + + // If we have already been marked as dead, don't prevent shutdown. + if (IsDead()) { + return false; + } + + // If everything is going down, there is no need to keep us alive, neither. + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + return false; + } + + if (!sBrowserContentParents) { + return false; + } + + auto contentParents = sBrowserContentParents->Get(mRemoteType); + if (!contentParents) { + return false; + } + + // We might want to keep some content processes alive for performance reasons. + // e.g. test runs and privileged content process for some about: pages. + // We don't want to alter behavior if the pref is not set, so default to 0. + int32_t processesToKeepAlive = 0; + + nsAutoCString keepAlivePref("dom.ipc.keepProcessesAlive."); + + if (StringBeginsWith(mRemoteType, FISSION_WEB_REMOTE_TYPE) && + xpc::IsInAutomation()) { + keepAlivePref.Append(FISSION_WEB_REMOTE_TYPE); + keepAlivePref.AppendLiteral(".perOrigin"); + } else { + keepAlivePref.Append(mRemoteType); + } + if (NS_FAILED( + Preferences::GetInt(keepAlivePref.get(), &processesToKeepAlive))) { + return false; + } + + int32_t numberOfAliveProcesses = contentParents->Length(); + + return numberOfAliveProcesses <= processesToKeepAlive; +} + +void ContentParent::NotifyTabDestroying() { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("NotifyTabDestroying %p:", this)); + // There can be more than one PBrowser for a given app process + // because of popup windows. PBrowsers can also destroy + // concurrently. When all the PBrowsers are destroying, kick off + // another task to ensure the child process *really* shuts down, + // even if the PBrowsers themselves never finish destroying. + ++mNumDestroyingTabs; + + /** + * We intentionally skip this code on Android: + * 1. Android has a fixed upper bound on the number of content processes, so + * we prefer to re-use them whenever possible (as opposed to letting an + * old process wind down while we launch a new one). + * 2. GeckoView always hard-kills content processes (and if it does not, + * Android itself will), so we don't concern ourselves with the ForceKill + * timer either. + */ +#if !defined(MOZ_WIDGET_ANDROID) + MaybeBeginShutDown(/* aExpectedBrowserCount */ mNumDestroyingTabs, + /* aSendShutDown */ false); +#endif // !defined(MOZ_WIDGET_ANDROID) +} + +void ContentParent::AddKeepAlive() { + AssertAlive(); + // Something wants to keep this content process alive. + ++mNumKeepaliveCalls; +} + +void ContentParent::RemoveKeepAlive() { + MOZ_DIAGNOSTIC_ASSERT(mNumKeepaliveCalls > 0); + --mNumKeepaliveCalls; + + MaybeBeginShutDown(); +} + +void ContentParent::StartSendShutdownTimer() { + if (mSendShutdownTimer || !CanSend()) { + return; + } + + uint32_t timeoutSecs = StaticPrefs::dom_ipc_tabs_shutdownTimeoutSecs(); + if (timeoutSecs > 0) { + NS_NewTimerWithFuncCallback(getter_AddRefs(mSendShutdownTimer), + ContentParent::SendShutdownTimerCallback, this, + timeoutSecs * 1000, nsITimer::TYPE_ONE_SHOT, + "dom::ContentParent::StartSendShutdownTimer"); + MOZ_ASSERT(mSendShutdownTimer); + } +} + +void ContentParent::StartForceKillTimer() { + if (mForceKillTimer || !CanSend()) { + return; + } + + uint32_t timeoutSecs = StaticPrefs::dom_ipc_tabs_shutdownTimeoutSecs(); + if (timeoutSecs > 0) { + NS_NewTimerWithFuncCallback(getter_AddRefs(mForceKillTimer), + ContentParent::ForceKillTimerCallback, this, + timeoutSecs * 1000, nsITimer::TYPE_ONE_SHOT, + "dom::ContentParent::StartForceKillTimer"); + MOZ_ASSERT(mForceKillTimer); + } +} + +void ContentParent::NotifyTabDestroyed(const TabId& aTabId, + bool aNotifiedDestroying) { + if (aNotifiedDestroying) { + --mNumDestroyingTabs; + } + + nsTArray<PContentPermissionRequestParent*> parentArray = + nsContentPermissionUtils::GetContentPermissionRequestParentById(aTabId); + + // Need to close undeleted ContentPermissionRequestParents before tab is + // closed. + for (auto& permissionRequestParent : parentArray) { + Unused << PContentPermissionRequestParent::Send__delete__( + permissionRequestParent); + } + + // There can be more than one PBrowser for a given app process + // because of popup windows. When the last one closes, shut + // us down. + MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose, + ("NotifyTabDestroyed %p", this)); + + MaybeBeginShutDown(/* aExpectedBrowserCount */ 1); +} + +TestShellParent* ContentParent::CreateTestShell() { + RefPtr<TestShellParent> actor = new TestShellParent(); + if (!SendPTestShellConstructor(actor)) { + return nullptr; + } + return actor; +} + +bool ContentParent::DestroyTestShell(TestShellParent* aTestShell) { + return PTestShellParent::Send__delete__(aTestShell); +} + +TestShellParent* ContentParent::GetTestShellSingleton() { + PTestShellParent* p = LoneManagedOrNullAsserts(ManagedPTestShellParent()); + return static_cast<TestShellParent*>(p); +} + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +// Append the sandbox command line parameters that are not static. i.e., +// parameters that can be different for different child processes. +void ContentParent::AppendDynamicSandboxParams( + std::vector<std::string>& aArgs) { + // For file content processes + if (GetRemoteType() == FILE_REMOTE_TYPE) { + MacSandboxInfo::AppendFileAccessParam(aArgs, true); + } +} + +// Generate the static sandbox command line parameters and store +// them in the provided params vector to be used each time a new +// content process is launched. +static void CacheSandboxParams(std::vector<std::string>& aCachedParams) { + // This must only be called once and we should + // be starting with an empty list of parameters. + MOZ_ASSERT(aCachedParams.empty()); + + MacSandboxInfo info; + info.type = MacSandboxType_Content; + info.level = GetEffectiveContentSandboxLevel(); + + // Sandbox logging + if (Preferences::GetBool("security.sandbox.logging.enabled") || + PR_GetEnv("MOZ_SANDBOX_LOGGING")) { + info.shouldLog = true; + } + + // Audio access + if (!StaticPrefs::media_cubeb_sandbox()) { + info.hasAudio = true; + } + + // Window server access. If the disconnect-windowserver pref is not + // "true" or out-of-process WebGL is not enabled, allow window server + // access in the sandbox policy. + if (!Preferences::GetBool( + "security.sandbox.content.mac.disconnect-windowserver") || + !Preferences::GetBool("webgl.out-of-process")) { + info.hasWindowServer = true; + } + + // .app path (normalized) + nsAutoCString appPath; + if (!nsMacUtilsImpl::GetAppPath(appPath)) { + MOZ_CRASH("Failed to get app dir paths"); + } + info.appPath = appPath.get(); + + // TESTING_READ_PATH1 + nsAutoCString testingReadPath1; + Preferences::GetCString("security.sandbox.content.mac.testing_read_path1", + testingReadPath1); + if (!testingReadPath1.IsEmpty()) { + info.testingReadPath1 = testingReadPath1.get(); + } + + // TESTING_READ_PATH2 + nsAutoCString testingReadPath2; + Preferences::GetCString("security.sandbox.content.mac.testing_read_path2", + testingReadPath2); + if (!testingReadPath2.IsEmpty()) { + info.testingReadPath2 = testingReadPath2.get(); + } + + // TESTING_READ_PATH3, TESTING_READ_PATH4. In non-packaged builds, + // these are used to whitelist the repo dir and object dir respectively. + nsresult rv; + if (!mozilla::IsPackagedBuild()) { + // Repo dir + nsCOMPtr<nsIFile> repoDir; + rv = nsMacUtilsImpl::GetRepoDir(getter_AddRefs(repoDir)); + if (NS_FAILED(rv)) { + MOZ_CRASH("Failed to get path to repo dir"); + } + nsCString repoDirPath; + Unused << repoDir->GetNativePath(repoDirPath); + info.testingReadPath3 = repoDirPath.get(); + + // Object dir + nsCOMPtr<nsIFile> objDir; + rv = nsMacUtilsImpl::GetObjDir(getter_AddRefs(objDir)); + if (NS_FAILED(rv)) { + MOZ_CRASH("Failed to get path to build object dir"); + } + nsCString objDirPath; + Unused << objDir->GetNativePath(objDirPath); + info.testingReadPath4 = objDirPath.get(); + } + + // DEBUG_WRITE_DIR +# ifdef DEBUG + // For bloat/leak logging or when a content process dies intentionally + // (|NoteIntentionalCrash|) for tests, it wants to log that it did this. + // Allow writing to this location. + nsAutoCString bloatLogDirPath; + if (NS_SUCCEEDED(nsMacUtilsImpl::GetBloatLogDir(bloatLogDirPath))) { + info.debugWriteDir = bloatLogDirPath.get(); + } +# endif // DEBUG + + info.AppendAsParams(aCachedParams); +} + +// Append sandboxing command line parameters. +void ContentParent::AppendSandboxParams(std::vector<std::string>& aArgs) { + MOZ_ASSERT(sMacSandboxParams != nullptr); + + // An empty sMacSandboxParams indicates this is the + // first invocation and we don't have cached params yet. + if (sMacSandboxParams->empty()) { + CacheSandboxParams(*sMacSandboxParams); + MOZ_ASSERT(!sMacSandboxParams->empty()); + } + + // Append cached arguments. + aArgs.insert(aArgs.end(), sMacSandboxParams->begin(), + sMacSandboxParams->end()); + + // Append remaining arguments. + AppendDynamicSandboxParams(aArgs); +} +#endif // XP_MACOSX && MOZ_SANDBOX + +bool ContentParent::BeginSubprocessLaunch(ProcessPriority aPriority) { + AUTO_PROFILER_LABEL("ContentParent::LaunchSubprocess", OTHER); + + // Ensure we will not rush through our shutdown phases while launching. + // LaunchSubprocessReject will remove them in case of failure, + // otherwise ActorDestroy will take care. + AddShutdownBlockers(); + + if (!ContentProcessManager::GetSingleton()) { + MOZ_ASSERT(false, "Unable to acquire ContentProcessManager singleton!"); + return false; + } + + std::vector<std::string> extraArgs; + geckoargs::sChildID.Put(mChildID, extraArgs); + geckoargs::sIsForBrowser.Put(IsForBrowser(), extraArgs); + geckoargs::sNotForBrowser.Put(!IsForBrowser(), extraArgs); + + // Prefs information is passed via anonymous shared memory to avoid bloating + // the command line. + + // Instantiate the pref serializer. It will be cleaned up in + // `LaunchSubprocessReject`/`LaunchSubprocessResolve`. + mPrefSerializer = MakeUnique<mozilla::ipc::SharedPreferenceSerializer>(); + if (!mPrefSerializer->SerializeToSharedMemory(GeckoProcessType_Content, + GetRemoteType())) { + NS_WARNING("SharedPreferenceSerializer::SerializeToSharedMemory failed"); + MarkAsDead(); + return false; + } + mPrefSerializer->AddSharedPrefCmdLineArgs(*mSubprocess, extraArgs); + + // The JS engine does some computation during the initialization which can be + // shared across processes. We add command line arguments to pass a file + // handle and its content length, to minimize the startup time of content + // processes. + ::mozilla::ipc::ExportSharedJSInit(*mSubprocess, extraArgs); + + // Register ContentParent as an observer for changes to any pref + // whose prefix matches the empty string, i.e. all of them. The + // observation starts here in order to capture pref updates that + // happen during async launch. + Preferences::AddStrongObserver(this, ""); + + if (gSafeMode) { + geckoargs::sSafeMode.Put(extraArgs); + } + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + if (IsContentSandboxEnabled()) { + AppendSandboxParams(extraArgs); + mSubprocess->DisableOSActivityMode(); + } +#endif + + nsCString parentBuildID(mozilla::PlatformBuildID()); + geckoargs::sParentBuildID.Put(parentBuildID.get(), extraArgs); + +#ifdef MOZ_WIDGET_GTK + // This is X11-only pending a solution for WebGL in Wayland mode. + if (StaticPrefs::dom_ipc_avoid_gtk() && + StaticPrefs::widget_non_native_theme_enabled() && + widget::GdkIsX11Display()) { + mSubprocess->SetEnv("MOZ_HEADLESS", "1"); + } +#endif + + mLaunchYieldTS = TimeStamp::Now(); + return mSubprocess->AsyncLaunch(std::move(extraArgs)); +} + +void ContentParent::LaunchSubprocessReject() { + NS_WARNING("failed to launch child in the parent"); + MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose, + ("failed to launch child in the parent")); + // Now that communication with the child is complete, we can cleanup + // the preference serializer. + mPrefSerializer = nullptr; + if (mIsAPreallocBlocker) { + PreallocatedProcessManager::RemoveBlocker(mRemoteType, this); + mIsAPreallocBlocker = false; + } + MarkAsDead(); + RemoveShutdownBlockers(); +} + +bool ContentParent::LaunchSubprocessResolve(bool aIsSync, + ProcessPriority aPriority) { + AUTO_PROFILER_LABEL("ContentParent::LaunchSubprocess::resolve", OTHER); + + if (mLaunchResolved) { + // We've already been called, return. + MOZ_ASSERT(sCreatedFirstContentProcess); + MOZ_ASSERT(!mPrefSerializer); + MOZ_ASSERT(mLifecycleState != LifecycleState::LAUNCHING); + return mLaunchResolvedOk; + } + mLaunchResolved = true; + + // Now that communication with the child is complete, we can cleanup + // the preference serializer. + mPrefSerializer = nullptr; + + const auto launchResumeTS = TimeStamp::Now(); + if (profiler_thread_is_being_profiled_for_markers()) { + nsPrintfCString marker("Process start%s for %u", + mIsAPreallocBlocker ? " (immediate)" : "", + (unsigned int)ChildID()); + PROFILER_MARKER_TEXT( + mIsAPreallocBlocker ? ProfilerString8View("Process Immediate Launch") + : ProfilerString8View("Process Launch"), + DOM, MarkerTiming::Interval(mLaunchTS, launchResumeTS), marker); + } + + if (!sCreatedFirstContentProcess) { + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + obs->NotifyObservers(nullptr, "ipc:first-content-process-created", nullptr); + sCreatedFirstContentProcess = true; + } + + mSubprocess->TakeInitialEndpoint().Bind(this); + + ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); + if (!cpm) { + NS_WARNING("immediately shutting-down caused by our shutdown"); + ShutDownProcess(SEND_SHUTDOWN_MESSAGE); + return false; + } + cpm->AddContentProcess(this); + +#ifdef MOZ_CODE_COVERAGE + Unused << SendShareCodeCoverageMutex( + CodeCoverageHandler::Get()->GetMutexHandle()); +#endif + + // We must be in the LAUNCHING state still. If we've somehow already been + // marked as DEAD, fail the process launch, and immediately begin tearing down + // the content process. + if (IsDead()) { + NS_WARNING("immediately shutting-down already-dead process"); + ShutDownProcess(SEND_SHUTDOWN_MESSAGE); + return false; + } + MOZ_ASSERT(mLifecycleState == LifecycleState::LAUNCHING); + mLifecycleState = LifecycleState::ALIVE; + + if (!InitInternal(aPriority)) { + NS_WARNING("failed to initialize child in the parent"); + // We've already called Open() by this point, so we need to close the + // channel to avoid leaking the process. + ShutDownProcess(SEND_SHUTDOWN_MESSAGE); + return false; + } + + mHangMonitorActor = ProcessHangMonitor::AddProcess(this); + + // Set a reply timeout for CPOWs. + SetReplyTimeoutMs(StaticPrefs::dom_ipc_cpow_timeout()); + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + nsAutoString cpId; + cpId.AppendInt(static_cast<uint64_t>(this->ChildID())); + obs->NotifyObservers(static_cast<nsIObserver*>(this), + "ipc:content-initializing", cpId.get()); + } + + Init(); + + mLifecycleState = LifecycleState::INITIALIZED; + + if (aIsSync) { + Telemetry::AccumulateTimeDelta(Telemetry::CONTENT_PROCESS_SYNC_LAUNCH_MS, + mLaunchTS); + } else { + Telemetry::AccumulateTimeDelta(Telemetry::CONTENT_PROCESS_LAUNCH_TOTAL_MS, + mLaunchTS); + + Telemetry::Accumulate( + Telemetry::CONTENT_PROCESS_LAUNCH_MAINTHREAD_MS, + static_cast<uint32_t>( + ((mLaunchYieldTS - mLaunchTS) + (TimeStamp::Now() - launchResumeTS)) + .ToMilliseconds())); + } + + mLaunchResolvedOk = true; + return true; +} + +bool ContentParent::LaunchSubprocessSync( + hal::ProcessPriority aInitialPriority) { + // We've started a sync content process launch. + Telemetry::Accumulate(Telemetry::CONTENT_PROCESS_LAUNCH_IS_SYNC, 1); + + if (BeginSubprocessLaunch(aInitialPriority)) { + const bool ok = mSubprocess->WaitForProcessHandle(); + if (ok && LaunchSubprocessResolve(/* aIsSync = */ true, aInitialPriority)) { + return true; + } + } + LaunchSubprocessReject(); + return false; +} + +RefPtr<ContentParent::LaunchPromise> ContentParent::LaunchSubprocessAsync( + hal::ProcessPriority aInitialPriority) { + // We've started an async content process launch. + Telemetry::Accumulate(Telemetry::CONTENT_PROCESS_LAUNCH_IS_SYNC, 0); + + if (!BeginSubprocessLaunch(aInitialPriority)) { + // Launch aborted because of shutdown. Bailout. + LaunchSubprocessReject(); + return LaunchPromise::CreateAndReject(NS_ERROR_ILLEGAL_DURING_SHUTDOWN, + __func__); + } + + // Otherwise, wait until the process is ready. + RefPtr<ProcessHandlePromise> ready = mSubprocess->WhenProcessHandleReady(); + RefPtr<ContentParent> self = this; + mLaunchYieldTS = TimeStamp::Now(); + + return ready->Then( + GetCurrentSerialEventTarget(), __func__, + [self, aInitialPriority]( + const ProcessHandlePromise::ResolveOrRejectValue& aValue) { + if (aValue.IsResolve() && + self->LaunchSubprocessResolve(/* aIsSync = */ false, + aInitialPriority)) { + return LaunchPromise::CreateAndResolve(self, __func__); + } + self->LaunchSubprocessReject(); + return LaunchPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + }); +} + +ContentParent::ContentParent(const nsACString& aRemoteType) + : mSubprocess(nullptr), + mLaunchTS(TimeStamp::Now()), + mLaunchYieldTS(mLaunchTS), + mActivateTS(mLaunchTS), + mIsAPreallocBlocker(false), + mRemoteType(aRemoteType), + mChildID(gContentChildID++), + mGeolocationWatchID(-1), + mThreadsafeHandle( + new ThreadsafeContentParentHandle(this, mChildID, mRemoteType)), + mNumDestroyingTabs(0), + mNumKeepaliveCalls(0), + mLifecycleState(LifecycleState::LAUNCHING), + mIsForBrowser(!mRemoteType.IsEmpty()), + mCalledClose(false), + mCalledKillHard(false), + mCreatedPairedMinidumps(false), + mShutdownPending(false), + mLaunchResolved(false), + mLaunchResolvedOk(false), + mIsRemoteInputEventQueueEnabled(false), + mIsInputPriorityEventEnabled(false), + mIsInPool(false), + mGMPCreated(false), +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + mBlockShutdownCalled(false), +#endif + mHangMonitorActor(nullptr) { + mRemoteTypeIsolationPrincipal = + CreateRemoteTypeIsolationPrincipal(aRemoteType); + + // Insert ourselves into the global linked list of ContentParent objects. + if (!sContentParents) { + sContentParents = new LinkedList<ContentParent>(); + } + sContentParents->insertBack(this); + + mMessageManager = nsFrameMessageManager::NewProcessMessageManager(true); + +#if defined(XP_WIN) + // Request Windows message deferral behavior on our side of the PContent + // channel. Generally only applies to the situation where we get caught in + // a deadlock with the plugin process when sending CPOWs. + GetIPCChannel()->SetChannelFlags( + MessageChannel::REQUIRE_DEFERRED_MESSAGE_PROTECTION); +#endif + + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + bool isFile = mRemoteType == FILE_REMOTE_TYPE; + mSubprocess = new GeckoChildProcessHost(GeckoProcessType_Content, isFile); + MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose, + ("CreateSubprocess: ContentParent %p mSubprocess %p handle %" PRIuPTR, + this, mSubprocess, + mSubprocess ? (uintptr_t)mSubprocess->GetChildProcessHandle() : -1)); + + // This is safe to do in the constructor, as it doesn't take a strong + // reference. + mScriptableHelper = new ScriptableCPInfo(this); +} + +ContentParent::~ContentParent() { + if (mSendShutdownTimer) { + mSendShutdownTimer->Cancel(); + } + if (mForceKillTimer) { + mForceKillTimer->Cancel(); + } + + AssertIsOnMainThread(); + + // Clear the weak reference from the threadsafe handle back to this actor. + mThreadsafeHandle->mWeakActor = nullptr; + + if (mIsAPreallocBlocker) { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("Removing blocker on ContentProcess destruction")); + PreallocatedProcessManager::RemoveBlocker(mRemoteType, this); + mIsAPreallocBlocker = false; + } + + // We should be removed from all these lists in ActorDestroy. + AssertNotInPool(); + + // Normally mSubprocess is destroyed in ActorDestroy, but that won't + // happen if the process wasn't launched or if it failed to launch. + if (mSubprocess) { + MOZ_LOG( + ContentParent::GetLog(), LogLevel::Verbose, + ("DestroySubprocess: ContentParent %p mSubprocess %p handle %" PRIuPTR, + this, mSubprocess, + mSubprocess ? (uintptr_t)mSubprocess->GetChildProcessHandle() : -1)); + mSubprocess->Destroy(); + } + + // Make sure to clear the connection from `mScriptableHelper` if it hasn't + // been cleared yet. + if (mScriptableHelper) { + static_cast<ScriptableCPInfo*>(mScriptableHelper.get())->ProcessDied(); + mScriptableHelper = nullptr; + } +} + +bool ContentParent::InitInternal(ProcessPriority aInitialPriority) { + // We can't access the locale service after shutdown has started. Since we + // can't init the process without it, and since we're going to be canceling + // whatever load attempt that initiated this process creation anyway, just + // bail out now if shutdown has already started. + if (PastShutdownPhase(ShutdownPhase::XPCOMShutdown)) { + return false; + } + + XPCOMInitData xpcomInit; + + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("ContentParent::InitInternal: %p", (void*)this)); + nsCOMPtr<nsIIOService> io(do_GetIOService()); + MOZ_ASSERT(io, "No IO service?"); + DebugOnly<nsresult> rv = io->GetOffline(&xpcomInit.isOffline()); + MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed getting offline?"); + + rv = io->GetConnectivity(&xpcomInit.isConnected()); + MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed getting connectivity?"); + + xpcomInit.captivePortalState() = nsICaptivePortalService::UNKNOWN; + nsCOMPtr<nsICaptivePortalService> cps = + do_GetService(NS_CAPTIVEPORTAL_CONTRACTID); + if (cps) { + cps->GetState(&xpcomInit.captivePortalState()); + } + + if (StaticPrefs::fission_processProfileName()) { + nsCOMPtr<nsIToolkitProfileService> profileSvc = + do_GetService(NS_PROFILESERVICE_CONTRACTID); + if (profileSvc) { + nsCOMPtr<nsIToolkitProfile> currentProfile; + nsresult rv = + profileSvc->GetCurrentProfile(getter_AddRefs(currentProfile)); + if (NS_SUCCEEDED(rv) && currentProfile) { + currentProfile->GetName(mProfile); + } + } + } + + nsIBidiKeyboard* bidi = nsContentUtils::GetBidiKeyboard(); + + xpcomInit.isLangRTL() = false; + xpcomInit.haveBidiKeyboards() = false; + if (bidi) { + bidi->IsLangRTL(&xpcomInit.isLangRTL()); + bidi->GetHaveBidiKeyboards(&xpcomInit.haveBidiKeyboards()); + } + + RefPtr<mozSpellChecker> spellChecker(mozSpellChecker::Create()); + MOZ_ASSERT(spellChecker, "No spell checker?"); + + spellChecker->GetDictionaryList(&xpcomInit.dictionaries()); + + LocaleService::GetInstance()->GetAppLocalesAsBCP47(xpcomInit.appLocales()); + LocaleService::GetInstance()->GetRequestedLocales( + xpcomInit.requestedLocales()); + + L10nRegistry::GetParentProcessFileSourceDescriptors( + xpcomInit.l10nFileSources()); + + nsCOMPtr<nsIClipboard> clipboard( + do_GetService("@mozilla.org/widget/clipboard;1")); + MOZ_ASSERT(clipboard, "No clipboard?"); + MOZ_ASSERT( + clipboard->IsClipboardTypeSupported(nsIClipboard::kGlobalClipboard), + "We should always support the global clipboard."); + + xpcomInit.clipboardCaps().supportsSelectionClipboard() = + clipboard->IsClipboardTypeSupported(nsIClipboard::kSelectionClipboard); + + xpcomInit.clipboardCaps().supportsFindClipboard() = + clipboard->IsClipboardTypeSupported(nsIClipboard::kFindClipboard); + + xpcomInit.clipboardCaps().supportsSelectionCache() = + clipboard->IsClipboardTypeSupported(nsIClipboard::kSelectionCache); + + // Let's copy the domain policy from the parent to the child (if it's active). + StructuredCloneData initialData; + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + if (ssm) { + ssm->CloneDomainPolicy(&xpcomInit.domainPolicy()); + + if (ParentProcessMessageManager* mm = + nsFrameMessageManager::sParentProcessManager) { + AutoJSAPI jsapi; + if (NS_WARN_IF(!jsapi.Init(xpc::PrivilegedJunkScope()))) { + MOZ_CRASH(); + } + JS::Rooted<JS::Value> init(jsapi.cx()); + // We'll crash on failure, so use a IgnoredErrorResult (which also + // auto-suppresses exceptions). + IgnoredErrorResult rv; + mm->GetInitialProcessData(jsapi.cx(), &init, rv); + if (NS_WARN_IF(rv.Failed())) { + MOZ_CRASH(); + } + + initialData.Write(jsapi.cx(), init, rv); + if (NS_WARN_IF(rv.Failed())) { + MOZ_CRASH(); + } + } + } + // This is only implemented (returns a non-empty list) by MacOSX and Linux + // at present. + SystemFontList fontList; + gfxPlatform::GetPlatform()->ReadSystemFontList(&fontList); + + const FullLookAndFeel& lnf = *RemoteLookAndFeel::ExtractData(); + + // If the shared fontlist is in use, collect its shmem block handles to pass + // to the child. + nsTArray<SharedMemoryHandle> sharedFontListBlocks; + gfxPlatformFontList::PlatformFontList()->ShareFontListToProcess( + &sharedFontListBlocks, OtherPid()); + + // Content processes have no permission to access profile directory, so we + // send the file URL instead. + auto* sheetCache = GlobalStyleSheetCache::Singleton(); + if (StyleSheet* ucs = sheetCache->GetUserContentSheet()) { + xpcomInit.userContentSheetURL() = ucs->GetSheetURI(); + } else { + xpcomInit.userContentSheetURL() = nullptr; + } + + // 1. Build ContentDeviceData first, as it may affect some gfxVars. + gfxPlatform::GetPlatform()->BuildContentDeviceData( + &xpcomInit.contentDeviceData()); + // 2. Gather non-default gfxVars. + xpcomInit.gfxNonDefaultVarUpdates() = gfxVars::FetchNonDefaultVars(); + // 3. Start listening for gfxVars updates, to notify content process later on. + gfxVars::AddReceiver(this); + + nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service(); + if (gfxInfo) { + GfxInfoBase* gfxInfoRaw = static_cast<GfxInfoBase*>(gfxInfo.get()); + xpcomInit.gfxFeatureStatus() = gfxInfoRaw->GetAllFeatures(); + } + + // Send the dynamic scalar definitions to the new process. + TelemetryIPC::GetDynamicScalarDefinitions(xpcomInit.dynamicScalarDefs()); + + for (auto const& [location, supported] : sCodecsSupported) { + Unused << SendUpdateMediaCodecsSupported(location, supported); + } + +#ifdef MOZ_WIDGET_ANDROID + if (!(StaticPrefs::media_utility_process_enabled() && + StaticPrefs::media_utility_android_media_codec_enabled())) { + Unused << SendDecoderSupportedMimeTypes( + AndroidDecoderModule::GetSupportedMimeTypesPrefixed()); + } +#endif + + // Must send screen info before send initialData + ScreenManager& screenManager = ScreenManager::GetSingleton(); + screenManager.CopyScreensToRemote(this); + + // Send the UA sheet shared memory buffer and the address it is mapped at. + Maybe<SharedMemoryHandle> sharedUASheetHandle; + uintptr_t sharedUASheetAddress = sheetCache->GetSharedMemoryAddress(); + + if (SharedMemoryHandle handle = sheetCache->CloneHandle()) { + sharedUASheetHandle.emplace(std::move(handle)); + } else { + sharedUASheetAddress = 0; + } + + bool isReadyForBackgroundProcessing = false; +#if defined(XP_WIN) + RefPtr<DllServices> dllSvc(DllServices::Get()); + isReadyForBackgroundProcessing = dllSvc->IsReadyForBackgroundProcessing(); +#endif + + xpcomInit.perfStatsMask() = PerfStats::GetCollectionMask(); + + nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID); + dns->GetTrrDomain(xpcomInit.trrDomain()); + + nsIDNSService::ResolverMode mode; + dns->GetCurrentTrrMode(&mode); + xpcomInit.trrMode() = mode; + xpcomInit.trrModeFromPref() = + static_cast<nsIDNSService::ResolverMode>(StaticPrefs::network_trr_mode()); + + Unused << SendSetXPCOMProcessAttributes( + xpcomInit, initialData, lnf, fontList, std::move(sharedUASheetHandle), + sharedUASheetAddress, std::move(sharedFontListBlocks), + isReadyForBackgroundProcessing); + + ipc::WritableSharedMap* sharedData = + nsFrameMessageManager::sParentProcessManager->SharedData(); + sharedData->Flush(); + sharedData->SendTo(this); + + nsCOMPtr<nsIChromeRegistry> registrySvc = nsChromeRegistry::GetService(); + nsChromeRegistryChrome* chromeRegistry = + static_cast<nsChromeRegistryChrome*>(registrySvc.get()); + chromeRegistry->SendRegisteredChrome(this); + + nsCOMPtr<nsIStringBundleService> stringBundleService = + components::StringBundle::Service(); + stringBundleService->SendContentBundles(this); + + if (gAppData) { + nsCString version(gAppData->version); + nsCString buildID(gAppData->buildID); + nsCString name(gAppData->name); + nsCString UAName(gAppData->UAName); + nsCString ID(gAppData->ID); + nsCString vendor(gAppData->vendor); + nsCString sourceURL(gAppData->sourceURL); + nsCString updateURL(gAppData->updateURL); + + // Sending all information to content process. + Unused << SendAppInfo(version, buildID, name, UAName, ID, vendor, sourceURL, + updateURL); + } + + // Send the child its remote type. On Mac, this needs to be sent prior + // to the message we send to enable the Sandbox (SendStartProcessSandbox) + // because different remote types require different sandbox privileges. + + Unused << SendRemoteType(mRemoteType, mProfile); + + ScriptPreloader::InitContentChild(*this); + + // Initialize the message manager (and load delayed scripts) now that we + // have established communications with the child. + mMessageManager->InitWithCallback(this); + mMessageManager->SetOsPid(Pid()); + + // Set the subprocess's priority. We do this early on because we're likely + // /lowering/ the process's CPU and memory priority, which it has inherited + // from this process. + // + // This call can cause us to send IPC messages to the child process, so it + // must come after the Open() call above. + ProcessPriorityManager::SetProcessPriority(this, aInitialPriority); + + // NB: internally, this will send an IPC message to the child + // process to get it to create the CompositorBridgeChild. This + // message goes through the regular IPC queue for this + // channel, so delivery will happen-before any other messages + // we send. The CompositorBridgeChild must be created before any + // PBrowsers are created, because they rely on the Compositor + // already being around. (Creation is async, so can't happen + // on demand.) + GPUProcessManager* gpm = GPUProcessManager::Get(); + + Endpoint<PCompositorManagerChild> compositor; + Endpoint<PImageBridgeChild> imageBridge; + Endpoint<PVRManagerChild> vrBridge; + Endpoint<PRemoteDecoderManagerChild> videoManager; + AutoTArray<uint32_t, 3> namespaces; + + if (!gpm->CreateContentBridges(OtherPid(), &compositor, &imageBridge, + &vrBridge, &videoManager, mChildID, + &namespaces)) { + // This can fail if we've already started shutting down the compositor + // thread. See Bug 1562763 comment 8. + MOZ_ASSERT(AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdown)); + return false; + } + + Unused << SendInitRendering(std::move(compositor), std::move(imageBridge), + std::move(vrBridge), std::move(videoManager), + namespaces); + + gpm->AddListener(this); + + nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance(); + if (sheetService) { + // This looks like a lot of work, but in a normal browser session we just + // send two loads. + // + // The URIs of the Gecko and Servo sheets should be the same, so it + // shouldn't matter which we look at. + + for (StyleSheet* sheet : *sheetService->AgentStyleSheets()) { + Unused << SendLoadAndRegisterSheet(sheet->GetSheetURI(), + nsIStyleSheetService::AGENT_SHEET); + } + + for (StyleSheet* sheet : *sheetService->UserStyleSheets()) { + Unused << SendLoadAndRegisterSheet(sheet->GetSheetURI(), + nsIStyleSheetService::USER_SHEET); + } + + for (StyleSheet* sheet : *sheetService->AuthorStyleSheets()) { + Unused << SendLoadAndRegisterSheet(sheet->GetSheetURI(), + nsIStyleSheetService::AUTHOR_SHEET); + } + } + +#ifdef MOZ_SANDBOX + bool shouldSandbox = true; + Maybe<FileDescriptor> brokerFd; + // XXX: Checking the pref here makes it possible to enable/disable sandboxing + // during an active session. Currently the pref is only used for testing + // purpose. If the decision is made to permanently rely on the pref, this + // should be changed so that it is required to restart firefox for the change + // of value to take effect. Always send SetProcessSandbox message on macOS. +# if !defined(XP_MACOSX) + shouldSandbox = IsContentSandboxEnabled(); +# endif + +# ifdef XP_LINUX + if (shouldSandbox) { + MOZ_ASSERT(!mSandboxBroker); + bool isFileProcess = mRemoteType == FILE_REMOTE_TYPE; + UniquePtr<SandboxBroker::Policy> policy = + sSandboxBrokerPolicyFactory->GetContentPolicy(Pid(), isFileProcess); + if (policy) { + brokerFd = Some(FileDescriptor()); + mSandboxBroker = + SandboxBroker::Create(std::move(policy), Pid(), brokerFd.ref()); + if (!mSandboxBroker) { + KillHard("SandboxBroker::Create failed"); + return false; + } + MOZ_ASSERT(brokerFd.ref().IsValid()); + } + } +# endif + if (shouldSandbox && !SendSetProcessSandbox(brokerFd)) { + KillHard("SandboxInitFailed"); + } +#endif + + // Ensure that the default set of permissions are avaliable in the content + // process before we try to load any URIs in it. + // + // NOTE: All default permissions has to be transmitted to the child process + // before the blob urls in the for loop below (See Bug 1738713 comment 12). + EnsurePermissionsByKey(""_ns, ""_ns); + + { + nsTArray<BlobURLRegistrationData> registrations; + BlobURLProtocolHandler::ForEachBlobURL([&](BlobImpl* aBlobImpl, + nsIPrincipal* aPrincipal, + const nsCString& aPartitionKey, + const nsACString& aURI, + bool aRevoked) { + // We send all moz-extension Blob URL's to all content processes + // because content scripts mean that a moz-extension can live in any + // process. Same thing for system principal Blob URLs. Content Blob + // URL's are sent for content principals on-demand by + // AboutToLoadHttpFtpDocumentForChild and RemoteWorkerManager. + if (!BlobURLProtocolHandler::IsBlobURLBroadcastPrincipal(aPrincipal)) { + return true; + } + + IPCBlob ipcBlob; + nsresult rv = IPCBlobUtils::Serialize(aBlobImpl, ipcBlob); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + registrations.AppendElement( + BlobURLRegistrationData(nsCString(aURI), ipcBlob, aPrincipal, + nsCString(aPartitionKey), aRevoked)); + + rv = TransmitPermissionsForPrincipal(aPrincipal); + Unused << NS_WARN_IF(NS_FAILED(rv)); + return true; + }); + + if (!registrations.IsEmpty()) { + Unused << SendInitBlobURLs(registrations); + } + } + + // Send down { Parent, Window }ActorOptions at startup to content process. + RefPtr<JSActorService> actorSvc = JSActorService::GetSingleton(); + if (actorSvc) { + nsTArray<JSProcessActorInfo> contentInfos; + actorSvc->GetJSProcessActorInfos(contentInfos); + + nsTArray<JSWindowActorInfo> windowInfos; + actorSvc->GetJSWindowActorInfos(windowInfos); + + Unused << SendInitJSActorInfos(contentInfos, windowInfos); + } + + // Begin subscribing to any BrowsingContextGroups which were hosted by this + // process before it finished launching. + for (const auto& group : mGroups) { + group->Subscribe(this); + } + + MaybeEnableRemoteInputEventQueue(); + + return true; +} + +bool ContentParent::IsAlive() const { + return mLifecycleState == LifecycleState::ALIVE || + mLifecycleState == LifecycleState::INITIALIZED; +} + +bool ContentParent::IsInitialized() const { + return mLifecycleState == LifecycleState::INITIALIZED; +} + +int32_t ContentParent::Pid() const { + if (!mSubprocess) { + return -1; + } + auto pid = mSubprocess->GetChildProcessId(); + if (pid == 0) { + return -1; + } + return ReleaseAssertedCast<int32_t>(pid); +} + +void ContentParent::OnCompositorUnexpectedShutdown() { + GPUProcessManager* gpm = GPUProcessManager::Get(); + + Endpoint<PCompositorManagerChild> compositor; + Endpoint<PImageBridgeChild> imageBridge; + Endpoint<PVRManagerChild> vrBridge; + Endpoint<PRemoteDecoderManagerChild> videoManager; + AutoTArray<uint32_t, 3> namespaces; + + if (!gpm->CreateContentBridges(OtherPid(), &compositor, &imageBridge, + &vrBridge, &videoManager, mChildID, + &namespaces)) { + MOZ_ASSERT(AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdown)); + return; + } + + Unused << SendReinitRendering(std::move(compositor), std::move(imageBridge), + std::move(vrBridge), std::move(videoManager), + namespaces); +} + +void ContentParent::OnCompositorDeviceReset() { + Unused << SendReinitRenderingForDeviceReset(); +} + +void ContentParent::MaybeEnableRemoteInputEventQueue() { + MOZ_ASSERT(!mIsRemoteInputEventQueueEnabled); + if (!IsInputEventQueueSupported()) { + return; + } + mIsRemoteInputEventQueueEnabled = true; + Unused << SendSetInputEventQueueEnabled(); + SetInputPriorityEventEnabled(true); +} + +void ContentParent::SetInputPriorityEventEnabled(bool aEnabled) { + if (!IsInputEventQueueSupported() || !mIsRemoteInputEventQueueEnabled || + mIsInputPriorityEventEnabled == aEnabled) { + return; + } + mIsInputPriorityEventEnabled = aEnabled; + // Send IPC messages to flush the pending events in the input event queue and + // the normal event queue. See PContent.ipdl for more details. + Unused << SendSuspendInputEventQueue(); + Unused << SendFlushInputEventQueue(); + Unused << SendResumeInputEventQueue(); +} + +/*static*/ +bool ContentParent::IsInputEventQueueSupported() { + static bool sSupported = false; + static bool sInitialized = false; + if (!sInitialized) { + MOZ_ASSERT(Preferences::IsServiceAvailable()); + sSupported = Preferences::GetBool("input_event_queue.supported", false); + sInitialized = true; + } + return sSupported; +} + +void ContentParent::OnVarChanged(const GfxVarUpdate& aVar) { + if (!CanSend()) { + return; + } + Unused << SendVarUpdate(aVar); +} + +mozilla::ipc::IPCResult ContentParent::RecvSetClipboard( + const IPCTransferable& aTransferable, const int32_t& aWhichClipboard) { + // aRequestingPrincipal is allowed to be nullptr here. + + if (!ValidatePrincipal(aTransferable.requestingPrincipal(), + {ValidatePrincipalOptions::AllowNullPtr, + ValidatePrincipalOptions::AllowExpanded, + ValidatePrincipalOptions::AllowSystem})) { + LogAndAssertFailedPrincipalValidationInfo( + aTransferable.requestingPrincipal(), __func__); + } + + nsresult rv; + nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv)); + NS_ENSURE_SUCCESS(rv, IPC_OK()); + + nsCOMPtr<nsITransferable> trans = + do_CreateInstance("@mozilla.org/widget/transferable;1", &rv); + NS_ENSURE_SUCCESS(rv, IPC_OK()); + trans->Init(nullptr); + + rv = nsContentUtils::IPCTransferableToTransferable( + aTransferable, true /* aAddDataFlavor */, trans, + true /* aFilterUnknownFlavors */); + NS_ENSURE_SUCCESS(rv, IPC_OK()); + + clipboard->SetData(trans, nullptr, aWhichClipboard); + return IPC_OK(); +} + +namespace { + +static Result<nsCOMPtr<nsITransferable>, nsresult> CreateTransferable( + const nsTArray<nsCString>& aTypes) { + nsresult rv; + nsCOMPtr<nsITransferable> trans = + do_CreateInstance("@mozilla.org/widget/transferable;1", &rv); + if (NS_FAILED(rv)) { + return Err(rv); + } + + MOZ_TRY(trans->Init(nullptr)); + // The private flag is only used to prevent the data from being cached to the + // disk. The flag is not exported to the IPCDataTransfer object. + // The flag is set because we are not sure whether the clipboard data is used + // in a private browsing context. The transferable is only used in this scope, + // so the cache would not reduce memory consumption anyway. + trans->SetIsPrivateData(true); + // Fill out flavors for transferable + for (uint32_t t = 0; t < aTypes.Length(); t++) { + MOZ_TRY(trans->AddDataFlavor(aTypes[t].get())); + } + + return std::move(trans); +} + +} // anonymous namespace + +mozilla::ipc::IPCResult ContentParent::RecvGetClipboard( + nsTArray<nsCString>&& aTypes, const int32_t& aWhichClipboard, + const MaybeDiscarded<WindowContext>& aRequestingWindowContext, + IPCTransferableData* aTransferableData) { + nsresult rv; + // We expect content processes to always pass a non-null window so Content + // Analysis can analyze it. (if Content Analysis is active) + // There may be some cases when a window is closing, etc., in + // which case returning no clipboard content should not be a problem. + if (aRequestingWindowContext.IsDiscarded()) { + NS_WARNING( + "discarded window passed to RecvGetClipboard(); returning no clipboard " + "content"); + return IPC_OK(); + } + if (aRequestingWindowContext.IsNull()) { + return IPC_FAIL(this, "passed null window to RecvGetClipboard()"); + } + RefPtr<WindowGlobalParent> window = aRequestingWindowContext.get_canonical(); + // Retrieve clipboard + nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv)); + if (NS_FAILED(rv)) { + return IPC_OK(); + } + + // Create transferable + auto result = CreateTransferable(aTypes); + if (result.isErr()) { + return IPC_OK(); + } + + // Get data from clipboard + nsCOMPtr<nsITransferable> trans = result.unwrap(); + clipboard->GetData(trans, aWhichClipboard, window); + + nsContentUtils::TransferableToIPCTransferableData( + trans, aTransferableData, true /* aInSyncMessage */, this); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvEmptyClipboard( + const int32_t& aWhichClipboard) { + nsresult rv; + nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv)); + NS_ENSURE_SUCCESS(rv, IPC_OK()); + + clipboard->EmptyClipboard(aWhichClipboard); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvClipboardHasType( + nsTArray<nsCString>&& aTypes, const int32_t& aWhichClipboard, + bool* aHasType) { + nsresult rv; + nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv)); + NS_ENSURE_SUCCESS(rv, IPC_OK()); + + clipboard->HasDataMatchingFlavors(aTypes, aWhichClipboard, aHasType); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvGetExternalClipboardFormats( + const int32_t& aWhichClipboard, const bool& aPlainTextOnly, + nsTArray<nsCString>* aTypes) { + MOZ_ASSERT(aTypes); + DataTransfer::GetExternalClipboardFormats(aWhichClipboard, aPlainTextOnly, + aTypes); + return IPC_OK(); +} + +namespace { + +class ClipboardGetCallback final : public nsIAsyncClipboardGetCallback { + public: + ClipboardGetCallback(ContentParent* aContentParent, + ContentParent::GetClipboardAsyncResolver&& aResolver) + : mContentParent(aContentParent), mResolver(std::move(aResolver)) {} + + // This object will never be held by a cycle-collected object, so it doesn't + // need to be cycle-collected despite holding alive cycle-collected objects. + NS_DECL_ISUPPORTS + + // nsIAsyncClipboardGetCallback + NS_IMETHOD OnSuccess( + nsIAsyncGetClipboardData* aAsyncGetClipboardData) override { + nsTArray<nsCString> flavors; + nsresult rv = aAsyncGetClipboardData->GetFlavorList(flavors); + if (NS_FAILED(rv)) { + return OnError(rv); + } + + auto requestParent = MakeNotNull<RefPtr<ClipboardReadRequestParent>>( + mContentParent, aAsyncGetClipboardData); + if (!mContentParent->SendPClipboardReadRequestConstructor( + requestParent, std::move(flavors))) { + return OnError(NS_ERROR_FAILURE); + } + + mResolver(PClipboardReadRequestOrError(requestParent)); + return NS_OK; + } + + NS_IMETHOD OnError(nsresult aResult) override { + mResolver(aResult); + return NS_OK; + } + + protected: + ~ClipboardGetCallback() = default; + + RefPtr<ContentParent> mContentParent; + ContentParent::GetClipboardAsyncResolver mResolver; +}; + +NS_IMPL_ISUPPORTS(ClipboardGetCallback, nsIAsyncClipboardGetCallback) + +} // namespace + +mozilla::ipc::IPCResult ContentParent::RecvGetClipboardAsync( + nsTArray<nsCString>&& aTypes, const int32_t& aWhichClipboard, + const MaybeDiscarded<WindowContext>& aRequestingWindowContext, + mozilla::NotNull<nsIPrincipal*> aRequestingPrincipal, + GetClipboardAsyncResolver&& aResolver) { + if (!ValidatePrincipal(aRequestingPrincipal, + {ValidatePrincipalOptions::AllowSystem, + ValidatePrincipalOptions::AllowExpanded})) { + LogAndAssertFailedPrincipalValidationInfo(aRequestingPrincipal, __func__); + } + + // If the requesting context has been discarded, cancel the paste. + if (aRequestingWindowContext.IsDiscarded()) { + aResolver(NS_ERROR_NOT_AVAILABLE); + return IPC_OK(); + } + + RefPtr<WindowGlobalParent> requestingWindow = + aRequestingWindowContext.get_canonical(); + if (requestingWindow && requestingWindow->GetContentParent() != this) { + return IPC_FAIL( + this, "attempt to paste into WindowContext loaded in another process"); + } + + nsresult rv; + // Retrieve clipboard + nsCOMPtr<nsIClipboard> clipboard(do_GetService(kCClipboardCID, &rv)); + if (NS_FAILED(rv)) { + aResolver(rv); + return IPC_OK(); + } + + auto callback = MakeRefPtr<ClipboardGetCallback>(this, std::move(aResolver)); + rv = clipboard->AsyncGetData(aTypes, aWhichClipboard, requestingWindow, + aRequestingPrincipal, callback); + if (NS_FAILED(rv)) { + callback->OnError(rv); + return IPC_OK(); + } + + return IPC_OK(); +} + +already_AddRefed<PClipboardWriteRequestParent> +ContentParent::AllocPClipboardWriteRequestParent( + const int32_t& aClipboardType) { + RefPtr<ClipboardWriteRequestParent> request = + MakeAndAddRef<ClipboardWriteRequestParent>(this); + request->Init(aClipboardType); + return request.forget(); +} + +mozilla::ipc::IPCResult ContentParent::RecvGetIconForExtension( + const nsACString& aFileExt, const uint32_t& aIconSize, + nsTArray<uint8_t>* bits) { +#ifdef MOZ_WIDGET_ANDROID + NS_ASSERTION(AndroidBridge::Bridge() != nullptr, + "AndroidBridge is not available"); + if (AndroidBridge::Bridge() == nullptr) { + // Do not fail - just no icon will be shown + return IPC_OK(); + } + + bits->AppendElements(aIconSize * aIconSize * 4); + + AndroidBridge::Bridge()->GetIconForExtension(aFileExt, aIconSize, + bits->Elements()); +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvFirstIdle() { + // When the ContentChild goes idle, it sends us a FirstIdle message + // which we use as a good time to signal the PreallocatedProcessManager + // that it can start allocating processes from now on. + if (mIsAPreallocBlocker) { + MOZ_LOG( + ContentParent::GetLog(), LogLevel::Verbose, + ("RecvFirstIdle %p: Removing Blocker for %s", this, mRemoteType.get())); + PreallocatedProcessManager::RemoveBlocker(mRemoteType, this); + mIsAPreallocBlocker = false; + } + return IPC_OK(); +} + +already_AddRefed<nsDocShellLoadState> ContentParent::TakePendingLoadStateForId( + uint64_t aLoadIdentifier) { + return mPendingLoadStates.Extract(aLoadIdentifier).valueOr(nullptr).forget(); +} + +void ContentParent::StorePendingLoadState(nsDocShellLoadState* aLoadState) { + MOZ_DIAGNOSTIC_ASSERT( + !mPendingLoadStates.Contains(aLoadState->GetLoadIdentifier()), + "The same nsDocShellLoadState was sent to the same content process " + "twice? This will mess with cross-process tracking of loads"); + mPendingLoadStates.InsertOrUpdate(aLoadState->GetLoadIdentifier(), + aLoadState); +} + +mozilla::ipc::IPCResult ContentParent::RecvCleanupPendingLoadState( + uint64_t aLoadIdentifier) { + mPendingLoadStates.Remove(aLoadIdentifier); + return IPC_OK(); +} + +// We want ContentParent to show up in CC logs for debugging purposes, but we +// don't actually cycle collect it. +NS_IMPL_CYCLE_COLLECTION_0(ContentParent) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(ContentParent) +NS_IMPL_CYCLE_COLLECTING_RELEASE(ContentParent) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ContentParent) + NS_INTERFACE_MAP_ENTRY_CONCRETE(ContentParent) + NS_INTERFACE_MAP_ENTRY(nsIDOMProcessParent) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsIDOMGeoPositionCallback) + NS_INTERFACE_MAP_ENTRY(nsIDOMGeoPositionErrorCallback) + NS_INTERFACE_MAP_ENTRY(nsIAsyncShutdownBlocker) + NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMProcessParent) +NS_INTERFACE_MAP_END + +class RequestContentJSInterruptRunnable final : public Runnable { + public: + explicit RequestContentJSInterruptRunnable(PProcessHangMonitorParent* aActor) + : Runnable("dom::RequestContentJSInterruptRunnable"), + mHangMonitorActor(aActor) {} + + NS_IMETHOD Run() override { + MOZ_ASSERT(mHangMonitorActor); + Unused << mHangMonitorActor->SendRequestContentJSInterrupt(); + + return NS_OK; + } + + private: + // The end-of-life of ContentParent::mHangMonitorActor is bound to + // ContentParent::ActorDestroy and then HangMonitorParent::Shutdown + // dispatches a shutdown runnable to this queue and waits for it to be + // executed. So the runnable needs not to care about keeping it alive, + // as it is surely dispatched earlier than the + // HangMonitorParent::ShutdownOnThread. + RefPtr<PProcessHangMonitorParent> mHangMonitorActor; +}; + +void ContentParent::SignalImpendingShutdownToContentJS() { + if (!mIsSignaledImpendingShutdown && + !AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdown)) { + MaybeLogBlockShutdownDiagnostics( + this, "BlockShutdown: NotifyImpendingShutdown.", __FILE__, __LINE__); + NotifyImpendingShutdown(); + mIsSignaledImpendingShutdown = true; + if (mHangMonitorActor && + StaticPrefs::dom_abort_script_on_child_shutdown()) { + MaybeLogBlockShutdownDiagnostics( + this, "BlockShutdown: RequestContentJSInterrupt.", __FILE__, + __LINE__); + RefPtr<RequestContentJSInterruptRunnable> r = + new RequestContentJSInterruptRunnable(mHangMonitorActor); + ProcessHangMonitor::Get()->Dispatch(r.forget()); + } + } +} + +// Async shutdown blocker +NS_IMETHODIMP +ContentParent::BlockShutdown(nsIAsyncShutdownClient* aClient) { + if (!AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdown)) { +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + mBlockShutdownCalled = true; +#endif + // Our real shutdown has not yet started. Just notify the impending + // shutdown and eventually cancel content JS. + SignalImpendingShutdownToContentJS(); + // This will make our process unusable for normal content, so we need to + // ensure we won't get re-used by GetUsedBrowserProcess as we have not yet + // done MarkAsDead. + PreallocatedProcessManager::Erase(this); + StopRecyclingE10SOnly(false); + + if (sQuitApplicationGrantedClient) { + Unused << sQuitApplicationGrantedClient->RemoveBlocker(this); + } +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + mBlockShutdownCalled = false; +#endif + return NS_OK; + } + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + // We register two final shutdown blockers and both would call us, but if + // things go well we will unregister both as (delayed) reaction to the first + // call we get and thus never receive a second call. Thus we believe that we + // will get called only once except for quit-application-granted, which is + // handled above. + MOZ_ASSERT(!mBlockShutdownCalled); + mBlockShutdownCalled = true; +#endif + + if (CanSend()) { + MaybeLogBlockShutdownDiagnostics(this, "BlockShutdown: CanSend.", __FILE__, + __LINE__); + + // Make sure that our process will get scheduled. + ProcessPriorityManager::SetProcessPriority(this, + PROCESS_PRIORITY_FOREGROUND); + // The normal shutdown sequence is to send a shutdown message + // to the child and then just wait for ActorDestroy which will + // cleanup everything and remove our blockers. + if (!ShutDownProcess(SEND_SHUTDOWN_MESSAGE)) { + KillHard("Failed to send Shutdown message. Destroying the process..."); + return NS_OK; + } + } else if (IsLaunching()) { + MaybeLogBlockShutdownDiagnostics( + this, "BlockShutdown: !CanSend && IsLaunching.", __FILE__, __LINE__); + + // If we get here while we are launching, we must wait for the child to + // be able to react on our commands. Mark this process as dead. This + // will make bail out LaunchSubprocessResolve and kick off the normal + // shutdown sequence. + MarkAsDead(); + } else { + MOZ_ASSERT(IsDead()); + if (!IsDead()) { + MaybeLogBlockShutdownDiagnostics( + this, "BlockShutdown: !!! !CanSend && !IsLaunching && !IsDead !!!", + __FILE__, __LINE__); + } else { + MaybeLogBlockShutdownDiagnostics( + this, "BlockShutdown: !CanSend && !IsLaunching && IsDead.", __FILE__, + __LINE__); + } + // Nothing left we can do. We must assume that we race with an ongoing + // process shutdown, such that we can expect our shutdown blockers to be + // removed normally. + } + + return NS_OK; +} + +NS_IMETHODIMP +ContentParent::GetName(nsAString& aName) { + aName.AssignLiteral("ContentParent:"); + aName.AppendPrintf(" id=%p", this); + return NS_OK; +} + +NS_IMETHODIMP +ContentParent::GetState(nsIPropertyBag** aResult) { + auto props = MakeRefPtr<nsHashPropertyBag>(); + props->SetPropertyAsACString(u"remoteTypePrefix"_ns, + RemoteTypePrefix(mRemoteType)); + *aResult = props.forget().downcast<nsIWritablePropertyBag>().take(); + return NS_OK; +} + +static void InitShutdownClients() { + if (!sXPCOMShutdownClient) { + nsresult rv; + nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService(); + if (!svc) { + return; + } + + nsCOMPtr<nsIAsyncShutdownClient> client; + // TODO: It seems as if getPhase from AsyncShutdown.sys.mjs does not check + // if we are beyond our phase already. See bug 1762840. + if (!AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMWillShutdown)) { + rv = svc->GetXpcomWillShutdown(getter_AddRefs(client)); + if (NS_SUCCEEDED(rv)) { + sXPCOMShutdownClient = client.forget(); + ClearOnShutdown(&sXPCOMShutdownClient); + } + } + if (!AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdown)) { + rv = svc->GetProfileBeforeChange(getter_AddRefs(client)); + if (NS_SUCCEEDED(rv)) { + sProfileBeforeChangeClient = client.forget(); + ClearOnShutdown(&sProfileBeforeChangeClient); + } + } + // TODO: ShutdownPhase::AppShutdownConfirmed is not mapping to + // QuitApplicationGranted, see bug 1762840 comment 4. + if (!AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + rv = svc->GetQuitApplicationGranted(getter_AddRefs(client)); + if (NS_SUCCEEDED(rv)) { + sQuitApplicationGrantedClient = client.forget(); + ClearOnShutdown(&sQuitApplicationGrantedClient); + } + } + } +} + +void ContentParent::AddShutdownBlockers() { + InitShutdownClients(); + MOZ_ASSERT(sXPCOMShutdownClient); + MOZ_ASSERT(sProfileBeforeChangeClient); + + if (sXPCOMShutdownClient) { + sXPCOMShutdownClient->AddBlocker( + this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__, u""_ns); + } + if (sProfileBeforeChangeClient) { + sProfileBeforeChangeClient->AddBlocker( + this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__, u""_ns); + } + if (sQuitApplicationGrantedClient) { + sQuitApplicationGrantedClient->AddBlocker( + this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__, u""_ns); + } +} + +void ContentParent::RemoveShutdownBlockers() { + MOZ_ASSERT(sXPCOMShutdownClient); + MOZ_ASSERT(sProfileBeforeChangeClient); + + MaybeLogBlockShutdownDiagnostics(this, "RemoveShutdownBlockers", __FILE__, + __LINE__); +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + mBlockShutdownCalled = false; +#endif + + if (sXPCOMShutdownClient) { + Unused << sXPCOMShutdownClient->RemoveBlocker(this); + } + if (sProfileBeforeChangeClient) { + Unused << sProfileBeforeChangeClient->RemoveBlocker(this); + } + if (sQuitApplicationGrantedClient) { + Unused << sQuitApplicationGrantedClient->RemoveBlocker(this); + } +} + +NS_IMETHODIMP +ContentParent::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (IsDead() || !mSubprocess) { + return NS_OK; + } + + if (!strcmp(aTopic, "nsPref:changed")) { + // We know prefs are ASCII here. + NS_LossyConvertUTF16toASCII strData(aData); + + Pref pref(strData, /* isLocked */ false, + /* isSanitized */ false, Nothing(), Nothing()); + + Preferences::GetPreference(&pref, GeckoProcessType_Content, + GetRemoteType()); + + // This check is a bit of a hack. We want to avoid sending excessive + // preference updates to subprocesses for performance reasons, but we + // currently don't have a great mechanism for doing so. (See Bug 1819714) + // We're going to hijack the sanitization mechanism to accomplish our goal + // but it imposes the following complications: + // 1) It doesn't avoid sending anything to other (non-web-content) + // subprocesses so we're not getting any perf gains there + // 2) It confuses the subprocesses w.r.t. sanitization. The point of + // sending a preference update of a sanitized preference is so that + // content process knows when it's asked to resolve a sanitized + // preference, and it can send telemetry and/or crash. With this + // change, a sanitized pref that is created during the browser session + // will not be sent to the content process, and therefore the content + // process won't know it should telemetry/crash on access - it'll just + // silently fail to resolve it. After browser restart, the sanitized + // pref will be populated into the content process via the shared pref + // map and _then_ if it is accessed, the content process will crash. + // We're seeing good telemetry/crash rates right now, so we're okay with + // this limitation. + if (pref.isSanitized()) { + return NS_OK; + } + + if (IsInitialized()) { + MOZ_ASSERT(mQueuedPrefs.IsEmpty()); + if (!SendPreferenceUpdate(pref)) { + return NS_ERROR_NOT_AVAILABLE; + } + } else { + MOZ_ASSERT(!IsDead()); + mQueuedPrefs.AppendElement(pref); + } + + return NS_OK; + } + + if (!IsAlive()) { + return NS_OK; + } + + // listening for memory pressure event + if (!strcmp(aTopic, "memory-pressure")) { + Unused << SendFlushMemory(nsDependentString(aData)); + } else if (!strcmp(aTopic, "application-background")) { + Unused << SendApplicationBackground(); + } else if (!strcmp(aTopic, "application-foreground")) { + Unused << SendApplicationForeground(); + } else if (!strcmp(aTopic, NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC)) { + NS_ConvertUTF16toUTF8 dataStr(aData); + const char* offline = dataStr.get(); + if (!SendSetOffline(!strcmp(offline, "true"))) { + return NS_ERROR_NOT_AVAILABLE; + } + } else if (!strcmp(aTopic, NS_IPC_IOSERVICE_SET_CONNECTIVITY_TOPIC)) { + if (!SendSetConnectivity(u"true"_ns.Equals(aData))) { + return NS_ERROR_NOT_AVAILABLE; + } + } else if (!strcmp(aTopic, NS_IPC_CAPTIVE_PORTAL_SET_STATE)) { + nsCOMPtr<nsICaptivePortalService> cps = do_QueryInterface(aSubject); + MOZ_ASSERT(cps, "Should QI to a captive portal service"); + if (!cps) { + return NS_ERROR_FAILURE; + } + int32_t state; + cps->GetState(&state); + if (!SendSetCaptivePortalState(state)) { + return NS_ERROR_NOT_AVAILABLE; + } + } + // listening for alert notifications + else if (!strcmp(aTopic, "alertfinished") || + !strcmp(aTopic, "alertclickcallback") || + !strcmp(aTopic, "alertshow") || + !strcmp(aTopic, "alertdisablecallback") || + !strcmp(aTopic, "alertsettingscallback")) { + if (!SendNotifyAlertsObserver(nsDependentCString(aTopic), + nsDependentString(aData))) + return NS_ERROR_NOT_AVAILABLE; + } else if (!strcmp(aTopic, "child-gc-request")) { + Unused << SendGarbageCollect(); + } else if (!strcmp(aTopic, "child-cc-request")) { + Unused << SendCycleCollect(); + } else if (!strcmp(aTopic, "child-mmu-request")) { + Unused << SendMinimizeMemoryUsage(); + } else if (!strcmp(aTopic, "child-ghost-request")) { + Unused << SendUnlinkGhosts(); + } else if (!strcmp(aTopic, "last-pb-context-exited")) { + Unused << SendLastPrivateDocShellDestroyed(); + } +#ifdef ACCESSIBILITY + else if (aData && !strcmp(aTopic, "a11y-init-or-shutdown")) { + if (*aData == '1') { + // Make sure accessibility is running in content process when + // accessibility gets initiated in chrome process. + Unused << SendActivateA11y(); + } else { + // If possible, shut down accessibility in content process when + // accessibility gets shutdown in chrome process. + Unused << SendShutdownA11y(); + } + } +#endif + else if (!strcmp(aTopic, "cacheservice:empty-cache")) { + Unused << SendNotifyEmptyHTTPCache(); + } else if (!strcmp(aTopic, "intl:app-locales-changed")) { + nsTArray<nsCString> appLocales; + LocaleService::GetInstance()->GetAppLocalesAsBCP47(appLocales); + Unused << SendUpdateAppLocales(appLocales); + } else if (!strcmp(aTopic, "intl:requested-locales-changed")) { + nsTArray<nsCString> requestedLocales; + LocaleService::GetInstance()->GetRequestedLocales(requestedLocales); + Unused << SendUpdateRequestedLocales(requestedLocales); + } else if (!strcmp(aTopic, "cookie-changed") || + !strcmp(aTopic, "private-cookie-changed")) { + MOZ_ASSERT(aSubject, "cookie changed notification must have subject."); + nsCOMPtr<nsICookieNotification> notification = do_QueryInterface(aSubject); + MOZ_ASSERT(notification, + "cookie changed notification must have nsICookieNotification."); + nsICookieNotification::Action action = notification->GetAction(); + + PNeckoParent* neckoParent = LoneManagedOrNullAsserts(ManagedPNeckoParent()); + if (!neckoParent) { + return NS_OK; + } + PCookieServiceParent* csParent = + LoneManagedOrNullAsserts(neckoParent->ManagedPCookieServiceParent()); + if (!csParent) { + return NS_OK; + } + auto* cs = static_cast<CookieServiceParent*>(csParent); + if (action == nsICookieNotification::COOKIES_BATCH_DELETED) { + nsCOMPtr<nsIArray> cookieList; + DebugOnly<nsresult> rv = + notification->GetBatchDeletedCookies(getter_AddRefs(cookieList)); + NS_ASSERTION(NS_SUCCEEDED(rv) && cookieList, "couldn't get cookie list"); + cs->RemoveBatchDeletedCookies(cookieList); + return NS_OK; + } + + if (action == nsICookieNotification::ALL_COOKIES_CLEARED) { + cs->RemoveAll(); + return NS_OK; + } + + // Do not push these cookie updates to the same process they originated + // from. + if (cs->ProcessingCookie()) { + return NS_OK; + } + + nsCOMPtr<nsICookie> xpcCookie; + DebugOnly<nsresult> rv = notification->GetCookie(getter_AddRefs(xpcCookie)); + NS_ASSERTION(NS_SUCCEEDED(rv) && xpcCookie, "couldn't get cookie"); + + // only broadcast the cookie change to content processes that need it + const Cookie& cookie = xpcCookie->AsCookie(); + + // do not send cookie if content process does not have similar cookie + if (!cs->ContentProcessHasCookie(cookie)) { + return NS_OK; + } + + if (action == nsICookieNotification::COOKIE_DELETED) { + cs->RemoveCookie(cookie); + } else if (action == nsICookieNotification::COOKIE_ADDED || + action == nsICookieNotification::COOKIE_CHANGED) { + cs->AddCookie(cookie); + } + } else if (!strcmp(aTopic, NS_NETWORK_LINK_TYPE_TOPIC)) { + UpdateNetworkLinkType(); + } else if (!strcmp(aTopic, "network:socket-process-crashed")) { + Unused << SendSocketProcessCrashed(); + } else if (!strcmp(aTopic, DEFAULT_TIMEZONE_CHANGED_OBSERVER_TOPIC)) { + Unused << SendSystemTimezoneChanged(); + } else if (!strcmp(aTopic, NS_NETWORK_TRR_MODE_CHANGED_TOPIC)) { + nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID); + nsIDNSService::ResolverMode mode; + dns->GetCurrentTrrMode(&mode); + Unused << SendSetTRRMode(mode, static_cast<nsIDNSService::ResolverMode>( + StaticPrefs::network_trr_mode())); + } + + return NS_OK; +} + +void ContentParent::UpdateNetworkLinkType() { + nsresult rv; + nsCOMPtr<nsINetworkLinkService> nls = + do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return; + } + + uint32_t linkType = nsINetworkLinkService::LINK_TYPE_UNKNOWN; + rv = nls->GetLinkType(&linkType); + if (NS_FAILED(rv)) { + return; + } + + Unused << SendNetworkLinkTypeChange(linkType); +} + +NS_IMETHODIMP +ContentParent::GetInterface(const nsIID& aIID, void** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + + if (aIID.Equals(NS_GET_IID(nsIMessageSender))) { + nsCOMPtr<nsIMessageSender> mm = GetMessageManager(); + mm.forget(aResult); + return NS_OK; + } + + return NS_NOINTERFACE; +} + +mozilla::ipc::IPCResult ContentParent::RecvInitBackground( + Endpoint<PBackgroundStarterParent>&& aEndpoint) { + if (!BackgroundParent::AllocStarter(this, std::move(aEndpoint))) { + NS_WARNING("BackgroundParent::Alloc failed"); + } + + return IPC_OK(); +} + +bool ContentParent::CanOpenBrowser(const IPCTabContext& aContext) { + // (PopupIPCTabContext lets the child process prove that it has access to + // the app it's trying to open.) + // On e10s we also allow UnsafeTabContext to allow service workers to open + // windows. This is enforced in MaybeInvalidTabContext. + if (aContext.type() != IPCTabContext::TPopupIPCTabContext) { + MOZ_CRASH_UNLESS_FUZZING( + "Unexpected IPCTabContext type. Aborting AllocPBrowserParent."); + return false; + } + + if (aContext.type() == IPCTabContext::TPopupIPCTabContext) { + const PopupIPCTabContext& popupContext = aContext.get_PopupIPCTabContext(); + + auto opener = BrowserParent::GetFrom(popupContext.opener().AsParent()); + if (!opener) { + MOZ_CRASH_UNLESS_FUZZING( + "Got null opener from child; aborting AllocPBrowserParent."); + return false; + } + } + + MaybeInvalidTabContext tc(aContext); + if (!tc.IsValid()) { + NS_ERROR(nsPrintfCString("Child passed us an invalid TabContext. (%s) " + "Aborting AllocPBrowserParent.", + tc.GetInvalidReason()) + .get()); + return false; + } + + return true; +} + +static bool CloneIsLegal(ContentParent* aCp, CanonicalBrowsingContext& aSource, + CanonicalBrowsingContext& aTarget) { + // Source and target must be in the same BCG + if (NS_WARN_IF(aSource.Group() != aTarget.Group())) { + return false; + } + // The source and target must be in different toplevel <browser>s + if (NS_WARN_IF(aSource.Top() == aTarget.Top())) { + return false; + } + + // Neither source nor target must be toplevel. + if (NS_WARN_IF(aSource.IsTop()) || NS_WARN_IF(aTarget.IsTop())) { + return false; + } + + // Both should be embedded by the same process. + auto* sourceEmbedder = aSource.GetParentWindowContext(); + if (NS_WARN_IF(!sourceEmbedder) || + NS_WARN_IF(sourceEmbedder->GetContentParent() != aCp)) { + return false; + } + + auto* targetEmbedder = aTarget.GetParentWindowContext(); + if (NS_WARN_IF(!targetEmbedder) || + NS_WARN_IF(targetEmbedder->GetContentParent() != aCp)) { + return false; + } + + // All seems sane. + return true; +} + +mozilla::ipc::IPCResult ContentParent::RecvCloneDocumentTreeInto( + const MaybeDiscarded<BrowsingContext>& aSource, + const MaybeDiscarded<BrowsingContext>& aTarget, PrintData&& aPrintData) { + if (aSource.IsNullOrDiscarded() || aTarget.IsNullOrDiscarded()) { + return IPC_OK(); + } + + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + // All existing processes have potentially been slated for removal already, + // such that any subsequent call to GetNewOrUsedLaunchingBrowserProcess + // (normally supposed to find an existing process here) will try to create + // a new process (but fail) that nobody would ever really use. Let's avoid + // this together with the expensive CloneDocumentTreeInto operation. + return IPC_OK(); + } + + auto* source = aSource.get_canonical(); + auto* target = aTarget.get_canonical(); + + if (!CloneIsLegal(this, *source, *target)) { + return IPC_FAIL(this, "Illegal subframe clone"); + } + + ContentParent* cp = source->GetContentParent(); + if (NS_WARN_IF(!cp)) { + return IPC_OK(); + } + + if (NS_WARN_IF(cp->GetRemoteType() == GetRemoteType())) { + // Wanted to switch to a target browsing context that's already local again. + // See bug 1676996 for how this can happen. + // + // Dropping the switch on the floor seems fine for this case, though we + // could also try to clone the local document. + // + // If the remote type matches & it's in the same group (which was confirmed + // by CloneIsLegal), it must be the exact same process. + MOZ_DIAGNOSTIC_ASSERT(cp == this); + return IPC_OK(); + } + + target->CloneDocumentTreeInto(source, cp->GetRemoteType(), + std::move(aPrintData)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvUpdateRemotePrintSettings( + const MaybeDiscarded<BrowsingContext>& aTarget, PrintData&& aPrintData) { + if (aTarget.IsNullOrDiscarded()) { + return IPC_OK(); + } + + auto* target = aTarget.get_canonical(); + auto* bp = target->GetBrowserParent(); + if (NS_WARN_IF(!bp)) { + return IPC_OK(); + } + + Unused << bp->SendUpdateRemotePrintSettings(std::move(aPrintData)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvConstructPopupBrowser( + ManagedEndpoint<PBrowserParent>&& aBrowserEp, + ManagedEndpoint<PWindowGlobalParent>&& aWindowEp, const TabId& aTabId, + const IPCTabContext& aContext, const WindowGlobalInit& aInitialWindowInit, + const uint32_t& aChromeFlags) { + MOZ_ASSERT(XRE_IsParentProcess()); + + if (!CanOpenBrowser(aContext)) { + return IPC_FAIL(this, "CanOpenBrowser Failed"); + } + + RefPtr<CanonicalBrowsingContext> browsingContext = + CanonicalBrowsingContext::Get( + aInitialWindowInit.context().mBrowsingContextId); + if (!browsingContext || browsingContext->IsDiscarded()) { + return IPC_FAIL(this, "Null or discarded initial BrowsingContext"); + } + if (!aInitialWindowInit.principal()) { + return IPC_FAIL(this, "Cannot create without valid initial principal"); + } + + if (!ValidatePrincipal(aInitialWindowInit.principal())) { + LogAndAssertFailedPrincipalValidationInfo(aInitialWindowInit.principal(), + __func__); + } + + if (browsingContext->GetBrowserParent()) { + return IPC_FAIL(this, "BrowsingContext already has a BrowserParent"); + } + + uint32_t chromeFlags = aChromeFlags; + TabId openerTabId(0); + ContentParentId openerCpId(0); + if (aContext.type() == IPCTabContext::TPopupIPCTabContext) { + // CanOpenBrowser has ensured that the IPCTabContext is of + // type PopupIPCTabContext, and that the opener BrowserParent is + // reachable. + const PopupIPCTabContext& popupContext = aContext.get_PopupIPCTabContext(); + auto opener = BrowserParent::GetFrom(popupContext.opener().AsParent()); + openerTabId = opener->GetTabId(); + openerCpId = opener->Manager()->ChildID(); + + // We must ensure that the private browsing and remoteness flags + // match those of the opener. + nsCOMPtr<nsILoadContext> loadContext = opener->GetLoadContext(); + if (!loadContext) { + return IPC_FAIL(this, "Missing Opener LoadContext"); + } + if (loadContext->UsePrivateBrowsing()) { + chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW; + } + if (loadContext->UseRemoteSubframes()) { + chromeFlags |= nsIWebBrowserChrome::CHROME_FISSION_WINDOW; + } + } + + // And because we're allocating a remote browser, of course the + // window is remote. + chromeFlags |= nsIWebBrowserChrome::CHROME_REMOTE_WINDOW; + + if (NS_WARN_IF(!browsingContext->IsOwnedByProcess(ChildID()))) { + return IPC_FAIL(this, "BrowsingContext Owned by Incorrect Process!"); + } + + MaybeInvalidTabContext tc(aContext); + MOZ_ASSERT(tc.IsValid()); + + RefPtr<WindowGlobalParent> initialWindow = + WindowGlobalParent::CreateDisconnected(aInitialWindowInit); + if (!initialWindow) { + return IPC_FAIL(this, "Failed to create WindowGlobalParent"); + } + + auto parent = MakeRefPtr<BrowserParent>(this, aTabId, tc.GetTabContext(), + browsingContext, chromeFlags); + + // Bind the created BrowserParent to IPC to actually link the actor. + if (NS_WARN_IF(!BindPBrowserEndpoint(std::move(aBrowserEp), parent))) { + return IPC_FAIL(this, "BindPBrowserEndpoint failed"); + } + + // XXX: Why are we checking these requirements? It seems we should register + // the created frame unconditionally? + if (openerTabId > 0) { + // The creation of PBrowser was triggered from content process through + // window.open(). + // We need to register remote frame with the child generated tab id. + auto* cpm = ContentProcessManager::GetSingleton(); + if (!cpm || !cpm->RegisterRemoteFrame(parent)) { + return IPC_FAIL(this, "RegisterRemoteFrame Failed"); + } + } + + if (NS_WARN_IF(!parent->BindPWindowGlobalEndpoint(std::move(aWindowEp), + initialWindow))) { + return IPC_FAIL(this, "BindPWindowGlobalEndpoint failed"); + } + + browsingContext->SetCurrentBrowserParent(parent); + + initialWindow->Init(); + + // When enabling input event prioritization, input events may preempt other + // normal priority IPC messages. To prevent the input events preempt + // PBrowserConstructor, we use an IPC 'RemoteIsReadyToHandleInputEvents' to + // notify parent that BrowserChild is created. In this case, PBrowser is + // initiated from content so that we can set BrowserParent as ready to handle + // input + parent->SetReadyToHandleInputEvents(); + return IPC_OK(); +} + +mozilla::PRemoteSpellcheckEngineParent* +ContentParent::AllocPRemoteSpellcheckEngineParent() { + mozilla::RemoteSpellcheckEngineParent* parent = + new mozilla::RemoteSpellcheckEngineParent(); + return parent; +} + +bool ContentParent::DeallocPRemoteSpellcheckEngineParent( + PRemoteSpellcheckEngineParent* parent) { + delete parent; + return true; +} + +/* static */ +void ContentParent::SendShutdownTimerCallback(nsITimer* aTimer, + void* aClosure) { + auto* self = static_cast<ContentParent*>(aClosure); + self->AsyncSendShutDownMessage(); +} + +/* static */ +void ContentParent::ForceKillTimerCallback(nsITimer* aTimer, void* aClosure) { + // We don't want to time out the content process during XPCShell tests. This + // is the easiest way to ensure that. + if (PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR")) { + return; + } + + auto* self = static_cast<ContentParent*>(aClosure); + self->KillHard("ShutDownKill"); +} + +void ContentParent::GeneratePairedMinidump(const char* aReason) { + // We're about to kill the child process associated with this content. + // Something has gone wrong to get us here, so we generate a minidump + // of the parent and child for submission to the crash server unless we're + // already shutting down. + if (mCrashReporter && + !AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed) && + StaticPrefs::dom_ipc_tabs_createKillHardCrashReports_AtStartup()) { + // GeneratePairedMinidump creates two minidumps for us - the main + // one is for the content process we're about to kill, and the other + // one is for the main browser process. That second one is the extra + // minidump tagging along, so we have to tell the crash reporter that + // it exists and is being appended. + nsAutoCString additionalDumps("browser"); + mCrashReporter->AddAnnotation( + CrashReporter::Annotation::additional_minidumps, additionalDumps); + nsDependentCString reason(aReason); + mCrashReporter->AddAnnotation(CrashReporter::Annotation::ipc_channel_error, + reason); + + // Generate the report and insert into the queue for submittal. + if (mCrashReporter->GenerateMinidumpAndPair(this, "browser"_ns)) { + mCrashReporter->FinalizeCrashReport(); + mCreatedPairedMinidumps = true; + } + } +} + +void ContentParent::HandleOrphanedMinidump(nsString* aDumpId) { + if (CrashReporter::FinalizeOrphanedMinidump( + OtherPid(), GeckoProcessType_Content, aDumpId)) { + CrashReporterHost::RecordCrash(GeckoProcessType_Content, + nsICrashService::CRASH_TYPE_CRASH, *aDumpId); + } else { + NS_WARNING(nsPrintfCString("content process pid = %" PRIPID + " crashed without leaving a minidump behind", + OtherPid()) + .get()); + } +} + +// WARNING: aReason appears in telemetry, so any new value passed in requires +// data review. +void ContentParent::KillHard(const char* aReason) { + AUTO_PROFILER_LABEL("ContentParent::KillHard", OTHER); + + // On Windows, calling KillHard multiple times causes problems - the + // process handle becomes invalid on the first call, causing a second call + // to crash our process - more details in bug 890840. + if (mCalledKillHard) { + return; + } + mCalledKillHard = true; + if (mSendShutdownTimer) { + mSendShutdownTimer->Cancel(); + mSendShutdownTimer = nullptr; + } + if (mForceKillTimer) { + mForceKillTimer->Cancel(); + mForceKillTimer = nullptr; + } + + RemoveShutdownBlockers(); + nsCString reason = nsDependentCString(aReason); + + // If we find mIsNotifiedShutdownSuccess there is no reason to blame this + // content process, most probably our parent process is just slow in + // processing its own main thread queue. + if (!mIsNotifiedShutdownSuccess) { + GeneratePairedMinidump(aReason); + } else { + reason = nsDependentCString("KillHard after IsNotifiedShutdownSuccess."); + } + Telemetry::Accumulate(Telemetry::SUBPROCESS_KILL_HARD, reason, 1); + + ProcessHandle otherProcessHandle; + if (!base::OpenProcessHandle(OtherPid(), &otherProcessHandle)) { + NS_ERROR("Failed to open child process when attempting kill."); + if (CanSend()) { + GetIPCChannel()->InduceConnectionError(); + } + return; + } + + if (!KillProcess(otherProcessHandle, base::PROCESS_END_KILLED_BY_USER)) { + if (mCrashReporter) { + mCrashReporter->DeleteCrashReport(); + } + NS_WARNING("failed to kill subprocess!"); + } + + if (mSubprocess) { + MOZ_LOG( + ContentParent::GetLog(), LogLevel::Verbose, + ("KillHard Subprocess(%s): ContentParent %p mSubprocess %p handle " + "%" PRIuPTR, + aReason, this, mSubprocess, + mSubprocess ? (uintptr_t)mSubprocess->GetChildProcessHandle() : -1)); + mSubprocess->SetAlreadyDead(); + } + + // After we've killed the remote process, also ensure we induce a connection + // error in the IPC channel to immediately stop all IPC communication on this + // channel. + if (CanSend()) { + GetIPCChannel()->InduceConnectionError(); + } + + // EnsureProcessTerminated has responsibilty for closing otherProcessHandle. + XRE_GetIOMessageLoop()->PostTask( + NewRunnableFunction("EnsureProcessTerminatedRunnable", + &ProcessWatcher::EnsureProcessTerminated, + otherProcessHandle, /*force=*/true)); +} + +void ContentParent::FriendlyName(nsAString& aName, bool aAnonymize) { + aName.Truncate(); + if (mIsForBrowser) { + aName.AssignLiteral("Browser"); + } else if (aAnonymize) { + aName.AssignLiteral("<anonymized-name>"); + } else { + aName.AssignLiteral("???"); + } +} + +mozilla::ipc::IPCResult ContentParent::RecvInitCrashReporter( + const NativeThreadId& aThreadId) { + mCrashReporter = + MakeUnique<CrashReporterHost>(GeckoProcessType_Content, aThreadId); + + return IPC_OK(); +} + +hal_sandbox::PHalParent* ContentParent::AllocPHalParent() { + return hal_sandbox::CreateHalParent(); +} + +bool ContentParent::DeallocPHalParent(hal_sandbox::PHalParent* aHal) { + delete aHal; + return true; +} + +devtools::PHeapSnapshotTempFileHelperParent* +ContentParent::AllocPHeapSnapshotTempFileHelperParent() { + return devtools::HeapSnapshotTempFileHelperParent::Create(); +} + +bool ContentParent::DeallocPHeapSnapshotTempFileHelperParent( + devtools::PHeapSnapshotTempFileHelperParent* aHeapSnapshotHelper) { + delete aHeapSnapshotHelper; + return true; +} + +bool ContentParent::SendRequestMemoryReport( + const uint32_t& aGeneration, const bool& aAnonymize, + const bool& aMinimizeMemoryUsage, const Maybe<FileDescriptor>& aDMDFile) { + // This automatically cancels the previous request. + mMemoryReportRequest = MakeUnique<MemoryReportRequestHost>(aGeneration); + // If we run the callback in response to a reply, then by definition |this| + // is still alive, so the ref pointer is redundant, but it seems easier + // to hold a strong reference than to worry about that. + RefPtr<ContentParent> self(this); + PContentParent::SendRequestMemoryReport( + aGeneration, aAnonymize, aMinimizeMemoryUsage, aDMDFile, + [&, self](const uint32_t& aGeneration2) { + if (self->mMemoryReportRequest) { + self->mMemoryReportRequest->Finish(aGeneration2); + self->mMemoryReportRequest = nullptr; + } + }, + [&, self](mozilla::ipc::ResponseRejectReason) { + self->mMemoryReportRequest = nullptr; + }); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvAddMemoryReport( + const MemoryReport& aReport) { + if (mMemoryReportRequest) { + mMemoryReportRequest->RecvReport(aReport); + } + return IPC_OK(); +} + +PCycleCollectWithLogsParent* ContentParent::AllocPCycleCollectWithLogsParent( + const bool& aDumpAllTraces, const FileDescriptor& aGCLog, + const FileDescriptor& aCCLog) { + MOZ_CRASH("Don't call this; use ContentParent::CycleCollectWithLogs"); +} + +bool ContentParent::DeallocPCycleCollectWithLogsParent( + PCycleCollectWithLogsParent* aActor) { + delete aActor; + return true; +} + +bool ContentParent::CycleCollectWithLogs( + bool aDumpAllTraces, nsICycleCollectorLogSink* aSink, + nsIDumpGCAndCCLogsCallback* aCallback) { + return CycleCollectWithLogsParent::AllocAndSendConstructor( + this, aDumpAllTraces, aSink, aCallback); +} + +PScriptCacheParent* ContentParent::AllocPScriptCacheParent( + const FileDescOrError& cacheFile, const bool& wantCacheData) { + return new loader::ScriptCacheParent(wantCacheData); +} + +bool ContentParent::DeallocPScriptCacheParent(PScriptCacheParent* cache) { + delete static_cast<loader::ScriptCacheParent*>(cache); + return true; +} + +already_AddRefed<PNeckoParent> ContentParent::AllocPNeckoParent() { + RefPtr<NeckoParent> actor = new NeckoParent(); + return actor.forget(); +} + +mozilla::ipc::IPCResult ContentParent::RecvInitStreamFilter( + const uint64_t& aChannelId, const nsAString& aAddonId, + InitStreamFilterResolver&& aResolver) { + extensions::StreamFilterParent::Create(this, aChannelId, aAddonId) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [aResolver](mozilla::ipc::Endpoint<PStreamFilterChild>&& aEndpoint) { + aResolver(std::move(aEndpoint)); + }, + [aResolver](bool aDummy) { + aResolver(mozilla::ipc::Endpoint<PStreamFilterChild>()); + }); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvAddSecurityState( + const MaybeDiscarded<WindowContext>& aContext, uint32_t aStateFlags) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + + aContext.get()->AddSecurityState(aStateFlags); + return IPC_OK(); +} + +already_AddRefed<PExternalHelperAppParent> +ContentParent::AllocPExternalHelperAppParent( + nsIURI* uri, const mozilla::net::LoadInfoArgs& aLoadInfoArgs, + const nsACString& aMimeContentType, const nsACString& aContentDisposition, + const uint32_t& aContentDispositionHint, + const nsAString& aContentDispositionFilename, const bool& aForceSave, + const int64_t& aContentLength, const bool& aWasFileChannel, + nsIURI* aReferrer, const MaybeDiscarded<BrowsingContext>& aContext, + const bool& aShouldCloseWindow) { + RefPtr<ExternalHelperAppParent> parent = new ExternalHelperAppParent( + uri, aContentLength, aWasFileChannel, aContentDisposition, + aContentDispositionHint, aContentDispositionFilename); + return parent.forget(); +} + +mozilla::ipc::IPCResult ContentParent::RecvPExternalHelperAppConstructor( + PExternalHelperAppParent* actor, nsIURI* uri, + const LoadInfoArgs& loadInfoArgs, const nsACString& aMimeContentType, + const nsACString& aContentDisposition, + const uint32_t& aContentDispositionHint, + const nsAString& aContentDispositionFilename, const bool& aForceSave, + const int64_t& aContentLength, const bool& aWasFileChannel, + nsIURI* aReferrer, const MaybeDiscarded<BrowsingContext>& aContext, + const bool& aShouldCloseWindow) { + BrowsingContext* context = aContext.IsDiscarded() ? nullptr : aContext.get(); + if (!static_cast<ExternalHelperAppParent*>(actor)->Init( + loadInfoArgs, aMimeContentType, aForceSave, aReferrer, context, + aShouldCloseWindow)) { + return IPC_FAIL(this, "Init failed."); + } + return IPC_OK(); +} + +already_AddRefed<PHandlerServiceParent> +ContentParent::AllocPHandlerServiceParent() { + RefPtr<HandlerServiceParent> actor = new HandlerServiceParent(); + return actor.forget(); +} + +media::PMediaParent* ContentParent::AllocPMediaParent() { + return media::AllocPMediaParent(); +} + +bool ContentParent::DeallocPMediaParent(media::PMediaParent* aActor) { + return media::DeallocPMediaParent(aActor); +} + +PBenchmarkStorageParent* ContentParent::AllocPBenchmarkStorageParent() { + return new BenchmarkStorageParent; +} + +bool ContentParent::DeallocPBenchmarkStorageParent( + PBenchmarkStorageParent* aActor) { + delete aActor; + return true; +} + +#ifdef MOZ_WEBSPEECH +already_AddRefed<PSpeechSynthesisParent> +ContentParent::AllocPSpeechSynthesisParent() { + if (!StaticPrefs::media_webspeech_synth_enabled()) { + return nullptr; + } + RefPtr<SpeechSynthesisParent> actor = new SpeechSynthesisParent(); + return actor.forget(); +} + +mozilla::ipc::IPCResult ContentParent::RecvPSpeechSynthesisConstructor( + PSpeechSynthesisParent* aActor) { + if (!static_cast<SpeechSynthesisParent*>(aActor)->SendInit()) { + return IPC_FAIL(this, "SpeechSynthesisParent::SendInit failed."); + } + return IPC_OK(); +} +#endif + +mozilla::ipc::IPCResult ContentParent::RecvStartVisitedQueries( + const nsTArray<RefPtr<nsIURI>>& aUris) { + nsCOMPtr<IHistory> history = components::History::Service(); + if (!history) { + return IPC_OK(); + } + for (const auto& uri : aUris) { + if (NS_WARN_IF(!uri)) { + continue; + } + history->ScheduleVisitedQuery(uri, this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvSetURITitle(nsIURI* uri, + const nsAString& title) { + if (!uri) { + return IPC_FAIL(this, "uri must not be null."); + } + nsCOMPtr<IHistory> history = components::History::Service(); + if (history) { + history->SetURITitle(uri, title); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvIsSecureURI( + nsIURI* aURI, const OriginAttributes& aOriginAttributes, + bool* aIsSecureURI) { + nsCOMPtr<nsISiteSecurityService> sss(do_GetService(NS_SSSERVICE_CONTRACTID)); + if (!sss) { + return IPC_FAIL(this, "Failed to get nsISiteSecurityService."); + } + if (!aURI) { + return IPC_FAIL(this, "aURI must not be null."); + } + nsresult rv = sss->IsSecureURI(aURI, aOriginAttributes, aIsSecureURI); + if (NS_FAILED(rv)) { + return IPC_FAIL(this, "IsSecureURI failed."); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvAccumulateMixedContentHSTS( + nsIURI* aURI, const bool& aActive, + const OriginAttributes& aOriginAttributes) { + if (!aURI) { + return IPC_FAIL(this, "aURI must not be null."); + } + nsMixedContentBlocker::AccumulateMixedContentHSTS(aURI, aActive, + aOriginAttributes); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvLoadURIExternal( + nsIURI* uri, nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aRedirectPrincipal, + const MaybeDiscarded<BrowsingContext>& aContext, + bool aWasExternallyTriggered, bool aHasValidUserGestureActivation) { + if (aContext.IsDiscarded()) { + return IPC_OK(); + } + + nsCOMPtr<nsIExternalProtocolService> extProtService( + do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID)); + if (!extProtService) { + return IPC_OK(); + } + + if (!uri) { + return IPC_FAIL(this, "uri must not be null."); + } + + BrowsingContext* bc = aContext.get(); + extProtService->LoadURI(uri, aTriggeringPrincipal, aRedirectPrincipal, bc, + aWasExternallyTriggered, + aHasValidUserGestureActivation); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvExtProtocolChannelConnectParent( + const uint64_t& registrarId) { + nsresult rv; + + // First get the real channel created before redirect on the parent. + nsCOMPtr<nsIChannel> channel; + rv = NS_LinkRedirectChannels(registrarId, nullptr, getter_AddRefs(channel)); + NS_ENSURE_SUCCESS(rv, IPC_OK()); + + nsCOMPtr<nsIParentChannel> parent = do_QueryInterface(channel, &rv); + NS_ENSURE_SUCCESS(rv, IPC_OK()); + + // The channel itself is its own (faked) parent, link it. + rv = NS_LinkRedirectChannels(registrarId, parent, getter_AddRefs(channel)); + NS_ENSURE_SUCCESS(rv, IPC_OK()); + + // Signal the parent channel that it's a redirect-to parent. This will + // make AsyncOpen on it do nothing (what we want). + // Yes, this is a bit of a hack, but I don't think it's necessary to invent + // a new interface just to set this flag on the channel. + parent->SetParentListener(nullptr); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvShowAlert( + nsIAlertNotification* aAlert) { + if (!aAlert) { + return IPC_FAIL(this, "aAlert must not be null."); + } + nsCOMPtr<nsIAlertsService> sysAlerts(components::Alerts::Service()); + if (sysAlerts) { + sysAlerts->ShowAlert(aAlert, this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvCloseAlert(const nsAString& aName, + bool aContextClosed) { + nsCOMPtr<nsIAlertsService> sysAlerts(components::Alerts::Service()); + if (sysAlerts) { + sysAlerts->CloseAlert(aName, aContextClosed); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvDisableNotifications( + nsIPrincipal* aPrincipal) { + if (!aPrincipal) { + return IPC_FAIL(this, "No principal"); + } + + if (!ValidatePrincipal(aPrincipal)) { + LogAndAssertFailedPrincipalValidationInfo(aPrincipal, __func__); + } + Unused << Notification::RemovePermission(aPrincipal); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvOpenNotificationSettings( + nsIPrincipal* aPrincipal) { + if (!aPrincipal) { + return IPC_FAIL(this, "No principal"); + } + + if (!ValidatePrincipal(aPrincipal)) { + LogAndAssertFailedPrincipalValidationInfo(aPrincipal, __func__); + } + Unused << Notification::OpenSettings(aPrincipal); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvNotificationEvent( + const nsAString& aType, const NotificationEventData& aData) { + nsCOMPtr<nsIServiceWorkerManager> swm = + mozilla::components::ServiceWorkerManager::Service(); + if (NS_WARN_IF(!swm)) { + // Probably shouldn't happen, but no need to crash the child process. + return IPC_OK(); + } + + if (aType.EqualsLiteral("click")) { + nsresult rv = swm->SendNotificationClickEvent( + aData.originSuffix(), aData.scope(), aData.ID(), aData.title(), + aData.dir(), aData.lang(), aData.body(), aData.tag(), aData.icon(), + aData.data(), aData.behavior()); + Unused << NS_WARN_IF(NS_FAILED(rv)); + } else { + MOZ_ASSERT(aType.EqualsLiteral("close")); + nsresult rv = swm->SendNotificationCloseEvent( + aData.originSuffix(), aData.scope(), aData.ID(), aData.title(), + aData.dir(), aData.lang(), aData.body(), aData.tag(), aData.icon(), + aData.data(), aData.behavior()); + Unused << NS_WARN_IF(NS_FAILED(rv)); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvSyncMessage( + const nsAString& aMsg, const ClonedMessageData& aData, + nsTArray<StructuredCloneData>* aRetvals) { + AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING("ContentParent::RecvSyncMessage", + OTHER, aMsg); + MMPrinter::Print("ContentParent::RecvSyncMessage", aMsg, aData); + + RefPtr<nsFrameMessageManager> ppm = mMessageManager; + if (ppm) { + ipc::StructuredCloneData data; + ipc::UnpackClonedMessageData(aData, data); + + ppm->ReceiveMessage(ppm, nullptr, aMsg, true, &data, aRetvals, + IgnoreErrors()); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvAsyncMessage( + const nsAString& aMsg, const ClonedMessageData& aData) { + AUTO_PROFILER_LABEL_DYNAMIC_LOSSY_NSSTRING("ContentParent::RecvAsyncMessage", + OTHER, aMsg); + MMPrinter::Print("ContentParent::RecvAsyncMessage", aMsg, aData); + + RefPtr<nsFrameMessageManager> ppm = mMessageManager; + if (ppm) { + ipc::StructuredCloneData data; + ipc::UnpackClonedMessageData(aData, data); + + ppm->ReceiveMessage(ppm, nullptr, aMsg, false, &data, nullptr, + IgnoreErrors()); + } + return IPC_OK(); +} + +MOZ_CAN_RUN_SCRIPT +static int32_t AddGeolocationListener( + nsIDOMGeoPositionCallback* watcher, + nsIDOMGeoPositionErrorCallback* errorCallBack, bool highAccuracy) { + RefPtr<Geolocation> geo = Geolocation::NonWindowSingleton(); + + UniquePtr<PositionOptions> options = MakeUnique<PositionOptions>(); + options->mTimeout = 0; + options->mMaximumAge = 0; + options->mEnableHighAccuracy = highAccuracy; + return geo->WatchPosition(watcher, errorCallBack, std::move(options)); +} + +mozilla::ipc::IPCResult ContentParent::RecvAddGeolocationListener( + const bool& aHighAccuracy) { + // To ensure no geolocation updates are skipped, we always force the + // creation of a new listener. + RecvRemoveGeolocationListener(); + mGeolocationWatchID = AddGeolocationListener(this, this, aHighAccuracy); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvRemoveGeolocationListener() { + if (mGeolocationWatchID != -1) { + RefPtr<Geolocation> geo = Geolocation::NonWindowSingleton(); + if (geo) { + geo->ClearWatch(mGeolocationWatchID); + } + mGeolocationWatchID = -1; + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvSetGeolocationHigherAccuracy( + const bool& aEnable) { + // This should never be called without a listener already present, + // so this check allows us to forgo securing privileges. + if (mGeolocationWatchID != -1) { + RecvRemoveGeolocationListener(); + mGeolocationWatchID = AddGeolocationListener(this, this, aEnable); + } + return IPC_OK(); +} + +NS_IMETHODIMP +ContentParent::HandleEvent(nsIDOMGeoPosition* postion) { + Unused << SendGeolocationUpdate(postion); + return NS_OK; +} + +NS_IMETHODIMP +ContentParent::HandleEvent(GeolocationPositionError* positionError) { + Unused << SendGeolocationError(positionError->Code()); + return NS_OK; +} + +mozilla::ipc::IPCResult ContentParent::RecvConsoleMessage( + const nsAString& aMessage) { + nsresult rv; + nsCOMPtr<nsIConsoleService> consoleService = + do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + RefPtr<nsConsoleMessage> msg(new nsConsoleMessage(aMessage)); + msg->SetIsForwardedFromContentProcess(true); + consoleService->LogMessageWithMode(msg, nsIConsoleService::SuppressLog); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvReportFrameTimingData( + const LoadInfoArgs& loadInfoArgs, const nsAString& entryName, + const nsAString& initiatorType, UniquePtr<PerformanceTimingData>&& aData) { + if (!aData) { + return IPC_FAIL(this, "aData should not be null"); + } + + RefPtr<WindowGlobalParent> parent = + WindowGlobalParent::GetByInnerWindowId(loadInfoArgs.innerWindowID()); + if (!parent || !parent->GetContentParent()) { + return IPC_OK(); + } + + MOZ_ASSERT(parent->GetContentParent() != this, + "No need to bounce around if in the same process"); + + Unused << parent->GetContentParent()->SendReportFrameTimingData( + loadInfoArgs, entryName, initiatorType, std::move(aData)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvScriptError( + const nsAString& aMessage, const nsAString& aSourceName, + const nsAString& aSourceLine, const uint32_t& aLineNumber, + const uint32_t& aColNumber, const uint32_t& aFlags, + const nsACString& aCategory, const bool& aFromPrivateWindow, + const uint64_t& aInnerWindowId, const bool& aFromChromeContext) { + return RecvScriptErrorInternal(aMessage, aSourceName, aSourceLine, + aLineNumber, aColNumber, aFlags, aCategory, + aFromPrivateWindow, aFromChromeContext); +} + +mozilla::ipc::IPCResult ContentParent::RecvScriptErrorWithStack( + const nsAString& aMessage, const nsAString& aSourceName, + const nsAString& aSourceLine, const uint32_t& aLineNumber, + const uint32_t& aColNumber, const uint32_t& aFlags, + const nsACString& aCategory, const bool& aFromPrivateWindow, + const bool& aFromChromeContext, const ClonedMessageData& aFrame) { + return RecvScriptErrorInternal( + aMessage, aSourceName, aSourceLine, aLineNumber, aColNumber, aFlags, + aCategory, aFromPrivateWindow, aFromChromeContext, &aFrame); +} + +mozilla::ipc::IPCResult ContentParent::RecvScriptErrorInternal( + const nsAString& aMessage, const nsAString& aSourceName, + const nsAString& aSourceLine, const uint32_t& aLineNumber, + const uint32_t& aColNumber, const uint32_t& aFlags, + const nsACString& aCategory, const bool& aFromPrivateWindow, + const bool& aFromChromeContext, const ClonedMessageData* aStack) { + nsresult rv; + nsCOMPtr<nsIConsoleService> consoleService = + do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return IPC_OK(); + } + + nsCOMPtr<nsIScriptError> msg; + + if (aStack) { + StructuredCloneData data; + UnpackClonedMessageData(*aStack, data); + + AutoJSAPI jsapi; + if (NS_WARN_IF(!jsapi.Init(xpc::PrivilegedJunkScope()))) { + MOZ_CRASH(); + } + JSContext* cx = jsapi.cx(); + + JS::Rooted<JS::Value> stack(cx); + ErrorResult rv; + data.Read(cx, &stack, rv); + if (rv.Failed() || !stack.isObject()) { + rv.SuppressException(); + return IPC_OK(); + } + + JS::Rooted<JSObject*> stackObj(cx, &stack.toObject()); + if (!JS::IsUnwrappedSavedFrame(stackObj)) { + return IPC_FAIL(this, "Unexpected object"); + } + + JS::Rooted<JSObject*> stackGlobal(cx, JS::GetNonCCWObjectGlobal(stackObj)); + msg = new nsScriptErrorWithStack(JS::NothingHandleValue, stackObj, + stackGlobal); + } else { + msg = new nsScriptError(); + } + + rv = msg->Init(aMessage, aSourceName, aSourceLine, aLineNumber, aColNumber, + aFlags, aCategory, aFromPrivateWindow, aFromChromeContext); + if (NS_FAILED(rv)) return IPC_OK(); + + msg->SetIsForwardedFromContentProcess(true); + + consoleService->LogMessageWithMode(msg, nsIConsoleService::SuppressLog); + return IPC_OK(); +} + +bool ContentParent::DoLoadMessageManagerScript(const nsAString& aURL, + bool aRunInGlobalScope) { + MOZ_ASSERT(!aRunInGlobalScope); + return SendLoadProcessScript(aURL); +} + +nsresult ContentParent::DoSendAsyncMessage(const nsAString& aMessage, + StructuredCloneData& aHelper) { + ClonedMessageData data; + if (!BuildClonedMessageData(aHelper, data)) { + return NS_ERROR_DOM_DATA_CLONE_ERR; + } + if (!SendAsyncMessage(aMessage, data)) { + return NS_ERROR_UNEXPECTED; + } + return NS_OK; +} + +mozilla::ipc::IPCResult ContentParent::RecvCopyFavicon( + nsIURI* aOldURI, nsIURI* aNewURI, const bool& aInPrivateBrowsing) { + if (!aOldURI) { + return IPC_FAIL(this, "aOldURI should not be null"); + } + if (!aNewURI) { + return IPC_FAIL(this, "aNewURI should not be null"); + } + + nsDocShell::CopyFavicon(aOldURI, aNewURI, aInPrivateBrowsing); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvFindImageText( + IPCImage&& aImage, nsTArray<nsCString>&& aLanguages, + FindImageTextResolver&& aResolver) { + if (!TextRecognition::IsSupported() || + !Preferences::GetBool("dom.text-recognition.enabled")) { + return IPC_FAIL(this, "Text recognition not available."); + } + + RefPtr<DataSourceSurface> surf = + nsContentUtils::IPCImageToSurface(std::move(aImage)); + if (!surf) { + aResolver(TextRecognitionResultOrError("Failed to read image"_ns)); + return IPC_OK(); + } + TextRecognition::FindText(*surf, aLanguages) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [resolver = std::move(aResolver)]( + TextRecognition::NativePromise::ResolveOrRejectValue&& aValue) { + if (aValue.IsResolve()) { + resolver(TextRecognitionResultOrError(aValue.ResolveValue())); + } else { + resolver(TextRecognitionResultOrError(aValue.RejectValue())); + } + }); + return IPC_OK(); +} + +bool ContentParent::ShouldContinueFromReplyTimeout() { + RefPtr<ProcessHangMonitor> monitor = ProcessHangMonitor::Get(); + return !monitor || !monitor->ShouldTimeOutCPOWs(); +} + +mozilla::ipc::IPCResult ContentParent::RecvAddIdleObserver( + const uint64_t& aObserver, const uint32_t& aIdleTimeInS) { + nsresult rv; + nsCOMPtr<nsIUserIdleService> idleService = + do_GetService("@mozilla.org/widget/useridleservice;1", &rv); + NS_ENSURE_SUCCESS(rv, IPC_FAIL(this, "Failed to get UserIdleService.")); + + RefPtr<ParentIdleListener> listener = + new ParentIdleListener(this, aObserver, aIdleTimeInS); + rv = idleService->AddIdleObserver(listener, aIdleTimeInS); + NS_ENSURE_SUCCESS(rv, IPC_FAIL(this, "AddIdleObserver failed.")); + mIdleListeners.AppendElement(listener); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvRemoveIdleObserver( + const uint64_t& aObserver, const uint32_t& aIdleTimeInS) { + RefPtr<ParentIdleListener> listener; + for (int32_t i = mIdleListeners.Length() - 1; i >= 0; --i) { + listener = static_cast<ParentIdleListener*>(mIdleListeners[i].get()); + if (listener->mObserver == aObserver && listener->mTime == aIdleTimeInS) { + nsresult rv; + nsCOMPtr<nsIUserIdleService> idleService = + do_GetService("@mozilla.org/widget/useridleservice;1", &rv); + NS_ENSURE_SUCCESS(rv, IPC_FAIL(this, "Failed to get UserIdleService.")); + idleService->RemoveIdleObserver(listener, aIdleTimeInS); + mIdleListeners.RemoveElementAt(i); + break; + } + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvBackUpXResources( + const FileDescriptor& aXSocketFd) { +#ifndef MOZ_X11 + MOZ_CRASH("This message only makes sense on X11 platforms"); +#else + MOZ_ASSERT(!mChildXSocketFdDup, "Already backed up X resources??"); + if (aXSocketFd.IsValid()) { + mChildXSocketFdDup = aXSocketFd.ClonePlatformHandle(); + } +#endif + return IPC_OK(); +} + +class AnonymousTemporaryFileRequestor final : public Runnable { + public: + AnonymousTemporaryFileRequestor(ContentParent* aCP, const uint64_t& aID) + : Runnable("dom::AnonymousTemporaryFileRequestor"), + mCP(aCP), + mID(aID), + mRv(NS_OK), + mPRFD(nullptr) {} + + NS_IMETHOD Run() override { + if (NS_IsMainThread()) { + FileDescOrError result; + if (NS_WARN_IF(NS_FAILED(mRv))) { + // Returning false will kill the child process; instead + // propagate the error and let the child handle it. + result = mRv; + } else { + result = FileDescriptor(FileDescriptor::PlatformHandleType( + PR_FileDesc2NativeHandle(mPRFD))); + // The FileDescriptor object owns a duplicate of the file handle; we + // must close the original (and clean up the NSPR descriptor). + PR_Close(mPRFD); + } + Unused << mCP->SendProvideAnonymousTemporaryFile(mID, result); + // It's important to release this reference while wr're on the main + // thread! + mCP = nullptr; + } else { + mRv = NS_OpenAnonymousTemporaryFile(&mPRFD); + NS_DispatchToMainThread(this); + } + return NS_OK; + } + + private: + RefPtr<ContentParent> mCP; + uint64_t mID; + nsresult mRv; + PRFileDesc* mPRFD; +}; + +mozilla::ipc::IPCResult ContentParent::RecvRequestAnonymousTemporaryFile( + const uint64_t& aID) { + // Make sure to send a callback to the child if we bail out early. + nsresult rv = NS_OK; + RefPtr<ContentParent> self(this); + auto autoNotifyChildOnError = MakeScopeExit([&, self]() { + if (NS_FAILED(rv)) { + FileDescOrError result(rv); + Unused << self->SendProvideAnonymousTemporaryFile(aID, result); + } + }); + + // We use a helper runnable to open the anonymous temporary file on the IO + // thread. The same runnable will call us back on the main thread when the + // file has been opened. + nsCOMPtr<nsIEventTarget> target = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); + if (!target) { + return IPC_OK(); + } + + rv = target->Dispatch(new AnonymousTemporaryFileRequestor(this, aID), + NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return IPC_OK(); + } + + rv = NS_OK; + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvCreateAudioIPCConnection( + CreateAudioIPCConnectionResolver&& aResolver) { + FileDescriptor fd = CubebUtils::CreateAudioIPCConnection(); + FileDescOrError result; + if (fd.IsValid()) { + result = fd; + } else { + result = NS_ERROR_FAILURE; + } + aResolver(std::move(result)); + return IPC_OK(); +} + +already_AddRefed<extensions::PExtensionsParent> +ContentParent::AllocPExtensionsParent() { + return MakeAndAddRef<extensions::ExtensionsParent>(); +} + +void ContentParent::NotifyUpdatedDictionaries() { + RefPtr<mozSpellChecker> spellChecker(mozSpellChecker::Create()); + MOZ_ASSERT(spellChecker, "No spell checker?"); + + nsTArray<nsCString> dictionaries; + spellChecker->GetDictionaryList(&dictionaries); + + for (auto* cp : AllProcesses(eLive)) { + Unused << cp->SendUpdateDictionaryList(dictionaries); + } +} + +void ContentParent::NotifyUpdatedFonts(bool aFullRebuild) { + if (gfxPlatformFontList::PlatformFontList()->SharedFontList()) { + for (auto* cp : AllProcesses(eLive)) { + Unused << cp->SendRebuildFontList(aFullRebuild); + } + return; + } + + SystemFontList fontList; + gfxPlatform::GetPlatform()->ReadSystemFontList(&fontList); + + for (auto* cp : AllProcesses(eLive)) { + Unused << cp->SendUpdateFontList(fontList); + } +} + +#ifdef MOZ_WEBRTC +PWebrtcGlobalParent* ContentParent::AllocPWebrtcGlobalParent() { + return WebrtcGlobalParent::Alloc(); +} + +bool ContentParent::DeallocPWebrtcGlobalParent(PWebrtcGlobalParent* aActor) { + WebrtcGlobalParent::Dealloc(static_cast<WebrtcGlobalParent*>(aActor)); + return true; +} +#endif + +void ContentParent::GetIPCTransferableData( + nsIDragSession* aSession, BrowserParent* aParent, + nsTArray<IPCTransferableData>& aIPCTransferables) { + RefPtr<DataTransfer> transfer = aSession->GetDataTransfer(); + if (!transfer) { + // Pass eDrop to get DataTransfer with external + // drag formats cached. + transfer = new DataTransfer(nullptr, eDrop, true, -1); + aSession->SetDataTransfer(transfer); + } + // Note, even though this fills the DataTransfer object with + // external data, the data is usually transfered over IPC lazily when + // needed. + transfer->FillAllExternalData(); + nsCOMPtr<nsILoadContext> lc = aParent ? aParent->GetLoadContext() : nullptr; + nsCOMPtr<nsIArray> transferables = transfer->GetTransferables(lc); + nsContentUtils::TransferablesToIPCTransferableDatas( + transferables, aIPCTransferables, false, this); +} + +void ContentParent::MaybeInvokeDragSession(BrowserParent* aParent, + EventMessage aMessage) { + // dnd uses IPCBlob to transfer data to the content process and the IPC + // message is sent as normal priority. When sending input events with input + // priority, the message may be preempted by the later dnd events. To make + // sure the input events and the blob message are processed in time order + // on the content process, we temporarily send the input events with normal + // priority when there is an active dnd session. + SetInputPriorityEventEnabled(false); + + nsCOMPtr<nsIDragService> dragService = + do_GetService("@mozilla.org/widget/dragservice;1"); + if (!dragService) { + return; + } + + if (dragService->MaybeAddChildProcess(this)) { + nsCOMPtr<nsIDragSession> session; + dragService->GetCurrentSession(getter_AddRefs(session)); + if (session) { + // We need to send transferable data to child process. + nsTArray<IPCTransferableData> ipcTransferables; + GetIPCTransferableData(session, aParent, ipcTransferables); + uint32_t action; + session->GetDragAction(&action); + + RefPtr<WindowContext> sourceWC; + session->GetSourceWindowContext(getter_AddRefs(sourceWC)); + RefPtr<WindowContext> sourceTopWC; + session->GetSourceTopWindowContext(getter_AddRefs(sourceTopWC)); + mozilla::Unused << SendInvokeDragSession( + sourceWC, sourceTopWC, std::move(ipcTransferables), action); + } + return; + } + + if (dragService->MustUpdateDataTransfer(aMessage)) { + nsCOMPtr<nsIDragSession> session; + dragService->GetCurrentSession(getter_AddRefs(session)); + if (session) { + // We need to send transferable data to child process. + nsTArray<IPCTransferableData> ipcTransferables; + GetIPCTransferableData(session, aParent, ipcTransferables); + mozilla::Unused << SendUpdateDragSession(std::move(ipcTransferables), + aMessage); + } + } +} + +mozilla::ipc::IPCResult ContentParent::RecvUpdateDropEffect( + const uint32_t& aDragAction, const uint32_t& aDropEffect) { + nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession(); + if (dragSession) { + dragSession->SetDragAction(aDragAction); + RefPtr<DataTransfer> dt = dragSession->GetDataTransfer(); + if (dt) { + dt->SetDropEffectInt(aDropEffect); + } + dragSession->UpdateDragEffect(); + } + return IPC_OK(); +} + +PContentPermissionRequestParent* +ContentParent::AllocPContentPermissionRequestParent( + const nsTArray<PermissionRequest>& aRequests, nsIPrincipal* aPrincipal, + nsIPrincipal* aTopLevelPrincipal, const bool& aIsHandlingUserInput, + const bool& aMaybeUnsafePermissionDelegate, const TabId& aTabId) { + RefPtr<BrowserParent> tp; + ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); + if (cpm) { + tp = + cpm->GetTopLevelBrowserParentByProcessAndTabId(this->ChildID(), aTabId); + } + if (!tp) { + return nullptr; + } + + nsIPrincipal* topPrincipal = aTopLevelPrincipal; + if (!topPrincipal) { + nsCOMPtr<nsIPrincipal> principal = tp->GetContentPrincipal(); + topPrincipal = principal; + } + return nsContentPermissionUtils::CreateContentPermissionRequestParent( + aRequests, tp->GetOwnerElement(), aPrincipal, topPrincipal, + aIsHandlingUserInput, aMaybeUnsafePermissionDelegate, aTabId); +} + +bool ContentParent::DeallocPContentPermissionRequestParent( + PContentPermissionRequestParent* actor) { + nsContentPermissionUtils::NotifyRemoveContentPermissionRequestParent(actor); + delete actor; + return true; +} + +already_AddRefed<PWebBrowserPersistDocumentParent> +ContentParent::AllocPWebBrowserPersistDocumentParent( + PBrowserParent* aBrowser, const MaybeDiscarded<BrowsingContext>& aContext) { + return MakeAndAddRef<WebBrowserPersistDocumentParent>(); +} + +mozilla::ipc::IPCResult ContentParent::CommonCreateWindow( + PBrowserParent* aThisTab, BrowsingContext& aParent, bool aSetOpener, + const uint32_t& aChromeFlags, const bool& aCalledFromJS, + const bool& aForPrinting, const bool& aForWindowDotPrint, + nsIURI* aURIToLoad, const nsACString& aFeatures, + const UserActivation::Modifiers& aModifiers, + BrowserParent* aNextRemoteBrowser, const nsAString& aName, + nsresult& aResult, nsCOMPtr<nsIRemoteTab>& aNewRemoteTab, + bool* aWindowIsNew, int32_t& aOpenLocation, + nsIPrincipal* aTriggeringPrincipal, nsIReferrerInfo* aReferrerInfo, + bool aLoadURI, nsIContentSecurityPolicy* aCsp, + const OriginAttributes& aOriginAttributes) { + // The content process should never be in charge of computing whether or + // not a window should be private - the parent will do that. + const uint32_t badFlags = nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW | + nsIWebBrowserChrome::CHROME_NON_PRIVATE_WINDOW | + nsIWebBrowserChrome::CHROME_PRIVATE_LIFETIME; + if (!!(aChromeFlags & badFlags)) { + return IPC_FAIL(this, "Forbidden aChromeFlags passed"); + } + + RefPtr<nsOpenWindowInfo> openInfo = new nsOpenWindowInfo(); + openInfo->mForceNoOpener = !aSetOpener; + openInfo->mParent = &aParent; + openInfo->mIsRemote = true; + openInfo->mIsForPrinting = aForPrinting; + openInfo->mIsForWindowDotPrint = aForWindowDotPrint; + openInfo->mNextRemoteBrowser = aNextRemoteBrowser; + openInfo->mOriginAttributes = aOriginAttributes; + + MOZ_ASSERT_IF(aForWindowDotPrint, aForPrinting); + + RefPtr<BrowserParent> topParent = BrowserParent::GetFrom(aThisTab); + while (topParent && topParent->GetBrowserBridgeParent()) { + topParent = topParent->GetBrowserBridgeParent()->Manager(); + } + RefPtr<BrowserHost> thisBrowserHost = + topParent ? topParent->GetBrowserHost() : nullptr; + MOZ_ASSERT_IF(topParent, thisBrowserHost); + RefPtr<BrowsingContext> topBC = + topParent ? topParent->GetBrowsingContext() : nullptr; + MOZ_ASSERT_IF(topParent, topBC); + + // The content process should have set its remote and fission flags correctly. + if (topBC) { + if ((!!(aChromeFlags & nsIWebBrowserChrome::CHROME_REMOTE_WINDOW) != + topBC->UseRemoteTabs()) || + (!!(aChromeFlags & nsIWebBrowserChrome::CHROME_FISSION_WINDOW) != + topBC->UseRemoteSubframes())) { + return IPC_FAIL(this, "Unexpected aChromeFlags passed"); + } + + if (!aOriginAttributes.EqualsIgnoringFPD(topBC->OriginAttributesRef())) { + return IPC_FAIL(this, "Passed-in OriginAttributes does not match opener"); + } + } + + nsCOMPtr<nsIContent> frame; + if (topParent) { + frame = topParent->GetOwnerElement(); + } + + nsCOMPtr<nsPIDOMWindowOuter> outerWin; + if (frame) { + outerWin = frame->OwnerDoc()->GetWindow(); + + // If our chrome window is in the process of closing, don't try to open a + // new tab in it. + if (outerWin && outerWin->Closed()) { + outerWin = nullptr; + } + } + + nsCOMPtr<nsIBrowserDOMWindow> browserDOMWin; + if (topParent) { + browserDOMWin = topParent->GetBrowserDOMWindow(); + } + + // If we haven't found a chrome window to open in, just use the most recently + // opened one. + if (!outerWin) { + outerWin = nsContentUtils::GetMostRecentNonPBWindow(); + if (NS_WARN_IF(!outerWin)) { + aResult = NS_ERROR_FAILURE; + return IPC_OK(); + } + + if (nsGlobalWindowOuter::Cast(outerWin)->IsChromeWindow()) { + browserDOMWin = + nsGlobalWindowOuter::Cast(outerWin)->GetBrowserDOMWindow(); + } + } + + aOpenLocation = nsWindowWatcher::GetWindowOpenLocation( + outerWin, aChromeFlags, aModifiers, aCalledFromJS, aForPrinting); + + MOZ_ASSERT(aOpenLocation == nsIBrowserDOMWindow::OPEN_NEWTAB || + aOpenLocation == nsIBrowserDOMWindow::OPEN_NEWTAB_BACKGROUND || + aOpenLocation == nsIBrowserDOMWindow::OPEN_NEWWINDOW || + aOpenLocation == nsIBrowserDOMWindow::OPEN_PRINT_BROWSER); + + if (NS_WARN_IF(!browserDOMWin)) { + // Opening in the same window or headless requires an nsIBrowserDOMWindow. + aOpenLocation = nsIBrowserDOMWindow::OPEN_NEWWINDOW; + } + + if (aOpenLocation == nsIBrowserDOMWindow::OPEN_NEWTAB || + aOpenLocation == nsIBrowserDOMWindow::OPEN_NEWTAB_BACKGROUND || + aOpenLocation == nsIBrowserDOMWindow::OPEN_PRINT_BROWSER) { + RefPtr<Element> openerElement = do_QueryObject(frame); + + nsCOMPtr<nsIOpenURIInFrameParams> params = + new nsOpenURIInFrameParams(openInfo, openerElement); + params->SetReferrerInfo(aReferrerInfo); + MOZ_ASSERT(aTriggeringPrincipal, "need a valid triggeringPrincipal"); + params->SetTriggeringPrincipal(aTriggeringPrincipal); + params->SetCsp(aCsp); + + RefPtr<Element> el; + + if (aLoadURI) { + aResult = browserDOMWin->OpenURIInFrame(aURIToLoad, params, aOpenLocation, + nsIBrowserDOMWindow::OPEN_NEW, + aName, getter_AddRefs(el)); + } else { + aResult = browserDOMWin->CreateContentWindowInFrame( + aURIToLoad, params, aOpenLocation, nsIBrowserDOMWindow::OPEN_NEW, + aName, getter_AddRefs(el)); + } + RefPtr<nsFrameLoaderOwner> frameLoaderOwner = do_QueryObject(el); + if (NS_SUCCEEDED(aResult) && frameLoaderOwner) { + RefPtr<nsFrameLoader> frameLoader = frameLoaderOwner->GetFrameLoader(); + if (frameLoader) { + aNewRemoteTab = frameLoader->GetRemoteTab(); + // At this point, it's possible the inserted frameloader hasn't gone + // through layout yet. To ensure that the dimensions that we send down + // when telling the frameloader to display will be correct (instead of + // falling back to a 10x10 default), we force layout if necessary to get + // the most up-to-date dimensions. See bug 1358712 for details. + frameLoader->ForceLayoutIfNecessary(); + } + } else if (NS_SUCCEEDED(aResult) && !frameLoaderOwner) { + // Fall through to the normal window opening code path when there is no + // window which we can open a new tab in. + aOpenLocation = nsIBrowserDOMWindow::OPEN_NEWWINDOW; + } else { + *aWindowIsNew = false; + } + + // If we didn't retarget our window open into a new window, we should return + // now. + if (aOpenLocation != nsIBrowserDOMWindow::OPEN_NEWWINDOW) { + return IPC_OK(); + } + } + + nsCOMPtr<nsPIWindowWatcher> pwwatch = + do_GetService(NS_WINDOWWATCHER_CONTRACTID, &aResult); + if (NS_WARN_IF(NS_FAILED(aResult))) { + return IPC_OK(); + } + + WindowFeatures features; + features.Tokenize(aFeatures); + + aResult = pwwatch->OpenWindowWithRemoteTab( + thisBrowserHost, features, aModifiers, aCalledFromJS, aParent.FullZoom(), + openInfo, getter_AddRefs(aNewRemoteTab)); + if (NS_WARN_IF(NS_FAILED(aResult))) { + return IPC_OK(); + } + + MOZ_ASSERT(aNewRemoteTab); + RefPtr<BrowserHost> newBrowserHost = BrowserHost::GetFrom(aNewRemoteTab); + RefPtr<BrowserParent> newBrowserParent = newBrowserHost->GetActor(); + + // At this point, it's possible the inserted frameloader hasn't gone through + // layout yet. To ensure that the dimensions that we send down when telling + // the frameloader to display will be correct (instead of falling back to a + // 10x10 default), we force layout if necessary to get the most up-to-date + // dimensions. See bug 1358712 for details. + nsCOMPtr<Element> frameElement = newBrowserHost->GetOwnerElement(); + MOZ_ASSERT(frameElement); + if (nsWindowWatcher::HaveSpecifiedSize(features)) { + // We want to flush the layout anyway because of the resize to the specified + // size. (Bug 1793605). + RefPtr<Document> chromeDoc = frameElement->OwnerDoc(); + MOZ_ASSERT(chromeDoc); + chromeDoc->FlushPendingNotifications(FlushType::Layout); + } else { + RefPtr<nsFrameLoaderOwner> frameLoaderOwner = do_QueryObject(frameElement); + MOZ_ASSERT(frameLoaderOwner); + RefPtr<nsFrameLoader> frameLoader = frameLoaderOwner->GetFrameLoader(); + MOZ_ASSERT(frameLoader); + frameLoader->ForceLayoutIfNecessary(); + } + + // If we were passed a name for the window which would override the default, + // we should send it down to the new tab. + if (nsContentUtils::IsOverridingWindowName(aName)) { + MOZ_ALWAYS_SUCCEEDS(newBrowserHost->GetBrowsingContext()->SetName(aName)); + } + + MOZ_ASSERT(newBrowserHost->GetBrowsingContext()->OriginAttributesRef() == + aOriginAttributes); + + if (aURIToLoad && aLoadURI) { + nsCOMPtr<mozIDOMWindowProxy> openerWindow; + if (aSetOpener && topParent) { + openerWindow = topParent->GetParentWindowOuter(); + } + nsCOMPtr<nsIBrowserDOMWindow> newBrowserDOMWin = + newBrowserParent->GetBrowserDOMWindow(); + if (NS_WARN_IF(!newBrowserDOMWin)) { + aResult = NS_ERROR_ABORT; + return IPC_OK(); + } + RefPtr<BrowsingContext> bc; + aResult = newBrowserDOMWin->OpenURI( + aURIToLoad, openInfo, nsIBrowserDOMWindow::OPEN_CURRENTWINDOW, + nsIBrowserDOMWindow::OPEN_NEW, aTriggeringPrincipal, aCsp, + getter_AddRefs(bc)); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvCreateWindow( + PBrowserParent* aThisTab, const MaybeDiscarded<BrowsingContext>& aParent, + PBrowserParent* aNewTab, const uint32_t& aChromeFlags, + const bool& aCalledFromJS, const bool& aForPrinting, + const bool& aForPrintPreview, nsIURI* aURIToLoad, + const nsACString& aFeatures, const UserActivation::Modifiers& aModifiers, + nsIPrincipal* aTriggeringPrincipal, nsIContentSecurityPolicy* aCsp, + nsIReferrerInfo* aReferrerInfo, const OriginAttributes& aOriginAttributes, + CreateWindowResolver&& aResolve) { + if (!aTriggeringPrincipal) { + return IPC_FAIL(this, "No principal"); + } + + if (!ValidatePrincipal(aTriggeringPrincipal)) { + LogAndAssertFailedPrincipalValidationInfo(aTriggeringPrincipal, __func__); + } + + nsresult rv = NS_OK; + CreatedWindowInfo cwi; + + // We always expect to open a new window here. If we don't, it's an error. + cwi.windowOpened() = true; + cwi.maxTouchPoints() = 0; + + // Make sure to resolve the resolver when this function exits, even if we + // failed to generate a valid response. + auto resolveOnExit = MakeScopeExit([&] { + // Copy over the nsresult, and then resolve. + cwi.rv() = rv; + aResolve(cwi); + }); + + RefPtr<BrowserParent> thisTab = BrowserParent::GetFrom(aThisTab); + RefPtr<BrowserParent> newTab = BrowserParent::GetFrom(aNewTab); + MOZ_ASSERT(newTab); + + auto destroyNewTabOnError = MakeScopeExit([&] { + // We always expect to open a new window here. If we don't, it's an error. + if (!cwi.windowOpened() || NS_FAILED(rv)) { + if (newTab) { + newTab->Destroy(); + } + } + }); + + // Don't continue to try to create a new window if we've been fully discarded. + RefPtr<BrowsingContext> parent = aParent.GetMaybeDiscarded(); + if (NS_WARN_IF(!parent)) { + rv = NS_ERROR_FAILURE; + return IPC_OK(); + } + + // Validate that our new BrowsingContext looks as we would expect it. + RefPtr<BrowsingContext> newBC = newTab->GetBrowsingContext(); + if (!newBC) { + return IPC_FAIL(this, "Missing BrowsingContext for new tab"); + } + + uint64_t newBCOpenerId = newBC->GetOpenerId(); + if (newBCOpenerId != 0 && parent->Id() != newBCOpenerId) { + return IPC_FAIL(this, "Invalid opener BrowsingContext for new tab"); + } + if (newBC->GetParent() != nullptr) { + return IPC_FAIL(this, + "Unexpected non-toplevel BrowsingContext for new tab"); + } + if (!!(aChromeFlags & nsIWebBrowserChrome::CHROME_REMOTE_WINDOW) != + newBC->UseRemoteTabs() || + !!(aChromeFlags & nsIWebBrowserChrome::CHROME_FISSION_WINDOW) != + newBC->UseRemoteSubframes()) { + return IPC_FAIL(this, "Unexpected aChromeFlags passed"); + } + if (!aOriginAttributes.EqualsIgnoringFPD(newBC->OriginAttributesRef())) { + return IPC_FAIL(this, "Opened tab has mismatched OriginAttributes"); + } + + if (thisTab && BrowserParent::GetFrom(thisTab)->GetBrowsingContext()) { + BrowsingContext* thisTabBC = thisTab->GetBrowsingContext(); + if (thisTabBC->UseRemoteTabs() != newBC->UseRemoteTabs() || + thisTabBC->UseRemoteSubframes() != newBC->UseRemoteSubframes() || + thisTabBC->UsePrivateBrowsing() != newBC->UsePrivateBrowsing()) { + return IPC_FAIL(this, "New BrowsingContext has mismatched LoadContext"); + } + } + BrowserParent::AutoUseNewTab aunt(newTab); + + nsCOMPtr<nsIRemoteTab> newRemoteTab; + int32_t openLocation = nsIBrowserDOMWindow::OPEN_NEWWINDOW; + mozilla::ipc::IPCResult ipcResult = CommonCreateWindow( + aThisTab, *parent, newBCOpenerId != 0, aChromeFlags, aCalledFromJS, + aForPrinting, aForPrintPreview, aURIToLoad, aFeatures, aModifiers, newTab, + VoidString(), rv, newRemoteTab, &cwi.windowOpened(), openLocation, + aTriggeringPrincipal, aReferrerInfo, /* aLoadUri = */ false, aCsp, + aOriginAttributes); + if (!ipcResult) { + return ipcResult; + } + + if (NS_WARN_IF(NS_FAILED(rv)) || !newRemoteTab) { + return IPC_OK(); + } + + MOZ_ASSERT(BrowserHost::GetFrom(newRemoteTab.get()) == + newTab->GetBrowserHost()); + + // This used to happen in the child - there may now be a better place to + // do this work. + MOZ_ALWAYS_SUCCEEDS(newBC->SetHasSiblings( + openLocation == nsIBrowserDOMWindow::OPEN_NEWTAB || + openLocation == nsIBrowserDOMWindow::OPEN_NEWTAB_BACKGROUND)); + + newTab->SwapFrameScriptsFrom(cwi.frameScripts()); + newTab->MaybeShowFrame(); + + nsCOMPtr<nsIWidget> widget = newTab->GetWidget(); + if (widget) { + cwi.dimensions() = newTab->GetDimensionInfo(); + } + + cwi.maxTouchPoints() = newTab->GetMaxTouchPoints(); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvCreateWindowInDifferentProcess( + PBrowserParent* aThisTab, const MaybeDiscarded<BrowsingContext>& aParent, + const uint32_t& aChromeFlags, const bool& aCalledFromJS, nsIURI* aURIToLoad, + const nsACString& aFeatures, const UserActivation::Modifiers& aModifiers, + const nsAString& aName, nsIPrincipal* aTriggeringPrincipal, + nsIContentSecurityPolicy* aCsp, nsIReferrerInfo* aReferrerInfo, + const OriginAttributes& aOriginAttributes) { + MOZ_DIAGNOSTIC_ASSERT(!nsContentUtils::IsSpecialName(aName)); + + // Don't continue to try to create a new window if we've been fully discarded. + RefPtr<BrowsingContext> parent = aParent.GetMaybeDiscarded(); + if (NS_WARN_IF(!parent)) { + return IPC_OK(); + } + + nsCOMPtr<nsIRemoteTab> newRemoteTab; + bool windowIsNew; + int32_t openLocation = nsIBrowserDOMWindow::OPEN_NEWWINDOW; + + // If we have enough data, check the schemes of the loader and loadee + // to make sure they make sense. + if (aURIToLoad && aURIToLoad->SchemeIs("file") && + GetRemoteType() != FILE_REMOTE_TYPE && + Preferences::GetBool("browser.tabs.remote.enforceRemoteTypeRestrictions", + false)) { +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED +# ifdef DEBUG + nsAutoCString uriToLoadStr; + nsAutoCString triggeringUriStr; + aURIToLoad->GetAsciiSpec(uriToLoadStr); + aTriggeringPrincipal->GetAsciiSpec(triggeringUriStr); + + NS_WARNING(nsPrintfCString( + "RecvCreateWindowInDifferentProcess blocked loading file " + "scheme from non-file remotetype: %s tried to load %s", + triggeringUriStr.get(), uriToLoadStr.get()) + .get()); +# endif + MOZ_CRASH( + "RecvCreateWindowInDifferentProcess blocked loading improper scheme"); +#endif + return IPC_OK(); + } + + nsresult rv; + mozilla::ipc::IPCResult ipcResult = CommonCreateWindow( + aThisTab, *parent, /* aSetOpener = */ false, aChromeFlags, aCalledFromJS, + /* aForPrinting = */ false, + /* aForPrintPreview = */ false, aURIToLoad, aFeatures, aModifiers, + /* aNextRemoteBrowser = */ nullptr, aName, rv, newRemoteTab, &windowIsNew, + openLocation, aTriggeringPrincipal, aReferrerInfo, + /* aLoadUri = */ true, aCsp, aOriginAttributes); + if (!ipcResult) { + return ipcResult; + } + + if (NS_FAILED(rv)) { + NS_WARNING("Call to CommonCreateWindow failed."); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvShutdownProfile( + const nsACString& aProfile) { + profiler_received_exit_profile(aProfile); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvShutdownPerfStats( + const nsACString& aPerfStats) { + PerfStats::StorePerfStats(this, aPerfStats); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvGetFontListShmBlock( + const uint32_t& aGeneration, const uint32_t& aIndex, + base::SharedMemoryHandle* aOut) { + auto* fontList = gfxPlatformFontList::PlatformFontList(); + MOZ_RELEASE_ASSERT(fontList, "gfxPlatformFontList not initialized?"); + fontList->ShareFontListShmBlockToProcess(aGeneration, aIndex, Pid(), aOut); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvInitializeFamily( + const uint32_t& aGeneration, const uint32_t& aFamilyIndex, + const bool& aLoadCmaps) { + auto* fontList = gfxPlatformFontList::PlatformFontList(); + MOZ_RELEASE_ASSERT(fontList, "gfxPlatformFontList not initialized?"); + fontList->InitializeFamily(aGeneration, aFamilyIndex, aLoadCmaps); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvSetCharacterMap( + const uint32_t& aGeneration, const uint32_t& aFamilyIndex, + const bool& aAlias, const uint32_t& aFaceIndex, + const gfxSparseBitSet& aMap) { + auto* fontList = gfxPlatformFontList::PlatformFontList(); + MOZ_RELEASE_ASSERT(fontList, "gfxPlatformFontList not initialized?"); + fontList->SetCharacterMap(aGeneration, aFamilyIndex, aAlias, aFaceIndex, + aMap); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvInitOtherFamilyNames( + const uint32_t& aGeneration, const bool& aDefer, bool* aLoaded) { + auto* fontList = gfxPlatformFontList::PlatformFontList(); + MOZ_RELEASE_ASSERT(fontList, "gfxPlatformFontList not initialized?"); + *aLoaded = fontList->InitOtherFamilyNames(aGeneration, aDefer); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvSetupFamilyCharMap( + const uint32_t& aGeneration, const uint32_t& aIndex, const bool& aAlias) { + auto* fontList = gfxPlatformFontList::PlatformFontList(); + MOZ_RELEASE_ASSERT(fontList, "gfxPlatformFontList not initialized?"); + fontList->SetupFamilyCharMap(aGeneration, aIndex, aAlias); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvStartCmapLoading( + const uint32_t& aGeneration, const uint32_t& aStartIndex) { + auto* fontList = gfxPlatformFontList::PlatformFontList(); + MOZ_RELEASE_ASSERT(fontList, "gfxPlatformFontList not initialized?"); + fontList->StartCmapLoading(aGeneration, aStartIndex); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvGetHyphDict( + nsIURI* aURI, base::SharedMemoryHandle* aOutHandle, uint32_t* aOutSize) { + if (!aURI) { + return IPC_FAIL(this, "aURI must not be null."); + } + nsHyphenationManager::Instance()->ShareHyphDictToProcess( + aURI, Pid(), aOutHandle, aOutSize); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvGraphicsError( + const nsACString& aError) { + if (gfx::LogForwarder* lf = gfx::Factory::GetLogForwarder()) { + std::stringstream message; + message << "CP+" << aError; + lf->UpdateStringsVector(message.str()); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvBeginDriverCrashGuard( + const uint32_t& aGuardType, bool* aOutCrashed) { + // Only one driver crash guard should be active at a time, per-process. + MOZ_ASSERT(!mDriverCrashGuard); + + UniquePtr<gfx::DriverCrashGuard> guard; + switch (gfx::CrashGuardType(aGuardType)) { + case gfx::CrashGuardType::D3D11Layers: + guard = MakeUnique<gfx::D3D11LayersCrashGuard>(this); + break; + case gfx::CrashGuardType::GLContext: + guard = MakeUnique<gfx::GLContextCrashGuard>(this); + break; + case gfx::CrashGuardType::WMFVPXVideo: + guard = MakeUnique<gfx::WMFVPXVideoCrashGuard>(this); + break; + default: + return IPC_FAIL(this, "unknown crash guard type"); + } + + if (guard->Crashed()) { + *aOutCrashed = true; + return IPC_OK(); + } + + *aOutCrashed = false; + mDriverCrashGuard = std::move(guard); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvEndDriverCrashGuard( + const uint32_t& aGuardType) { + mDriverCrashGuard = nullptr; + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvNotifyBenchmarkResult( + const nsAString& aCodecName, const uint32_t& aDecodeFPS) + +{ + if (aCodecName.EqualsLiteral("VP9")) { + Preferences::SetUint(VP9Benchmark::sBenchmarkFpsPref, aDecodeFPS); + Preferences::SetUint(VP9Benchmark::sBenchmarkFpsVersionCheck, + VP9Benchmark::sBenchmarkVersionID); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvNotifyPushObservers( + const nsACString& aScope, nsIPrincipal* aPrincipal, + const nsAString& aMessageId) { + if (!aPrincipal) { + return IPC_FAIL(this, "No principal"); + } + + if (!ValidatePrincipal(aPrincipal)) { + LogAndAssertFailedPrincipalValidationInfo(aPrincipal, __func__); + } + PushMessageDispatcher dispatcher(aScope, aPrincipal, aMessageId, Nothing()); + Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObserversAndWorkers())); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvNotifyPushObserversWithData( + const nsACString& aScope, nsIPrincipal* aPrincipal, + const nsAString& aMessageId, nsTArray<uint8_t>&& aData) { + if (!aPrincipal) { + return IPC_FAIL(this, "No principal"); + } + + if (!ValidatePrincipal(aPrincipal)) { + LogAndAssertFailedPrincipalValidationInfo(aPrincipal, __func__); + } + PushMessageDispatcher dispatcher(aScope, aPrincipal, aMessageId, + Some(std::move(aData))); + Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObserversAndWorkers())); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +ContentParent::RecvNotifyPushSubscriptionChangeObservers( + const nsACString& aScope, nsIPrincipal* aPrincipal) { + if (!aPrincipal) { + return IPC_FAIL(this, "No principal"); + } + + if (!ValidatePrincipal(aPrincipal)) { + LogAndAssertFailedPrincipalValidationInfo(aPrincipal, __func__); + } + PushSubscriptionChangeDispatcher dispatcher(aScope, aPrincipal); + Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObserversAndWorkers())); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvPushError(const nsACString& aScope, + nsIPrincipal* aPrincipal, + const nsAString& aMessage, + const uint32_t& aFlags) { + if (!aPrincipal) { + return IPC_FAIL(this, "No principal"); + } + + if (!ValidatePrincipal(aPrincipal)) { + LogAndAssertFailedPrincipalValidationInfo(aPrincipal, __func__); + } + PushErrorDispatcher dispatcher(aScope, aPrincipal, aMessage, aFlags); + Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObserversAndWorkers())); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +ContentParent::RecvNotifyPushSubscriptionModifiedObservers( + const nsACString& aScope, nsIPrincipal* aPrincipal) { + if (!aPrincipal) { + return IPC_FAIL(this, "No principal"); + } + + if (!ValidatePrincipal(aPrincipal)) { + LogAndAssertFailedPrincipalValidationInfo(aPrincipal, __func__); + } + PushSubscriptionModifiedDispatcher dispatcher(aScope, aPrincipal); + Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObservers())); + return IPC_OK(); +} + +/* static */ +void ContentParent::BroadcastBlobURLRegistration(const nsACString& aURI, + BlobImpl* aBlobImpl, + nsIPrincipal* aPrincipal, + const nsCString& aPartitionKey, + ContentParent* aIgnoreThisCP) { + uint64_t originHash = ComputeLoadedOriginHash(aPrincipal); + + bool toBeSent = + BlobURLProtocolHandler::IsBlobURLBroadcastPrincipal(aPrincipal); + + nsCString uri(aURI); + + for (auto* cp : AllProcesses(eLive)) { + if (cp != aIgnoreThisCP) { + if (!toBeSent && !cp->mLoadedOriginHashes.Contains(originHash)) { + continue; + } + + nsresult rv = cp->TransmitPermissionsForPrincipal(aPrincipal); + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + + IPCBlob ipcBlob; + rv = IPCBlobUtils::Serialize(aBlobImpl, ipcBlob); + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + + Unused << cp->SendBlobURLRegistration(uri, ipcBlob, aPrincipal, + aPartitionKey); + } + } +} + +/* static */ +void ContentParent::BroadcastBlobURLUnregistration( + const nsACString& aURI, nsIPrincipal* aPrincipal, + ContentParent* aIgnoreThisCP) { + uint64_t originHash = ComputeLoadedOriginHash(aPrincipal); + + bool toBeSent = + BlobURLProtocolHandler::IsBlobURLBroadcastPrincipal(aPrincipal); + + nsCString uri(aURI); + + for (auto* cp : AllProcesses(eLive)) { + if (cp != aIgnoreThisCP && + (toBeSent || cp->mLoadedOriginHashes.Contains(originHash))) { + Unused << cp->SendBlobURLUnregistration(uri); + } + } +} + +mozilla::ipc::IPCResult ContentParent::RecvStoreAndBroadcastBlobURLRegistration( + const nsACString& aURI, const IPCBlob& aBlob, nsIPrincipal* aPrincipal, + const nsCString& aPartitionKey) { + if (!aPrincipal) { + return IPC_FAIL(this, "No principal"); + } + + if (!ValidatePrincipal(aPrincipal, {ValidatePrincipalOptions::AllowSystem})) { + LogAndAssertFailedPrincipalValidationInfo(aPrincipal, __func__); + } + RefPtr<BlobImpl> blobImpl = IPCBlobUtils::Deserialize(aBlob); + if (NS_WARN_IF(!blobImpl)) { + return IPC_FAIL(this, "Blob deserialization failed."); + } + + BlobURLProtocolHandler::AddDataEntry(aURI, aPrincipal, aPartitionKey, + blobImpl); + BroadcastBlobURLRegistration(aURI, blobImpl, aPrincipal, aPartitionKey, this); + + // We want to store this blobURL, so we can unregister it if the child + // crashes. + mBlobURLs.AppendElement(aURI); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult +ContentParent::RecvUnstoreAndBroadcastBlobURLUnregistration( + const nsACString& aURI, nsIPrincipal* aPrincipal) { + if (!aPrincipal) { + return IPC_FAIL(this, "No principal"); + } + + if (!ValidatePrincipal(aPrincipal, {ValidatePrincipalOptions::AllowSystem})) { + LogAndAssertFailedPrincipalValidationInfo(aPrincipal, __func__); + } + BlobURLProtocolHandler::RemoveDataEntry(aURI, false /* Don't broadcast */); + BroadcastBlobURLUnregistration(aURI, aPrincipal, this); + mBlobURLs.RemoveElement(aURI); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvGetFilesRequest( + const nsID& aUUID, const nsAString& aDirectoryPath, + const bool& aRecursiveFlag) { + MOZ_ASSERT(!mGetFilesPendingRequests.GetWeak(aUUID)); + + if (!mozilla::Preferences::GetBool("dom.filesystem.pathcheck.disabled", + false)) { + RefPtr<FileSystemSecurity> fss = FileSystemSecurity::Get(); + if (!fss) { + return IPC_FAIL(this, "Failed to get FileSystemSecurity."); + } + + if (!fss->ContentProcessHasAccessTo(ChildID(), aDirectoryPath)) { + return IPC_FAIL(this, "ContentProcessHasAccessTo failed."); + } + } + + ErrorResult rv; + RefPtr<GetFilesHelper> helper = GetFilesHelperParent::Create( + aUUID, aDirectoryPath, aRecursiveFlag, this, rv); + + if (NS_WARN_IF(rv.Failed())) { + if (!SendGetFilesResponse(aUUID, + GetFilesResponseFailure(rv.StealNSResult()))) { + return IPC_FAIL(this, "SendGetFilesResponse failed."); + } + return IPC_OK(); + } + + mGetFilesPendingRequests.InsertOrUpdate(aUUID, std::move(helper)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvDeleteGetFilesRequest( + const nsID& aUUID) { + mGetFilesPendingRequests.Remove(aUUID); + return IPC_OK(); +} + +void ContentParent::SendGetFilesResponseAndForget( + const nsID& aUUID, const GetFilesResponseResult& aResult) { + if (mGetFilesPendingRequests.Remove(aUUID)) { + Unused << SendGetFilesResponse(aUUID, aResult); + } +} + +void ContentParent::PaintTabWhileInterruptingJS(BrowserParent* aBrowserParent) { + if (!mHangMonitorActor) { + return; + } + ProcessHangMonitor::PaintWhileInterruptingJS(mHangMonitorActor, + aBrowserParent); +} + +void ContentParent::UnloadLayersWhileInterruptingJS( + BrowserParent* aBrowserParent) { + if (!mHangMonitorActor) { + return; + } + ProcessHangMonitor::UnloadLayersWhileInterruptingJS(mHangMonitorActor, + aBrowserParent); +} + +void ContentParent::CancelContentJSExecutionIfRunning( + BrowserParent* aBrowserParent, nsIRemoteTab::NavigationType aNavigationType, + const CancelContentJSOptions& aCancelContentJSOptions) { + if (!mHangMonitorActor) { + return; + } + + ProcessHangMonitor::CancelContentJSExecutionIfRunning( + mHangMonitorActor, aBrowserParent, aNavigationType, + aCancelContentJSOptions); +} + +void ContentParent::SetMainThreadQoSPriority( + nsIThread::QoSPriority aQoSPriority) { + if (!mHangMonitorActor) { + return; + } + + ProcessHangMonitor::SetMainThreadQoSPriority(mHangMonitorActor, aQoSPriority); +} + +void ContentParent::UpdateCookieStatus(nsIChannel* aChannel) { + PNeckoParent* neckoParent = LoneManagedOrNullAsserts(ManagedPNeckoParent()); + PCookieServiceParent* csParent = + LoneManagedOrNullAsserts(neckoParent->ManagedPCookieServiceParent()); + if (csParent) { + auto* cs = static_cast<CookieServiceParent*>(csParent); + cs->TrackCookieLoad(aChannel); + } +} + +nsresult ContentParent::AboutToLoadHttpFtpDocumentForChild( + nsIChannel* aChannel, bool* aShouldWaitForPermissionCookieUpdate) { + MOZ_ASSERT(aChannel); + + if (aShouldWaitForPermissionCookieUpdate) { + *aShouldWaitForPermissionCookieUpdate = false; + } + + nsresult rv; + bool isDocument = aChannel->IsDocument(); + if (!isDocument) { + // We may be looking at a nsIHttpChannel which has isMainDocumentChannel set + // (e.g. the internal http channel for a view-source: load.). + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel); + if (httpChannel) { + rv = httpChannel->GetIsMainDocumentChannel(&isDocument); + NS_ENSURE_SUCCESS(rv, rv); + } + } + if (!isDocument) { + return NS_OK; + } + + // Get the principal for the channel result, so that we can get the permission + // key for the document which will be created from this response. + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + if (NS_WARN_IF(!ssm)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIPrincipal> principal; + nsCOMPtr<nsIPrincipal> partitionedPrincipal; + rv = ssm->GetChannelResultPrincipals(aChannel, getter_AddRefs(principal), + getter_AddRefs(partitionedPrincipal)); + NS_ENSURE_SUCCESS(rv, rv); + + // Let the caller know we're going to send main thread IPC for updating + // permisssions/cookies. + if (aShouldWaitForPermissionCookieUpdate) { + *aShouldWaitForPermissionCookieUpdate = true; + } + + TransmitBlobURLsForPrincipal(principal); + + // Tranmit permissions for both regular and partitioned principal so that the + // content process can get permissions for the partitioned principal. For + // example, the desk-notification permission for a partitioned service worker. + rv = TransmitPermissionsForPrincipal(principal); + NS_ENSURE_SUCCESS(rv, rv); + + rv = TransmitPermissionsForPrincipal(partitionedPrincipal); + NS_ENSURE_SUCCESS(rv, rv); + + nsLoadFlags newLoadFlags; + aChannel->GetLoadFlags(&newLoadFlags); + if (newLoadFlags & nsIRequest::LOAD_DOCUMENT_NEEDS_COOKIE) { + UpdateCookieStatus(aChannel); + } + + RefPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); + RefPtr<BrowsingContext> browsingContext; + rv = loadInfo->GetTargetBrowsingContext(getter_AddRefs(browsingContext)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!NextGenLocalStorageEnabled()) { + return NS_OK; + } + + if (principal->GetIsContentPrincipal()) { + nsCOMPtr<nsILocalStorageManager> lsm = + do_GetService("@mozilla.org/dom/localStorage-manager;1"); + if (NS_WARN_IF(!lsm)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIPrincipal> storagePrincipal; + rv = ssm->GetChannelResultStoragePrincipal( + aChannel, getter_AddRefs(storagePrincipal)); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<Promise> dummy; + rv = lsm->Preload(storagePrincipal, nullptr, getter_AddRefs(dummy)); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to preload local storage!"); + } + } + + return NS_OK; +} + +nsresult ContentParent::TransmitPermissionsForPrincipal( + nsIPrincipal* aPrincipal) { + // Create the key, and send it down to the content process. + nsTArray<std::pair<nsCString, nsCString>> pairs = + PermissionManager::GetAllKeysForPrincipal(aPrincipal); + MOZ_ASSERT(pairs.Length() >= 1); + for (auto& pair : pairs) { + EnsurePermissionsByKey(pair.first, pair.second); + } + + // We need to add the Site to the secondary keys of interest here. + // This allows site-scoped permission updates to propogate when the + // port is non-standard. + nsAutoCString siteKey; + nsresult rv = + PermissionManager::GetKeyForPrincipal(aPrincipal, false, true, siteKey); + if (NS_SUCCEEDED(rv) && !siteKey.IsEmpty()) { + mActiveSecondaryPermissionKeys.EnsureInserted(siteKey); + } + + return NS_OK; +} + +void ContentParent::TransmitBlobURLsForPrincipal(nsIPrincipal* aPrincipal) { + // If we're already broadcasting BlobURLs with this principal, we don't need + // to send them here. + if (BlobURLProtocolHandler::IsBlobURLBroadcastPrincipal(aPrincipal)) { + return; + } + + // We shouldn't have any Blob URLs with expanded principals, so transmit URLs + // for each principal in the AllowList instead. + if (nsCOMPtr<nsIExpandedPrincipal> ep = do_QueryInterface(aPrincipal)) { + for (const auto& prin : ep->AllowList()) { + TransmitBlobURLsForPrincipal(prin); + } + return; + } + + uint64_t originHash = ComputeLoadedOriginHash(aPrincipal); + + if (!mLoadedOriginHashes.Contains(originHash)) { + mLoadedOriginHashes.AppendElement(originHash); + + nsTArray<BlobURLRegistrationData> registrations; + BlobURLProtocolHandler::ForEachBlobURL( + [&](BlobImpl* aBlobImpl, nsIPrincipal* aBlobPrincipal, + const nsCString& aPartitionKey, const nsACString& aURI, + bool aRevoked) { + // This check uses `ComputeLoadedOriginHash` to compare, rather than + // doing the more accurate `Equals` check, as it needs to match the + // behaviour of the logic to broadcast new registrations. + if (originHash != ComputeLoadedOriginHash(aBlobPrincipal)) { + return true; + } + + IPCBlob ipcBlob; + nsresult rv = IPCBlobUtils::Serialize(aBlobImpl, ipcBlob); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + registrations.AppendElement( + BlobURLRegistrationData(nsCString(aURI), ipcBlob, aPrincipal, + nsCString(aPartitionKey), aRevoked)); + + rv = TransmitPermissionsForPrincipal(aBlobPrincipal); + Unused << NS_WARN_IF(NS_FAILED(rv)); + return true; + }); + + if (!registrations.IsEmpty()) { + Unused << SendInitBlobURLs(registrations); + } + } +} + +void ContentParent::TransmitBlobDataIfBlobURL(nsIURI* aURI) { + MOZ_ASSERT(aURI); + + nsCOMPtr<nsIPrincipal> principal; + if (BlobURLProtocolHandler::GetBlobURLPrincipal(aURI, + getter_AddRefs(principal))) { + TransmitBlobURLsForPrincipal(principal); + } +} + +void ContentParent::EnsurePermissionsByKey(const nsACString& aKey, + const nsACString& aOrigin) { + // NOTE: Make sure to initialize the permission manager before updating the + // mActivePermissionKeys list. If the permission manager is being initialized + // by this call to GetPermissionManager, and we've added the key to + // mActivePermissionKeys, then the permission manager will send down a + // SendAddPermission before receiving the SendSetPermissionsWithKey message. + RefPtr<PermissionManager> permManager = PermissionManager::GetInstance(); + if (!permManager) { + return; + } + + if (!mActivePermissionKeys.EnsureInserted(aKey)) { + return; + } + + nsTArray<IPC::Permission> perms; + if (permManager->GetPermissionsFromOriginOrKey(aOrigin, aKey, perms)) { + Unused << SendSetPermissionsWithKey(aKey, perms); + } +} + +bool ContentParent::NeedsPermissionsUpdate( + const nsACString& aPermissionKey) const { + return mActivePermissionKeys.Contains(aPermissionKey); +} + +bool ContentParent::NeedsSecondaryKeyPermissionsUpdate( + const nsACString& aPermissionKey) const { + return mActiveSecondaryPermissionKeys.Contains(aPermissionKey); +} + +mozilla::ipc::IPCResult ContentParent::RecvAccumulateChildHistograms( + nsTArray<HistogramAccumulation>&& aAccumulations) { + TelemetryIPC::AccumulateChildHistograms(GetTelemetryProcessID(mRemoteType), + aAccumulations); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvAccumulateChildKeyedHistograms( + nsTArray<KeyedHistogramAccumulation>&& aAccumulations) { + TelemetryIPC::AccumulateChildKeyedHistograms( + GetTelemetryProcessID(mRemoteType), aAccumulations); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvUpdateChildScalars( + nsTArray<ScalarAction>&& aScalarActions) { + TelemetryIPC::UpdateChildScalars(GetTelemetryProcessID(mRemoteType), + aScalarActions); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvUpdateChildKeyedScalars( + nsTArray<KeyedScalarAction>&& aScalarActions) { + TelemetryIPC::UpdateChildKeyedScalars(GetTelemetryProcessID(mRemoteType), + aScalarActions); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvRecordChildEvents( + nsTArray<mozilla::Telemetry::ChildEventData>&& aEvents) { + TelemetryIPC::RecordChildEvents(GetTelemetryProcessID(mRemoteType), aEvents); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvRecordDiscardedData( + const mozilla::Telemetry::DiscardedData& aDiscardedData) { + TelemetryIPC::RecordDiscardedData(GetTelemetryProcessID(mRemoteType), + aDiscardedData); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvRecordPageLoadEvent( + const mozilla::glean::perf::PageLoadExtra& aPageLoadEventExtra) { + mozilla::glean::perf::page_load.Record(mozilla::Some(aPageLoadEventExtra)); + + // Send the PageLoadPing after every 30 page loads, or on startup. + if (++sPageLoadEventCounter >= 30) { + NS_SUCCEEDED(NS_DispatchToMainThreadQueue( + NS_NewRunnableFunction( + "PageLoadPingIdleTask", + [] { mozilla::glean_pings::Pageload.Submit("threshold"_ns); }), + EventQueuePriority::Idle)); + sPageLoadEventCounter = 0; + } + + return IPC_OK(); +} + +////////////////////////////////////////////////////////////////// +// PURLClassifierParent + +PURLClassifierParent* ContentParent::AllocPURLClassifierParent( + nsIPrincipal* aPrincipal, bool* aSuccess) { + MOZ_ASSERT(NS_IsMainThread()); + + *aSuccess = true; + RefPtr<URLClassifierParent> actor = new URLClassifierParent(); + return actor.forget().take(); +} + +mozilla::ipc::IPCResult ContentParent::RecvPURLClassifierConstructor( + PURLClassifierParent* aActor, nsIPrincipal* aPrincipal, bool* aSuccess) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aActor); + *aSuccess = false; + + auto* actor = static_cast<URLClassifierParent*>(aActor); + nsCOMPtr<nsIPrincipal> principal(aPrincipal); + if (!principal) { + actor->ClassificationFailed(); + return IPC_OK(); + } + if (!ValidatePrincipal(aPrincipal)) { + LogAndAssertFailedPrincipalValidationInfo(aPrincipal, __func__); + } + return actor->StartClassify(principal, aSuccess); +} + +bool ContentParent::DeallocPURLClassifierParent(PURLClassifierParent* aActor) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aActor); + + RefPtr<URLClassifierParent> actor = + dont_AddRef(static_cast<URLClassifierParent*>(aActor)); + return true; +} + +////////////////////////////////////////////////////////////////// +// PURLClassifierLocalParent + +PURLClassifierLocalParent* ContentParent::AllocPURLClassifierLocalParent( + nsIURI* aURI, const nsTArray<IPCURLClassifierFeature>& aFeatures) { + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr<URLClassifierLocalParent> actor = new URLClassifierLocalParent(); + return actor.forget().take(); +} + +mozilla::ipc::IPCResult ContentParent::RecvPURLClassifierLocalConstructor( + PURLClassifierLocalParent* aActor, nsIURI* aURI, + nsTArray<IPCURLClassifierFeature>&& aFeatures) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aActor); + + nsTArray<IPCURLClassifierFeature> features = std::move(aFeatures); + + if (!aURI) { + return IPC_FAIL(this, "aURI should not be null"); + } + + auto* actor = static_cast<URLClassifierLocalParent*>(aActor); + return actor->StartClassify(aURI, features); +} + +bool ContentParent::DeallocPURLClassifierLocalParent( + PURLClassifierLocalParent* aActor) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aActor); + + RefPtr<URLClassifierLocalParent> actor = + dont_AddRef(static_cast<URLClassifierLocalParent*>(aActor)); + return true; +} + +PSessionStorageObserverParent* +ContentParent::AllocPSessionStorageObserverParent() { + MOZ_ASSERT(NS_IsMainThread()); + + return mozilla::dom::AllocPSessionStorageObserverParent(); +} + +mozilla::ipc::IPCResult ContentParent::RecvPSessionStorageObserverConstructor( + PSessionStorageObserverParent* aActor) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aActor); + + if (!mozilla::dom::RecvPSessionStorageObserverConstructor(aActor)) { + return IPC_FAIL(this, "RecvPSessionStorageObserverConstructor failed."); + } + return IPC_OK(); +} + +bool ContentParent::DeallocPSessionStorageObserverParent( + PSessionStorageObserverParent* aActor) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aActor); + + return mozilla::dom::DeallocPSessionStorageObserverParent(aActor); +} + +mozilla::ipc::IPCResult ContentParent::RecvBHRThreadHang( + const HangDetails& aDetails) { + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + // Copy the HangDetails recieved over the network into a nsIHangDetails, and + // then fire our own observer notification. + // XXX: We should be able to avoid this potentially expensive copy here by + // moving our deserialized argument. + nsCOMPtr<nsIHangDetails> hangDetails = + new nsHangDetails(HangDetails(aDetails), PersistedToDisk::No); + obs->NotifyObservers(hangDetails, "bhr-thread-hang", nullptr); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvAddCertException( + nsIX509Cert* aCert, const nsACString& aHostName, int32_t aPort, + const OriginAttributes& aOriginAttributes, bool aIsTemporary, + AddCertExceptionResolver&& aResolver) { + nsCOMPtr<nsICertOverrideService> overrideService = + do_GetService(NS_CERTOVERRIDE_CONTRACTID); + if (!overrideService) { + aResolver(NS_ERROR_FAILURE); + return IPC_OK(); + } + nsresult rv = overrideService->RememberValidityOverride( + aHostName, aPort, aOriginAttributes, aCert, aIsTemporary); + aResolver(rv); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +ContentParent::RecvAutomaticStorageAccessPermissionCanBeGranted( + nsIPrincipal* aPrincipal, + AutomaticStorageAccessPermissionCanBeGrantedResolver&& aResolver) { + if (!aPrincipal) { + return IPC_FAIL(this, "No principal"); + } + + if (!ValidatePrincipal(aPrincipal)) { + LogAndAssertFailedPrincipalValidationInfo(aPrincipal, __func__); + } + aResolver(Document::AutomaticStorageAccessPermissionCanBeGranted(aPrincipal)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +ContentParent::RecvStorageAccessPermissionGrantedForOrigin( + uint64_t aTopLevelWindowId, + const MaybeDiscarded<BrowsingContext>& aParentContext, + nsIPrincipal* aTrackingPrincipal, const nsACString& aTrackingOrigin, + const int& aAllowMode, + const Maybe<ContentBlockingNotifier::StorageAccessPermissionGrantedReason>& + aReason, + const bool& aFrameOnly, + StorageAccessPermissionGrantedForOriginResolver&& aResolver) { + if (aParentContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + + if (!aTrackingPrincipal) { + return IPC_FAIL(this, "No principal"); + } + + // We only report here if we cannot report the console directly in the content + // process. In that case, the `aReason` would be given a value. Otherwise, it + // will be nothing. + if (aReason) { + ContentBlockingNotifier::ReportUnblockingToConsole( + aParentContext.get_canonical(), NS_ConvertUTF8toUTF16(aTrackingOrigin), + aReason.value()); + } + + StorageAccessAPIHelper::SaveAccessForOriginOnParentProcess( + aTopLevelWindowId, aParentContext.get_canonical(), aTrackingPrincipal, + aAllowMode, aFrameOnly) + ->Then(GetCurrentSerialEventTarget(), __func__, + [aResolver = std::move(aResolver)]( + StorageAccessAPIHelper::ParentAccessGrantPromise:: + ResolveOrRejectValue&& aValue) { + bool success = + aValue.IsResolve() && NS_SUCCEEDED(aValue.ResolveValue()); + aResolver(success); + }); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvCompleteAllowAccessFor( + const MaybeDiscarded<BrowsingContext>& aParentContext, + uint64_t aTopLevelWindowId, nsIPrincipal* aTrackingPrincipal, + const nsACString& aTrackingOrigin, uint32_t aCookieBehavior, + const ContentBlockingNotifier::StorageAccessPermissionGrantedReason& + aReason, + CompleteAllowAccessForResolver&& aResolver) { + if (aParentContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + + StorageAccessAPIHelper::CompleteAllowAccessForOnParentProcess( + aParentContext.get_canonical(), aTopLevelWindowId, aTrackingPrincipal, + aTrackingOrigin, aCookieBehavior, aReason, nullptr) + ->Then(GetCurrentSerialEventTarget(), __func__, + [aResolver = std::move(aResolver)]( + StorageAccessAPIHelper::StorageAccessPermissionGrantPromise:: + ResolveOrRejectValue&& aValue) { + Maybe<StorageAccessPromptChoices> choice; + if (aValue.IsResolve()) { + choice.emplace(static_cast<StorageAccessPromptChoices>( + aValue.ResolveValue())); + } + aResolver(choice); + }); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvSetAllowStorageAccessRequestFlag( + nsIPrincipal* aEmbeddedPrincipal, nsIURI* aEmbeddingOrigin, + SetAllowStorageAccessRequestFlagResolver&& aResolver) { + MOZ_ASSERT(aEmbeddedPrincipal); + MOZ_ASSERT(aEmbeddingOrigin); + + if (!aEmbeddedPrincipal || !aEmbeddingOrigin) { + aResolver(false); + return IPC_OK(); + } + + // Get the permission manager and build the key. + RefPtr<PermissionManager> permManager = PermissionManager::GetInstance(); + if (!permManager) { + aResolver(false); + return IPC_OK(); + } + nsCOMPtr<nsIURI> embeddedURI = aEmbeddedPrincipal->GetURI(); + nsCString permissionKey; + bool success = AntiTrackingUtils::CreateStorageRequestPermissionKey( + embeddedURI, permissionKey); + if (!success) { + aResolver(false); + return IPC_OK(); + } + + // Set the permission to ALLOW for a prefence specified amount of seconds. + // Time units are inconsistent, be careful + int64_t when = (PR_Now() / PR_USEC_PER_MSEC) + + StaticPrefs::dom_storage_access_forward_declared_lifetime() * + PR_MSEC_PER_SEC; + nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal( + aEmbeddingOrigin, aEmbeddedPrincipal->OriginAttributesRef()); + nsresult rv = permManager->AddFromPrincipal( + principal, permissionKey, nsIPermissionManager::ALLOW_ACTION, + nsIPermissionManager::EXPIRE_TIME, when); + if (NS_FAILED(rv)) { + aResolver(false); + return IPC_OK(); + } + + // Resolve with success if we set the permission. + aResolver(true); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvTestAllowStorageAccessRequestFlag( + nsIPrincipal* aEmbeddingPrincipal, nsIURI* aEmbeddedOrigin, + TestAllowStorageAccessRequestFlagResolver&& aResolver) { + MOZ_ASSERT(aEmbeddingPrincipal); + MOZ_ASSERT(aEmbeddedOrigin); + + // Get the permission manager and build the key. + RefPtr<PermissionManager> permManager = PermissionManager::GetInstance(); + if (!permManager) { + aResolver(false); + return IPC_OK(); + } + nsCString requestPermissionKey; + bool success = AntiTrackingUtils::CreateStorageRequestPermissionKey( + aEmbeddedOrigin, requestPermissionKey); + if (!success) { + aResolver(false); + return IPC_OK(); + } + + // Get the permission and resolve false if it is not set to ALLOW. + uint32_t access = nsIPermissionManager::UNKNOWN_ACTION; + nsresult rv = permManager->TestPermissionFromPrincipal( + aEmbeddingPrincipal, requestPermissionKey, &access); + if (NS_FAILED(rv)) { + aResolver(false); + return IPC_OK(); + } + if (access != nsIPermissionManager::ALLOW_ACTION) { + aResolver(false); + return IPC_OK(); + } + + // Remove the permission, failing if the permission manager fails + rv = permManager->RemoveFromPrincipal(aEmbeddingPrincipal, + requestPermissionKey); + if (NS_FAILED(rv)) { + aResolver(false); + return IPC_OK(); + } + + // At this point, signal to our caller that the permission was set + aResolver(true); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvStoreUserInteractionAsPermission( + nsIPrincipal* aPrincipal) { + if (!aPrincipal) { + return IPC_FAIL(this, "No principal"); + } + + if (!ValidatePrincipal(aPrincipal)) { + LogAndAssertFailedPrincipalValidationInfo(aPrincipal, __func__); + } + ContentBlockingUserInteraction::Observe(aPrincipal); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvTestCookiePermissionDecided( + const MaybeDiscarded<BrowsingContext>& aContext, nsIPrincipal* aPrincipal, + const TestCookiePermissionDecidedResolver&& aResolver) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + + if (!aPrincipal) { + return IPC_FAIL(this, "No principal"); + } + + RefPtr<WindowGlobalParent> wgp = + aContext.get_canonical()->GetCurrentWindowGlobal(); + nsCOMPtr<nsICookieJarSettings> cjs = wgp->CookieJarSettings(); + + Maybe<bool> result = + StorageAccessAPIHelper::CheckCookiesPermittedDecidesStorageAccessAPI( + cjs, aPrincipal); + aResolver(result); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvTestStorageAccessPermission( + nsIPrincipal* aEmbeddingPrincipal, const nsCString& aEmbeddedOrigin, + const TestStorageAccessPermissionResolver&& aResolver) { + // Get the permission manager and build the key. + RefPtr<PermissionManager> permManager = PermissionManager::GetInstance(); + if (!permManager) { + aResolver(Nothing()); + return IPC_OK(); + } + nsCString requestPermissionKey; + AntiTrackingUtils::CreateStoragePermissionKey(aEmbeddedOrigin, + requestPermissionKey); + + // Test the permission + uint32_t access = nsIPermissionManager::UNKNOWN_ACTION; + nsresult rv = permManager->TestPermissionFromPrincipal( + aEmbeddingPrincipal, requestPermissionKey, &access); + if (NS_FAILED(rv)) { + aResolver(Nothing()); + return IPC_OK(); + } + if (access == nsIPermissionManager::ALLOW_ACTION) { + aResolver(Some(true)); + } else if (access == nsIPermissionManager::DENY_ACTION) { + aResolver(Some(false)); + } else { + aResolver(Nothing()); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvNotifyMediaPlaybackChanged( + const MaybeDiscarded<BrowsingContext>& aContext, + MediaPlaybackState aState) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + if (RefPtr<IMediaInfoUpdater> updater = + aContext.get_canonical()->GetMediaController()) { + updater->NotifyMediaPlaybackChanged(aContext.ContextId(), aState); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvNotifyMediaAudibleChanged( + const MaybeDiscarded<BrowsingContext>& aContext, MediaAudibleState aState) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + if (RefPtr<IMediaInfoUpdater> updater = + aContext.get_canonical()->GetMediaController()) { + updater->NotifyMediaAudibleChanged(aContext.ContextId(), aState); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvNotifyPictureInPictureModeChanged( + const MaybeDiscarded<BrowsingContext>& aContext, bool aEnabled) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + if (RefPtr<MediaController> controller = + aContext.get_canonical()->GetMediaController()) { + controller->SetIsInPictureInPictureMode(aContext.ContextId(), aEnabled); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvAbortOtherOrientationPendingPromises( + const MaybeDiscarded<BrowsingContext>& aContext) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + + CanonicalBrowsingContext* context = aContext.get_canonical(); + + context->Group()->EachOtherParent(this, [&](ContentParent* aParent) { + Unused << aParent->SendAbortOrientationPendingPromises(context); + }); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvNotifyMediaSessionUpdated( + const MaybeDiscarded<BrowsingContext>& aContext, bool aIsCreated) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + + RefPtr<IMediaInfoUpdater> updater = + aContext.get_canonical()->GetMediaController(); + if (!updater) { + return IPC_OK(); + } + if (aIsCreated) { + updater->NotifySessionCreated(aContext->Id()); + } else { + updater->NotifySessionDestroyed(aContext->Id()); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvNotifyUpdateMediaMetadata( + const MaybeDiscarded<BrowsingContext>& aContext, + const Maybe<MediaMetadataBase>& aMetadata) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + if (RefPtr<IMediaInfoUpdater> updater = + aContext.get_canonical()->GetMediaController()) { + updater->UpdateMetadata(aContext.ContextId(), aMetadata); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult +ContentParent::RecvNotifyMediaSessionPlaybackStateChanged( + const MaybeDiscarded<BrowsingContext>& aContext, + MediaSessionPlaybackState aPlaybackState) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + if (RefPtr<IMediaInfoUpdater> updater = + aContext.get_canonical()->GetMediaController()) { + updater->SetDeclaredPlaybackState(aContext.ContextId(), aPlaybackState); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult +ContentParent::RecvNotifyMediaSessionSupportedActionChanged( + const MaybeDiscarded<BrowsingContext>& aContext, MediaSessionAction aAction, + bool aEnabled) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + RefPtr<IMediaInfoUpdater> updater = + aContext.get_canonical()->GetMediaController(); + if (!updater) { + return IPC_OK(); + } + if (aEnabled) { + updater->EnableAction(aContext.ContextId(), aAction); + } else { + updater->DisableAction(aContext.ContextId(), aAction); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvNotifyMediaFullScreenState( + const MaybeDiscarded<BrowsingContext>& aContext, bool aIsInFullScreen) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + if (RefPtr<IMediaInfoUpdater> updater = + aContext.get_canonical()->GetMediaController()) { + updater->NotifyMediaFullScreenState(aContext.ContextId(), aIsInFullScreen); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvNotifyPositionStateChanged( + const MaybeDiscarded<BrowsingContext>& aContext, + const PositionState& aState) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + if (RefPtr<IMediaInfoUpdater> updater = + aContext.get_canonical()->GetMediaController()) { + updater->UpdatePositionState(aContext.ContextId(), aState); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvAddOrRemovePageAwakeRequest( + const MaybeDiscarded<BrowsingContext>& aContext, + const bool& aShouldAddCount) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + if (aShouldAddCount) { + aContext.get_canonical()->AddPageAwakeRequest(); + } else { + aContext.get_canonical()->RemovePageAwakeRequest(); + } + return IPC_OK(); +} + +#if defined(XP_WIN) +mozilla::ipc::IPCResult ContentParent::RecvGetModulesTrust( + ModulePaths&& aModPaths, bool aRunAtNormalPriority, + GetModulesTrustResolver&& aResolver) { + RefPtr<DllServices> dllSvc(DllServices::Get()); + dllSvc->GetModulesTrust(std::move(aModPaths), aRunAtNormalPriority) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [aResolver](ModulesMapResult&& aResult) { + aResolver(Some(ModulesMapResult(std::move(aResult)))); + }, + [aResolver](nsresult aRv) { aResolver(Nothing()); }); + return IPC_OK(); +} +#endif // defined(XP_WIN) + +mozilla::ipc::IPCResult ContentParent::RecvCreateBrowsingContext( + uint64_t aGroupId, BrowsingContext::IPCInitializer&& aInit) { + RefPtr<WindowGlobalParent> parent; + if (aInit.mParentId != 0) { + parent = WindowGlobalParent::GetByInnerWindowId(aInit.mParentId); + if (!parent) { + return IPC_FAIL(this, "Parent doesn't exist in parent process"); + } + } + + if (parent && parent->GetContentParent() != this) { + // We're trying attach a child BrowsingContext to a parent + // WindowContext in another process. This is illegal since the + // only thing that could create that child BrowsingContext is the parent + // window's process. + return IPC_FAIL(this, + "Must create BrowsingContext from the parent's process"); + } + + RefPtr<BrowsingContext> opener; + if (aInit.GetOpenerId() != 0) { + opener = BrowsingContext::Get(aInit.GetOpenerId()); + if (!opener) { + return IPC_FAIL(this, "Opener doesn't exist in parent process"); + } + } + + RefPtr<BrowsingContext> child = BrowsingContext::Get(aInit.mId); + if (child) { + // This is highly suspicious. BrowsingContexts should only be created once, + // so finding one indicates that someone is doing something they shouldn't. + return IPC_FAIL(this, "A BrowsingContext with this ID already exists"); + } + + // Ensure that the passed-in BrowsingContextGroup is valid. + RefPtr<BrowsingContextGroup> group = + BrowsingContextGroup::GetOrCreate(aGroupId); + if (parent && parent->Group() != group) { + if (parent->Group()->Id() != aGroupId) { + return IPC_FAIL(this, "Parent has different group ID"); + } else { + return IPC_FAIL(this, "Parent has different group object"); + } + } + if (opener && opener->Group() != group) { + if (opener->Group()->Id() != aGroupId) { + return IPC_FAIL(this, "Opener has different group ID"); + } else { + return IPC_FAIL(this, "Opener has different group object"); + } + } + if (!parent && !opener && !group->Toplevels().IsEmpty()) { + return IPC_FAIL(this, "Unrelated context from child in stale group"); + } + + return BrowsingContext::CreateFromIPC(std::move(aInit), group, this); +} + +bool ContentParent::CheckBrowsingContextEmbedder(CanonicalBrowsingContext* aBC, + const char* aOperation) const { + if (!aBC->IsEmbeddedInProcess(ChildID())) { + MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Warning, + ("ParentIPC: Trying to %s out of process context 0x%08" PRIx64, + aOperation, aBC->Id())); + return false; + } + return true; +} + +mozilla::ipc::IPCResult ContentParent::RecvDiscardBrowsingContext( + const MaybeDiscarded<BrowsingContext>& aContext, bool aDoDiscard, + DiscardBrowsingContextResolver&& aResolve) { + if (CanonicalBrowsingContext* context = + CanonicalBrowsingContext::Cast(aContext.GetMaybeDiscarded())) { + if (aDoDiscard && !context->IsDiscarded()) { + if (!CheckBrowsingContextEmbedder(context, "discard")) { + return IPC_FAIL(this, "Illegal Discard attempt"); + } + + context->Detach(/* aFromIPC */ true); + } + context->AddFinalDiscardListener(aResolve); + return IPC_OK(); + } + + // Resolve the promise, as we've received and handled the message. This will + // allow the content process to fully-discard references to this BC. + aResolve(true); + return IPC_OK(); +} + +void ContentParent::UnregisterRemoveWorkerActor() { + MOZ_ASSERT(NS_IsMainThread()); + + { + RecursiveMutexAutoLock lock(mThreadsafeHandle->mMutex); + if (--mThreadsafeHandle->mRemoteWorkerActorCount) { + return; + } + } + + MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose, + ("UnregisterRemoveWorkerActor %p", this)); + MaybeBeginShutDown(); +} + +mozilla::ipc::IPCResult ContentParent::RecvWindowClose( + const MaybeDiscarded<BrowsingContext>& aContext, bool aTrustedCaller) { + if (aContext.IsNullOrDiscarded()) { + MOZ_LOG( + BrowsingContext::GetLog(), LogLevel::Debug, + ("ParentIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + CanonicalBrowsingContext* context = aContext.get_canonical(); + + // FIXME Need to check that the sending process has access to the unit of + // related + // browsing contexts of bc. + + if (ContentParent* cp = context->GetContentParent()) { + Unused << cp->SendWindowClose(context, aTrustedCaller); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvWindowFocus( + const MaybeDiscarded<BrowsingContext>& aContext, CallerType aCallerType, + uint64_t aActionId) { + if (aContext.IsNullOrDiscarded()) { + MOZ_LOG( + BrowsingContext::GetLog(), LogLevel::Debug, + ("ParentIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + LOGFOCUS(("ContentParent::RecvWindowFocus actionid: %" PRIu64, aActionId)); + CanonicalBrowsingContext* context = aContext.get_canonical(); + + if (ContentParent* cp = context->GetContentParent()) { + Unused << cp->SendWindowFocus(context, aCallerType, aActionId); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvWindowBlur( + const MaybeDiscarded<BrowsingContext>& aContext, CallerType aCallerType) { + if (aContext.IsNullOrDiscarded()) { + MOZ_LOG( + BrowsingContext::GetLog(), LogLevel::Debug, + ("ParentIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + CanonicalBrowsingContext* context = aContext.get_canonical(); + + if (ContentParent* cp = context->GetContentParent()) { + Unused << cp->SendWindowBlur(context, aCallerType); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvRaiseWindow( + const MaybeDiscarded<BrowsingContext>& aContext, CallerType aCallerType, + uint64_t aActionId) { + if (aContext.IsNullOrDiscarded()) { + MOZ_LOG( + BrowsingContext::GetLog(), LogLevel::Debug, + ("ParentIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + LOGFOCUS(("ContentParent::RecvRaiseWindow actionid: %" PRIu64, aActionId)); + + CanonicalBrowsingContext* context = aContext.get_canonical(); + + if (ContentParent* cp = context->GetContentParent()) { + Unused << cp->SendRaiseWindow(context, aCallerType, aActionId); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvAdjustWindowFocus( + const MaybeDiscarded<BrowsingContext>& aContext, bool aIsVisible, + uint64_t aActionId) { + if (aContext.IsNullOrDiscarded()) { + MOZ_LOG( + BrowsingContext::GetLog(), LogLevel::Debug, + ("ParentIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + LOGFOCUS( + ("ContentParent::RecvAdjustWindowFocus isVisible %d actionid: %" PRIu64, + aIsVisible, aActionId)); + + nsTHashMap<nsPtrHashKey<ContentParent>, bool> processes(2); + processes.InsertOrUpdate(this, true); + + ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); + if (cpm) { + CanonicalBrowsingContext* context = aContext.get_canonical(); + while (context) { + BrowsingContext* parent = context->GetParent(); + if (!parent) { + break; + } + + CanonicalBrowsingContext* canonicalParent = parent->Canonical(); + ContentParent* cp = cpm->GetContentProcessById( + ContentParentId(canonicalParent->OwnerProcessId())); + if (cp && !processes.Get(cp)) { + Unused << cp->SendAdjustWindowFocus(context, aIsVisible, aActionId); + processes.InsertOrUpdate(cp, true); + } + context = canonicalParent; + } + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvClearFocus( + const MaybeDiscarded<BrowsingContext>& aContext) { + if (aContext.IsNullOrDiscarded()) { + MOZ_LOG( + BrowsingContext::GetLog(), LogLevel::Debug, + ("ParentIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + CanonicalBrowsingContext* context = aContext.get_canonical(); + + if (ContentParent* cp = context->GetContentParent()) { + Unused << cp->SendClearFocus(context); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvSetFocusedBrowsingContext( + const MaybeDiscarded<BrowsingContext>& aContext, uint64_t aActionId) { + if (aContext.IsNullOrDiscarded()) { + MOZ_LOG( + BrowsingContext::GetLog(), LogLevel::Debug, + ("ParentIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + LOGFOCUS(("ContentParent::RecvSetFocusedBrowsingContext actionid: %" PRIu64, + aActionId)); + CanonicalBrowsingContext* context = aContext.get_canonical(); + + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (!fm) { + return IPC_OK(); + } + + if (!fm->SetFocusedBrowsingContextInChrome(context, aActionId)) { + LOGFOCUS(( + "Ignoring out-of-sequence attempt [%p] to set focused browsing context " + "in parent.", + context)); + Unused << SendReviseFocusedBrowsingContext( + aActionId, fm->GetFocusedBrowsingContextInChrome(), + fm->GetActionIdForFocusedBrowsingContextInChrome()); + return IPC_OK(); + } + + BrowserParent::UpdateFocusFromBrowsingContext(); + + context->Group()->EachOtherParent(this, [&](ContentParent* aParent) { + Unused << aParent->SendSetFocusedBrowsingContext(context, aActionId); + }); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvSetActiveBrowsingContext( + const MaybeDiscarded<BrowsingContext>& aContext, uint64_t aActionId) { + if (aContext.IsNullOrDiscarded()) { + MOZ_LOG( + BrowsingContext::GetLog(), LogLevel::Debug, + ("ParentIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + LOGFOCUS(("ContentParent::RecvSetActiveBrowsingContext actionid: %" PRIu64, + aActionId)); + CanonicalBrowsingContext* context = aContext.get_canonical(); + + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (!fm) { + return IPC_OK(); + } + + if (!fm->SetActiveBrowsingContextInChrome(context, aActionId)) { + LOGFOCUS( + ("Ignoring out-of-sequence attempt [%p] to set active browsing context " + "in parent.", + context)); + Unused << SendReviseActiveBrowsingContext( + aActionId, fm->GetActiveBrowsingContextInChrome(), + fm->GetActionIdForActiveBrowsingContextInChrome()); + return IPC_OK(); + } + + context->Group()->EachOtherParent(this, [&](ContentParent* aParent) { + Unused << aParent->SendSetActiveBrowsingContext(context, aActionId); + }); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvUnsetActiveBrowsingContext( + const MaybeDiscarded<BrowsingContext>& aContext, uint64_t aActionId) { + if (aContext.IsNullOrDiscarded()) { + MOZ_LOG( + BrowsingContext::GetLog(), LogLevel::Debug, + ("ParentIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + LOGFOCUS(("ContentParent::RecvUnsetActiveBrowsingContext actionid: %" PRIu64, + aActionId)); + CanonicalBrowsingContext* context = aContext.get_canonical(); + + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (!fm) { + return IPC_OK(); + } + + if (!fm->SetActiveBrowsingContextInChrome(nullptr, aActionId)) { + LOGFOCUS( + ("Ignoring out-of-sequence attempt to unset active browsing context in " + "parent [%p].", + context)); + Unused << SendReviseActiveBrowsingContext( + aActionId, fm->GetActiveBrowsingContextInChrome(), + fm->GetActionIdForActiveBrowsingContextInChrome()); + return IPC_OK(); + } + + context->Group()->EachOtherParent(this, [&](ContentParent* aParent) { + Unused << aParent->SendUnsetActiveBrowsingContext(context, aActionId); + }); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvSetFocusedElement( + const MaybeDiscarded<BrowsingContext>& aContext, bool aNeedsFocus) { + if (aContext.IsNullOrDiscarded()) { + MOZ_LOG( + BrowsingContext::GetLog(), LogLevel::Debug, + ("ParentIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + LOGFOCUS(("ContentParent::RecvSetFocusedElement")); + CanonicalBrowsingContext* context = aContext.get_canonical(); + + if (ContentParent* cp = context->GetContentParent()) { + Unused << cp->SendSetFocusedElement(context, aNeedsFocus); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvFinalizeFocusOuter( + const MaybeDiscarded<BrowsingContext>& aContext, bool aCanFocus, + CallerType aCallerType) { + if (aContext.IsNullOrDiscarded()) { + MOZ_LOG( + BrowsingContext::GetLog(), LogLevel::Debug, + ("ParentIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + LOGFOCUS(("ContentParent::RecvFinalizeFocusOuter")); + CanonicalBrowsingContext* context = aContext.get_canonical(); + ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); + if (cpm) { + ContentParent* cp = cpm->GetContentProcessById( + ContentParentId(context->EmbedderProcessId())); + if (cp) { + Unused << cp->SendFinalizeFocusOuter(context, aCanFocus, aCallerType); + } + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvInsertNewFocusActionId( + uint64_t aActionId) { + LOGFOCUS(("ContentParent::RecvInsertNewFocusActionId actionid: %" PRIu64, + aActionId)); + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + fm->InsertNewFocusActionId(aActionId); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvBlurToParent( + const MaybeDiscarded<BrowsingContext>& aFocusedBrowsingContext, + const MaybeDiscarded<BrowsingContext>& aBrowsingContextToClear, + const MaybeDiscarded<BrowsingContext>& aAncestorBrowsingContextToFocus, + bool aIsLeavingDocument, bool aAdjustWidget, + bool aBrowsingContextToClearHandled, + bool aAncestorBrowsingContextToFocusHandled, uint64_t aActionId) { + if (aFocusedBrowsingContext.IsNullOrDiscarded()) { + MOZ_LOG( + BrowsingContext::GetLog(), LogLevel::Debug, + ("ParentIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + + LOGFOCUS( + ("ContentParent::RecvBlurToParent isLeavingDocument %d adjustWidget %d " + "browsingContextToClearHandled %d ancestorBrowsingContextToFocusHandled " + "%d actionid: %" PRIu64, + aIsLeavingDocument, aAdjustWidget, aBrowsingContextToClearHandled, + aAncestorBrowsingContextToFocusHandled, aActionId)); + + CanonicalBrowsingContext* focusedBrowsingContext = + aFocusedBrowsingContext.get_canonical(); + + // If aBrowsingContextToClear and aAncestorBrowsingContextToFocusHandled + // didn't get handled in the process that sent this IPC message and they + // aren't in the same process as aFocusedBrowsingContext, we need to split + // off their handling here and use SendSetFocusedElement to send them + // elsewhere than the blurring itself. + + bool ancestorDifferent = + (!aAncestorBrowsingContextToFocusHandled && + !aAncestorBrowsingContextToFocus.IsNullOrDiscarded() && + (focusedBrowsingContext->OwnerProcessId() != + aAncestorBrowsingContextToFocus.get_canonical()->OwnerProcessId())); + if (!aBrowsingContextToClearHandled && + !aBrowsingContextToClear.IsNullOrDiscarded() && + (focusedBrowsingContext->OwnerProcessId() != + aBrowsingContextToClear.get_canonical()->OwnerProcessId())) { + MOZ_RELEASE_ASSERT(!ancestorDifferent, + "This combination is not supposed to happen."); + if (ContentParent* cp = + aBrowsingContextToClear.get_canonical()->GetContentParent()) { + Unused << cp->SendSetFocusedElement(aBrowsingContextToClear, false); + } + } else if (ancestorDifferent) { + if (ContentParent* cp = aAncestorBrowsingContextToFocus.get_canonical() + ->GetContentParent()) { + Unused << cp->SendSetFocusedElement(aAncestorBrowsingContextToFocus, + true); + } + } + + if (ContentParent* cp = focusedBrowsingContext->GetContentParent()) { + Unused << cp->SendBlurToChild(aFocusedBrowsingContext, + aBrowsingContextToClear, + aAncestorBrowsingContextToFocus, + aIsLeavingDocument, aAdjustWidget, aActionId); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvMaybeExitFullscreen( + const MaybeDiscarded<BrowsingContext>& aContext) { + if (aContext.IsNullOrDiscarded()) { + MOZ_LOG( + BrowsingContext::GetLog(), LogLevel::Debug, + ("ParentIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + CanonicalBrowsingContext* context = aContext.get_canonical(); + + if (ContentParent* cp = context->GetContentParent()) { + Unused << cp->SendMaybeExitFullscreen(context); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvWindowPostMessage( + const MaybeDiscarded<BrowsingContext>& aContext, + const ClonedOrErrorMessageData& aMessage, const PostMessageData& aData) { + if (aContext.IsNullOrDiscarded()) { + MOZ_LOG( + BrowsingContext::GetLog(), LogLevel::Debug, + ("ParentIPC: Trying to send a message to dead or detached context")); + return IPC_OK(); + } + CanonicalBrowsingContext* context = aContext.get_canonical(); + + if (aData.source().IsDiscarded()) { + MOZ_LOG( + BrowsingContext::GetLog(), LogLevel::Debug, + ("ParentIPC: Trying to send a message from dead or detached context")); + return IPC_OK(); + } + + RefPtr<ContentParent> cp = context->GetContentParent(); + if (!cp) { + MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug, + ("ParentIPC: Trying to send PostMessage to dead content process")); + return IPC_OK(); + } + + ClonedOrErrorMessageData message; + StructuredCloneData messageFromChild; + if (aMessage.type() == ClonedOrErrorMessageData::TClonedMessageData) { + UnpackClonedMessageData(aMessage, messageFromChild); + + ClonedMessageData clonedMessageData; + if (BuildClonedMessageData(messageFromChild, clonedMessageData)) { + message = std::move(clonedMessageData); + } else { + // FIXME Logging? + message = ErrorMessageData(); + } + } else { + MOZ_ASSERT(aMessage.type() == ClonedOrErrorMessageData::TErrorMessageData); + message = ErrorMessageData(); + } + + Unused << cp->SendWindowPostMessage(context, message, aData); + return IPC_OK(); +} + +void ContentParent::AddBrowsingContextGroup(BrowsingContextGroup* aGroup) { + MOZ_DIAGNOSTIC_ASSERT(aGroup); + // Ensure that the group has been inserted, and if we're not launching + // anymore, also begin subscribing. Launching processes will be subscribed if + // they finish launching in `LaunchSubprocessResolve`. + if (mGroups.EnsureInserted(aGroup) && !IsLaunching()) { + aGroup->Subscribe(this); + } +} + +void ContentParent::RemoveBrowsingContextGroup(BrowsingContextGroup* aGroup) { + MOZ_DIAGNOSTIC_ASSERT(aGroup); + // Remove the group from our list. This is called from the + // BrowsingContextGroup when unsubscribing, so we don't need to do it here. + if (mGroups.EnsureRemoved(aGroup) && CanSend()) { + // If we're removing the entry for the first time, tell the content process + // to clean up the group. + Unused << SendDestroyBrowsingContextGroup(aGroup->Id()); + } +} + +mozilla::ipc::IPCResult ContentParent::RecvCommitBrowsingContextTransaction( + const MaybeDiscarded<BrowsingContext>& aContext, + BrowsingContext::BaseTransaction&& aTransaction, uint64_t aEpoch) { + // Record the new BrowsingContextFieldEpoch associated with this transaction. + // This should be done unconditionally, so that we're always in-sync. + // + // The order the parent process receives transactions is considered the + // "canonical" ordering, so we don't need to worry about doing any + // epoch-related validation. + MOZ_ASSERT(aEpoch == mBrowsingContextFieldEpoch + 1, + "Child process skipped an epoch?"); + mBrowsingContextFieldEpoch = aEpoch; + + return aTransaction.CommitFromIPC(aContext, this); +} + +mozilla::ipc::IPCResult ContentParent::RecvBlobURLDataRequest( + const nsACString& aBlobURL, nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aLoadingPrincipal, const OriginAttributes& aOriginAttributes, + uint64_t aInnerWindowId, const nsCString& aPartitionKey, + BlobURLDataRequestResolver&& aResolver) { + RefPtr<BlobImpl> blobImpl; + + // Since revoked blobs are also retrieved, it is possible that the blob no + // longer exists (due to the 5 second timeout) when execution reaches here + if (!BlobURLProtocolHandler::GetDataEntry( + aBlobURL, getter_AddRefs(blobImpl), aLoadingPrincipal, + aTriggeringPrincipal, aOriginAttributes, aInnerWindowId, + aPartitionKey, true /* AlsoIfRevoked */)) { + aResolver(NS_ERROR_DOM_BAD_URI); + return IPC_OK(); + } + + IPCBlob ipcBlob; + nsresult rv = IPCBlobUtils::Serialize(blobImpl, ipcBlob); + + if (NS_WARN_IF(NS_FAILED(rv))) { + aResolver(rv); + return IPC_OK(); + } + + aResolver(ipcBlob); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvReportServiceWorkerShutdownProgress( + uint32_t aShutdownStateId, ServiceWorkerShutdownState::Progress aProgress) { + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + MOZ_RELEASE_ASSERT(swm, "ServiceWorkers should shutdown before SWM."); + + swm->ReportServiceWorkerShutdownProgress(aShutdownStateId, aProgress); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvNotifyOnHistoryReload( + const MaybeDiscarded<BrowsingContext>& aContext, const bool& aForceReload, + NotifyOnHistoryReloadResolver&& aResolver) { + bool canReload = false; + Maybe<NotNull<RefPtr<nsDocShellLoadState>>> loadState; + Maybe<bool> reloadActiveEntry; + if (!aContext.IsNullOrDiscarded()) { + aContext.get_canonical()->NotifyOnHistoryReload( + aForceReload, canReload, loadState, reloadActiveEntry); + } + aResolver( + std::tuple<const bool&, + const Maybe<NotNull<RefPtr<nsDocShellLoadState>>>&, + const Maybe<bool>&>(canReload, loadState, reloadActiveEntry)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvHistoryCommit( + const MaybeDiscarded<BrowsingContext>& aContext, const uint64_t& aLoadID, + const nsID& aChangeID, const uint32_t& aLoadType, const bool& aPersist, + const bool& aCloneEntryChildren, const bool& aChannelExpired, + const uint32_t& aCacheKey) { + if (!aContext.IsDiscarded()) { + CanonicalBrowsingContext* canonical = aContext.get_canonical(); + if (!canonical) { + return IPC_FAIL( + this, "Could not get canonical. aContext.get_canonical() fails."); + } + canonical->SessionHistoryCommit(aLoadID, aChangeID, aLoadType, aPersist, + aCloneEntryChildren, aChannelExpired, + aCacheKey); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvHistoryGo( + const MaybeDiscarded<BrowsingContext>& aContext, int32_t aOffset, + uint64_t aHistoryEpoch, bool aRequireUserInteraction, bool aUserActivation, + HistoryGoResolver&& aResolveRequestedIndex) { + if (!aContext.IsNullOrDiscarded()) { + RefPtr<CanonicalBrowsingContext> canonical = aContext.get_canonical(); + aResolveRequestedIndex( + canonical->HistoryGo(aOffset, aHistoryEpoch, aRequireUserInteraction, + aUserActivation, Some(ChildID()))); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvSynchronizeLayoutHistoryState( + const MaybeDiscarded<BrowsingContext>& aContext, + nsILayoutHistoryState* aState) { + if (aContext.IsNull()) { + return IPC_OK(); + } + + BrowsingContext* bc = aContext.GetMaybeDiscarded(); + if (!bc) { + return IPC_OK(); + } + SessionHistoryEntry* entry = bc->Canonical()->GetActiveSessionHistoryEntry(); + if (entry) { + entry->SetLayoutHistoryState(aState); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvSessionHistoryEntryTitle( + const MaybeDiscarded<BrowsingContext>& aContext, const nsAString& aTitle) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + + SessionHistoryEntry* entry = + aContext.get_canonical()->GetActiveSessionHistoryEntry(); + if (entry) { + entry->SetTitle(aTitle); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult +ContentParent::RecvSessionHistoryEntryScrollRestorationIsManual( + const MaybeDiscarded<BrowsingContext>& aContext, const bool& aIsManual) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + + SessionHistoryEntry* entry = + aContext.get_canonical()->GetActiveSessionHistoryEntry(); + if (entry) { + entry->SetScrollRestorationIsManual(aIsManual); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvSessionHistoryEntryScrollPosition( + const MaybeDiscarded<BrowsingContext>& aContext, const int32_t& aX, + const int32_t& aY) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + + SessionHistoryEntry* entry = + aContext.get_canonical()->GetActiveSessionHistoryEntry(); + if (entry) { + entry->SetScrollPosition(aX, aY); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult +ContentParent::RecvSessionHistoryEntryStoreWindowNameInContiguousEntries( + const MaybeDiscarded<BrowsingContext>& aContext, const nsAString& aName) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + + // Per https://html.spec.whatwg.org/#history-traversal 4.2.1, we need to set + // the name to all contiguous entries. This has to be called before + // CanonicalBrowsingContext::SessionHistoryCommit(), so the active entry is + // still the old entry that we want to set. + + SessionHistoryEntry* entry = + aContext.get_canonical()->GetActiveSessionHistoryEntry(); + + if (entry) { + nsSHistory::WalkContiguousEntries( + entry, [&](nsISHEntry* aEntry) { aEntry->SetName(aName); }); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvSessionHistoryEntryCacheKey( + const MaybeDiscarded<BrowsingContext>& aContext, + const uint32_t& aCacheKey) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + + SessionHistoryEntry* entry = + aContext.get_canonical()->GetActiveSessionHistoryEntry(); + if (entry) { + entry->SetCacheKey(aCacheKey); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvSessionHistoryEntryWireframe( + const MaybeDiscarded<BrowsingContext>& aContext, + const Wireframe& aWireframe) { + if (aContext.IsNull()) { + return IPC_OK(); + } + + BrowsingContext* bc = aContext.GetMaybeDiscarded(); + if (!bc) { + return IPC_OK(); + } + + SessionHistoryEntry* entry = bc->Canonical()->GetActiveSessionHistoryEntry(); + if (entry) { + entry->SetWireframe(Some(aWireframe)); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult +ContentParent::RecvGetLoadingSessionHistoryInfoFromParent( + const MaybeDiscarded<BrowsingContext>& aContext, + GetLoadingSessionHistoryInfoFromParentResolver&& aResolver) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + + Maybe<LoadingSessionHistoryInfo> info; + aContext.get_canonical()->GetLoadingSessionHistoryInfoFromParent(info); + aResolver(info); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvRemoveFromBFCache( + const MaybeDiscarded<BrowsingContext>& aContext) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + + nsCOMPtr<nsFrameLoaderOwner> owner = + do_QueryInterface(aContext.get_canonical()->GetEmbedderElement()); + if (!owner) { + return IPC_OK(); + } + + RefPtr<nsFrameLoader> frameLoader = owner->GetFrameLoader(); + if (!frameLoader || !frameLoader->GetMaybePendingBrowsingContext()) { + return IPC_OK(); + } + + nsCOMPtr<nsISHistory> shistory = frameLoader->GetMaybePendingBrowsingContext() + ->Canonical() + ->GetSessionHistory(); + if (!shistory) { + return IPC_OK(); + } + + uint32_t count = shistory->GetCount(); + for (uint32_t i = 0; i < count; ++i) { + nsCOMPtr<nsISHEntry> entry; + shistory->GetEntryAtIndex(i, getter_AddRefs(entry)); + nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(entry); + if (she) { + if (RefPtr<nsFrameLoader> frameLoader = she->GetFrameLoader()) { + if (frameLoader->GetMaybePendingBrowsingContext() == aContext.get()) { + she->SetFrameLoader(nullptr); + frameLoader->Destroy(); + break; + } + } + } + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvSetActiveSessionHistoryEntry( + const MaybeDiscarded<BrowsingContext>& aContext, + const Maybe<nsPoint>& aPreviousScrollPos, SessionHistoryInfo&& aInfo, + uint32_t aLoadType, uint32_t aUpdatedCacheKey, const nsID& aChangeID) { + if (!aContext.IsNullOrDiscarded()) { + aContext.get_canonical()->SetActiveSessionHistoryEntry( + aPreviousScrollPos, &aInfo, aLoadType, aUpdatedCacheKey, aChangeID); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvReplaceActiveSessionHistoryEntry( + const MaybeDiscarded<BrowsingContext>& aContext, + SessionHistoryInfo&& aInfo) { + if (!aContext.IsNullOrDiscarded()) { + aContext.get_canonical()->ReplaceActiveSessionHistoryEntry(&aInfo); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult +ContentParent::RecvRemoveDynEntriesFromActiveSessionHistoryEntry( + const MaybeDiscarded<BrowsingContext>& aContext) { + if (!aContext.IsNullOrDiscarded()) { + aContext.get_canonical()->RemoveDynEntriesFromActiveSessionHistoryEntry(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvRemoveFromSessionHistory( + const MaybeDiscarded<BrowsingContext>& aContext, const nsID& aChangeID) { + if (!aContext.IsNullOrDiscarded()) { + aContext.get_canonical()->RemoveFromSessionHistory(aChangeID); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvHistoryReload( + const MaybeDiscarded<BrowsingContext>& aContext, + const uint32_t aReloadFlags) { + if (!aContext.IsNullOrDiscarded()) { + nsCOMPtr<nsISHistory> shistory = + aContext.get_canonical()->GetSessionHistory(); + if (shistory) { + shistory->Reload(aReloadFlags); + } + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvCommitWindowContextTransaction( + const MaybeDiscarded<WindowContext>& aContext, + WindowContext::BaseTransaction&& aTransaction, uint64_t aEpoch) { + // Record the new BrowsingContextFieldEpoch associated with this transaction. + // This should be done unconditionally, so that we're always in-sync. + // + // The order the parent process receives transactions is considered the + // "canonical" ordering, so we don't need to worry about doing any + // epoch-related validation. + MOZ_ASSERT(aEpoch == mBrowsingContextFieldEpoch + 1, + "Child process skipped an epoch?"); + mBrowsingContextFieldEpoch = aEpoch; + + return aTransaction.CommitFromIPC(aContext, this); +} + +NS_IMETHODIMP ContentParent::GetChildID(uint64_t* aOut) { + *aOut = this->ChildID(); + return NS_OK; +} + +NS_IMETHODIMP ContentParent::GetOsPid(int32_t* aOut) { + *aOut = Pid(); + return NS_OK; +} + +NS_IMETHODIMP ContentParent::GetRemoteType(nsACString& aRemoteType) { + aRemoteType = GetRemoteType(); + return NS_OK; +} + +IPCResult ContentParent::RecvRawMessage( + const JSActorMessageMeta& aMeta, const Maybe<ClonedMessageData>& aData, + const Maybe<ClonedMessageData>& aStack) { + Maybe<StructuredCloneData> data; + if (aData) { + data.emplace(); + data->BorrowFromClonedMessageData(*aData); + } + Maybe<StructuredCloneData> stack; + if (aStack) { + stack.emplace(); + stack->BorrowFromClonedMessageData(*aStack); + } + ReceiveRawMessage(aMeta, std::move(data), std::move(stack)); + return IPC_OK(); +} + +NS_IMETHODIMP ContentParent::GetActor(const nsACString& aName, JSContext* aCx, + JSProcessActorParent** retval) { + ErrorResult error; + RefPtr<JSProcessActorParent> actor = + JSActorManager::GetActor(aCx, aName, error) + .downcast<JSProcessActorParent>(); + if (error.MaybeSetPendingException(aCx)) { + return NS_ERROR_FAILURE; + } + actor.forget(retval); + return NS_OK; +} + +NS_IMETHODIMP ContentParent::GetExistingActor(const nsACString& aName, + JSProcessActorParent** retval) { + RefPtr<JSProcessActorParent> actor = + JSActorManager::GetExistingActor(aName).downcast<JSProcessActorParent>(); + actor.forget(retval); + return NS_OK; +} + +already_AddRefed<JSActor> ContentParent::InitJSActor( + JS::Handle<JSObject*> aMaybeActor, const nsACString& aName, + ErrorResult& aRv) { + RefPtr<JSProcessActorParent> actor; + if (aMaybeActor.get()) { + aRv = UNWRAP_OBJECT(JSProcessActorParent, aMaybeActor.get(), actor); + if (aRv.Failed()) { + return nullptr; + } + } else { + actor = new JSProcessActorParent(); + } + + MOZ_RELEASE_ASSERT(!actor->Manager(), + "mManager was already initialized once!"); + actor->Init(aName, this); + return actor.forget(); +} + +IPCResult ContentParent::RecvFOGData(ByteBuf&& buf) { + glean::FOGData(std::move(buf)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvSetContainerFeaturePolicy( + const MaybeDiscardedBrowsingContext& aContainerContext, + FeaturePolicy* aContainerFeaturePolicy) { + if (aContainerContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + + auto* context = aContainerContext.get_canonical(); + context->SetContainerFeaturePolicy(aContainerFeaturePolicy); + + return IPC_OK(); +} + +NS_IMETHODIMP ContentParent::GetCanSend(bool* aCanSend) { + *aCanSend = CanSend(); + return NS_OK; +} + +ContentParent* ContentParent::AsContentParent() { return this; } + +JSActorManager* ContentParent::AsJSActorManager() { return this; } + +IPCResult ContentParent::RecvGetSystemIcon(nsIURI* aURI, + GetSystemIconResolver&& aResolver) { + using ResolverArgs = std::tuple<const nsresult&, mozilla::Maybe<ByteBuf>&&>; + + if (!aURI) { + Maybe<ByteBuf> bytebuf = Nothing(); + aResolver(ResolverArgs(NS_ERROR_NULL_POINTER, std::move(bytebuf))); + return IPC_OK(); + } + +#if defined(MOZ_WIDGET_GTK) + Maybe<ByteBuf> bytebuf = Some(ByteBuf{}); + nsresult rv = nsIconChannel::GetIcon(aURI, bytebuf.ptr()); + if (NS_WARN_IF(NS_FAILED(rv))) { + bytebuf = Nothing(); + } + aResolver(ResolverArgs(rv, std::move(bytebuf))); + return IPC_OK(); +#elif defined(XP_WIN) + nsIconChannel::GetIconAsync(aURI)->Then( + GetCurrentSerialEventTarget(), __func__, + [aResolver](ByteBuf&& aByteBuf) { + Maybe<ByteBuf> bytebuf = Some(std::move(aByteBuf)); + aResolver(ResolverArgs(NS_OK, std::move(bytebuf))); + }, + [aResolver](nsresult aErr) { + Maybe<ByteBuf> bytebuf = Nothing(); + aResolver(ResolverArgs(aErr, std::move(bytebuf))); + }); + return IPC_OK(); +#else + MOZ_CRASH( + "This message is currently implemented only on GTK and Windows " + "platforms"); +#endif +} + +#ifdef FUZZING_SNAPSHOT +IPCResult ContentParent::RecvSignalFuzzingReady() { + // No action needed here, we already observe this message directly + // on the channel and act accordingly. + return IPC_OK(); +} +#endif + +nsCString ThreadsafeContentParentHandle::GetRemoteType() { + RecursiveMutexAutoLock lock(mMutex); + return mRemoteType; +} + +bool ThreadsafeContentParentHandle::MaybeRegisterRemoteWorkerActor( + MoveOnlyFunction<bool(uint32_t, bool)> aCallback) { + RecursiveMutexAutoLock lock(mMutex); + if (aCallback(mRemoteWorkerActorCount, mShutdownStarted)) { + // TODO: I'd wish we could assert here that our ContentParent is alive. + ++mRemoteWorkerActorCount; + return true; + } + return false; +} + +} // namespace dom +} // namespace mozilla + +NS_IMPL_ISUPPORTS(ParentIdleListener, nsIObserver) + +NS_IMETHODIMP +ParentIdleListener::Observe(nsISupports*, const char* aTopic, + const char16_t* aData) { + mozilla::Unused << mParent->SendNotifyIdleObserver( + mObserver, nsDependentCString(aTopic), nsDependentString(aData)); + return NS_OK; +} + +#undef LOGPDM diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h new file mode 100644 index 0000000000..53994e45e7 --- /dev/null +++ b/dom/ipc/ContentParent.h @@ -0,0 +1,1699 @@ +/* -*- 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_dom_ContentParent_h +#define mozilla_dom_ContentParent_h + +#include "mozilla/dom/PContentParent.h" +#include "mozilla/dom/ipc/IdType.h" +#include "mozilla/dom/MessageManagerCallback.h" +#include "mozilla/dom/MediaSessionBinding.h" +#include "mozilla/dom/RemoteBrowser.h" +#include "mozilla/dom/RemoteType.h" +#include "mozilla/dom/JSProcessActorParent.h" +#include "mozilla/dom/ProcessActor.h" +#include "mozilla/dom/UserActivation.h" +#include "mozilla/gfx/gfxVarReceiver.h" +#include "mozilla/gfx/GPUProcessListener.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/ipc/GeckoChildProcessHost.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/DataMutex.h" +#include "mozilla/HalTypes.h" +#include "mozilla/LinkedList.h" +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReportingProcess.h" +#include "mozilla/MozPromise.h" +#include "mozilla/RecursiveMutex.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" + +#include "MainThreadUtils.h" +#include "nsClassHashtable.h" +#include "nsTHashMap.h" +#include "nsTHashSet.h" +#include "nsHashKeys.h" +#include "nsIAsyncShutdown.h" +#include "nsIDOMProcessParent.h" +#include "nsIInterfaceRequestor.h" +#include "nsIObserver.h" +#include "nsIRemoteTab.h" +#include "nsIDOMGeoPositionCallback.h" +#include "nsIDOMGeoPositionErrorCallback.h" +#include "nsRefPtrHashtable.h" +#include "PermissionMessageUtils.h" +#include "DriverCrashGuard.h" +#include "nsIReferrerInfo.h" + +#define CHILD_PROCESS_SHUTDOWN_MESSAGE u"child-process-shutdown"_ns + +class nsConsoleService; +class nsIContentProcessInfo; +class nsICycleCollectorLogSink; +class nsIDumpGCAndCCLogsCallback; +class nsIRemoteTab; +class nsITimer; +class ParentIdleListener; +class nsIWidget; +class nsIX509Cert; + +namespace mozilla { +class PClipboardWriteRequestParent; +class PRemoteSpellcheckEngineParent; + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) +class SandboxBroker; +class SandboxBrokerPolicyFactory; +#endif + +class PreallocatedProcessManagerImpl; +class BenchmarkStorageParent; + +using mozilla::loader::PScriptCacheParent; + +namespace ipc { +class CrashReporterHost; +class TestShellParent; +class SharedPreferenceSerializer; +} // namespace ipc + +namespace layers { +struct TextureFactoryIdentifier; +} // namespace layers + +namespace dom { + +class BrowsingContextGroup; +class Element; +class BrowserParent; +class ClonedMessageData; +class MemoryReport; +class TabContext; +class GetFilesHelper; +class MemoryReportRequestHost; +class RemoteWorkerManager; +class ThreadsafeContentParentHandle; +struct CancelContentJSOptions; + +#define NS_CONTENTPARENT_IID \ + { \ + 0xeeec9ebf, 0x8ecf, 0x4e38, { \ + 0x81, 0xda, 0xb7, 0x34, 0x13, 0x7e, 0xac, 0xf3 \ + } \ + } + +class ContentParent final : public PContentParent, + public nsIDOMProcessParent, + public nsIObserver, + public nsIDOMGeoPositionCallback, + public nsIDOMGeoPositionErrorCallback, + public nsIAsyncShutdownBlocker, + public nsIInterfaceRequestor, + public gfx::gfxVarReceiver, + public mozilla::LinkedListElement<ContentParent>, + public gfx::GPUProcessListener, + public mozilla::MemoryReportingProcess, + public mozilla::dom::ipc::MessageManagerCallback, + public mozilla::ipc::IShmemAllocator, + public ProcessActor { + typedef mozilla::ipc::GeckoChildProcessHost GeckoChildProcessHost; + typedef mozilla::ipc::TestShellParent TestShellParent; + typedef mozilla::ipc::PrincipalInfo PrincipalInfo; + typedef mozilla::dom::ClonedMessageData ClonedMessageData; + typedef mozilla::dom::BrowsingContextGroup BrowsingContextGroup; + + friend class mozilla::PreallocatedProcessManagerImpl; + friend class PContentParent; + friend class mozilla::dom::RemoteWorkerManager; + + public: + using LaunchPromise = + mozilla::MozPromise<RefPtr<ContentParent>, nsresult, false>; + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_CONTENTPARENT_IID) + + static LogModule* GetLog(); + + static ContentParent* Cast(PContentParent* aActor) { + return static_cast<ContentParent*>(aActor); + } + + /** + * Create a ContentParent suitable for use later as a content process. + */ + static already_AddRefed<ContentParent> MakePreallocProcess(); + + /** + * Start up the content-process machinery. This might include + * scheduling pre-launch tasks. + */ + static void StartUp(); + + /** Shut down the content-process machinery. */ + static void ShutDown(); + + static uint32_t GetPoolSize(const nsACString& aContentProcessType); + + static uint32_t GetMaxProcessCount(const nsACString& aContentProcessType); + + static bool IsMaxProcessCountReached(const nsACString& aContentProcessType); + + static void ReleaseCachedProcesses(); + + static void LogAndAssertFailedPrincipalValidationInfo( + nsIPrincipal* aPrincipal, const char* aMethod); + + /** + * Picks a random content parent from |aContentParents| respecting the index + * limit set by |aMaxContentParents|. + * Returns null if non available. + */ + static already_AddRefed<ContentParent> MinTabSelect( + const nsTArray<ContentParent*>& aContentParents, + int32_t maxContentParents); + + /** + * Get or create a content process for: + * 1. browser iframe + * 2. remote xul <browser> + * 3. normal iframe + */ + static RefPtr<ContentParent::LaunchPromise> GetNewOrUsedBrowserProcessAsync( + const nsACString& aRemoteType, BrowsingContextGroup* aGroup = nullptr, + hal::ProcessPriority aPriority = + hal::ProcessPriority::PROCESS_PRIORITY_FOREGROUND, + bool aPreferUsed = false); + static already_AddRefed<ContentParent> GetNewOrUsedBrowserProcess( + const nsACString& aRemoteType, BrowsingContextGroup* aGroup = nullptr, + hal::ProcessPriority aPriority = + hal::ProcessPriority::PROCESS_PRIORITY_FOREGROUND, + bool aPreferUsed = false); + + /** + * Get or create a content process, but without waiting for the process + * launch to have completed. The returned `ContentParent` may still be in the + * "Launching" state. + * + * Can return `nullptr` in the case of an error. + * + * Use the `WaitForLaunchAsync` or `WaitForLaunchSync` methods to wait for + * the process to be fully launched. + */ + static already_AddRefed<ContentParent> GetNewOrUsedLaunchingBrowserProcess( + const nsACString& aRemoteType, BrowsingContextGroup* aGroup = nullptr, + hal::ProcessPriority aPriority = + hal::ProcessPriority::PROCESS_PRIORITY_FOREGROUND, + bool aPreferUsed = false); + + RefPtr<ContentParent::LaunchPromise> WaitForLaunchAsync( + hal::ProcessPriority aPriority = + hal::ProcessPriority::PROCESS_PRIORITY_FOREGROUND); + bool WaitForLaunchSync(hal::ProcessPriority aPriority = + hal::ProcessPriority::PROCESS_PRIORITY_FOREGROUND); + + /** + * Get or create a content process for the given TabContext. aFrameElement + * should be the frame/iframe element with which this process will + * associated. + */ + static already_AddRefed<RemoteBrowser> CreateBrowser( + const TabContext& aContext, Element* aFrameElement, + const nsACString& aRemoteType, BrowsingContext* aBrowsingContext, + ContentParent* aOpenerContentParent); + + /** + * Get all content parents. + * + * # Lifetime + * + * These pointers are ONLY valid for synchronous use from the main thread. + * + * Do NOT attempt to use them after the main thread has had a chance to handle + * messages or you could end up with dangling pointers. + */ + static void GetAll(nsTArray<ContentParent*>& aArray); + + static void GetAllEvenIfDead(nsTArray<ContentParent*>& aArray); + + static void BroadcastStringBundle(const StringBundleDescriptor&); + + static void BroadcastFontListChanged(); + static void BroadcastShmBlockAdded(uint32_t aGeneration, uint32_t aIndex); + + static void BroadcastThemeUpdate(widget::ThemeChangeKind); + + static void BroadcastMediaCodecsSupportedUpdate( + RemoteDecodeIn aLocation, const media::MediaCodecsSupported& aSupported); + + const nsACString& GetRemoteType() const override; + + virtual void DoGetRemoteType(nsACString& aRemoteType, + ErrorResult& aError) const override { + aRemoteType = GetRemoteType(); + } + + enum CPIteratorPolicy { eLive, eAll }; + + class ContentParentIterator { + private: + ContentParent* mCurrent; + CPIteratorPolicy mPolicy; + + public: + ContentParentIterator(CPIteratorPolicy aPolicy, ContentParent* aCurrent) + : mCurrent(aCurrent), mPolicy(aPolicy) {} + + ContentParentIterator begin() { + // Move the cursor to the first element that matches the policy. + while (mPolicy != eAll && mCurrent && !mCurrent->IsAlive()) { + mCurrent = mCurrent->LinkedListElement<ContentParent>::getNext(); + } + + return *this; + } + ContentParentIterator end() { + return ContentParentIterator(mPolicy, nullptr); + } + + const ContentParentIterator& operator++() { + MOZ_ASSERT(mCurrent); + do { + mCurrent = mCurrent->LinkedListElement<ContentParent>::getNext(); + } while (mPolicy != eAll && mCurrent && !mCurrent->IsAlive()); + + return *this; + } + + bool operator!=(const ContentParentIterator& aOther) const { + MOZ_ASSERT(mPolicy == aOther.mPolicy); + return mCurrent != aOther.mCurrent; + } + + ContentParent* operator*() { return mCurrent; } + }; + + static ContentParentIterator AllProcesses(CPIteratorPolicy aPolicy) { + ContentParent* first = + sContentParents ? sContentParents->getFirst() : nullptr; + return ContentParentIterator(aPolicy, first); + } + + static void NotifyUpdatedDictionaries(); + + // Tell content processes the font list has changed. If aFullRebuild is true, + // the shared list has been rebuilt and must be freshly mapped by child + // processes; if false, existing mappings are still valid but the data has + // been updated and so full reflows are in order. + static void NotifyUpdatedFonts(bool aFullRebuild); + + mozilla::ipc::IPCResult RecvCreateGMPService(); + + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(ContentParent, nsIObserver) + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_NSIDOMPROCESSPARENT + NS_DECL_NSIOBSERVER + NS_DECL_NSIDOMGEOPOSITIONCALLBACK + NS_DECL_NSIDOMGEOPOSITIONERRORCALLBACK + NS_DECL_NSIASYNCSHUTDOWNBLOCKER + NS_DECL_NSIINTERFACEREQUESTOR + + /** + * MessageManagerCallback methods that we override. + */ + virtual bool DoLoadMessageManagerScript(const nsAString& aURL, + bool aRunInGlobalScope) override; + + virtual nsresult DoSendAsyncMessage(const nsAString& aMessage, + StructuredCloneData& aData) override; + + RecursiveMutex& ThreadsafeHandleMutex(); + + /** Notify that a tab is about to send Destroy to its child. */ + void NotifyTabWillDestroy(); + + /** Notify that a tab is beginning its destruction sequence. */ + void NotifyTabDestroying(); + + /** Notify that a tab was destroyed during normal operation. */ + void NotifyTabDestroyed(const TabId& aTabId, bool aNotifiedDestroying); + + // Manage the set of `KeepAlive`s on this ContentParent which are preventing + // it from being destroyed. + void AddKeepAlive(); + void RemoveKeepAlive(); + + TestShellParent* CreateTestShell(); + + bool DestroyTestShell(TestShellParent* aTestShell); + + TestShellParent* GetTestShellSingleton(); + + // This method can be called on any thread. + void RegisterRemoteWorkerActor(); + + // This method _must_ be called on main-thread because it can start the + // shutting down of the content process. + void UnregisterRemoveWorkerActor(); + + void ReportChildAlreadyBlocked(); + + bool RequestRunToCompletion(); + + void UpdateCookieStatus(nsIChannel* aChannel); + + bool IsLaunching() const { + return mLifecycleState == LifecycleState::LAUNCHING; + } + bool IsAlive() const override; + bool IsInitialized() const; + bool IsSignaledImpendingShutdown() const { + return mIsSignaledImpendingShutdown; + } + bool IsShuttingDown() const { + return IsDead() || IsSignaledImpendingShutdown(); + } + bool IsDead() const { return mLifecycleState == LifecycleState::DEAD; } + + bool IsForBrowser() const { return mIsForBrowser; } + + GeckoChildProcessHost* Process() const { return mSubprocess; } + + nsIContentProcessInfo* ScriptableHelper() const { return mScriptableHelper; } + + mozilla::dom::ProcessMessageManager* GetMessageManager() const { + return mMessageManager; + } + + bool NeedsPermissionsUpdate(const nsACString& aPermissionKey) const; + + // Getter for which permission keys should signal that a content + // process needs to know about the change of a permission with this as the + // secondary key, like for 3rdPartyFrameStorage^https://secondary.com + bool NeedsSecondaryKeyPermissionsUpdate( + const nsACString& aPermissionKey) const; + + // Manage pending load states which have been sent to this process, and are + // expected to be used to start a load imminently. + already_AddRefed<nsDocShellLoadState> TakePendingLoadStateForId( + uint64_t aLoadIdentifier); + void StorePendingLoadState(nsDocShellLoadState* aLoadState); + + /** + * Kill our subprocess and make sure it dies. Should only be used + * in emergency situations since it bypasses the normal shutdown + * process. + * + * WARNING: aReason appears in telemetry, so any new value passed in requires + * data review. + */ + void KillHard(const char* aWhy); + + ContentParentId ChildID() const { return mChildID; } + + /** + * Get a user-friendly name for this ContentParent. We make no guarantees + * about this name: It might not be unique, apps can spoof special names, + * etc. So please don't use this name to make any decisions about the + * ContentParent based on the value returned here. + */ + void FriendlyName(nsAString& aName, bool aAnonymize = false); + + virtual void OnChannelError() override; + + mozilla::ipc::IPCResult RecvInitCrashReporter( + const NativeThreadId& aThreadId); + + already_AddRefed<PNeckoParent> AllocPNeckoParent(); + + virtual mozilla::ipc::IPCResult RecvPNeckoConstructor( + PNeckoParent* aActor) override { + return PContentParent::RecvPNeckoConstructor(aActor); + } + + mozilla::ipc::IPCResult RecvInitStreamFilter( + const uint64_t& aChannelId, const nsAString& aAddonId, + InitStreamFilterResolver&& aResolver); + + PHalParent* AllocPHalParent(); + + virtual mozilla::ipc::IPCResult RecvPHalConstructor( + PHalParent* aActor) override { + return PContentParent::RecvPHalConstructor(aActor); + } + + PHeapSnapshotTempFileHelperParent* AllocPHeapSnapshotTempFileHelperParent(); + + PRemoteSpellcheckEngineParent* AllocPRemoteSpellcheckEngineParent(); + + bool CycleCollectWithLogs(bool aDumpAllTraces, + nsICycleCollectorLogSink* aSink, + nsIDumpGCAndCCLogsCallback* aCallback); + + mozilla::ipc::IPCResult RecvNotifyTabDestroying(const TabId& aTabId, + const ContentParentId& aCpId); + + mozilla::ipc::IPCResult RecvFinishShutdown(); + + mozilla::ipc::IPCResult RecvNotifyShutdownSuccess(); + + void MaybeInvokeDragSession(BrowserParent* aParent, EventMessage aMessage); + + PContentPermissionRequestParent* AllocPContentPermissionRequestParent( + const nsTArray<PermissionRequest>& aRequests, nsIPrincipal* aPrincipal, + nsIPrincipal* aTopLevelPrincipal, const bool& aIsHandlingUserInput, + const bool& aMaybeUnsafePermissionDelegate, const TabId& aTabId); + + bool DeallocPContentPermissionRequestParent( + PContentPermissionRequestParent* actor); + + void ForkNewProcess(bool aBlocking); + + mozilla::ipc::IPCResult RecvCreateWindow( + PBrowserParent* aThisBrowserParent, + const MaybeDiscarded<BrowsingContext>& aParent, PBrowserParent* aNewTab, + const uint32_t& aChromeFlags, const bool& aCalledFromJS, + const bool& aForPrinting, const bool& aForWindowDotPrint, + nsIURI* aURIToLoad, const nsACString& aFeatures, + const UserActivation::Modifiers& aModifiers, + nsIPrincipal* aTriggeringPrincipal, nsIContentSecurityPolicy* aCsp, + nsIReferrerInfo* aReferrerInfo, const OriginAttributes& aOriginAttributes, + CreateWindowResolver&& aResolve); + + mozilla::ipc::IPCResult RecvCreateWindowInDifferentProcess( + PBrowserParent* aThisTab, const MaybeDiscarded<BrowsingContext>& aParent, + const uint32_t& aChromeFlags, const bool& aCalledFromJS, + nsIURI* aURIToLoad, const nsACString& aFeatures, + const UserActivation::Modifiers& aModifiers, const nsAString& aName, + nsIPrincipal* aTriggeringPrincipal, nsIContentSecurityPolicy* aCsp, + nsIReferrerInfo* aReferrerInfo, + const OriginAttributes& aOriginAttributes); + + static void BroadcastBlobURLRegistration( + const nsACString& aURI, BlobImpl* aBlobImpl, nsIPrincipal* aPrincipal, + const nsCString& aPartitionKey, ContentParent* aIgnoreThisCP = nullptr); + + static void BroadcastBlobURLUnregistration( + const nsACString& aURI, nsIPrincipal* aPrincipal, + ContentParent* aIgnoreThisCP = nullptr); + + mozilla::ipc::IPCResult RecvStoreAndBroadcastBlobURLRegistration( + const nsACString& aURI, const IPCBlob& aBlob, nsIPrincipal* aPrincipal, + const nsCString& aPartitionKey); + + mozilla::ipc::IPCResult RecvUnstoreAndBroadcastBlobURLUnregistration( + const nsACString& aURI, nsIPrincipal* aPrincipal); + + virtual int32_t Pid() const override; + + // PURLClassifierParent. + PURLClassifierParent* AllocPURLClassifierParent(nsIPrincipal* aPrincipal, + bool* aSuccess); + virtual mozilla::ipc::IPCResult RecvPURLClassifierConstructor( + PURLClassifierParent* aActor, nsIPrincipal* aPrincipal, + bool* aSuccess) override; + + // PURLClassifierLocalParent. + PURLClassifierLocalParent* AllocPURLClassifierLocalParent( + nsIURI* aURI, const nsTArray<IPCURLClassifierFeature>& aFeatures); + + virtual mozilla::ipc::IPCResult RecvPURLClassifierLocalConstructor( + PURLClassifierLocalParent* aActor, nsIURI* aURI, + nsTArray<IPCURLClassifierFeature>&& aFeatures) override; + + PSessionStorageObserverParent* AllocPSessionStorageObserverParent(); + + virtual mozilla::ipc::IPCResult RecvPSessionStorageObserverConstructor( + PSessionStorageObserverParent* aActor) override; + + bool DeallocPSessionStorageObserverParent( + PSessionStorageObserverParent* aActor); + + bool DeallocPURLClassifierLocalParent(PURLClassifierLocalParent* aActor); + + bool DeallocPURLClassifierParent(PURLClassifierParent* aActor); + + // Use the PHangMonitor channel to ask the child to repaint a tab. + void PaintTabWhileInterruptingJS(BrowserParent*); + + void UnloadLayersWhileInterruptingJS(BrowserParent*); + + void CancelContentJSExecutionIfRunning( + BrowserParent* aBrowserParent, + nsIRemoteTab::NavigationType aNavigationType, + const CancelContentJSOptions& aCancelContentJSOptions); + + void SetMainThreadQoSPriority(nsIThread::QoSPriority aQoSPriority); + + // This function is called when we are about to load a document from an + // HTTP(S) or FTP channel for a content process. It is a useful place + // to start to kick off work as early as possible in response to such + // document loads. + // aShouldWaitForPermissionCookieUpdate is set to true if main thread IPCs for + // updating permissions/cookies are sent. + nsresult AboutToLoadHttpFtpDocumentForChild( + nsIChannel* aChannel, + bool* aShouldWaitForPermissionCookieUpdate = nullptr); + + // Send Blob URLs for this aPrincipal if they are not already known to this + // content process and mark the process to receive any new/revoked Blob URLs + // to this content process forever. + void TransmitBlobURLsForPrincipal(nsIPrincipal* aPrincipal); + + nsresult TransmitPermissionsForPrincipal(nsIPrincipal* aPrincipal); + + // Whenever receiving a Principal we need to validate that Principal case + // by case, where we grant individual callsites to customize the checks! + enum class ValidatePrincipalOptions { + AllowNullPtr, // Not a NullPrincipal but a nullptr as Principal. + AllowSystem, + AllowExpanded, + }; + bool ValidatePrincipal( + nsIPrincipal* aPrincipal, + const EnumSet<ValidatePrincipalOptions>& aOptions = {}); + + // This function is called in BrowsingContext immediately before IPC call to + // load a URI. If aURI is a BlobURL, this method transmits all BlobURLs for + // aURI's principal that were previously not transmitted. This allows for + // opening a locally created BlobURL in a new tab. + // + // The reason all previously untransmitted Blobs are transmitted is that the + // current BlobURL could contain html code, referring to another untransmitted + // BlobURL. + // + // Should eventually be made obsolete by broader design changes that only + // store BlobURLs in the parent process. + void TransmitBlobDataIfBlobURL(nsIURI* aURI); + + void OnCompositorDeviceReset() override; + + // Control the priority of the IPC messages for input events. + void SetInputPriorityEventEnabled(bool aEnabled); + bool IsInputPriorityEventEnabled() { return mIsInputPriorityEventEnabled; } + + static bool IsInputEventQueueSupported(); + + mozilla::ipc::IPCResult RecvCreateBrowsingContext( + uint64_t aGroupId, BrowsingContext::IPCInitializer&& aInit); + + mozilla::ipc::IPCResult RecvDiscardBrowsingContext( + const MaybeDiscarded<BrowsingContext>& aContext, bool aDoDiscard, + DiscardBrowsingContextResolver&& aResolve); + + mozilla::ipc::IPCResult RecvWindowClose( + const MaybeDiscarded<BrowsingContext>& aContext, bool aTrustedCaller); + mozilla::ipc::IPCResult RecvWindowFocus( + const MaybeDiscarded<BrowsingContext>& aContext, CallerType aCallerType, + uint64_t aActionId); + mozilla::ipc::IPCResult RecvWindowBlur( + const MaybeDiscarded<BrowsingContext>& aContext, CallerType aCallerType); + mozilla::ipc::IPCResult RecvRaiseWindow( + const MaybeDiscarded<BrowsingContext>& aContext, CallerType aCallerType, + uint64_t aActionId); + mozilla::ipc::IPCResult RecvAdjustWindowFocus( + const MaybeDiscarded<BrowsingContext>& aContext, bool aIsVisible, + uint64_t aActionId); + mozilla::ipc::IPCResult RecvClearFocus( + const MaybeDiscarded<BrowsingContext>& aContext); + mozilla::ipc::IPCResult RecvSetFocusedBrowsingContext( + const MaybeDiscarded<BrowsingContext>& aContext, uint64_t aActionId); + mozilla::ipc::IPCResult RecvSetActiveBrowsingContext( + const MaybeDiscarded<BrowsingContext>& aContext, uint64_t aActionId); + mozilla::ipc::IPCResult RecvUnsetActiveBrowsingContext( + const MaybeDiscarded<BrowsingContext>& aContext, uint64_t aActionId); + mozilla::ipc::IPCResult RecvSetFocusedElement( + const MaybeDiscarded<BrowsingContext>& aContext, bool aNeedsFocus); + mozilla::ipc::IPCResult RecvFinalizeFocusOuter( + const MaybeDiscarded<BrowsingContext>& aContext, bool aCanFocus, + CallerType aCallerType); + mozilla::ipc::IPCResult RecvInsertNewFocusActionId(uint64_t aActionId); + mozilla::ipc::IPCResult RecvBlurToParent( + const MaybeDiscarded<BrowsingContext>& aFocusedBrowsingContext, + const MaybeDiscarded<BrowsingContext>& aBrowsingContextToClear, + const MaybeDiscarded<BrowsingContext>& aAncestorBrowsingContextToFocus, + bool aIsLeavingDocument, bool aAdjustWidget, + bool aBrowsingContextToClearHandled, + bool aAncestorBrowsingContextToFocusHandled, uint64_t aActionId); + mozilla::ipc::IPCResult RecvMaybeExitFullscreen( + const MaybeDiscarded<BrowsingContext>& aContext); + + mozilla::ipc::IPCResult RecvWindowPostMessage( + const MaybeDiscarded<BrowsingContext>& aContext, + const ClonedOrErrorMessageData& aMessage, const PostMessageData& aData); + + FORWARD_SHMEM_ALLOCATOR_TO(PContentParent) + + mozilla::ipc::IPCResult RecvBlobURLDataRequest( + const nsACString& aBlobURL, nsIPrincipal* pTriggeringPrincipal, + nsIPrincipal* pLoadingPrincipal, + const OriginAttributes& aOriginAttributes, uint64_t aInnerWindowId, + const nsCString& aPartitionKey, BlobURLDataRequestResolver&& aResolver); + + protected: + bool CheckBrowsingContextEmbedder(CanonicalBrowsingContext* aBC, + const char* aOperation) const; + + void ActorDestroy(ActorDestroyReason why) override; + + bool ShouldContinueFromReplyTimeout() override; + + void OnVarChanged(const GfxVarUpdate& aVar) override; + void OnCompositorUnexpectedShutdown() override; + + private: + /** + * A map of the remote content process type to a list of content parents + * currently available to host *new* tabs/frames of that type. + * + * If a content process is identified as troubled or dead, it will be + * removed from this list, but will still be in the sContentParents list for + * the GetAll/GetAllEvenIfDead APIs. + */ + static nsClassHashtable<nsCStringHashKey, nsTArray<ContentParent*>>* + sBrowserContentParents; + static mozilla::StaticAutoPtr<LinkedList<ContentParent>> sContentParents; + + /** + * In order to avoid rapidly creating and destroying content processes when + * running under e10s, we may keep alive a single unused "web" content + * process if it previously had a very short lifetime. + * + * This process will be re-used during process selection, avoiding spawning a + * new process, if the "web" remote type is being requested. + */ + static StaticRefPtr<ContentParent> sRecycledE10SProcess; + + void AddShutdownBlockers(); + void RemoveShutdownBlockers(); + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + // Cached Mac sandbox params used when launching content processes. + static mozilla::StaticAutoPtr<std::vector<std::string>> sMacSandboxParams; +#endif + + // Set aLoadUri to true to load aURIToLoad and to false to only create the + // window. aURIToLoad should always be provided, if available, to ensure + // compatibility with GeckoView. + mozilla::ipc::IPCResult CommonCreateWindow( + PBrowserParent* aThisTab, BrowsingContext& aParent, bool aSetOpener, + const uint32_t& aChromeFlags, const bool& aCalledFromJS, + const bool& aForPrinting, const bool& aForWindowDotPrint, + nsIURI* aURIToLoad, const nsACString& aFeatures, + const UserActivation::Modifiers& aModifiers, + BrowserParent* aNextRemoteBrowser, const nsAString& aName, + nsresult& aResult, nsCOMPtr<nsIRemoteTab>& aNewRemoteTab, + bool* aWindowIsNew, int32_t& aOpenLocation, + nsIPrincipal* aTriggeringPrincipal, nsIReferrerInfo* aReferrerInfo, + bool aLoadUri, nsIContentSecurityPolicy* aCsp, + const OriginAttributes& aOriginAttributes); + + explicit ContentParent(const nsACString& aRemoteType); + + // Launch the subprocess and associated initialization. + // Returns false if the process fails to start. + // Deprecated in favor of LaunchSubprocessAsync. + bool LaunchSubprocessSync(hal::ProcessPriority aInitialPriority); + + // Launch the subprocess and associated initialization; + // returns a promise and signals failure by rejecting. + // OS-level launching work is dispatched to another thread, but some + // initialization (creating IPDL actors, etc.; see Init()) is run on + // the main thread. + RefPtr<LaunchPromise> LaunchSubprocessAsync( + hal::ProcessPriority aInitialPriority); + + // Common implementation of LaunchSubprocess{Sync,Async}. + // Return `true` in case of success, `false` if launch was + // aborted because of shutdown. + bool BeginSubprocessLaunch(ProcessPriority aPriority); + void LaunchSubprocessReject(); + bool LaunchSubprocessResolve(bool aIsSync, ProcessPriority aPriority); + + // Common initialization after sub process launch. + bool InitInternal(ProcessPriority aPriority); + + // Generate a minidump for the child process and one for the main process + void GeneratePairedMinidump(const char* aReason); + void HandleOrphanedMinidump(nsString* aDumpId); + + virtual ~ContentParent(); + + void Init(); + + // Some information could be sent to content very early, it + // should be send from this function. This function should only be + // called after the process has been transformed to browser. + void ForwardKnownInfo(); + + /** + * We might want to reuse barely used content processes if certain criteria + * are met. + * + * With Fission this is a no-op. + */ + bool TryToRecycleE10SOnly(); + + /** + * If this process is currently being recycled, unmark it as the recycled + * content process. + * If `aForeground` is true, will also restore the process' foreground + * priority if it was previously the recycled content process. + * + * With Fission this is a no-op. + */ + void StopRecyclingE10SOnly(bool aForeground); + + /** + * Removing it from the static array so it won't be returned for new tabs in + * GetNewOrUsedBrowserProcess. + */ + void RemoveFromList(); + + /** + * Return if the process has an active worker. + */ + bool HasActiveWorker(); + + /** + * Decide whether the process should be kept alive even when it would normally + * be shut down, for example when all its tabs are closed. + */ + bool ShouldKeepProcessAlive(); + + /** + * Mark this ContentParent as dead for the purposes of Get*(). + * This method is idempotent. + */ + void MarkAsDead(); + + /** + * Let the process know we are about to send a shutdown through a + * non-mainthread side channel in order to bypass mainthread congestion. + * This potentially cancels mainthread content JS execution. + */ + void SignalImpendingShutdownToContentJS(); + + bool CheckTabDestroyWillKeepAlive(uint32_t aExpectedBrowserCount); + + /** + * Check if this process is ready to be shut down, and if it is, begin the + * shutdown process. Should be called whenever a change occurs which could + * cause the decisions made by `ShouldKeepProcessAlive` to change. + * + * @param aExpectedBrowserCount The number of PBrowser actors which should + * not block shutdown. This should usually be 0. + * @param aSendShutDown If true, will send the shutdown message in addition + * to marking the process as dead and starting the force + * kill timer. + */ + void MaybeBeginShutDown(uint32_t aExpectedBrowserCount = 0, + bool aSendShutDown = true); + + /** + * How we will shut down this ContentParent and its subprocess. + */ + enum ShutDownMethod { + // Send a shutdown message and wait for FinishShutdown call back. + SEND_SHUTDOWN_MESSAGE, + // Close the channel ourselves and let the subprocess clean up itself. + CLOSE_CHANNEL, + }; + + void AsyncSendShutDownMessage(); + + /** + * Exit the subprocess and vamoose. After this call IsAlive() + * will return false and this ContentParent will not be returned + * by the Get*() funtions. However, the shutdown sequence itself + * may be asynchronous. + */ + bool ShutDownProcess(ShutDownMethod aMethod); + + // Perform any steps necesssary to gracefully shtudown the message + // manager and null out mMessageManager. + void ShutDownMessageManager(); + + // Start the send shutdown timer on shutdown. + void StartSendShutdownTimer(); + + // Start the force-kill timer on shutdown. + void StartForceKillTimer(); + + // Ensure that the permissions for the giben Permission key are set in the + // content process. + // + // See nsIPermissionManager::GetPermissionsForKey for more information on + // these keys. + void EnsurePermissionsByKey(const nsACString& aKey, + const nsACString& aOrigin); + + static void SendShutdownTimerCallback(nsITimer* aTimer, void* aClosure); + static void ForceKillTimerCallback(nsITimer* aTimer, void* aClosure); + + bool CanOpenBrowser(const IPCTabContext& aContext); + + /** + * Get or create the corresponding content parent array to + * |aContentProcessType|. + */ + static nsTArray<ContentParent*>& GetOrCreatePool( + const nsACString& aContentProcessType); + + mozilla::ipc::IPCResult RecvInitBackground( + Endpoint<mozilla::ipc::PBackgroundStarterParent>&& aEndpoint); + + mozilla::ipc::IPCResult RecvAddMemoryReport(const MemoryReport& aReport); + + bool DeallocPRemoteSpellcheckEngineParent(PRemoteSpellcheckEngineParent*); + + mozilla::ipc::IPCResult RecvCloneDocumentTreeInto( + const MaybeDiscarded<BrowsingContext>& aSource, + const MaybeDiscarded<BrowsingContext>& aTarget, PrintData&& aPrintData); + + mozilla::ipc::IPCResult RecvUpdateRemotePrintSettings( + const MaybeDiscarded<BrowsingContext>& aTarget, PrintData&& aPrintData); + + mozilla::ipc::IPCResult RecvConstructPopupBrowser( + ManagedEndpoint<PBrowserParent>&& actor, + ManagedEndpoint<PWindowGlobalParent>&& windowEp, const TabId& tabId, + const IPCTabContext& context, const WindowGlobalInit& initialWindowInit, + const uint32_t& chromeFlags); + + mozilla::ipc::IPCResult RecvIsSecureURI( + nsIURI* aURI, const OriginAttributes& aOriginAttributes, + bool* aIsSecureURI); + + mozilla::ipc::IPCResult RecvAccumulateMixedContentHSTS( + nsIURI* aURI, const bool& aActive, + const OriginAttributes& aOriginAttributes); + + bool DeallocPHalParent(PHalParent*); + + bool DeallocPHeapSnapshotTempFileHelperParent( + PHeapSnapshotTempFileHelperParent*); + + PCycleCollectWithLogsParent* AllocPCycleCollectWithLogsParent( + const bool& aDumpAllTraces, const FileDescriptor& aGCLog, + const FileDescriptor& aCCLog); + + bool DeallocPCycleCollectWithLogsParent(PCycleCollectWithLogsParent* aActor); + + PScriptCacheParent* AllocPScriptCacheParent(const FileDescOrError& cacheFile, + const bool& wantCacheData); + + bool DeallocPScriptCacheParent(PScriptCacheParent* shell); + + already_AddRefed<PExternalHelperAppParent> AllocPExternalHelperAppParent( + nsIURI* aUri, const mozilla::net::LoadInfoArgs& aLoadInfoArgs, + const nsACString& aMimeContentType, const nsACString& aContentDisposition, + const uint32_t& aContentDispositionHint, + const nsAString& aContentDispositionFilename, const bool& aForceSave, + const int64_t& aContentLength, const bool& aWasFileChannel, + nsIURI* aReferrer, const MaybeDiscarded<BrowsingContext>& aContext, + const bool& aShouldCloseWindow); + + mozilla::ipc::IPCResult RecvPExternalHelperAppConstructor( + PExternalHelperAppParent* actor, nsIURI* uri, + const LoadInfoArgs& loadInfoArgs, const nsACString& aMimeContentType, + const nsACString& aContentDisposition, + const uint32_t& aContentDispositionHint, + const nsAString& aContentDispositionFilename, const bool& aForceSave, + const int64_t& aContentLength, const bool& aWasFileChannel, + nsIURI* aReferrer, const MaybeDiscarded<BrowsingContext>& aContext, + const bool& aShouldCloseWindow) override; + + already_AddRefed<PHandlerServiceParent> AllocPHandlerServiceParent(); + + PMediaParent* AllocPMediaParent(); + + bool DeallocPMediaParent(PMediaParent* aActor); + + PBenchmarkStorageParent* AllocPBenchmarkStorageParent(); + + bool DeallocPBenchmarkStorageParent(PBenchmarkStorageParent* aActor); + +#ifdef MOZ_WEBSPEECH + already_AddRefed<PSpeechSynthesisParent> AllocPSpeechSynthesisParent(); + + virtual mozilla::ipc::IPCResult RecvPSpeechSynthesisConstructor( + PSpeechSynthesisParent* aActor) override; +#endif + + already_AddRefed<PWebBrowserPersistDocumentParent> + AllocPWebBrowserPersistDocumentParent( + PBrowserParent* aBrowser, + const MaybeDiscarded<BrowsingContext>& aContext); + + mozilla::ipc::IPCResult RecvSetClipboard(const IPCTransferable& aTransferable, + const int32_t& aWhichClipboard); + + mozilla::ipc::IPCResult RecvGetClipboard( + nsTArray<nsCString>&& aTypes, const int32_t& aWhichClipboard, + const MaybeDiscarded<WindowContext>& aRequestingWindowContext, + IPCTransferableData* aTransferableData); + + mozilla::ipc::IPCResult RecvEmptyClipboard(const int32_t& aWhichClipboard); + + mozilla::ipc::IPCResult RecvClipboardHasType(nsTArray<nsCString>&& aTypes, + const int32_t& aWhichClipboard, + bool* aHasType); + + mozilla::ipc::IPCResult RecvGetExternalClipboardFormats( + const int32_t& aWhichClipboard, const bool& aPlainTextOnly, + nsTArray<nsCString>* aTypes); + + mozilla::ipc::IPCResult RecvGetClipboardAsync( + nsTArray<nsCString>&& aTypes, const int32_t& aWhichClipboard, + const MaybeDiscarded<WindowContext>& aRequestingWindowContext, + mozilla::NotNull<nsIPrincipal*> aRequestingPrincipal, + GetClipboardAsyncResolver&& aResolver); + + already_AddRefed<PClipboardWriteRequestParent> + AllocPClipboardWriteRequestParent(const int32_t& aClipboardType); + + mozilla::ipc::IPCResult RecvGetIconForExtension(const nsACString& aFileExt, + const uint32_t& aIconSize, + nsTArray<uint8_t>* bits); + + mozilla::ipc::IPCResult RecvStartVisitedQueries( + const nsTArray<RefPtr<nsIURI>>&); + + mozilla::ipc::IPCResult RecvSetURITitle(nsIURI* uri, const nsAString& title); + + mozilla::ipc::IPCResult RecvShowAlert(nsIAlertNotification* aAlert); + + mozilla::ipc::IPCResult RecvCloseAlert(const nsAString& aName, + bool aContextClosed); + + mozilla::ipc::IPCResult RecvDisableNotifications(nsIPrincipal* aPrincipal); + + mozilla::ipc::IPCResult RecvOpenNotificationSettings( + nsIPrincipal* aPrincipal); + + mozilla::ipc::IPCResult RecvNotificationEvent( + const nsAString& aType, const NotificationEventData& aData); + + mozilla::ipc::IPCResult RecvLoadURIExternal( + nsIURI* uri, nsIPrincipal* triggeringPrincipal, + nsIPrincipal* redirectPrincipal, + const MaybeDiscarded<BrowsingContext>& aContext, + bool aWasExternallyTriggered, bool aHasValidUserGestureActivation); + mozilla::ipc::IPCResult RecvExtProtocolChannelConnectParent( + const uint64_t& registrarId); + + mozilla::ipc::IPCResult RecvSyncMessage( + const nsAString& aMsg, const ClonedMessageData& aData, + nsTArray<StructuredCloneData>* aRetvals); + + mozilla::ipc::IPCResult RecvAsyncMessage(const nsAString& aMsg, + const ClonedMessageData& aData); + + // MOZ_CAN_RUN_SCRIPT_BOUNDARY because we don't have MOZ_CAN_RUN_SCRIPT bits + // in IPC code yet. + MOZ_CAN_RUN_SCRIPT_BOUNDARY + mozilla::ipc::IPCResult RecvAddGeolocationListener(const bool& aHighAccuracy); + mozilla::ipc::IPCResult RecvRemoveGeolocationListener(); + + // MOZ_CAN_RUN_SCRIPT_BOUNDARY because we don't have MOZ_CAN_RUN_SCRIPT bits + // in IPC code yet. + MOZ_CAN_RUN_SCRIPT_BOUNDARY + mozilla::ipc::IPCResult RecvSetGeolocationHigherAccuracy(const bool& aEnable); + + mozilla::ipc::IPCResult RecvConsoleMessage(const nsAString& aMessage); + + mozilla::ipc::IPCResult RecvScriptError( + const nsAString& aMessage, const nsAString& aSourceName, + const nsAString& aSourceLine, const uint32_t& aLineNumber, + const uint32_t& aColNumber, const uint32_t& aFlags, + const nsACString& aCategory, const bool& aIsFromPrivateWindow, + const uint64_t& aInnerWindowId, const bool& aIsFromChromeContext); + + mozilla::ipc::IPCResult RecvReportFrameTimingData( + const LoadInfoArgs& loadInfoArgs, const nsAString& entryName, + const nsAString& initiatorType, UniquePtr<PerformanceTimingData>&& aData); + + mozilla::ipc::IPCResult RecvScriptErrorWithStack( + const nsAString& aMessage, const nsAString& aSourceName, + const nsAString& aSourceLine, const uint32_t& aLineNumber, + const uint32_t& aColNumber, const uint32_t& aFlags, + const nsACString& aCategory, const bool& aIsFromPrivateWindow, + const bool& aIsFromChromeContext, const ClonedMessageData& aStack); + + private: + mozilla::ipc::IPCResult RecvScriptErrorInternal( + const nsAString& aMessage, const nsAString& aSourceName, + const nsAString& aSourceLine, const uint32_t& aLineNumber, + const uint32_t& aColNumber, const uint32_t& aFlags, + const nsACString& aCategory, const bool& aIsFromPrivateWindow, + const bool& aIsFromChromeContext, + const ClonedMessageData* aStack = nullptr); + + public: + mozilla::ipc::IPCResult RecvCommitBrowsingContextTransaction( + const MaybeDiscarded<BrowsingContext>& aContext, + BrowsingContext::BaseTransaction&& aTransaction, uint64_t aEpoch); + + mozilla::ipc::IPCResult RecvCommitWindowContextTransaction( + const MaybeDiscarded<WindowContext>& aContext, + WindowContext::BaseTransaction&& aTransaction, uint64_t aEpoch); + + mozilla::ipc::IPCResult RecvAddSecurityState( + const MaybeDiscarded<WindowContext>& aContext, uint32_t aStateFlags); + + mozilla::ipc::IPCResult RecvFirstIdle(); + + mozilla::ipc::IPCResult RecvDeviceReset(); + + mozilla::ipc::IPCResult RecvCopyFavicon(nsIURI* aOldURI, nsIURI* aNewURI, + const bool& aInPrivateBrowsing); + + mozilla::ipc::IPCResult RecvFindImageText(IPCImage&&, nsTArray<nsCString>&&, + FindImageTextResolver&&); + + virtual void ProcessingError(Result aCode, const char* aMsgName) override; + + mozilla::ipc::IPCResult RecvGraphicsError(const nsACString& aError); + + mozilla::ipc::IPCResult RecvBeginDriverCrashGuard(const uint32_t& aGuardType, + bool* aOutCrashed); + + mozilla::ipc::IPCResult RecvEndDriverCrashGuard(const uint32_t& aGuardType); + + mozilla::ipc::IPCResult RecvAddIdleObserver(const uint64_t& observerId, + const uint32_t& aIdleTimeInS); + + mozilla::ipc::IPCResult RecvRemoveIdleObserver(const uint64_t& observerId, + const uint32_t& aIdleTimeInS); + + mozilla::ipc::IPCResult RecvBackUpXResources( + const FileDescriptor& aXSocketFd); + + mozilla::ipc::IPCResult RecvRequestAnonymousTemporaryFile( + const uint64_t& aID); + + mozilla::ipc::IPCResult RecvCreateAudioIPCConnection( + CreateAudioIPCConnectionResolver&& aResolver); + + already_AddRefed<extensions::PExtensionsParent> AllocPExtensionsParent(); + +#ifdef MOZ_WEBRTC + PWebrtcGlobalParent* AllocPWebrtcGlobalParent(); + bool DeallocPWebrtcGlobalParent(PWebrtcGlobalParent* aActor); +#endif + + mozilla::ipc::IPCResult RecvUpdateDropEffect(const uint32_t& aDragAction, + const uint32_t& aDropEffect); + + mozilla::ipc::IPCResult RecvShutdownProfile(const nsACString& aProfile); + + mozilla::ipc::IPCResult RecvShutdownPerfStats(const nsACString& aPerfStats); + + mozilla::ipc::IPCResult RecvGetFontListShmBlock( + const uint32_t& aGeneration, const uint32_t& aIndex, + base::SharedMemoryHandle* aOut); + + mozilla::ipc::IPCResult RecvInitializeFamily(const uint32_t& aGeneration, + const uint32_t& aFamilyIndex, + const bool& aLoadCmaps); + + mozilla::ipc::IPCResult RecvSetCharacterMap(const uint32_t& aGeneration, + const uint32_t& aFamilyIndex, + const bool& aAlias, + const uint32_t& aFaceIndex, + const gfxSparseBitSet& aMap); + + mozilla::ipc::IPCResult RecvInitOtherFamilyNames(const uint32_t& aGeneration, + const bool& aDefer, + bool* aLoaded); + + mozilla::ipc::IPCResult RecvSetupFamilyCharMap(const uint32_t& aGeneration, + const uint32_t& aIndex, + const bool& aAlias); + + mozilla::ipc::IPCResult RecvStartCmapLoading(const uint32_t& aGeneration, + const uint32_t& aStartIndex); + + mozilla::ipc::IPCResult RecvGetHyphDict(nsIURI* aURIParams, + base::SharedMemoryHandle* aOutHandle, + uint32_t* aOutSize); + + mozilla::ipc::IPCResult RecvNotifyBenchmarkResult(const nsAString& aCodecName, + const uint32_t& aDecodeFPS); + + mozilla::ipc::IPCResult RecvNotifyPushObservers(const nsACString& aScope, + nsIPrincipal* aPrincipal, + const nsAString& aMessageId); + + mozilla::ipc::IPCResult RecvNotifyPushObserversWithData( + const nsACString& aScope, nsIPrincipal* aPrincipal, + const nsAString& aMessageId, nsTArray<uint8_t>&& aData); + + mozilla::ipc::IPCResult RecvNotifyPushSubscriptionChangeObservers( + const nsACString& aScope, nsIPrincipal* aPrincipal); + + mozilla::ipc::IPCResult RecvPushError(const nsACString& aScope, + nsIPrincipal* aPrincipal, + const nsAString& aMessage, + const uint32_t& aFlags); + + mozilla::ipc::IPCResult RecvNotifyPushSubscriptionModifiedObservers( + const nsACString& aScope, nsIPrincipal* aPrincipal); + + mozilla::ipc::IPCResult RecvGetFilesRequest(const nsID& aID, + const nsAString& aDirectoryPath, + const bool& aRecursiveFlag); + + mozilla::ipc::IPCResult RecvDeleteGetFilesRequest(const nsID& aID); + + 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); + mozilla::ipc::IPCResult RecvRecordPageLoadEvent( + const mozilla::glean::perf::PageLoadExtra& aPageLoadEventExtra); + mozilla::ipc::IPCResult RecvRecordOrigin(const uint32_t& aMetricId, + const nsACString& aOrigin); + mozilla::ipc::IPCResult RecvReportContentBlockingLog( + const IPCStream& aIPCStream); + + mozilla::ipc::IPCResult RecvBHRThreadHang(const HangDetails& aHangDetails); + + mozilla::ipc::IPCResult RecvAddCertException( + nsIX509Cert* aCert, const nsACString& aHostName, int32_t aPort, + const OriginAttributes& aOriginAttributes, bool aIsTemporary, + AddCertExceptionResolver&& aResolver); + + mozilla::ipc::IPCResult RecvAutomaticStorageAccessPermissionCanBeGranted( + nsIPrincipal* aPrincipal, + AutomaticStorageAccessPermissionCanBeGrantedResolver&& aResolver); + + mozilla::ipc::IPCResult RecvStorageAccessPermissionGrantedForOrigin( + uint64_t aTopLevelWindowId, + const MaybeDiscarded<BrowsingContext>& aParentContext, + nsIPrincipal* aTrackingPrincipal, const nsACString& aTrackingOrigin, + const int& aAllowMode, + const Maybe< + ContentBlockingNotifier::StorageAccessPermissionGrantedReason>& + aReason, + const bool& aFrameOnly, + StorageAccessPermissionGrantedForOriginResolver&& aResolver); + + mozilla::ipc::IPCResult RecvCompleteAllowAccessFor( + const MaybeDiscarded<BrowsingContext>& aParentContext, + uint64_t aTopLevelWindowId, nsIPrincipal* aTrackingPrincipal, + const nsACString& aTrackingOrigin, uint32_t aCookieBehavior, + const ContentBlockingNotifier::StorageAccessPermissionGrantedReason& + aReason, + CompleteAllowAccessForResolver&& aResolver); + + mozilla::ipc::IPCResult RecvSetAllowStorageAccessRequestFlag( + nsIPrincipal* aEmbeddedPrincipal, nsIURI* aEmbeddingOrigin, + SetAllowStorageAccessRequestFlagResolver&& aResolver); + + mozilla::ipc::IPCResult RecvTestAllowStorageAccessRequestFlag( + nsIPrincipal* aEmbeddingPrincipal, nsIURI* aEmbeddedOrigin, + TestAllowStorageAccessRequestFlagResolver&& aResolver); + + mozilla::ipc::IPCResult RecvStoreUserInteractionAsPermission( + nsIPrincipal* aPrincipal); + + mozilla::ipc::IPCResult RecvTestCookiePermissionDecided( + const MaybeDiscarded<BrowsingContext>& aContext, nsIPrincipal* aPrincipal, + const TestCookiePermissionDecidedResolver&& aResolver); + + mozilla::ipc::IPCResult RecvTestStorageAccessPermission( + nsIPrincipal* aEmbeddingPrincipal, const nsCString& aEmbeddedOrigin, + const TestStorageAccessPermissionResolver&& aResolver); + + mozilla::ipc::IPCResult RecvNotifyMediaPlaybackChanged( + const MaybeDiscarded<BrowsingContext>& aContext, + MediaPlaybackState aState); + + mozilla::ipc::IPCResult RecvNotifyMediaAudibleChanged( + const MaybeDiscarded<BrowsingContext>& aContext, + MediaAudibleState aState); + + mozilla::ipc::IPCResult RecvNotifyPictureInPictureModeChanged( + const MaybeDiscarded<BrowsingContext>& aContext, bool aEnabled); + + mozilla::ipc::IPCResult RecvNotifyMediaSessionUpdated( + const MaybeDiscarded<BrowsingContext>& aContext, bool aIsCreated); + + mozilla::ipc::IPCResult RecvNotifyUpdateMediaMetadata( + const MaybeDiscarded<BrowsingContext>& aContext, + const Maybe<MediaMetadataBase>& aMetadata); + + mozilla::ipc::IPCResult RecvNotifyMediaSessionPlaybackStateChanged( + const MaybeDiscarded<BrowsingContext>& aContext, + MediaSessionPlaybackState aPlaybackState); + + mozilla::ipc::IPCResult RecvNotifyMediaSessionSupportedActionChanged( + const MaybeDiscarded<BrowsingContext>& aContext, + MediaSessionAction aAction, bool aEnabled); + + mozilla::ipc::IPCResult RecvNotifyMediaFullScreenState( + const MaybeDiscarded<BrowsingContext>& aContext, bool aIsInFullScreen); + + mozilla::ipc::IPCResult RecvNotifyPositionStateChanged( + const MaybeDiscarded<BrowsingContext>& aContext, + const PositionState& aState); + + mozilla::ipc::IPCResult RecvAddOrRemovePageAwakeRequest( + const MaybeDiscarded<BrowsingContext>& aContext, + const bool& aShouldAddCount); + +#if defined(XP_WIN) + mozilla::ipc::IPCResult RecvGetModulesTrust( + ModulePaths&& aModPaths, bool aRunAtNormalPriority, + GetModulesTrustResolver&& aResolver); +#endif // defined(XP_WIN) + + mozilla::ipc::IPCResult RecvReportServiceWorkerShutdownProgress( + uint32_t aShutdownStateId, + ServiceWorkerShutdownState::Progress aProgress); + + mozilla::ipc::IPCResult RecvRawMessage( + const JSActorMessageMeta& aMeta, const Maybe<ClonedMessageData>& aData, + const Maybe<ClonedMessageData>& aStack); + + mozilla::ipc::IPCResult RecvAbortOtherOrientationPendingPromises( + const MaybeDiscarded<BrowsingContext>& aContext); + + mozilla::ipc::IPCResult RecvNotifyOnHistoryReload( + const MaybeDiscarded<BrowsingContext>& aContext, const bool& aForceReload, + NotifyOnHistoryReloadResolver&& aResolver); + + mozilla::ipc::IPCResult RecvHistoryCommit( + const MaybeDiscarded<BrowsingContext>& aContext, const uint64_t& aLoadID, + const nsID& aChangeID, const uint32_t& aLoadType, const bool& aPersist, + const bool& aCloneEntryChildren, const bool& aChannelExpired, + const uint32_t& aCacheKey); + + mozilla::ipc::IPCResult RecvHistoryGo( + const MaybeDiscarded<BrowsingContext>& aContext, int32_t aOffset, + uint64_t aHistoryEpoch, bool aRequireUserInteraction, + bool aUserActivation, HistoryGoResolver&& aResolveRequestedIndex); + + mozilla::ipc::IPCResult RecvSynchronizeLayoutHistoryState( + const MaybeDiscarded<BrowsingContext>& aContext, + nsILayoutHistoryState* aState); + + mozilla::ipc::IPCResult RecvSessionHistoryEntryTitle( + const MaybeDiscarded<BrowsingContext>& aContext, const nsAString& aTitle); + + mozilla::ipc::IPCResult RecvSessionHistoryEntryScrollRestorationIsManual( + const MaybeDiscarded<BrowsingContext>& aContext, const bool& aIsManual); + + mozilla::ipc::IPCResult RecvSessionHistoryEntryScrollPosition( + const MaybeDiscarded<BrowsingContext>& aContext, const int32_t& aX, + const int32_t& aY); + + mozilla::ipc::IPCResult RecvSessionHistoryEntryCacheKey( + const MaybeDiscarded<BrowsingContext>& aContext, + const uint32_t& aCacheKey); + + mozilla::ipc::IPCResult RecvSessionHistoryEntryWireframe( + const MaybeDiscarded<BrowsingContext>& aContext, + const Wireframe& aWireframe); + + mozilla::ipc::IPCResult + RecvSessionHistoryEntryStoreWindowNameInContiguousEntries( + const MaybeDiscarded<BrowsingContext>& aContext, const nsAString& aName); + + mozilla::ipc::IPCResult RecvGetLoadingSessionHistoryInfoFromParent( + const MaybeDiscarded<BrowsingContext>& aContext, + GetLoadingSessionHistoryInfoFromParentResolver&& aResolver); + + mozilla::ipc::IPCResult RecvRemoveFromBFCache( + const MaybeDiscarded<BrowsingContext>& aContext); + + mozilla::ipc::IPCResult RecvSetActiveSessionHistoryEntry( + const MaybeDiscarded<BrowsingContext>& aContext, + const Maybe<nsPoint>& aPreviousScrollPos, SessionHistoryInfo&& aInfo, + uint32_t aLoadType, uint32_t aUpdatedCacheKey, const nsID& aChangeID); + + mozilla::ipc::IPCResult RecvReplaceActiveSessionHistoryEntry( + const MaybeDiscarded<BrowsingContext>& aContext, + SessionHistoryInfo&& aInfo); + + mozilla::ipc::IPCResult RecvRemoveDynEntriesFromActiveSessionHistoryEntry( + const MaybeDiscarded<BrowsingContext>& aContext); + + mozilla::ipc::IPCResult RecvRemoveFromSessionHistory( + const MaybeDiscarded<BrowsingContext>& aContext, const nsID& aChangeID); + + mozilla::ipc::IPCResult RecvHistoryReload( + const MaybeDiscarded<BrowsingContext>& aContext, + const uint32_t aReloadFlags); + + mozilla::ipc::IPCResult RecvCleanupPendingLoadState(uint64_t aLoadIdentifier); + + // Notify the ContentChild to enable the input event prioritization when + // initializing. + void MaybeEnableRemoteInputEventQueue(); + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + void AppendSandboxParams(std::vector<std::string>& aArgs); + void AppendDynamicSandboxParams(std::vector<std::string>& aArgs); +#endif + + mozilla::ipc::IPCResult RecvFOGData(ByteBuf&& buf); + + mozilla::ipc::IPCResult RecvSetContainerFeaturePolicy( + const MaybeDiscardedBrowsingContext& aContainerContext, + FeaturePolicy* aContainerFeaturePolicy); + + mozilla::ipc::IPCResult RecvGetSystemIcon(nsIURI* aURI, + GetSystemIconResolver&& aResolver); + +#ifdef FUZZING_SNAPSHOT + mozilla::ipc::IPCResult RecvSignalFuzzingReady(); +#endif + + public: + void SendGetFilesResponseAndForget(const nsID& aID, + const GetFilesResponseResult& aResult); + + bool SendRequestMemoryReport(const uint32_t& aGeneration, + const bool& aAnonymize, + const bool& aMinimizeMemoryUsage, + const Maybe<FileDescriptor>& aDMDFile) override; + + void AddBrowsingContextGroup(BrowsingContextGroup* aGroup); + void RemoveBrowsingContextGroup(BrowsingContextGroup* aGroup); + + // See `BrowsingContext::mEpochs` for an explanation of this field. + uint64_t GetBrowsingContextFieldEpoch() const { + return mBrowsingContextFieldEpoch; + } + + void UpdateNetworkLinkType(); + + already_AddRefed<JSActor> InitJSActor(JS::Handle<JSObject*> aMaybeActor, + const nsACString& aName, + ErrorResult& aRv) override; + mozilla::ipc::IProtocol* AsNativeActor() override { return this; } + + static already_AddRefed<nsIPrincipal> CreateRemoteTypeIsolationPrincipal( + const nsACString& aRemoteType); + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + bool IsBlockingShutdown() { return mBlockShutdownCalled; } +#endif + + ThreadsafeContentParentHandle* ThreadsafeHandle() const { + return mThreadsafeHandle; + } + + void GetIPCTransferableData(nsIDragSession* aSession, BrowserParent* aParent, + nsTArray<IPCTransferableData>& aIPCTransferables); + + private: + // Return an existing ContentParent if possible. Otherwise, `nullptr`. + static already_AddRefed<ContentParent> GetUsedBrowserProcess( + const nsACString& aRemoteType, nsTArray<ContentParent*>& aContentParents, + uint32_t aMaxContentParents, bool aPreferUsed, ProcessPriority aPriority); + + void AddToPool(nsTArray<ContentParent*>&); + void RemoveFromPool(nsTArray<ContentParent*>&); + void AssertNotInPool(); + + void AssertAlive(); + + private: + // If you add strong pointers to cycle collected objects here, be sure to + // release these objects in ShutDownProcess. See the comment there for more + // details. + + GeckoChildProcessHost* mSubprocess; + const TimeStamp mLaunchTS; // used to calculate time to start content process + TimeStamp mLaunchYieldTS; // used to calculate async launch main thread time + TimeStamp mActivateTS; + + bool mIsAPreallocBlocker; // We called AddBlocker for this ContentParent + + nsCString mRemoteType; + nsCString mProfile; + nsCOMPtr<nsIPrincipal> mRemoteTypeIsolationPrincipal; + + ContentParentId mChildID; + int32_t mGeolocationWatchID; + + // After we destroy the last Browser, we also start a timer to ensure + // that even content processes that are not responding will get a + // second chance and a shutdown message. + nsCOMPtr<nsITimer> mSendShutdownTimer; + bool mSentShutdownMessage = false; + + // After we initiate shutdown, we also start a timer to ensure + // that even content processes that are 100% blocked (say from + // SIGSTOP), are still killed eventually. This task enforces that + // timer. + nsCOMPtr<nsITimer> mForceKillTimer; + + // Threadsafe handle object which can be used by actors like PBackground to + // track the identity and other relevant information about the content process + // they're attached to. + const RefPtr<ThreadsafeContentParentHandle> mThreadsafeHandle; + + // How many tabs we're waiting to finish their destruction + // sequence. Precisely, how many BrowserParents have called + // NotifyTabDestroying() but not called NotifyTabDestroyed(). + int32_t mNumDestroyingTabs; + + uint32_t mNumKeepaliveCalls; + + // The process starts in the LAUNCHING state, and transitions to + // ALIVE once it can accept IPC messages. It remains ALIVE only + // while remote content is being actively used from this process. + // After the state becaomes DEAD, some previously scheduled IPC + // traffic may still pass through. + enum class LifecycleState : uint8_t { + LAUNCHING, + ALIVE, + INITIALIZED, + DEAD, + }; + + LifecycleState mLifecycleState; + + uint8_t mIsForBrowser : 1; + + // These variables track whether we've called Close() and KillHard() on our + // channel. + uint8_t mCalledClose : 1; + uint8_t mCalledKillHard : 1; + uint8_t mCreatedPairedMinidumps : 1; + uint8_t mShutdownPending : 1; + + // Whether or not `LaunchSubprocessResolve` has been called, and whether or + // not it returned `true` when called. + uint8_t mLaunchResolved : 1; + uint8_t mLaunchResolvedOk : 1; + + // True if the input event queue on the main thread of the content process is + // enabled. + uint8_t mIsRemoteInputEventQueueEnabled : 1; + + // True if we send input events with input priority. Otherwise, we send input + // events with normal priority. + uint8_t mIsInputPriorityEventEnabled : 1; + + uint8_t mIsInPool : 1; + + // True if we already created a GMP service. + uint8_t mGMPCreated : 1; + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + bool mNotifiedImpendingShutdownOnTabWillDestroy = false; + bool mBlockShutdownCalled; +#endif + + nsCOMPtr<nsIContentProcessInfo> mScriptableHelper; + + nsTArray<nsCOMPtr<nsIObserver>> mIdleListeners; + +#ifdef MOZ_X11 + // Dup of child's X socket, used to scope its resources to this + // object instead of the child process's lifetime. + UniqueFileHandle mChildXSocketFdDup; +#endif + + RefPtr<PProcessHangMonitorParent> mHangMonitorActor; + + UniquePtr<gfx::DriverCrashGuard> mDriverCrashGuard; + UniquePtr<MemoryReportRequestHost> mMemoryReportRequest; + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + mozilla::UniquePtr<SandboxBroker> mSandboxBroker; + static mozilla::StaticAutoPtr<SandboxBrokerPolicyFactory> + sSandboxBrokerPolicyFactory; +#endif + + // This hashtable is used to run GetFilesHelper objects in the parent process. + // GetFilesHelper can be aborted by receiving RecvDeleteGetFilesRequest. + nsRefPtrHashtable<nsIDHashKey, GetFilesHelper> mGetFilesPendingRequests; + + nsTHashSet<nsCString> mActivePermissionKeys; + nsTHashSet<nsCString> mActiveSecondaryPermissionKeys; + + nsTArray<nsCString> mBlobURLs; + + // This is intended to be a memory and time efficient means of determining + // whether an origin has ever existed in a process so that Blob URL broadcast + // doesn't need to transmit every Blob URL to every content process. False + // positives are acceptable because receiving a Blob URL does not grant access + // to its contents, and the act of creating/revoking a Blob is currently + // viewed as an acceptable side-channel leak. In the future bug 1491018 will + // moot the need for this structure. + nsTArray<uint64_t> mLoadedOriginHashes; + + UniquePtr<mozilla::ipc::CrashReporterHost> mCrashReporter; + + // Collects any pref changes that occur during process launch (after + // the initial map is passed in command-line arguments) to be sent + // when the process can receive IPC messages. + nsTArray<Pref> mQueuedPrefs; + + RefPtr<mozilla::dom::ProcessMessageManager> mMessageManager; + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + // When set to true, indicates that content processes should + // initialize their sandbox during startup instead of waiting + // for the SetProcessSandbox IPDL message. + static bool sEarlySandboxInit; +#endif + + nsTHashSet<RefPtr<BrowsingContextGroup>> mGroups; + + // When we request a content process to load a document on our behalf, we'll + // record the nsDocShellLoadState we sent to the content process mapped by the + // load ID. If the load is then requested from the content process, we can + // compare the load state and ensure it matches. + nsTHashMap<uint64_t, RefPtr<nsDocShellLoadState>> mPendingLoadStates; + + // See `BrowsingContext::mEpochs` for an explanation of this field. + uint64_t mBrowsingContextFieldEpoch = 0; + + // A preference serializer used to share preferences with the process. + // Cleared once startup is complete. + UniquePtr<mozilla::ipc::SharedPreferenceSerializer> mPrefSerializer; + + static uint32_t sMaxContentProcesses; + static uint32_t sPageLoadEventCounter; + + bool mIsSignaledImpendingShutdown = false; + bool mIsNotifiedShutdownSuccess = false; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(ContentParent, NS_CONTENTPARENT_IID) + +// Threadsafe handle object allowing off-main-thread code to get some +// information and maintain a weak reference to a ContentParent. +class ThreadsafeContentParentHandle final { + friend class ContentParent; + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ThreadsafeContentParentHandle); + + // Get the ChildID of this process. Safe to call from any thread. + ContentParentId ChildID() const { return mChildID; } + + // Get the current RemoteType of this ContentParent. Safe to call from any + // thread. If the returned RemoteType is PREALLOC_REMOTE_TYPE, it may change + // again in the future. + nsCString GetRemoteType() MOZ_EXCLUDES(mMutex); + + // Try to get a reference to the real `ContentParent` object from this weak + // reference. This may only be called on the main thread. + already_AddRefed<ContentParent> GetContentParent() + MOZ_REQUIRES(sMainThreadCapability) { + return do_AddRef(mWeakActor); + } + + // Calls `aCallback` with the current remote worker count and whether or not + // shutdown has been started. If the callback returns `true`, registers a new + // actor, and returns `true`, otherwise returns `false`. + // + // NOTE: The internal mutex is held while evaluating `aCallback`. + bool MaybeRegisterRemoteWorkerActor( + MoveOnlyFunction<bool(uint32_t, bool)> aCallback) MOZ_EXCLUDES(mMutex); + + // Like `MaybeRegisterRemoteWorkerActor`, but unconditional. + void RegisterRemoteWorkerActor() MOZ_EXCLUDES(mMutex) { + MaybeRegisterRemoteWorkerActor([](uint32_t, bool) { return true; }); + } + + RecursiveMutex& Mutex() { return mMutex; } + + private: + ThreadsafeContentParentHandle(ContentParent* aActor, ContentParentId aChildID, + const nsACString& aRemoteType) + : mChildID(aChildID), mRemoteType(aRemoteType), mWeakActor(aActor) {} + ~ThreadsafeContentParentHandle() { MOZ_ASSERT(!mWeakActor); } + + mozilla::RecursiveMutex mMutex{"ContentParentIdentity"}; + + const ContentParentId mChildID; + + nsCString mRemoteType MOZ_GUARDED_BY(mMutex); + uint32_t mRemoteWorkerActorCount MOZ_GUARDED_BY(mMutex) = 0; + bool mShutdownStarted MOZ_GUARDED_BY(mMutex) = false; + + // Weak reference to the actual ContentParent actor. Only touched on the main + // thread to read or clear. + ContentParent* mWeakActor MOZ_GUARDED_BY(sMainThreadCapability); +}; + +// This is the C++ version of remoteTypePrefix in E10SUtils.sys.mjs. +const nsDependentCSubstring RemoteTypePrefix( + const nsACString& aContentProcessType); + +// This is based on isWebRemoteType in E10SUtils.sys.mjs. +bool IsWebRemoteType(const nsACString& aContentProcessType); + +bool IsWebCoopCoepRemoteType(const nsACString& aContentProcessType); + +bool IsExtensionRemoteType(const nsACString& aContentProcessType); + +inline nsISupports* ToSupports(mozilla::dom::ContentParent* aContentParent) { + return static_cast<nsIDOMProcessParent*>(aContentParent); +} + +} // namespace dom +} // namespace mozilla + +class ParentIdleListener : public nsIObserver { + friend class mozilla::dom::ContentParent; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + ParentIdleListener(mozilla::dom::ContentParent* aParent, uint64_t aObserver, + uint32_t aTime) + : mParent(aParent), mObserver(aObserver), mTime(aTime) {} + + private: + virtual ~ParentIdleListener() = default; + + RefPtr<mozilla::dom::ContentParent> mParent; + uint64_t mObserver; + uint32_t mTime; +}; + +#endif // mozilla_dom_ContentParent_h diff --git a/dom/ipc/ContentProcess.cpp b/dom/ipc/ContentProcess.cpp new file mode 100644 index 0000000000..10118cd38f --- /dev/null +++ b/dom/ipc/ContentProcess.cpp @@ -0,0 +1,192 @@ +/* -*- 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 "mozilla/ipc/IOThreadChild.h" + +#include "ContentProcess.h" +#include "base/shared_memory.h" +#include "mozilla/Preferences.h" + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +# include <stdlib.h> +# include "mozilla/Sandbox.h" +# include "mozilla/SandboxSettings.h" +#endif + +#include "nsAppRunner.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/ProcessUtils.h" +#include "mozilla/GeckoArgs.h" +#include "mozilla/Omnijar.h" +#include "nsCategoryManagerUtils.h" + +using mozilla::ipc::IOThreadChild; + +namespace mozilla::dom { + +static nsresult GetGREDir(nsIFile** aResult) { + nsCOMPtr<nsIFile> current; + nsresult rv = XRE_GetBinaryPath(getter_AddRefs(current)); + NS_ENSURE_SUCCESS(rv, rv); + +#ifdef XP_DARWIN + // Walk out of [subprocess].app/Contents/MacOS to the real GRE dir + const int depth = 4; +#else + const int depth = 1; +#endif + + for (int i = 0; i < depth; ++i) { + nsCOMPtr<nsIFile> parent; + rv = current->GetParent(getter_AddRefs(parent)); + NS_ENSURE_SUCCESS(rv, rv); + + current = parent; + NS_ENSURE_TRUE(current, NS_ERROR_UNEXPECTED); + } + +#ifdef XP_DARWIN + rv = current->SetNativeLeafName("Resources"_ns); + NS_ENSURE_SUCCESS(rv, rv); +#endif + + current.forget(aResult); + + return NS_OK; +} + +ContentProcess::ContentProcess(ProcessId aParentPid, + const nsID& aMessageChannelId) + : ProcessChild(aParentPid, aMessageChannelId) { + NS_LogInit(); +} + +ContentProcess::~ContentProcess() { NS_LogTerm(); } + +bool ContentProcess::Init(int aArgc, char* aArgv[]) { + Maybe<uint64_t> childID = geckoargs::sChildID.Get(aArgc, aArgv); + Maybe<bool> isForBrowser = Nothing(); + Maybe<const char*> parentBuildID = + geckoargs::sParentBuildID.Get(aArgc, aArgv); + Maybe<uint64_t> jsInitHandle; + Maybe<uint64_t> jsInitLen = geckoargs::sJsInitLen.Get(aArgc, aArgv); + + nsCOMPtr<nsIFile> appDirArg; + Maybe<const char*> appDir = geckoargs::sAppDir.Get(aArgc, aArgv); + if (appDir.isSome()) { + bool flag; + nsresult rv = XRE_GetFileFromPath(*appDir, getter_AddRefs(appDirArg)); + if (NS_FAILED(rv) || NS_FAILED(appDirArg->Exists(&flag)) || !flag) { + NS_WARNING("Invalid application directory passed to content process."); + appDirArg = nullptr; + } + } + + Maybe<bool> safeMode = geckoargs::sSafeMode.Get(aArgc, aArgv); + if (safeMode.isSome()) { + gSafeMode = *safeMode; + } + + Maybe<bool> isForBrowerParam = geckoargs::sIsForBrowser.Get(aArgc, aArgv); + Maybe<bool> notForBrowserParam = geckoargs::sNotForBrowser.Get(aArgc, aArgv); + if (isForBrowerParam.isSome()) { + isForBrowser = Some(true); + } + if (notForBrowserParam.isSome()) { + isForBrowser = Some(false); + } + + // command line: [-jsInitHandle handle] -jsInitLen length +#ifdef XP_WIN + jsInitHandle = geckoargs::sJsInitHandle.Get(aArgc, aArgv); +#endif + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + nsCOMPtr<nsIFile> profileDir; + bool flag; + Maybe<const char*> profile = geckoargs::sProfile.Get(aArgc, aArgv); + // xpcshell self-test on macOS will hit this, so check isSome() otherwise + // Maybe<> assertions will MOZ_CRASH() us. + if (profile.isSome()) { + nsresult rv = XRE_GetFileFromPath(*profile, getter_AddRefs(profileDir)); + if (NS_FAILED(rv) || NS_FAILED(profileDir->Exists(&flag)) || !flag) { + NS_WARNING("Invalid profile directory passed to content process."); + profileDir = nullptr; + } + } else { + NS_WARNING("No profile directory passed to content process."); + } +#endif /* XP_MACOSX && MOZ_SANDBOX */ + + // Did we find all the mandatory flags? + if (childID.isNothing() || isForBrowser.isNothing() || + parentBuildID.isNothing()) { + return false; + } + + if (!ProcessChild::InitPrefs(aArgc, aArgv)) { + return false; + } + + if (!::mozilla::ipc::ImportSharedJSInit(jsInitHandle.valueOr(0), + jsInitLen.valueOr(0))) { + return false; + } + + mContent.Init(TakeInitialEndpoint(), *parentBuildID, *childID, *isForBrowser); + + nsCOMPtr<nsIFile> greDir; + nsresult rv = GetGREDir(getter_AddRefs(greDir)); + if (NS_FAILED(rv)) { + return false; + } + + nsCOMPtr<nsIFile> xpcomAppDir = appDirArg ? appDirArg : greDir; + + rv = mDirProvider.Initialize(xpcomAppDir, greDir); + if (NS_FAILED(rv)) { + return false; + } + + // Handle the -greomni/-appomni flags (unless the forkserver already + // preloaded the jar(s)). + if (!Omnijar::IsInitialized()) { + Omnijar::ChildProcessInit(aArgc, aArgv); + } + + rv = NS_InitXPCOM(nullptr, xpcomAppDir, &mDirProvider); + if (NS_FAILED(rv)) { + return false; + } + + // "app-startup" is the name of both the category and the event + NS_CreateServicesFromCategory("app-startup", nullptr, "app-startup", nullptr); + +#if (defined(XP_MACOSX)) && defined(MOZ_SANDBOX) + mContent.SetProfileDir(profileDir); +# if defined(DEBUG) + if (IsContentSandboxEnabled()) { + AssertMacSandboxEnabled(); + } +# endif /* DEBUG */ +#endif /* XP_MACOSX && MOZ_SANDBOX */ + + // Do this as early as possible to get the parent process to initialize the + // background thread since we'll likely need database information very soon. + mozilla::ipc::BackgroundChild::Startup(); + mozilla::ipc::BackgroundChild::InitContentStarter(&mContent); + + return true; +} + +// Note: CleanUp() never gets called in non-debug builds because we exit early +// in ContentChild::ActorDestroy(). +void ContentProcess::CleanUp() { + mDirProvider.DoShutdown(); + NS_ShutdownXPCOM(nullptr); +} + +} // namespace mozilla::dom diff --git a/dom/ipc/ContentProcess.h b/dom/ipc/ContentProcess.h new file mode 100644 index 0000000000..46d34098e7 --- /dev/null +++ b/dom/ipc/ContentProcess.h @@ -0,0 +1,46 @@ +/* -*- 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 dom_tabs_ContentThread_h +#define dom_tabs_ContentThread_h 1 + +#include "mozilla/ipc/ProcessChild.h" +#include "ContentChild.h" +#include "nsXREDirProvider.h" + +#if defined(XP_WIN) +# include "mozilla/mscom/ProcessRuntime.h" +#endif + +namespace mozilla::dom { + +/** + * ContentProcess is a singleton on the content process which represents + * the main thread where tab instances live. + */ +class ContentProcess : public mozilla::ipc::ProcessChild { + using ProcessChild = mozilla::ipc::ProcessChild; + + public: + ContentProcess(ProcessId aParentPid, const nsID& aMessageChannelId); + ~ContentProcess(); + + virtual bool Init(int aArgc, char* aArgv[]) override; + virtual void CleanUp() override; + + private: + ContentChild mContent; +#if defined(XP_WIN) + // This object initializes and configures COM. This must happen prior to + // constructing mXREEmbed. + mozilla::mscom::ProcessRuntime mCOMRuntime; +#endif + nsXREDirProvider mDirProvider; +}; + +} // namespace mozilla::dom + +#endif // ifndef dom_tabs_ContentThread_h diff --git a/dom/ipc/ContentProcessManager.cpp b/dom/ipc/ContentProcessManager.cpp new file mode 100644 index 0000000000..c5647b4f26 --- /dev/null +++ b/dom/ipc/ContentProcessManager.cpp @@ -0,0 +1,142 @@ +/* -*- 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 "ContentProcessManager.h" +#include "ContentParent.h" +#include "mozilla/AppShutdown.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/BrowsingContextGroup.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" + +#include "mozilla/StaticPtr.h" +#include "mozilla/ClearOnShutdown.h" + +#include "nsPrintfCString.h" + +namespace mozilla::dom { + +/* static */ +StaticAutoPtr<ContentProcessManager> ContentProcessManager::sSingleton; + +/* static */ +ContentProcessManager* ContentProcessManager::GetSingleton() { + MOZ_ASSERT(XRE_IsParentProcess()); + + if (!sSingleton && + !AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownFinal)) { + sSingleton = new ContentProcessManager(); + ClearOnShutdown(&sSingleton); + } + return sSingleton; +} + +void ContentProcessManager::AddContentProcess(ContentParent* aChildCp) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aChildCp); + + mContentParentMap.WithEntryHandle(aChildCp->ChildID(), [&](auto&& entry) { + MOZ_ASSERT_IF(entry, entry.Data() == aChildCp); + entry.OrInsert(aChildCp); + }); +} + +void ContentProcessManager::RemoveContentProcess( + const ContentParentId& aChildCpId) { + MOZ_ASSERT(NS_IsMainThread()); + + MOZ_ALWAYS_TRUE(mContentParentMap.Remove(aChildCpId)); +} + +ContentParent* ContentProcessManager::GetContentProcessById( + const ContentParentId& aChildCpId) { + MOZ_ASSERT(NS_IsMainThread()); + + ContentParent* contentParent = mContentParentMap.Get(aChildCpId); + if (NS_WARN_IF(!contentParent)) { + return nullptr; + } + return contentParent; +} + +bool ContentProcessManager::RegisterRemoteFrame(BrowserParent* aChildBp) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aChildBp); + + return mBrowserParentMap.WithEntryHandle( + aChildBp->GetTabId(), [&](auto&& entry) { + if (entry) { + MOZ_ASSERT(entry.Data() == aChildBp); + return false; + } + + // Ensure that this BrowserParent's BrowsingContextGroup is kept alive + // until the BrowserParent has been unregistered, ensuring the group + // isn't destroyed while this BrowserParent can still send messages. + aChildBp->GetBrowsingContext()->Group()->AddKeepAlive(); + entry.Insert(aChildBp); + return true; + }); +} + +void ContentProcessManager::UnregisterRemoteFrame(const TabId& aChildTabId) { + MOZ_ASSERT(NS_IsMainThread()); + + auto childBp = mBrowserParentMap.Extract(aChildTabId); + MOZ_DIAGNOSTIC_ASSERT(childBp); + + // Clear the corresponding keepalive which was added in `RegisterRemoteFrame`. + (*childBp)->GetBrowsingContext()->Group()->RemoveKeepAlive(); +} + +ContentParentId ContentProcessManager::GetTabProcessId( + const TabId& aChildTabId) { + MOZ_ASSERT(NS_IsMainThread()); + + if (BrowserParent* browserParent = mBrowserParentMap.Get(aChildTabId)) { + return browserParent->Manager()->ChildID(); + } + return ContentParentId(0); +} + +uint32_t ContentProcessManager::GetBrowserParentCountByProcessId( + const ContentParentId& aChildCpId) { + MOZ_ASSERT(NS_IsMainThread()); + + ContentParent* contentParent = mContentParentMap.Get(aChildCpId); + if (NS_WARN_IF(!contentParent)) { + return 0; + } + return contentParent->ManagedPBrowserParent().Count(); +} + +already_AddRefed<BrowserParent> +ContentProcessManager::GetBrowserParentByProcessAndTabId( + const ContentParentId& aChildCpId, const TabId& aChildTabId) { + RefPtr<BrowserParent> browserParent = mBrowserParentMap.Get(aChildTabId); + if (NS_WARN_IF(!browserParent)) { + return nullptr; + } + + if (NS_WARN_IF(browserParent->Manager()->ChildID() != aChildCpId)) { + return nullptr; + } + + return browserParent.forget(); +} + +already_AddRefed<BrowserParent> +ContentProcessManager::GetTopLevelBrowserParentByProcessAndTabId( + const ContentParentId& aChildCpId, const TabId& aChildTabId) { + RefPtr<BrowserParent> browserParent = + GetBrowserParentByProcessAndTabId(aChildCpId, aChildTabId); + while (browserParent && browserParent->GetBrowserBridgeParent()) { + browserParent = browserParent->GetBrowserBridgeParent()->Manager(); + } + + return browserParent.forget(); +} + +} // namespace mozilla::dom diff --git a/dom/ipc/ContentProcessManager.h b/dom/ipc/ContentProcessManager.h new file mode 100644 index 0000000000..bf76051a66 --- /dev/null +++ b/dom/ipc/ContentProcessManager.h @@ -0,0 +1,90 @@ +/* -*- 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_dom_ContentProcessManager_h +#define mozilla_dom_ContentProcessManager_h + +#include "mozilla/StaticPtr.h" +#include "mozilla/dom/TabContext.h" +#include "mozilla/dom/ipc/IdType.h" +#include "nsTArray.h" +#include "nsTHashMap.h" + +namespace mozilla::dom { +class ContentParent; + +class ContentProcessManager final { + public: + static ContentProcessManager* GetSingleton(); + MOZ_COUNTED_DTOR(ContentProcessManager); + + /** + * Add a new content process into the map. + */ + void AddContentProcess(ContentParent* aChildCp); + + /** + * Remove the content process by id. + */ + void RemoveContentProcess(const ContentParentId& aChildCpId); + + /** + * Return the ContentParent pointer by id. + */ + ContentParent* GetContentProcessById(const ContentParentId& aChildCpId); + + /** + * Add a new browser parent into the map. + */ + bool RegisterRemoteFrame(BrowserParent* aChildBp); + + /** + * Remove the browser parent by the given tab id. + */ + void UnregisterRemoteFrame(const TabId& aChildTabId); + + /** + * Get the ContentParentId of the parent of the given tab id. + */ + ContentParentId GetTabProcessId(const TabId& aChildTabId); + + /** + * Get the number of BrowserParents managed by the givent content process. + * Return 0 when ContentParent couldn't be found via aChildCpId. + */ + uint32_t GetBrowserParentCountByProcessId(const ContentParentId& aChildCpId); + + /** + * Get the BrowserParent by the given content process and tab id. + * Return nullptr when BrowserParent couldn't be found via aChildCpId + * and aChildTabId. + */ + already_AddRefed<BrowserParent> GetBrowserParentByProcessAndTabId( + const ContentParentId& aChildCpId, const TabId& aChildTabId); + + /** + * Get the BrowserParent on top level by the given content process and tab id. + * + * This function returns the BrowserParent directly within a BrowserHost, + * called top-level BrowserParent here, by given aChildCpId and aChildTabId. + * The given aChildCpId and aChildTabId are related to a content process + * and a tab respectively. + */ + already_AddRefed<BrowserParent> GetTopLevelBrowserParentByProcessAndTabId( + const ContentParentId& aChildCpId, const TabId& aChildTabId); + + private: + static StaticAutoPtr<ContentProcessManager> sSingleton; + + nsTHashMap<nsUint64HashKey, ContentParent*> mContentParentMap; + nsTHashMap<nsUint64HashKey, BrowserParent*> mBrowserParentMap; + + MOZ_COUNTED_DEFAULT_CTOR(ContentProcessManager); +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_ContentProcessManager_h diff --git a/dom/ipc/CustomElementTypes.ipdlh b/dom/ipc/CustomElementTypes.ipdlh new file mode 100644 index 0000000000..bdbf004ca9 --- /dev/null +++ b/dom/ipc/CustomElementTypes.ipdlh @@ -0,0 +1,39 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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 "mozilla/dom/IPCBlobUtils.h"; + +[RefCounted] using class mozilla::dom::BlobImpl from "mozilla/dom/BlobImpl.h"; +using struct mozilla::void_t from "mozilla/ipc/IPCCore.h"; + +namespace mozilla { +namespace dom { + +// Types used to store form-associated custom element state. +union FormDataValue { + BlobImpl; + nsString; +}; + +struct FormDataTuple { + nsString name; + FormDataValue value; +}; + +union CustomElementFormValue { + void_t; + nullable BlobImpl; + nsString; + FormDataTuple[]; +}; + +struct CustomElementTuple { + CustomElementFormValue value; + CustomElementFormValue state; +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/ipc/DOMTypes.ipdlh b/dom/ipc/DOMTypes.ipdlh new file mode 100644 index 0000000000..faee4e861c --- /dev/null +++ b/dom/ipc/DOMTypes.ipdlh @@ -0,0 +1,329 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* vim: set sw=4 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 "mozilla/GfxMessageUtils.h"; +include "mozilla/dom/CSPMessageUtils.h"; +include "mozilla/dom/DocShellMessageUtils.h"; +include "mozilla/dom/PermissionMessageUtils.h"; +include "mozilla/dom/PropertyBagUtils.h"; +include "mozilla/dom/ReferrerInfoUtils.h"; +include "mozilla/dom/TabMessageUtils.h"; +include "mozilla/ipc/URIUtils.h"; +include "mozilla/layers/LayersMessageUtils.h"; +include "mozilla/net/ClassOfService.h"; + +include IPCBlob; +include IPCStream; +include ProtocolTypes; + +using struct mozilla::void_t from "mozilla/ipc/IPCCore.h"; + +[MoveOnly=data] using struct mozilla::SerializedStructuredCloneBuffer + from "mozilla/ipc/SerializedStructuredCloneBuffer.h"; + +using struct mozilla::dom::LoadingSessionHistoryInfo + from "mozilla/dom/SessionHistoryEntry.h"; + +using mozilla::net::ClassOfService from "mozilla/net/ClassOfService.h"; + + +using mozilla::hal::ScreenOrientation from "mozilla/HalIPCUtils.h"; +using mozilla::LayoutDeviceIntRect from "Units.h"; +using mozilla::DesktopIntRect from "Units.h"; +using mozilla::DesktopToLayoutDeviceScale from "Units.h"; +using mozilla::CSSToLayoutDeviceScale from "Units.h"; +using mozilla::CSSRect from "Units.h"; +using mozilla::CSSSize from "Units.h"; +using mozilla::ScreenIntSize from "Units.h"; +using mozilla::LayoutDeviceIntPoint from "Units.h"; +using nsSizeMode from "nsIWidgetListener.h"; +using mozilla::ScrollbarPreference from "mozilla/ScrollbarPreferences.h"; +using mozilla::gfx::SurfaceFormat from "mozilla/gfx/Types.h"; +[RefCounted] using class nsIPrincipal from "nsIPrincipal.h"; +using mozilla::dom::MaybeDiscardedBrowsingContext from "mozilla/dom/BrowsingContext.h"; +[RefCounted] using class nsIURI from "nsIURI.h"; +[RefCounted] using class nsIContentSecurityPolicy from "nsIContentSecurityPolicy.h"; +[RefCounted] using class nsIInputStream from "mozilla/ipc/IPCStreamUtils.h"; +[RefCounted] using class nsIReferrerInfo from "nsIReferrerInfo.h"; +[RefCounted] using class nsIVariant from "nsIVariant.h"; +using mozilla::TimeStamp from "mozilla/TimeStamp.h"; +[RefCounted] using class mozilla::RemoteLazyInputStream from "mozilla/RemoteLazyInputStream.h"; +[MoveOnly] using class mozilla::ipc::BigBuffer from "mozilla/ipc/BigBuffer.h"; + +namespace mozilla { +namespace dom { + +struct MessagePortIdentifier +{ + nsID uuid; + nsID destinationUuid; + uint32_t sequenceId; + bool neutered; +}; + +/** + * Cross-process representation for postMessage() style payloads where Blobs may + * be referenced/"cloned" and (optionally) messageports transferred. Use + * StructuredCloneData in your code to convert between this wire representation + * and the StructuredCloneData StructuredCloneHolder-subclass. + */ +struct ClonedMessageData +{ + SerializedStructuredCloneBuffer data; + IPCBlob[] blobs; + IPCStream[] inputStreams; + MessagePortIdentifier[] identifiers; +}; + +struct ErrorMessageData { +}; + +union ClonedOrErrorMessageData { + ClonedMessageData; + ErrorMessageData; +}; + +struct RefMessageData { + nsID uuid; +}; + +union MessageDataType { + ClonedMessageData; + RefMessageData; +}; + +struct MessageData { + nsID? agentClusterId; + MessageDataType data; +}; + +struct ScreenDetails { + LayoutDeviceIntRect rect; + DesktopIntRect rectDisplayPix; + LayoutDeviceIntRect availRect; + DesktopIntRect availRectDisplayPix; + int32_t pixelDepth; + int32_t colorDepth; + uint32_t refreshRate; // In Hz, or 0 if not known. + DesktopToLayoutDeviceScale contentsScaleFactor; + CSSToLayoutDeviceScale defaultCSSScaleFactor; + float dpi; + ScreenOrientation orientation; + uint16_t orientationAngle; + bool isPseudoDisplay; +}; + +struct DimensionInfo +{ + CSSRect rect; + CSSSize size; + LayoutDeviceIntPoint clientOffset; + LayoutDeviceIntPoint chromeOffset; +}; + +struct FrameScriptInfo +{ + nsString url; + bool runInGlobalScope; +}; + +struct FeaturePolicyInfo +{ + nsString[] inheritedDeniedFeatureNames; + nsString[] attributeEnabledFeatureNames; + nsString declaredString; + nullable nsIPrincipal defaultOrigin; + nullable nsIPrincipal selfOrigin; + nullable nsIPrincipal srcOrigin; +}; + +/** + * The information required to complete a window creation request. + */ +struct CreatedWindowInfo +{ + nsresult rv; + bool windowOpened; + FrameScriptInfo[] frameScripts; + uint32_t maxTouchPoints; + DimensionInfo dimensions; +}; + + +struct DocShellLoadStateInit +{ + nullable nsIURI URI; + nullable nsIURI OriginalURI; + nullable nsIURI ResultPrincipalURI; + nullable nsIPrincipal TriggeringPrincipal; + nullable nsIReferrerInfo ReferrerInfo; + nullable nsIPrincipal PrincipalToInherit; + nullable nsIPrincipal PartitionedPrincipalToInherit; + nullable nsIURI BaseURI; + // The Content Security Policy of the load, that is, the CSP of the entity + // responsible for causing the load to occur. Most likely this is the CSP + // of the document that started the load. In case the entity starting the + // load did not use a CSP, then Csp can be null. Please note that this is + // also the CSP that will be applied to the load in case the load + // encounters a server side redirect. + nullable nsIContentSecurityPolicy Csp; + nullable nsIInputStream PostDataStream; + nullable nsIInputStream HeadersStream; + nullable nsIURI UnstrippedURI; + uint64_t LoadIdentifier; + nsString Target; + nsCString TypeHint; + nsString FileName; + + MaybeDiscardedBrowsingContext SourceBrowsingContext; + MaybeDiscardedBrowsingContext TargetBrowsingContext; + + // The provided remote type of the process responsible for causing the load to + // occur. Validated in the parent process. + nsCString TriggeringRemoteType; + + nsString SrcdocData; // useless without sourcedocshell + + nsCString? OriginalURIString; + + nsCString? RemoteTypeOverride; + + LoadingSessionHistoryInfo? loadingSessionHistoryInfo; + + uint32_t LoadType; + uint32_t LoadFlags; + uint32_t InternalLoadFlags; + + // The TriggineringSandboxFlags are the SandboxFlags of the entity + // responsible for causing the load to occur. + uint32_t TriggeringSandboxFlags; + uint64_t TriggeringWindowId; + bool TriggeringStorageAccess; + int32_t? CancelContentJSEpoch; + + bool ResultPrincipalURIIsSome; + bool KeepResultPrincipalURIIfSet; + bool LoadReplace; + bool InheritPrincipal; + bool PrincipalIsExplicit; + bool ForceAllowDataURI; + bool IsExemptFromHTTPSFirstMode; + bool OriginalFrameSrc; + bool IsFormSubmission; + bool FirstParty; + bool HasValidUserGestureActivation; + bool AllowFocusMove; + bool IsFromProcessingFrameAttributes; + bool WasSchemelessInput; + + // Fields missing due to lack of need or serialization + // nsCOMPtr<nsIDocShell> mSourceDocShell; + // bool mIsSrcDocLoad; // useless without sourcedocshell + // nsIChannel pendingRedirectedChannel; // sent through other mechanism + + bool ChannelInitialized; + + bool TryToReplaceWithSessionHistoryLoad; + + bool IsMetaRefresh; +}; + +struct TimedChannelInfo +{ + bool timingEnabled; + int8_t redirectCount; + int8_t internalRedirectCount; + TimeStamp asyncOpen; + TimeStamp channelCreation; + TimeStamp redirectStart; + TimeStamp redirectEnd; + nsString initiatorType; + bool allRedirectsSameOrigin; + bool allRedirectsPassTimingAllowCheck; + bool? timingAllowCheckForPrincipal; + TimeStamp launchServiceWorkerStart; + TimeStamp launchServiceWorkerEnd; + TimeStamp dispatchFetchEventStart; + TimeStamp dispatchFetchEventEnd; + TimeStamp handleFetchEventStart; + TimeStamp handleFetchEventEnd; + TimeStamp responseStart; + TimeStamp responseEnd; +}; + +struct ReplacementChannelConfigInit +{ + uint32_t redirectFlags; + ClassOfService classOfService; + bool? privateBrowsing; + nsCString? method; + nullable nsIReferrerInfo referrerInfo; + TimedChannelInfo? timedChannelInfo; + nullable RemoteLazyInputStream uploadStream; + uint64_t uploadStreamLength; + bool uploadStreamHasHeaders; + nsCString? contentType; + nsCString? contentLength; +}; + +union IPDLVariantValue +{ + bool; + uint8_t; // In practice, uint8_t and uint16_t are likely unneeded, + int16_t; // as signed->unsigned->signed has universal behavior. + uint16_t; // but those conversions are only guaranteed in C++20. + int32_t; + uint32_t; + float; + double; + nsID; + nsString; + nsCString; + nullable nsIURI; + nullable nsIPrincipal; +}; + +struct IDPLVariant +{ + uint32_t type; // We explicitly store the original nsIVariant type so that + // the conversion back into a nsVariant later is lossless. + IPDLVariantValue data; +}; + +struct IPDLProperty +{ + nsString name; + nullable nsIVariant value; +}; + +// Struct with information to show a frame from the parent process. +struct ParentShowInfo +{ + nsString name; + bool fakeShowInfo; + bool isTransparent; + float dpi; + int32_t widgetRounding; + double defaultScale; +}; + +// Struct with information to show an iframe from the process that owns the +// frame. +struct OwnerShowInfo { + // This can be an IntSize rather than a Rect because content processes always + // render to a virtual <0, 0> top-left point. + ScreenIntSize size; + + // TODO(emilio): Margin preferences go here. + ScrollbarPreference scrollbarPreference; + + // TODO(emilio): I think we should really be able to figure this out from the + // parent process too instead. + nsSizeMode sizeMode; +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/ipc/DocShellMessageUtils.cpp b/dom/ipc/DocShellMessageUtils.cpp new file mode 100644 index 0000000000..011abe3246 --- /dev/null +++ b/dom/ipc/DocShellMessageUtils.cpp @@ -0,0 +1,46 @@ +/* -*- 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 "mozilla/dom/DocShellMessageUtils.h" +#include "mozilla/dom/DOMTypes.h" +#include "mozilla/ipc/IPDLParamTraits.h" +#include "nsSerializationHelper.h" + +namespace IPC { + +void ParamTraits<nsDocShellLoadState*>::Write(IPC::MessageWriter* aWriter, + nsDocShellLoadState* aParam) { + MOZ_RELEASE_ASSERT(aParam); + WriteParam(aWriter, aParam->Serialize(aWriter->GetActor())); +} + +bool ParamTraits<nsDocShellLoadState*>::Read( + IPC::MessageReader* aReader, RefPtr<nsDocShellLoadState>* aResult) { + mozilla::dom::DocShellLoadStateInit loadState; + if (!ReadParam(aReader, &loadState)) { + return false; + } + + // Assert if we somehow don't have a URI in our IPDL type, because we can't + // construct anything out of it. This mimics the assertion in the constructor + // for nsDocShellLoadState, but makes it clearer that the + // DocShellLoadStateInit IPC object can't be clearly converted into a + // nsDocShellLoadState. + if (!loadState.URI()) { + MOZ_ASSERT_UNREACHABLE("no URI in load state from IPC"); + return false; + } + + bool readSuccess = false; + RefPtr result = + new nsDocShellLoadState(loadState, aReader->GetActor(), &readSuccess); + if (readSuccess) { + *aResult = result.forget(); + } + return readSuccess; +} + +} // namespace IPC diff --git a/dom/ipc/DocShellMessageUtils.h b/dom/ipc/DocShellMessageUtils.h new file mode 100644 index 0000000000..934ba77f48 --- /dev/null +++ b/dom/ipc/DocShellMessageUtils.h @@ -0,0 +1,48 @@ +/* -*- 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_dom_docshell_message_utils_h__ +#define mozilla_dom_docshell_message_utils_h__ + +#include "ipc/EnumSerializer.h" +#include "nsCOMPtr.h" +#include "nsDocShellLoadState.h" +#include "nsIDocumentViewer.h" +#include "mozilla/ScrollbarPreferences.h" +#include "mozilla/ipc/IPDLParamTraits.h" + +namespace IPC { + +template <> +struct ParamTraits<nsDocShellLoadState*> { + static void Write(IPC::MessageWriter* aWriter, nsDocShellLoadState* aParam); + static bool Read(IPC::MessageReader* aReader, + RefPtr<nsDocShellLoadState>* aResult); +}; + +template <> +struct ParamTraits<mozilla::ScrollbarPreference> + : public ContiguousEnumSerializerInclusive< + mozilla::ScrollbarPreference, mozilla::ScrollbarPreference::Auto, + mozilla::ScrollbarPreference::LAST> {}; + +template <> +struct ParamTraits<mozilla::dom::PermitUnloadResult> + : public ContiguousEnumSerializerInclusive< + mozilla::dom::PermitUnloadResult, + mozilla::dom::PermitUnloadResult::eAllowNavigation, + mozilla::dom::PermitUnloadResult::eRequestBlockNavigation> {}; + +template <> +struct ParamTraits<mozilla::dom::XPCOMPermitUnloadAction> + : public ContiguousEnumSerializerInclusive< + mozilla::dom::XPCOMPermitUnloadAction, + mozilla::dom::XPCOMPermitUnloadAction::ePrompt, + mozilla::dom::XPCOMPermitUnloadAction::eDontPromptAndUnload> {}; + +} // namespace IPC + +#endif // mozilla_dom_docshell_message_utils_h__ diff --git a/dom/ipc/EffectsInfo.h b/dom/ipc/EffectsInfo.h new file mode 100644 index 0000000000..3c24b7a1cf --- /dev/null +++ b/dom/ipc/EffectsInfo.h @@ -0,0 +1,67 @@ +/* -*- 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_dom_EffectsInfo_h +#define mozilla_dom_EffectsInfo_h + +#include "nsRect.h" +#include "Units.h" + +namespace mozilla::dom { + +/** + * An EffectsInfo contains information for a remote browser about the graphical + * effects that are being applied to it by ancestor browsers in different + * processes. + */ +class EffectsInfo { + public: + EffectsInfo() = default; + + static EffectsInfo VisibleWithinRect( + const Maybe<nsRect>& aVisibleRect, const Scale2D& aRasterScale, + const ParentLayerToScreenScale2D& aTransformToAncestorScale) { + return EffectsInfo{aVisibleRect, aRasterScale, aTransformToAncestorScale}; + } + static EffectsInfo FullyHidden() { return {}; } + + bool operator==(const EffectsInfo& aOther) const { + return mVisibleRect == aOther.mVisibleRect && + mRasterScale == aOther.mRasterScale && + mTransformToAncestorScale == aOther.mTransformToAncestorScale; + } + bool operator!=(const EffectsInfo& aOther) const { + return !(*this == aOther); + } + + bool IsVisible() const { return mVisibleRect.isSome(); } + + // The visible rect of this browser relative to the root frame. This might be + // empty in cases where we might still be considered visible, like if we're + // zero-size but inside the viewport. + Maybe<nsRect> mVisibleRect; + // The desired scale factors to apply to rasterized content to match + // transforms applied in ancestor browsers. This gets propagated into the + // scale in StackingContextHelper. + Scale2D mRasterScale; + // TransformToAncestorScale to be set on FrameMetrics. It includes CSS + // transform scales and cumulative presshell resolution. + ParentLayerToScreenScale2D mTransformToAncestorScale; + + // If you add new fields here, you must also update operator== and + // TabMessageUtils. + + private: + EffectsInfo(const Maybe<nsRect>& aVisibleRect, const Scale2D& aRasterScale, + const ParentLayerToScreenScale2D& aTransformToAncestorScale) + : mVisibleRect(aVisibleRect), + mRasterScale(aRasterScale), + mTransformToAncestorScale(aTransformToAncestorScale) {} +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_EffectsInfo_h diff --git a/dom/ipc/FilePickerMessageUtils.h b/dom/ipc/FilePickerMessageUtils.h new file mode 100644 index 0000000000..2f4338c8a1 --- /dev/null +++ b/dom/ipc/FilePickerMessageUtils.h @@ -0,0 +1,34 @@ +/* -*- 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_dom_filepicker_message_utils_h__ +#define mozilla_dom_filepicker_message_utils_h__ + +#include "ipc/EnumSerializer.h" +#include "nsIFilePicker.h" + +namespace IPC { +template <> +struct ParamTraits<nsIFilePicker::Mode> + : public ContiguousEnumSerializerInclusive< + nsIFilePicker::Mode, nsIFilePicker::Mode::modeOpen, + nsIFilePicker::Mode::modeOpenMultiple> {}; + +template <> +struct ParamTraits<nsIFilePicker::CaptureTarget> + : public ContiguousEnumSerializerInclusive< + nsIFilePicker::CaptureTarget, + nsIFilePicker::CaptureTarget::captureNone, + nsIFilePicker::CaptureTarget::captureEnv> {}; + +template <> +struct ParamTraits<nsIFilePicker::ResultCode> + : public ContiguousEnumSerializerInclusive< + nsIFilePicker::ResultCode, nsIFilePicker::ResultCode::returnOK, + nsIFilePicker::ResultCode::returnReplace> {}; +} // namespace IPC + +#endif // mozilla_dom_filepicker_message_utils_h__ diff --git a/dom/ipc/FilePickerParent.cpp b/dom/ipc/FilePickerParent.cpp new file mode 100644 index 0000000000..ebd24cb0d3 --- /dev/null +++ b/dom/ipc/FilePickerParent.cpp @@ -0,0 +1,304 @@ +/* -*- 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 "FilePickerParent.h" +#include "nsComponentManagerUtils.h" +#include "nsNetCID.h" +#include "mozilla/dom/Document.h" +#include "nsIFile.h" +#include "nsISimpleEnumerator.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/FileBlobImpl.h" +#include "mozilla/dom/FileSystemSecurity.h" +#include "mozilla/dom/IPCBlobUtils.h" + +using mozilla::Unused; +using namespace mozilla::dom; + +NS_IMPL_ISUPPORTS(FilePickerParent::FilePickerShownCallback, + nsIFilePickerShownCallback); + +NS_IMETHODIMP +FilePickerParent::FilePickerShownCallback::Done( + nsIFilePicker::ResultCode aResult) { + if (mFilePickerParent) { + mFilePickerParent->Done(aResult); + } + return NS_OK; +} + +void FilePickerParent::FilePickerShownCallback::Destroy() { + mFilePickerParent = nullptr; +} + +FilePickerParent::~FilePickerParent() = default; + +// We run code in three places: +// 1. The main thread calls Dispatch() to start the runnable. +// 2. The stream transport thread stat()s the file in Run() and then dispatches +// the same runnable on the main thread. +// 3. The main thread sends the results over IPC. +FilePickerParent::IORunnable::IORunnable(FilePickerParent* aFPParent, + nsTArray<nsCOMPtr<nsIFile>>&& aFiles, + bool aIsDirectory) + : mozilla::Runnable("dom::FilePickerParent::IORunnable"), + mFilePickerParent(aFPParent), + mFiles(std::move(aFiles)), + mIsDirectory(aIsDirectory) { + MOZ_ASSERT_IF(aIsDirectory, mFiles.Length() == 1); +} + +bool FilePickerParent::IORunnable::Dispatch() { + MOZ_ASSERT(NS_IsMainThread()); + + mEventTarget = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + if (!mEventTarget) { + return false; + } + + nsresult rv = mEventTarget->Dispatch(this, NS_DISPATCH_NORMAL); + return NS_SUCCEEDED(rv); +} + +NS_IMETHODIMP +FilePickerParent::IORunnable::Run() { + // If we're on the main thread, then that means we're done. Just send the + // results. + if (NS_IsMainThread()) { + if (mFilePickerParent) { + mFilePickerParent->SendFilesOrDirectories(mResults); + } + return NS_OK; + } + + // We're not on the main thread, so do the IO. + + for (uint32_t i = 0; i < mFiles.Length(); ++i) { + if (mIsDirectory) { + nsAutoString path; + nsresult rv = mFiles[i]->GetPath(path); + if (NS_WARN_IF(NS_FAILED(rv))) { + continue; + } + + BlobImplOrString* data = mResults.AppendElement(); + data->mType = BlobImplOrString::eDirectoryPath; + data->mDirectoryPath = path; + continue; + } + + RefPtr<BlobImpl> blobImpl = new FileBlobImpl(mFiles[i]); + + ErrorResult error; + blobImpl->GetSize(error); + if (NS_WARN_IF(error.Failed())) { + error.SuppressException(); + continue; + } + + blobImpl->GetLastModified(error); + if (NS_WARN_IF(error.Failed())) { + error.SuppressException(); + continue; + } + + BlobImplOrString* data = mResults.AppendElement(); + data->mType = BlobImplOrString::eBlobImpl; + data->mBlobImpl = blobImpl; + } + + // Dispatch ourselves back on the main thread. + if (NS_FAILED(NS_DispatchToMainThread(this))) { + // It's hard to see how we can recover gracefully in this case. The child + // process is waiting for an IPC, but that can only happen on the main + // thread. + MOZ_CRASH(); + } + + return NS_OK; +} + +void FilePickerParent::IORunnable::Destroy() { mFilePickerParent = nullptr; } + +void FilePickerParent::SendFilesOrDirectories( + const nsTArray<BlobImplOrString>& aData) { + ContentParent* parent = BrowserParent::GetFrom(Manager())->Manager(); + + if (mMode == nsIFilePicker::modeGetFolder) { + MOZ_ASSERT(aData.Length() <= 1); + if (aData.IsEmpty()) { + Unused << Send__delete__(this, void_t(), mResult); + return; + } + + MOZ_ASSERT(aData[0].mType == BlobImplOrString::eDirectoryPath); + + // Let's inform the security singleton about the given access of this tab on + // this directory path. + RefPtr<FileSystemSecurity> fss = FileSystemSecurity::GetOrCreate(); + fss->GrantAccessToContentProcess(parent->ChildID(), + aData[0].mDirectoryPath); + + InputDirectory input; + input.directoryPath() = aData[0].mDirectoryPath; + Unused << Send__delete__(this, input, mResult); + return; + } + + nsTArray<IPCBlob> ipcBlobs; + + for (unsigned i = 0; i < aData.Length(); i++) { + IPCBlob ipcBlob; + + MOZ_ASSERT(aData[i].mType == BlobImplOrString::eBlobImpl); + nsresult rv = IPCBlobUtils::Serialize(aData[i].mBlobImpl, ipcBlob); + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + + ipcBlobs.AppendElement(ipcBlob); + } + + InputBlobs inblobs; + inblobs.blobs() = std::move(ipcBlobs); + + Unused << Send__delete__(this, inblobs, mResult); +} + +void FilePickerParent::Done(nsIFilePicker::ResultCode aResult) { + mResult = aResult; + + if (mResult != nsIFilePicker::returnOK) { + Unused << Send__delete__(this, void_t(), mResult); + return; + } + + nsTArray<nsCOMPtr<nsIFile>> files; + if (mMode == nsIFilePicker::modeOpenMultiple) { + nsCOMPtr<nsISimpleEnumerator> iter; + NS_ENSURE_SUCCESS_VOID(mFilePicker->GetFiles(getter_AddRefs(iter))); + + nsCOMPtr<nsISupports> supports; + bool loop = true; + while (NS_SUCCEEDED(iter->HasMoreElements(&loop)) && loop) { + iter->GetNext(getter_AddRefs(supports)); + if (supports) { + nsCOMPtr<nsIFile> file = do_QueryInterface(supports); + MOZ_ASSERT(file); + files.AppendElement(file); + } + } + } else { + nsCOMPtr<nsIFile> file; + mFilePicker->GetFile(getter_AddRefs(file)); + if (file) { + files.AppendElement(file); + } + } + + if (files.IsEmpty()) { + Unused << Send__delete__(this, void_t(), mResult); + return; + } + + MOZ_ASSERT(!mRunnable); + mRunnable = new IORunnable(this, std::move(files), + mMode == nsIFilePicker::modeGetFolder); + + // Dispatch to background thread to do I/O: + if (!mRunnable->Dispatch()) { + Unused << Send__delete__(this, void_t(), nsIFilePicker::returnCancel); + } +} + +bool FilePickerParent::CreateFilePicker() { + mFilePicker = do_CreateInstance("@mozilla.org/filepicker;1"); + if (!mFilePicker) { + return false; + } + + auto* browserParent = BrowserParent::GetFrom(Manager()); + auto* browsingContext = browserParent->GetBrowsingContext(); + Element* element = browserParent->GetOwnerElement(); + if (!element) { + return false; + } + + nsCOMPtr<mozIDOMWindowProxy> window = element->OwnerDoc()->GetWindow(); + if (!window) { + return false; + } + + return NS_SUCCEEDED( + mFilePicker->Init(window, mTitle, mMode, browsingContext)); +} + +mozilla::ipc::IPCResult FilePickerParent::RecvOpen( + const int16_t& aSelectedType, const bool& aAddToRecentDocs, + const nsString& aDefaultFile, const nsString& aDefaultExtension, + nsTArray<nsString>&& aFilters, nsTArray<nsString>&& aFilterNames, + nsTArray<nsString>&& aRawFilters, const nsString& aDisplayDirectory, + const nsString& aDisplaySpecialDirectory, const nsString& aOkButtonLabel, + const nsIFilePicker::CaptureTarget& aCapture) { + if (!CreateFilePicker()) { + Unused << Send__delete__(this, void_t(), nsIFilePicker::returnCancel); + return IPC_OK(); + } + + mFilePicker->SetAddToRecentDocs(aAddToRecentDocs); + + for (uint32_t i = 0; i < aFilters.Length(); ++i) { + mFilePicker->AppendFilter(aFilterNames[i], aFilters[i]); + } + + for (uint32_t i = 0; i < aRawFilters.Length(); ++i) { + mFilePicker->AppendRawFilter(aRawFilters[i]); + } + + mFilePicker->SetDefaultString(aDefaultFile); + mFilePicker->SetDefaultExtension(aDefaultExtension); + mFilePicker->SetFilterIndex(aSelectedType); + mFilePicker->SetOkButtonLabel(aOkButtonLabel); + mFilePicker->SetCapture(aCapture); + + if (!aDisplayDirectory.IsEmpty()) { + nsCOMPtr<nsIFile> localFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); + if (localFile) { + localFile->InitWithPath(aDisplayDirectory); + mFilePicker->SetDisplayDirectory(localFile); + } + } else if (!aDisplaySpecialDirectory.IsEmpty()) { + mFilePicker->SetDisplaySpecialDirectory(aDisplaySpecialDirectory); + } + + MOZ_ASSERT(!mCallback); + mCallback = new FilePickerShownCallback(this); + + mFilePicker->Open(mCallback); + return IPC_OK(); +} + +mozilla::ipc::IPCResult FilePickerParent::RecvClose() { + if (mFilePicker) { + mFilePicker->Close(); + } + return IPC_OK(); +} + +void FilePickerParent::ActorDestroy(ActorDestroyReason aWhy) { + if (mCallback) { + mCallback->Destroy(); + mCallback = nullptr; + } + if (mRunnable) { + mRunnable->Destroy(); + mRunnable = nullptr; + } +} diff --git a/dom/ipc/FilePickerParent.h b/dom/ipc/FilePickerParent.h new file mode 100644 index 0000000000..f0fe0dc2d0 --- /dev/null +++ b/dom/ipc/FilePickerParent.h @@ -0,0 +1,101 @@ +/* -*- 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_dom_FilePickerParent_h +#define mozilla_dom_FilePickerParent_h + +#include "nsIEventTarget.h" +#include "nsIFilePicker.h" +#include "nsCOMArray.h" +#include "nsThreadUtils.h" +#include "mozilla/dom/File.h" +#include "mozilla/dom/PFilePickerParent.h" + +class nsIFile; + +namespace mozilla::dom { + +class FilePickerParent : public PFilePickerParent { + public: + FilePickerParent(const nsString& aTitle, const nsIFilePicker::Mode& aMode) + : mTitle(aTitle), mMode(aMode), mResult(nsIFilePicker::returnOK) {} + + private: + virtual ~FilePickerParent(); + + public: + NS_INLINE_DECL_REFCOUNTING(FilePickerParent, final) + + void Done(nsIFilePicker::ResultCode aResult); + + struct BlobImplOrString { + RefPtr<BlobImpl> mBlobImpl; + nsString mDirectoryPath; + + enum { eBlobImpl, eDirectoryPath } mType; + }; + + void SendFilesOrDirectories(const nsTArray<BlobImplOrString>& aData); + + mozilla::ipc::IPCResult RecvOpen( + const int16_t& aSelectedType, const bool& aAddToRecentDocs, + const nsString& aDefaultFile, const nsString& aDefaultExtension, + nsTArray<nsString>&& aFilters, nsTArray<nsString>&& aFilterNames, + nsTArray<nsString>&& aRawFilters, const nsString& aDisplayDirectory, + const nsString& aDisplaySpecialDirectory, const nsString& aOkButtonLabel, + const nsIFilePicker::CaptureTarget& aCapture); + + mozilla::ipc::IPCResult RecvClose(); + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + class FilePickerShownCallback : public nsIFilePickerShownCallback { + public: + explicit FilePickerShownCallback(FilePickerParent* aFilePickerParent) + : mFilePickerParent(aFilePickerParent) {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIFILEPICKERSHOWNCALLBACK + + void Destroy(); + + private: + virtual ~FilePickerShownCallback() = default; + RefPtr<FilePickerParent> mFilePickerParent; + }; + + private: + bool CreateFilePicker(); + + // This runnable is used to do some I/O operation on a separate thread. + class IORunnable : public Runnable { + RefPtr<FilePickerParent> mFilePickerParent; + nsTArray<nsCOMPtr<nsIFile>> mFiles; + nsTArray<BlobImplOrString> mResults; + nsCOMPtr<nsIEventTarget> mEventTarget; + bool mIsDirectory; + + public: + IORunnable(FilePickerParent* aFPParent, + nsTArray<nsCOMPtr<nsIFile>>&& aFiles, bool aIsDirectory); + + bool Dispatch(); + NS_IMETHOD Run() override; + void Destroy(); + }; + + RefPtr<IORunnable> mRunnable; + RefPtr<FilePickerShownCallback> mCallback; + nsCOMPtr<nsIFilePicker> mFilePicker; + + nsString mTitle; + nsIFilePicker::Mode mMode; + nsIFilePicker::ResultCode mResult; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_FilePickerParent_h diff --git a/dom/ipc/IPCTransferable.ipdlh b/dom/ipc/IPCTransferable.ipdlh new file mode 100644 index 0000000000..1e277d05dd --- /dev/null +++ b/dom/ipc/IPCTransferable.ipdlh @@ -0,0 +1,93 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* vim: set sw=4 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 "mozilla/GfxMessageUtils.h"; +include "mozilla/dom/PermissionMessageUtils.h"; + +include IPCBlob; +include NeckoChannelParams; + +using mozilla::gfx::SurfaceFormat from "mozilla/gfx/Types.h"; +[RefCounted] using class nsIPrincipal from "nsIPrincipal.h"; +[RefCounted] using class nsIReferrerInfo from "nsIReferrerInfo.h"; +[MoveOnly] using class mozilla::ipc::BigBuffer from "mozilla/ipc/BigBuffer.h"; + +namespace mozilla { +namespace dom { + +struct IPCTransferableDataString +{ + BigBuffer data; +}; + +struct IPCTransferableDataCString +{ + BigBuffer data; +}; + +struct IPCTransferableDataInputStream +{ + // NOTE: Editor currently relies on these input streams being synchronous, so + // we can't safely serialize them using IPCStream (see bug 1778565). Instead, + // they're serialized as a `BigBuffer`, and converted to a nsStringInputStream + // on the receiving side. If we are able to use async streams reliably in the + // future, we could consider switching the code which adds `nsIInputStream`s + // to the transferable to use `BlobImpl` instead, for more consistency between + // image formats. + BigBuffer data; +}; + +struct IPCTransferableDataImageContainer +{ + BigBuffer data; + uint32_t width; + uint32_t height; + uint32_t stride; + SurfaceFormat format; +}; + +struct IPCTransferableDataBlob +{ + IPCBlob blob; +}; + +union IPCTransferableDataType +{ + IPCTransferableDataString; + IPCTransferableDataCString; + IPCTransferableDataInputStream; + IPCTransferableDataImageContainer; + IPCTransferableDataBlob; +}; + +struct IPCTransferableDataItem +{ + nsCString flavor; + IPCTransferableDataType data; +}; + +struct IPCTransferableData +{ + IPCTransferableDataItem[] items; +}; + +union IPCTransferableDataOrError { + IPCTransferableData; + nsresult; +}; + +struct IPCTransferable +{ + IPCTransferableData data; + bool isPrivateData; + nullable nsIPrincipal requestingPrincipal; + CookieJarSettingsArgs? cookieJarSettings; + nsContentPolicyType contentPolicyType; + nullable nsIReferrerInfo referrerInfo; +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/ipc/IdType.h b/dom/ipc/IdType.h new file mode 100644 index 0000000000..ccf8b52693 --- /dev/null +++ b/dom/ipc/IdType.h @@ -0,0 +1,64 @@ +/* -*- 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_dom_IdType_h +#define mozilla_dom_IdType_h + +#include "ipc/IPCMessageUtils.h" + +namespace IPC { +template <typename T> +struct ParamTraits; +} // namespace IPC + +namespace mozilla::dom { +class BrowsingContext; +class ContentParent; +class BrowserParent; + +template <typename T> +class IdType { + friend struct IPC::ParamTraits<IdType<T>>; + + public: + IdType() : mId(0) {} + explicit IdType(uint64_t aId) : mId(aId) {} + + operator uint64_t() const { return mId; } + + IdType& operator=(uint64_t aId) { + mId = aId; + return *this; + } + + bool operator<(const IdType& rhs) { return mId < rhs.mId; } + + private: + uint64_t mId; +}; + +using TabId = IdType<BrowserParent>; +using ContentParentId = IdType<ContentParent>; +} // namespace mozilla::dom + +namespace IPC { + +template <typename T> +struct ParamTraits<mozilla::dom::IdType<T>> { + using paramType = mozilla::dom::IdType<T>; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.mId); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &aResult->mId); + } +}; + +} // namespace IPC + +#endif // mozilla_dom_IdType_h diff --git a/dom/ipc/InProcessChild.h b/dom/ipc/InProcessChild.h new file mode 100644 index 0000000000..16176ef98b --- /dev/null +++ b/dom/ipc/InProcessChild.h @@ -0,0 +1,69 @@ +/* -*- 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_dom_InProcessChild_h +#define mozilla_dom_InProcessChild_h + +#include "mozilla/dom/PInProcessChild.h" +#include "mozilla/dom/JSProcessActorChild.h" +#include "mozilla/dom/ProcessActor.h" +#include "mozilla/dom/RemoteType.h" +#include "mozilla/StaticPtr.h" +#include "nsIDOMProcessChild.h" + +namespace mozilla::dom { +class PWindowGlobalParent; +class PWindowGlobalChild; +class InProcessParent; + +/** + * The `InProcessChild` class represents the child half of a main-thread to + * main-thread actor. + * + * The `PInProcess` actor should be used as an alternate manager to `PContent` + * for async actors which want to communicate uniformly between Content->Chrome + * and Chrome->Chrome situations. + */ +class InProcessChild final : public nsIDOMProcessChild, + public PInProcessChild, + public ProcessActor { + public: + friend class InProcessParent; + friend class PInProcessChild; + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMPROCESSCHILD + + // Get the singleton instance of this actor. + static InProcessChild* Singleton(); + + // Get the parent side of the in-process child actor |aActor|. If |aActor| is + // not an in-process actor, or is not connected, this method will return + // |nullptr|. + static IProtocol* ParentActorFor(IProtocol* aActor); + + const nsACString& GetRemoteType() const override { return NOT_REMOTE_TYPE; } + + protected: + already_AddRefed<JSActor> InitJSActor(JS::Handle<JSObject*> aMaybeActor, + const nsACString& aName, + ErrorResult& aRv) override; + mozilla::ipc::IProtocol* AsNativeActor() override { return this; } + + private: + // NOTE: PInProcess lifecycle management is declared as staic methods and + // state on InProcessParent, and implemented in InProcessImpl.cpp. + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + ~InProcessChild() = default; + + static StaticRefPtr<InProcessChild> sSingleton; + + nsRefPtrHashtable<nsCStringHashKey, JSProcessActorChild> mProcessActors; +}; + +} // namespace mozilla::dom + +#endif // defined(mozilla_dom_InProcessChild_h) diff --git a/dom/ipc/InProcessImpl.cpp b/dom/ipc/InProcessImpl.cpp new file mode 100644 index 0000000000..e076a84c44 --- /dev/null +++ b/dom/ipc/InProcessImpl.cpp @@ -0,0 +1,324 @@ +/* -*- 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 "mozilla/dom/InProcessParent.h" +#include "mozilla/dom/InProcessChild.h" +#include "mozilla/dom/JSProcessActorBinding.h" +#include "nsIObserverService.h" +#include "mozilla/Services.h" + +using namespace mozilla::ipc; + +// This file contains the implementation of core InProcess lifecycle management +// facilities. + +namespace mozilla::dom { + +StaticRefPtr<InProcessParent> InProcessParent::sSingleton; +StaticRefPtr<InProcessChild> InProcessChild::sSingleton; +bool InProcessParent::sShutdown = false; + +////////////////////////////////////////// +// InProcess actor lifecycle management // +////////////////////////////////////////// + +/* static */ +InProcessChild* InProcessChild::Singleton() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!sSingleton) { + InProcessParent::Startup(); + } + return sSingleton; +} + +/* static */ +InProcessParent* InProcessParent::Singleton() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!sSingleton) { + InProcessParent::Startup(); + } + return sSingleton; +} + +/* static */ +void InProcessParent::Startup() { + MOZ_ASSERT(NS_IsMainThread()); + + if (sShutdown) { + NS_WARNING("Could not get in-process actor while shutting down!"); + return; + } + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (!obs) { + sShutdown = true; + NS_WARNING("Failed to get nsIObserverService for in-process actor"); + return; + } + + RefPtr<InProcessParent> parent = new InProcessParent(); + RefPtr<InProcessChild> child = new InProcessChild(); + + // Observe the shutdown event to close & clean up after ourselves. + nsresult rv = obs->AddObserver(parent, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + // Link the two actors + if (!child->OpenOnSameThread(parent, ChildSide)) { + MOZ_CRASH("Failed to open InProcessChild!"); + } + + parent->SetOtherProcessId(base::GetCurrentProcId()); + + // Stash global references to fetch the other side of the reference. + InProcessParent::sSingleton = std::move(parent); + InProcessChild::sSingleton = std::move(child); +} + +/* static */ +void InProcessParent::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!sSingleton || sShutdown) { + return; + } + + sShutdown = true; + + RefPtr<InProcessParent> parent = sSingleton; + InProcessParent::sSingleton = nullptr; + InProcessChild::sSingleton = nullptr; + + // Calling `Close` on the actor will cause the `Dealloc` methods to be called, + // freeing the remaining references. + parent->Close(); +} + +NS_IMETHODIMP +InProcessParent::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + MOZ_ASSERT(!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)); + InProcessParent::Shutdown(); + return NS_OK; +} + +void InProcessParent::ActorDestroy(ActorDestroyReason aWhy) { + JSActorDidDestroy(); + InProcessParent::Shutdown(); +} + +void InProcessChild::ActorDestroy(ActorDestroyReason aWhy) { + JSActorDidDestroy(); + InProcessParent::Shutdown(); +} + +///////////////////////// +// nsIDOMProcessParent // +///////////////////////// + +NS_IMETHODIMP +InProcessParent::GetChildID(uint64_t* aChildID) { + *aChildID = 0; + return NS_OK; +} + +NS_IMETHODIMP +InProcessParent::GetOsPid(int32_t* aOsPid) { + // InProcessParent always run in the parent process, + // so we can return the current process id. + *aOsPid = base::GetCurrentProcId(); + return NS_OK; +} + +NS_IMETHODIMP InProcessParent::GetRemoteType(nsACString& aRemoteType) { + aRemoteType = NOT_REMOTE_TYPE; + return NS_OK; +} + +NS_IMETHODIMP +InProcessParent::GetActor(const nsACString& aName, JSContext* aCx, + JSProcessActorParent** aActor) { + ErrorResult error; + RefPtr<JSProcessActorParent> actor = + JSActorManager::GetActor(aCx, aName, error) + .downcast<JSProcessActorParent>(); + if (error.MaybeSetPendingException(aCx)) { + return NS_ERROR_FAILURE; + } + actor.forget(aActor); + return NS_OK; +} + +NS_IMETHODIMP +InProcessParent::GetExistingActor(const nsACString& aName, + JSProcessActorParent** aActor) { + RefPtr<JSProcessActorParent> actor = + JSActorManager::GetExistingActor(aName).downcast<JSProcessActorParent>(); + actor.forget(aActor); + return NS_OK; +} + +already_AddRefed<JSActor> InProcessParent::InitJSActor( + JS::Handle<JSObject*> aMaybeActor, const nsACString& aName, + ErrorResult& aRv) { + RefPtr<JSProcessActorParent> actor; + if (aMaybeActor.get()) { + aRv = UNWRAP_OBJECT(JSProcessActorParent, aMaybeActor.get(), actor); + if (aRv.Failed()) { + return nullptr; + } + } else { + actor = new JSProcessActorParent(); + } + + MOZ_RELEASE_ASSERT(!actor->Manager(), + "mManager was already initialized once!"); + actor->Init(aName, this); + return actor.forget(); +} + +NS_IMETHODIMP +InProcessParent::GetCanSend(bool* aCanSend) { + *aCanSend = CanSend(); + return NS_OK; +} + +ContentParent* InProcessParent::AsContentParent() { return nullptr; } + +JSActorManager* InProcessParent::AsJSActorManager() { return this; } + +//////////////////////// +// nsIDOMProcessChild // +//////////////////////// + +NS_IMETHODIMP +InProcessChild::GetChildID(uint64_t* aChildID) { + *aChildID = 0; + return NS_OK; +} + +NS_IMETHODIMP +InProcessChild::GetActor(const nsACString& aName, JSContext* aCx, + JSProcessActorChild** aActor) { + ErrorResult error; + RefPtr<JSProcessActorChild> actor = + JSActorManager::GetActor(aCx, aName, error) + .downcast<JSProcessActorChild>(); + if (error.MaybeSetPendingException(aCx)) { + return NS_ERROR_FAILURE; + } + actor.forget(aActor); + return NS_OK; +} + +NS_IMETHODIMP +InProcessChild::GetExistingActor(const nsACString& aName, + JSProcessActorChild** aActor) { + RefPtr<JSProcessActorChild> actor = + JSActorManager::GetExistingActor(aName).downcast<JSProcessActorChild>(); + actor.forget(aActor); + return NS_OK; +} + +already_AddRefed<JSActor> InProcessChild::InitJSActor( + JS::Handle<JSObject*> aMaybeActor, const nsACString& aName, + ErrorResult& aRv) { + RefPtr<JSProcessActorChild> actor; + if (aMaybeActor.get()) { + aRv = UNWRAP_OBJECT(JSProcessActorChild, aMaybeActor.get(), actor); + if (aRv.Failed()) { + return nullptr; + } + } else { + actor = new JSProcessActorChild(); + } + + MOZ_RELEASE_ASSERT(!actor->Manager(), + "mManager was already initialized once!"); + actor->Init(aName, this); + return actor.forget(); +} + +NS_IMETHODIMP +InProcessChild::GetCanSend(bool* aCanSend) { + *aCanSend = CanSend(); + return NS_OK; +} + +ContentChild* InProcessChild::AsContentChild() { return nullptr; } + +JSActorManager* InProcessChild::AsJSActorManager() { return this; } + +//////////////////////////////// +// In-Process Actor Utilities // +//////////////////////////////// + +// Helper method for implementing ParentActorFor and ChildActorFor. +static IProtocol* GetOtherInProcessActor(IProtocol* aActor) { + MOZ_ASSERT(aActor->GetSide() != UnknownSide, "bad unknown side"); + + // Discover the manager of aActor which is PInProcess. + IProtocol* current = aActor; + while (current && current->CanRecv()) { + if (current->GetProtocolId() == PInProcessMsgStart) { + break; // Found the correct actor. + } + current = current->Manager(); + } + if (!current || !current->CanRecv()) { + return nullptr; // Not a live PInProcess actor, return |nullptr| + } + + MOZ_ASSERT(current->GetSide() == aActor->GetSide(), "side changed?"); + MOZ_ASSERT_IF(aActor->GetSide() == ParentSide, + current == InProcessParent::Singleton()); + MOZ_ASSERT_IF(aActor->GetSide() == ChildSide, + current == InProcessChild::Singleton()); + + // Check whether this is InProcessParent or InProcessChild, and get the other + // side's toplevel actor. + IProtocol* otherRoot = nullptr; + if (aActor->GetSide() == ParentSide) { + otherRoot = InProcessChild::Singleton(); + } else { + otherRoot = InProcessParent::Singleton(); + } + if (NS_WARN_IF(!otherRoot)) { + return nullptr; + } + + // Look up the actor on the other side, and return it. + IProtocol* otherActor = otherRoot->Lookup(aActor->Id()); + if (otherActor) { + MOZ_ASSERT(otherActor->GetSide() != UnknownSide, "bad unknown side"); + MOZ_ASSERT(otherActor->GetSide() != aActor->GetSide(), "Wrong side!"); + MOZ_ASSERT(otherActor->GetProtocolId() == aActor->GetProtocolId(), + "Wrong type of protocol!"); + } + + return otherActor; +} + +/* static */ +IProtocol* InProcessParent::ChildActorFor(IProtocol* aActor) { + MOZ_ASSERT(aActor && aActor->GetSide() == ParentSide); + return GetOtherInProcessActor(aActor); +} + +/* static */ +IProtocol* InProcessChild::ParentActorFor(IProtocol* aActor) { + MOZ_ASSERT(aActor && aActor->GetSide() == ChildSide); + return GetOtherInProcessActor(aActor); +} + +NS_IMPL_ISUPPORTS(InProcessParent, nsIDOMProcessParent, nsIObserver) +NS_IMPL_ISUPPORTS(InProcessChild, nsIDOMProcessChild) + +} // namespace mozilla::dom diff --git a/dom/ipc/InProcessParent.h b/dom/ipc/InProcessParent.h new file mode 100644 index 0000000000..112558f5bd --- /dev/null +++ b/dom/ipc/InProcessParent.h @@ -0,0 +1,74 @@ +/* -*- 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_dom_InProcessParent_h +#define mozilla_dom_InProcessParent_h + +#include "mozilla/dom/PInProcessParent.h" +#include "mozilla/dom/JSProcessActorParent.h" +#include "mozilla/dom/ProcessActor.h" +#include "mozilla/dom/RemoteType.h" +#include "mozilla/StaticPtr.h" +#include "nsIDOMProcessParent.h" + +namespace mozilla::dom { +class PWindowGlobalParent; +class PWindowGlobalChild; +class InProcessChild; + +/** + * The `InProcessParent` class represents the parent half of a main-thread to + * main-thread actor. + * + * The `PInProcess` actor should be used as an alternate manager to `PContent` + * for async actors which want to communicate uniformly between Content->Chrome + * and Chrome->Chrome situations. + */ +class InProcessParent final : public nsIDOMProcessParent, + public nsIObserver, + public PInProcessParent, + public ProcessActor { + public: + friend class InProcessChild; + friend class PInProcessParent; + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMPROCESSPARENT + NS_DECL_NSIOBSERVER + + // Get the singleton instance of this actor. + static InProcessParent* Singleton(); + + // Get the child side of the in-process child actor |aActor|. If |aActor| is + // not an in-process actor, or is not connected, this method will return + // |nullptr|. + static IProtocol* ChildActorFor(IProtocol* aActor); + + const nsACString& GetRemoteType() const override { return NOT_REMOTE_TYPE; }; + + protected: + already_AddRefed<JSActor> InitJSActor(JS::Handle<JSObject*> aMaybeActor, + const nsACString& aName, + ErrorResult& aRv) override; + mozilla::ipc::IProtocol* AsNativeActor() override { return this; } + + private: + // Lifecycle management is implemented in InProcessImpl.cpp + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + ~InProcessParent() = default; + + static void Startup(); + static void Shutdown(); + + static StaticRefPtr<InProcessParent> sSingleton; + static bool sShutdown; + + nsRefPtrHashtable<nsCStringHashKey, JSProcessActorParent> mProcessActors; +}; + +} // namespace mozilla::dom + +#endif // defined(mozilla_dom_InProcessParent_h) diff --git a/dom/ipc/JSOracleChild.cpp b/dom/ipc/JSOracleChild.cpp new file mode 100644 index 0000000000..1ac495ab87 --- /dev/null +++ b/dom/ipc/JSOracleChild.cpp @@ -0,0 +1,51 @@ +/* -*- 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 "mozilla/dom/JSOracleChild.h" + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/dom/JSValidatorChild.h" +#include "mozilla/dom/PJSValidatorChild.h" +#include "mozilla/ipc/Endpoint.h" + +using namespace mozilla::dom; + +static mozilla::StaticRefPtr<JSOracleChild> sOracleSingletonChild; + +static mozilla::StaticAutoPtr<JSFrontendContextHolder> sJSFrontendContextHolder; + +/* static */ +void JSFrontendContextHolder::MaybeInit() { + if (!sJSFrontendContextHolder) { + sJSFrontendContextHolder = new JSFrontendContextHolder(); + ClearOnShutdown(&sJSFrontendContextHolder); + } +} + +/* static */ +JS::FrontendContext* JSOracleChild::JSFrontendContext() { + MOZ_ASSERT(sJSFrontendContextHolder); + return sJSFrontendContextHolder->mFc; +} + +JSOracleChild* JSOracleChild::GetSingleton() { + MOZ_ASSERT(NS_IsMainThread()); + if (!sOracleSingletonChild) { + sOracleSingletonChild = new JSOracleChild(); + ClearOnShutdown(&sOracleSingletonChild); + } + return sOracleSingletonChild; +} + +already_AddRefed<PJSValidatorChild> JSOracleChild::AllocPJSValidatorChild() { + return MakeAndAddRef<JSValidatorChild>(); +} + +void JSOracleChild::Start(Endpoint<PJSOracleChild>&& aEndpoint) { + DebugOnly<bool> ok = std::move(aEndpoint).Bind(this); + JSFrontendContextHolder::MaybeInit(); + MOZ_ASSERT(ok); +} diff --git a/dom/ipc/JSOracleChild.h b/dom/ipc/JSOracleChild.h new file mode 100644 index 0000000000..f62ef7134c --- /dev/null +++ b/dom/ipc/JSOracleChild.h @@ -0,0 +1,69 @@ +/* -*- 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_dom_JSOracleChild +#define mozilla_dom_JSOracleChild + +#include "mozilla/dom/PJSOracleChild.h" + +#include "js/experimental/JSStencil.h" +#include "js/experimental/CompileScript.h" +#include "js/Initialization.h" +#include "jsapi.h" + +namespace mozilla::ipc { +class UtilityProcessParent; +} + +namespace mozilla::dom { +struct JSFrontendContextHolder { + JSFrontendContextHolder() { + MOZ_RELEASE_ASSERT(JS_IsInitialized(), + "UtilityProcessChild::Init should have JS initialized"); + + mFc = JS::NewFrontendContext(); + if (!mFc) { + MOZ_CRASH("Failed to create JS FrontendContext"); + return; + } + + // See the comment in XPCJSContext::Initialize. + const size_t kDefaultStackQuota = 128 * sizeof(size_t) * 1024; + + JS::SetNativeStackQuota(mFc, kDefaultStackQuota); + } + + ~JSFrontendContextHolder() { + if (mFc) { + JS::DestroyFrontendContext(mFc); + } + } + + static void MaybeInit(); + + JS::FrontendContext* mFc; +}; + +class PJSValidatorChild; + +class JSOracleChild final : public PJSOracleChild { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(JSOracleChild, override); + + already_AddRefed<PJSValidatorChild> AllocPJSValidatorChild(); + + void Start(Endpoint<PJSOracleChild>&& aEndpoint); + + static JS::FrontendContext* JSFrontendContext(); + + private: + ~JSOracleChild() = default; + + static JSOracleChild* GetSingleton(); +}; +} // namespace mozilla::dom + +#endif // defined(mozilla_dom_JSOracleChild) diff --git a/dom/ipc/JSOracleParent.cpp b/dom/ipc/JSOracleParent.cpp new file mode 100644 index 0000000000..31a8768422 --- /dev/null +++ b/dom/ipc/JSOracleParent.cpp @@ -0,0 +1,81 @@ +/* -*- 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 "mozilla/dom/JSOracleParent.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/RefPtr.h" +#include "mozilla/dom/PJSOracle.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/UtilityProcessManager.h" + +using namespace mozilla; +using namespace mozilla::dom; + +static StaticRefPtr<JSOracleParent> sOracleSingleton; + +/* static */ +void JSOracleParent::WithJSOracle( + const std::function<void(JSOracleParent* aParent)>& aCallback) { + GetSingleton()->StartJSOracle()->Then( + GetMainThreadSerialEventTarget(), __func__, + [aCallback](const JSOraclePromise::ResolveOrRejectValue& aResult) { + aCallback(aResult.IsReject() || !aResult.ResolveValue() + ? nullptr + : GetSingleton()); + }); +} + +void JSOracleParent::ActorDestroy(ActorDestroyReason aReason) { + // Given an actor can only be bound to one process, + // if the utility process crashes and we create a new one, + // we can't reuse the same JSOracleParent instance for it, + // so we always create a new JSOracleParent to replace + // the existing one. + if (aReason == ActorDestroyReason::AbnormalShutdown) { + sOracleSingleton = new JSOracleParent(); + } +} + +/* static */ +JSOracleParent* JSOracleParent::GetSingleton() { + if (!sOracleSingleton) { + sOracleSingleton = new JSOracleParent(); + ClearOnShutdown(&sOracleSingleton); + } + + return sOracleSingleton; +} + +RefPtr<JSOracleParent::JSOraclePromise> JSOracleParent::StartJSOracle() { + using namespace mozilla::ipc; + RefPtr<JSOracleParent> parent = JSOracleParent::GetSingleton(); + return UtilityProcessManager::GetSingleton()->StartJSOracle(parent); +} + +nsresult JSOracleParent::BindToUtilityProcess( + const RefPtr<mozilla::ipc::UtilityProcessParent>& aUtilityParent) { + Endpoint<PJSOracleParent> parentEnd; + Endpoint<PJSOracleChild> childEnd; + MOZ_ASSERT(aUtilityParent); + if (NS_FAILED(PJSOracle::CreateEndpoints(base::GetCurrentProcId(), + aUtilityParent->OtherPid(), + &parentEnd, &childEnd))) { + return NS_ERROR_FAILURE; + } + + if (!aUtilityParent->SendStartJSOracleService(std::move(childEnd))) { + return NS_ERROR_FAILURE; + } + + Bind(std::move(parentEnd)); + + return NS_OK; +} + +void JSOracleParent::Bind(Endpoint<PJSOracleParent>&& aEndpoint) { + DebugOnly<bool> ok = aEndpoint.Bind(this); + MOZ_ASSERT(ok); +} diff --git a/dom/ipc/JSOracleParent.h b/dom/ipc/JSOracleParent.h new file mode 100644 index 0000000000..f318123369 --- /dev/null +++ b/dom/ipc/JSOracleParent.h @@ -0,0 +1,48 @@ +/* -*- 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_dom_JSOracleParent +#define mozilla_dom_JSOracleParent + +#include "mozilla/MozPromise.h" +#include "mozilla/ProcInfo.h" +#include "mozilla/dom/PJSOracleParent.h" + +namespace mozilla::ipc { +class UtilityProcessParent; +} + +namespace mozilla::dom { + +class JSOracleParent final : public PJSOracleParent { + public: + JSOracleParent() = default; + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(JSOracleParent, override); + + static void WithJSOracle( + const std::function<void(JSOracleParent* aParent)>& aCallback); + + UtilityActorName GetActorName() { return UtilityActorName::JSOracle; } + + void ActorDestroy(ActorDestroyReason aReason) override; + + nsresult BindToUtilityProcess( + const RefPtr<mozilla::ipc::UtilityProcessParent>& aUtilityParent); + + void Bind(Endpoint<PJSOracleParent>&& aEndpoint); + + private: + ~JSOracleParent() = default; + + static JSOracleParent* GetSingleton(); + + using JSOraclePromise = GenericNonExclusivePromise; + RefPtr<JSOraclePromise> StartJSOracle(); +}; +} // namespace mozilla::dom + +#endif // defined(mozilla_dom_JSOracleParent) diff --git a/dom/ipc/JSValidatorChild.cpp b/dom/ipc/JSValidatorChild.cpp new file mode 100644 index 0000000000..5070a46492 --- /dev/null +++ b/dom/ipc/JSValidatorChild.cpp @@ -0,0 +1,242 @@ +/* -*- 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 "mozilla/dom/JSValidatorChild.h" +#include "mozilla/dom/JSOracleChild.h" + +#include "mozilla/Encoding.h" +#include "mozilla/dom/ScriptDecoding.h" +#include "mozilla/ipc/Endpoint.h" + +#include "js/CompileOptions.h" +#include "js/JSON.h" +#include "js/SourceText.h" +#include "js/experimental/CompileScript.h" +#include "js/experimental/JSStencil.h" +#include "xpcpublic.h" + +using namespace mozilla::dom; +using Encoding = mozilla::Encoding; + +mozilla::UniquePtr<mozilla::Decoder> TryGetDecoder( + const mozilla::Span<const uint8_t>& aSourceBytes, + const nsACString& aContentCharset, const nsAString& aHintCharset, + const nsAString& aDocumentCharset) { + const Encoding* encoding; + mozilla::UniquePtr<mozilla::Decoder> unicodeDecoder; + + std::tie(encoding, std::ignore) = Encoding::ForBOM(aSourceBytes); + if (encoding) { + unicodeDecoder = encoding->NewDecoderWithBOMRemoval(); + } + + if (!unicodeDecoder) { + encoding = Encoding::ForLabel(aContentCharset); + if (encoding) { + unicodeDecoder = encoding->NewDecoderWithoutBOMHandling(); + } + + if (!unicodeDecoder) { + encoding = Encoding::ForLabel(aHintCharset); + if (encoding) { + unicodeDecoder = encoding->NewDecoderWithoutBOMHandling(); + } + } + + if (!unicodeDecoder) { + encoding = Encoding::ForLabel(aDocumentCharset); + if (encoding) { + unicodeDecoder = encoding->NewDecoderWithoutBOMHandling(); + } + } + } + + if (!unicodeDecoder && !IsUtf8(mozilla::Span(reinterpret_cast<const char*>( + aSourceBytes.Elements()), + aSourceBytes.Length()))) { + // Curiously, there are various callers that don't pass aDocument. The + // fallback in the old code was ISO-8859-1, which behaved like + // windows-1252. + unicodeDecoder = WINDOWS_1252_ENCODING->NewDecoderWithoutBOMHandling(); + } + + return unicodeDecoder; +} + +mozilla::ipc::IPCResult JSValidatorChild::RecvIsOpaqueResponseAllowed( + IsOpaqueResponseAllowedResolver&& aResolver) { + mResolver.emplace(aResolver); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult JSValidatorChild::RecvOnDataAvailable(Shmem&& aData) { + if (!mResolver) { + MOZ_ASSERT(!CanSend()); + return IPC_OK(); + } + + if (!mSourceBytes.Append(Span(aData.get<char>(), aData.Size<char>()), + mozilla::fallible)) { + // To prevent an attacker from flood the validation process, + // we don't validate here. + Resolve(ValidatorResult::Failure); + } + DeallocShmem(aData); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult JSValidatorChild::RecvOnStopRequest( + const nsresult& aReason, const nsACString& aContentCharset, + const nsAString& aHintCharset, const nsAString& aDocumentCharset) { + if (!mResolver) { + return IPC_OK(); + } + + if (NS_FAILED(aReason)) { + Resolve(ValidatorResult::Failure); + } else if (mSourceBytes.IsEmpty()) { + // The empty document parses as JavaScript. + Resolve(ValidatorResult::JavaScript); + } else { + UniquePtr<Decoder> unicodeDecoder = TryGetDecoder( + mSourceBytes, aContentCharset, aHintCharset, aDocumentCharset); + + if (!unicodeDecoder) { + Resolve(ShouldAllowJS(mSourceBytes)); + } else { + BufferUniquePtr<Utf8Unit[]> buffer; + auto result = GetUTF8EncodedContent(mSourceBytes, buffer, unicodeDecoder); + if (result.isErr()) { + Resolve(ValidatorResult::Failure); + } else { + Resolve(ShouldAllowJS(result.unwrap())); + } + } + } + + return IPC_OK(); +} + +void JSValidatorChild::ActorDestroy(ActorDestroyReason aReason) { + if (mResolver) { + Resolve(ValidatorResult::Failure); + } +}; + +void JSValidatorChild::Resolve(ValidatorResult aResult) { + MOZ_ASSERT(mResolver); + Maybe<Shmem> data = Nothing(); + if (aResult == ValidatorResult::JavaScript && !mSourceBytes.IsEmpty()) { + Shmem sharedData; + nsresult rv = + JSValidatorUtils::CopyCStringToShmem(this, mSourceBytes, sharedData); + if (NS_SUCCEEDED(rv)) { + data = Some(std::move(sharedData)); + } + } + + mResolver.ref()(std::tuple<mozilla::Maybe<Shmem>&&, const ValidatorResult&>( + std::move(data), aResult)); + mResolver.reset(); +} + +mozilla::Result<mozilla::Span<const char>, nsresult> +JSValidatorChild::GetUTF8EncodedContent( + const mozilla::Span<const uint8_t>& aData, + BufferUniquePtr<Utf8Unit[]>& aBuffer, UniquePtr<Decoder>& aDecoder) { + MOZ_ASSERT(aDecoder); + // We need the output buffer to be UTF8 + CheckedInt<size_t> bufferLength = + ScriptDecoding<Utf8Unit>::MaxBufferLength(aDecoder, aData.Length()); + if (!bufferLength.isValid()) { + return mozilla::Err(NS_ERROR_FAILURE); + } + + CheckedInt<size_t> bufferByteSize = bufferLength * sizeof(Utf8Unit); + if (!bufferByteSize.isValid()) { + return mozilla::Err(NS_ERROR_FAILURE); + } + + aBuffer.reset(static_cast<Utf8Unit*>(js_malloc(bufferByteSize.value()))); + if (!aBuffer) { + return mozilla::Err(NS_ERROR_FAILURE); + } + + size_t written = ScriptDecoding<Utf8Unit>::DecodeInto( + aDecoder, aData, Span(aBuffer.get(), bufferLength.value()), + /* aEndOfSource = */ true); + MOZ_ASSERT(written <= bufferLength.value()); + MOZ_ASSERT( + IsUtf8(Span(reinterpret_cast<const char*>(aBuffer.get()), written))); + + return Span(reinterpret_cast<const char*>(aBuffer.get()), written); +} + +JSValidatorChild::ValidatorResult JSValidatorChild::ShouldAllowJS( + const mozilla::Span<const char>& aSpan) const { + MOZ_ASSERT(!aSpan.IsEmpty()); + + MOZ_DIAGNOSTIC_ASSERT(IsUtf8(aSpan)); + + JS::FrontendContext* fc = JSOracleChild::JSFrontendContext(); + if (!fc) { + return ValidatorResult::Failure; + } + + JS::SourceText<Utf8Unit> srcBuf; + if (!srcBuf.init(fc, aSpan.Elements(), aSpan.Length(), + JS::SourceOwnership::Borrowed)) { + JS::ClearFrontendErrors(fc); + return ValidatorResult::Failure; + } + + // Parse to JavaScript + JS::PrefableCompileOptions prefableOptions; + xpc::SetPrefableCompileOptions(prefableOptions); + // For the syntax validation purpose, asm.js doesn't need to be enabled. + prefableOptions.setAsmJSOption(JS::AsmJSOption::DisabledByAsmJSPref); + + JS::CompileOptions options(prefableOptions); + JS::CompilationStorage storage; + RefPtr<JS::Stencil> stencil = + JS::CompileGlobalScriptToStencil(fc, options, srcBuf, storage); + + if (!stencil) { + JS::ClearFrontendErrors(fc); + return ValidatorResult::Other; + } + + MOZ_ASSERT(!aSpan.IsEmpty()); + + // Parse to JSON + if (IsAscii(aSpan)) { + // Ascii is a subset of Latin1, and JS_ParseJSON can take Latin1 directly + if (JS::IsValidJSON( + reinterpret_cast<const JS::Latin1Char*>(aSpan.Elements()), + aSpan.Length())) { + return ValidatorResult::JSON; + } + } else { + nsString decoded; + nsresult rv = UTF_8_ENCODING->DecodeWithBOMRemoval( + Span(reinterpret_cast<const uint8_t*>(aSpan.Elements()), + aSpan.Length()), + decoded); + if (NS_FAILED(rv)) { + return ValidatorResult::Failure; + } + + if (JS::IsValidJSON(decoded.BeginReading(), decoded.Length())) { + return ValidatorResult::JSON; + } + } + + // Since the JSON parsing failed, we confirmed the file is Javascript and not + // JSON. + return ValidatorResult::JavaScript; +} diff --git a/dom/ipc/JSValidatorChild.h b/dom/ipc/JSValidatorChild.h new file mode 100644 index 0000000000..b8899afd5b --- /dev/null +++ b/dom/ipc/JSValidatorChild.h @@ -0,0 +1,57 @@ +/* -*- 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_dom_JSValidatorChild +#define mozilla_dom_JSValidatorChild + +#include "mozilla/Result.h" +#include "mozilla/ProcInfo.h" +#include "mozilla/dom/PJSValidatorChild.h" +#include "mozilla/dom/JSValidatorUtils.h" +#include "mozilla/net/OpaqueResponseUtils.h" + +template <typename T> +using BufferUniquePtr = mozilla::UniquePtr<T, JS::FreePolicy>; + +namespace mozilla { +class Decoder; +} // namespace mozilla + +namespace mozilla::dom { +class JSValidatorChild final : public PJSValidatorChild { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(JSValidatorChild, override); + + mozilla::ipc::IPCResult RecvIsOpaqueResponseAllowed( + IsOpaqueResponseAllowedResolver&& aResolver); + + mozilla::ipc::IPCResult RecvOnDataAvailable(Shmem&& aData); + + mozilla::ipc::IPCResult RecvOnStopRequest(const nsresult& aReason, + const nsACString& aContentCharset, + const nsAString& aHintCharset, + const nsAString& aDocumentCharset); + + void ActorDestroy(ActorDestroyReason aReason) override; + + private: + virtual ~JSValidatorChild() = default; + + using ValidatorResult = net::OpaqueResponseBlocker::ValidatorResult; + void Resolve(ValidatorResult aResult); + ValidatorResult ShouldAllowJS(const mozilla::Span<const char>& aSpan) const; + + mozilla::Result<mozilla::Span<const char>, nsresult> GetUTF8EncodedContent( + const mozilla::Span<const uint8_t>& aData, + BufferUniquePtr<Utf8Unit[]>& aBuffer, + UniquePtr<mozilla::Decoder>& aDecoder); + + nsCString mSourceBytes; + Maybe<IsOpaqueResponseAllowedResolver> mResolver; +}; +} // namespace mozilla::dom + +#endif // defined(mozilla_dom_JSValidatorChild) diff --git a/dom/ipc/JSValidatorParent.cpp b/dom/ipc/JSValidatorParent.cpp new file mode 100644 index 0000000000..45b6d9de4c --- /dev/null +++ b/dom/ipc/JSValidatorParent.cpp @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/net/OpaqueResponseUtils.h" +#include "mozilla/dom/JSValidatorParent.h" +#include "mozilla/dom/JSValidatorUtils.h" +#include "mozilla/dom/JSOracleParent.h" +#include "mozilla/RefPtr.h" +#include "nsCOMPtr.h" +#include "HttpBaseChannel.h" + +namespace mozilla::dom { +/* static */ +already_AddRefed<JSValidatorParent> JSValidatorParent::Create() { + RefPtr<JSValidatorParent> validator = new JSValidatorParent(); + JSOracleParent::WithJSOracle([validator](JSOracleParent* aParent) { + MOZ_ASSERT_IF(aParent, aParent->CanSend()); + if (aParent) { + MOZ_ALWAYS_TRUE(aParent->SendPJSValidatorConstructor(validator)); + } + }); + return validator.forget(); +} + +void JSValidatorParent::IsOpaqueResponseAllowed( + const std::function<void(Maybe<Shmem>, ValidatorResult)>& aCallback) { + JSOracleParent::WithJSOracle([=, self = RefPtr{this}](const auto* aParent) { + if (aParent) { + MOZ_DIAGNOSTIC_ASSERT(self->CanSend()); + self->SendIsOpaqueResponseAllowed()->Then( + GetMainThreadSerialEventTarget(), __func__, + [aCallback]( + const IsOpaqueResponseAllowedPromise::ResolveOrRejectValue& + aResult) { + if (aResult.IsResolve()) { + auto [data, result] = aResult.ResolveValue(); + aCallback(std::move(data), result); + } else { + // For cases like the Utility Process crashes, the promise will be + // rejected due to sending failures, and we'll block the request + // since we can't validate it. + aCallback(Nothing(), ValidatorResult::Failure); + } + }); + } else { + aCallback(Nothing(), ValidatorResult::Failure); + } + }); +} + +void JSValidatorParent::OnDataAvailable(const nsACString& aData) { + JSOracleParent::WithJSOracle( + [self = RefPtr{this}, data = nsCString{aData}](const auto* aParent) { + if (!aParent) { + return; + } + + if (self->CanSend()) { + Shmem sharedData; + nsresult rv = + JSValidatorUtils::CopyCStringToShmem(self, data, sharedData); + if (NS_FAILED(rv)) { + return; + } + Unused << self->SendOnDataAvailable(std::move(sharedData)); + } + }); +} + +void JSValidatorParent::OnStopRequest(nsresult aResult, nsIRequest& aRequest) { + JSOracleParent::WithJSOracle( + [self = RefPtr{this}, aResult, + request = nsCOMPtr{&aRequest}](const auto* aParent) { + if (!aParent) { + return; + } + if (self->CanSend() && request) { + nsCOMPtr<net::HttpBaseChannel> httpBaseChannel = + do_QueryInterface(request); + MOZ_ASSERT(httpBaseChannel); + + nsAutoCString contentCharset; + Unused << httpBaseChannel->GetContentCharset(contentCharset); + + nsAutoString hintCharset; + Unused << httpBaseChannel->GetClassicScriptHintCharset(hintCharset); + + nsAutoString documentCharset; + Unused << httpBaseChannel->GetDocumentCharacterSet(documentCharset); + + Unused << self->SendOnStopRequest(aResult, contentCharset, + hintCharset, documentCharset); + } + }); +} +} // namespace mozilla::dom diff --git a/dom/ipc/JSValidatorParent.h b/dom/ipc/JSValidatorParent.h new file mode 100644 index 0000000000..66528a6470 --- /dev/null +++ b/dom/ipc/JSValidatorParent.h @@ -0,0 +1,40 @@ +/* -*- 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_dom_JSValidatorParent +#define mozilla_dom_JSValidatorParent + +#include "mozilla/MozPromise.h" +#include "mozilla/ProcInfo.h" +#include "mozilla/dom/PJSValidatorParent.h" + +namespace mozilla::ipc { +class UtilityProcessParent; +class IProtocol; +} // namespace mozilla::ipc + +namespace mozilla::dom { + +class JSValidatorParent final : public PJSValidatorParent { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(JSValidatorParent, override); + + static already_AddRefed<JSValidatorParent> Create(); + + void IsOpaqueResponseAllowed( + const std::function<void(Maybe<mozilla::ipc::Shmem>, ValidatorResult)>& + aCallback); + + void OnDataAvailable(const nsACString& aData); + + void OnStopRequest(nsresult aResult, nsIRequest& aRequest); + + private: + virtual ~JSValidatorParent() = default; +}; +} // namespace mozilla::dom + +#endif // defined(mozilla_dom_JSValidatorParent) diff --git a/dom/ipc/JSValidatorUtils.cpp b/dom/ipc/JSValidatorUtils.cpp new file mode 100644 index 0000000000..cde22a453f --- /dev/null +++ b/dom/ipc/JSValidatorUtils.cpp @@ -0,0 +1,25 @@ +/* -*- 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 "mozilla/dom/JSValidatorUtils.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/ipc/Shmem.h" +#include "nsStringFwd.h" + +using namespace mozilla::ipc; + +namespace mozilla::dom { +/* static */ +nsresult JSValidatorUtils::CopyCStringToShmem(IProtocol* aActor, + const nsCString& aCString, + Shmem& aMem) { + if (!aActor->AllocShmem(aCString.Length(), &aMem)) { + return NS_ERROR_FAILURE; + } + memcpy(aMem.get<char>(), aCString.BeginReading(), aCString.Length()); + return NS_OK; +} +} // namespace mozilla::dom diff --git a/dom/ipc/JSValidatorUtils.h b/dom/ipc/JSValidatorUtils.h new file mode 100644 index 0000000000..a0541b3992 --- /dev/null +++ b/dom/ipc/JSValidatorUtils.h @@ -0,0 +1,26 @@ +/* -*- 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_dom_JSValidatorUtils +#define mozilla_dom_JSValidatorUtils +#include "ErrorList.h" +#include "nsStringFwd.h" + +namespace mozilla::ipc { +class IProtocol; +class Shmem; +} // namespace mozilla::ipc + +using namespace mozilla::ipc; + +namespace mozilla::dom { +class JSValidatorUtils final { + public: + static nsresult CopyCStringToShmem(IProtocol* aActor, + const nsCString& aCString, Shmem& aMem); +}; +}; // namespace mozilla::dom +#endif diff --git a/dom/ipc/LoginDetectionService.cpp b/dom/ipc/LoginDetectionService.cpp new file mode 100644 index 0000000000..ddbe1096fb --- /dev/null +++ b/dom/ipc/LoginDetectionService.cpp @@ -0,0 +1,158 @@ +/* -*- 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 "LoginDetectionService.h" + +#include "nsILoginInfo.h" +#include "nsILoginManager.h" +#include "nsIObserver.h" +#include "nsIXULRuntime.h" +#include "nsServiceManagerUtils.h" +#include "nsXULAppAPI.h" + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/StaticPrefs_fission.h" +#include "mozilla/dom/ProcessIsolation.h" + +namespace mozilla::dom { + +static StaticRefPtr<LoginDetectionService> gLoginDetectionService; + +namespace { + +void OnFissionPrefsChange(const char* aPrefName, void* aData) { + MOZ_ASSERT(gLoginDetectionService); + + gLoginDetectionService->MaybeStartMonitoring(); +} + +} // namespace + +NS_IMPL_ISUPPORTS(LoginDetectionService, nsILoginDetectionService, + nsILoginSearchCallback, nsIObserver, nsISupportsWeakReference) + +// static +already_AddRefed<LoginDetectionService> LoginDetectionService::GetSingleton() { + if (gLoginDetectionService) { + return do_AddRef(gLoginDetectionService); + } + + gLoginDetectionService = new LoginDetectionService(); + ClearOnShutdown(&gLoginDetectionService); + + return do_AddRef(gLoginDetectionService); +} + +LoginDetectionService::LoginDetectionService() : mIsLoginsLoaded(false) {} +LoginDetectionService::~LoginDetectionService() { UnregisterObserver(); } + +void LoginDetectionService::MaybeStartMonitoring() { + if (IsIsolateHighValueSiteEnabled()) { + // We want to isolate sites with a saved password, so fetch saved logins + // from the password manager, and then add the 'HighValue' permission. + + // Note that we don't monitor whether a login is added or removed after + // logins are fetched. For adding logins, this will be covered by form + // submission detection heuristic. As for removing logins, it doesn't + // provide security benefit just to NOT isolate the removed site. The site + // will not be isolated when its permission expired. + FetchLogins(); + } + + if (IsIsolateHighValueSiteEnabled() || + StaticPrefs::fission_highValue_login_monitor()) { + // When the pref is on, we monitor users' login attempt event when we + // are not isolating high value sites. This is because We can't detect the + // case where a user is already logged in to a site, and don't save a + // password for it. So we want to start monitoring login attempts prior + // to releasing the feature. + if (!mObs) { + mObs = mozilla::services::GetObserverService(); + mObs->AddObserver(this, "passwordmgr-form-submission-detected", false); + } + } else { + UnregisterObserver(); + } +} + +void LoginDetectionService::FetchLogins() { + nsresult rv; + nsCOMPtr<nsILoginManager> loginManager = + do_GetService(NS_LOGINMANAGER_CONTRACTID, &rv); + if (NS_WARN_IF(!loginManager)) { + return; + } + + Unused << loginManager->GetAllLoginsWithCallback(this); +} + +void LoginDetectionService::UnregisterObserver() { + if (mObs) { + mObs->RemoveObserver(this, "passwordmgr-form-submission-detected"); + mObs = nullptr; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// nsILoginDetectionService implementation +NS_IMETHODIMP LoginDetectionService::Init() { + if (XRE_IsContentProcess()) { + return NS_OK; + } + + Preferences::RegisterCallback(OnFissionPrefsChange, "fission.autostart"); + Preferences::RegisterCallback(OnFissionPrefsChange, + "fission.webContentIsolationStrategy"); + + MaybeStartMonitoring(); + + return NS_OK; +} + +NS_IMETHODIMP LoginDetectionService::IsLoginsLoaded(bool* aResult) { + if (IsIsolateHighValueSiteEnabled()) { + *aResult = mIsLoginsLoaded; + } else { + // When the feature is disabled, just returns true so testcases don't + // block on waiting for us to load logins. + *aResult = true; + } + return NS_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +// nsILoginSearchObserver implementation +NS_IMETHODIMP +LoginDetectionService::OnSearchComplete( + const nsTArray<RefPtr<nsILoginInfo>>& aLogins) { + // Add all origins with saved passwords to the permission manager. + for (const auto& login : aLogins) { + nsString origin; + login->GetOrigin(origin); + + AddHighValuePermission(NS_ConvertUTF16toUTF8(origin), + mozilla::dom::kHighValueHasSavedLoginPermission); + } + + mIsLoginsLoaded = true; + return NS_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +// nsIObserver implementation +NS_IMETHODIMP +LoginDetectionService::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if ("passwordmgr-form-submission-detected"_ns.Equals(aTopic)) { + nsDependentString origin(aData); + AddHighValuePermission(NS_ConvertUTF16toUTF8(origin), + mozilla::dom::kHighValueIsLoggedInPermission); + } + + return NS_OK; +} + +} // namespace mozilla::dom diff --git a/dom/ipc/LoginDetectionService.h b/dom/ipc/LoginDetectionService.h new file mode 100644 index 0000000000..b0fd170855 --- /dev/null +++ b/dom/ipc/LoginDetectionService.h @@ -0,0 +1,60 @@ +/* -*- 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_dom_LoginDetectionService_h +#define mozilla_dom_LoginDetectionService_h + +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsILoginDetectionService.h" +#include "nsILoginManager.h" +#include "nsWeakReference.h" +#include "mozilla/Logging.h" +#include "mozilla/Preferences.h" + +namespace mozilla::dom { + +/** + * Detect whether the user is 'possibly' logged in to a site, and add the + * HighValue permission to the permission manager. We say 'possibly' because + * the detection is done in a very loose way. For example, for sites that have + * an associated login stored in the password manager are considered `logged in` + * by the service, which is not always true in terms of whether the users is + * really logged in to the site. + */ +class LoginDetectionService final : public nsILoginDetectionService, + public nsILoginSearchCallback, + public nsIObserver, + public nsSupportsWeakReference { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSILOGINDETECTIONSERVICE + NS_DECL_NSILOGINSEARCHCALLBACK + NS_DECL_NSIOBSERVER + + static already_AddRefed<LoginDetectionService> GetSingleton(); + + void MaybeStartMonitoring(); + + private: + LoginDetectionService(); + virtual ~LoginDetectionService(); + + // Fetch saved logins from the password manager. + void FetchLogins(); + + void RegisterObserver(); + void UnregisterObserver(); + + nsCOMPtr<nsIObserverService> mObs; + + // Used by testcase to make sure logins are fetched. + bool mIsLoginsLoaded; +}; + +} // namespace mozilla::dom + +#endif diff --git a/dom/ipc/MMPrinter.cpp b/dom/ipc/MMPrinter.cpp new file mode 100644 index 0000000000..be2911341a --- /dev/null +++ b/dom/ipc/MMPrinter.cpp @@ -0,0 +1,92 @@ +/* -*- 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 "MMPrinter.h" + +#include "jsapi.h" +#include "nsJSUtils.h" +#include "Logging.h" +#include "mozilla/Bootstrap.h" +#include "mozilla/dom/ipc/StructuredCloneData.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/RandomNum.h" +#include "nsFrameMessageManager.h" +#include "prenv.h" + +namespace mozilla::dom { + +LazyLogModule MMPrinter::sMMLog("MessageManager"); + +// You can use +// https://gist.github.com/tomrittervg/adb8688426a9a5340da96004e2c8af79 to parse +// the output of the logs into logs more friendly to reading. + +/* static */ +void MMPrinter::PrintImpl(char const* aLocation, const nsAString& aMsg, + ClonedMessageData const& aData) { + NS_ConvertUTF16toUTF8 charMsg(aMsg); + + /* + * The topic will be skipped if the topic name appears anywhere as a substring + * of the filter. + * + * Example: + * MOZ_LOG_MESSAGEMANAGER_SKIP="foobar|extension" + * Will match the topics 'foobar', 'foo', 'bar', and 'ten' (even though + * you may not have intended to match the latter three) and it will not match + * the topics 'extensionresult' or 'Foo'. + */ + char* mmSkipLog = PR_GetEnv("MOZ_LOG_MESSAGEMANAGER_SKIP"); + + if (mmSkipLog && strstr(mmSkipLog, charMsg.get())) { + return; + } + + uint64_t msg_id = RandomUint64OrDie(); + + MOZ_LOG(MMPrinter::sMMLog, LogLevel::Debug, + ("%" PRIu64 " %s Message: %s in process type: %s", msg_id, aLocation, + charMsg.get(), XRE_GetProcessTypeString())); + + if (!MOZ_LOG_TEST(sMMLog, LogLevel::Verbose)) { + return; + } + + ErrorResult rv; + + AutoJSAPI jsapi; + // We're using this context to deserialize, stringify, and print a message + // manager message here. Since the messages are always sent from and to system + // scopes, we need to do this in a system scope, or attempting to deserialize + // certain privileged objects will fail. + MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope())); + JSContext* cx = jsapi.cx(); + + ipc::StructuredCloneData data; + ipc::UnpackClonedMessageData(aData, data); + + /* Read original StructuredCloneData. */ + JS::Rooted<JS::Value> scdContent(cx); + data.Read(cx, &scdContent, rv); + if (rv.Failed()) { + // In testing, the only reason this would fail was if there was no data in + // the message; so it seems like this is safe-ish. + MOZ_LOG(MMPrinter::sMMLog, LogLevel::Verbose, + ("%" PRIu64 " (No Data)", msg_id)); + rv.SuppressException(); + return; + } + + JS::Rooted<JSString*> unevalObj(cx, JS_ValueToSource(cx, scdContent)); + nsAutoJSString srcString; + if (!srcString.init(cx, unevalObj)) return; + + MOZ_LOG(MMPrinter::sMMLog, LogLevel::Verbose, + ("%" PRIu64 " %s", msg_id, NS_ConvertUTF16toUTF8(srcString).get())); +} + +} // namespace mozilla::dom diff --git a/dom/ipc/MMPrinter.h b/dom/ipc/MMPrinter.h new file mode 100644 index 0000000000..7d89f9ee8f --- /dev/null +++ b/dom/ipc/MMPrinter.h @@ -0,0 +1,32 @@ +/* -*- 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 MMPrinter_h +#define MMPrinter_h + +#include "mozilla/dom/DOMTypes.h" +#include "nsString.h" + +namespace mozilla::dom { + +class MMPrinter { + public: + static void Print(char const* aLocation, const nsAString& aMsg, + ClonedMessageData const& aData) { + if (MOZ_UNLIKELY(MOZ_LOG_TEST(MMPrinter::sMMLog, LogLevel::Debug))) { + MMPrinter::PrintImpl(aLocation, aMsg, aData); + } + } + + private: + static LazyLogModule sMMLog; + static void PrintImpl(char const* aLocation, const nsAString& aMsg, + ClonedMessageData const& aData); +}; + +} // namespace mozilla::dom + +#endif /* MMPrinter_h */ diff --git a/dom/ipc/ManifestMessagesChild.sys.mjs b/dom/ipc/ManifestMessagesChild.sys.mjs new file mode 100644 index 0000000000..5a42fd3206 --- /dev/null +++ b/dom/ipc/ManifestMessagesChild.sys.mjs @@ -0,0 +1,112 @@ +/* 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/.*/ +/* + * Manifest obtainer frame script implementation of: + * http://www.w3.org/TR/appmanifest/#obtaining + * + * It searches a top-level browsing context for + * a <link rel=manifest> element. Then fetches + * and processes the linked manifest. + * + * BUG: https://bugzilla.mozilla.org/show_bug.cgi?id=1083410 + */ + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + ManifestFinder: "resource://gre/modules/ManifestFinder.sys.mjs", + ManifestIcons: "resource://gre/modules/ManifestIcons.sys.mjs", + ManifestObtainer: "resource://gre/modules/ManifestObtainer.sys.mjs", +}); + +export class ManifestMessagesChild extends JSWindowActorChild { + receiveMessage(message) { + switch (message.name) { + case "DOM:WebManifest:hasManifestLink": + return this.hasManifestLink(); + case "DOM:ManifestObtainer:Obtain": + return this.obtainManifest(message.data); + case "DOM:WebManifest:fetchIcon": + return this.fetchIcon(message); + } + return undefined; + } + + /** + * Check if the document includes a link to a web manifest. + */ + hasManifestLink() { + const response = makeMsgResponse(); + response.result = lazy.ManifestFinder.contentHasManifestLink( + this.contentWindow + ); + response.success = true; + return response; + } + + /** + * Asynchronously obtains a web manifest from this window by using the + * ManifestObtainer and returns the result. + * @param {Object} checkConformance True if spec conformance messages should be collected. + */ + async obtainManifest(options) { + const { checkConformance } = options; + const response = makeMsgResponse(); + try { + response.result = await lazy.ManifestObtainer.contentObtainManifest( + this.contentWindow, + { checkConformance } + ); + response.success = true; + } catch (err) { + response.result = serializeError(err); + } + return response; + } + + /** + * Given a manifest and an expected icon size, ask ManifestIcons + * to fetch the appropriate icon and send along result + */ + async fetchIcon({ data: { manifest, iconSize } }) { + const response = makeMsgResponse(); + try { + response.result = await lazy.ManifestIcons.contentFetchIcon( + this.contentWindow, + manifest, + iconSize + ); + response.success = true; + } catch (err) { + response.result = serializeError(err); + } + return response; + } +} + +/** + * Utility function to Serializes an JS Error, so it can be transferred over + * the message channel. + * FIX ME: https://bugzilla.mozilla.org/show_bug.cgi?id=1172586 + * @param {Error} aError The error to serialize. + * @return {Object} The serialized object. + */ +function serializeError(aError) { + const clone = { + fileName: aError.fileName, + lineNumber: aError.lineNumber, + columnNumber: aError.columnNumber, + stack: aError.stack, + message: aError.message, + name: aError.name, + }; + return clone; +} + +function makeMsgResponse() { + return { + success: false, + result: undefined, + }; +} diff --git a/dom/ipc/MaybeDiscarded.h b/dom/ipc/MaybeDiscarded.h new file mode 100644 index 0000000000..32c0dde3b9 --- /dev/null +++ b/dom/ipc/MaybeDiscarded.h @@ -0,0 +1,133 @@ +/* -*- 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_dom_MaybeDiscarded_h +#define mozilla_dom_MaybeDiscarded_h + +#include "mozilla/RefPtr.h" + +namespace mozilla::dom { + +// Wrapper type for a WindowContext or BrowsingContext instance which may be +// discarded, and thus unavailable in the current process. This type is used to +// pass WindowContext and BrowsingContext instances over IPC, as they may be +// discarded in the receiving process. +// +// A MaybeDiscarded can generally be implicitly converted to from a +// BrowsingContext* or WindowContext*, but requires an explicit check of +// |IsDiscarded| and call to |get| to read from. +template <typename T> +class MaybeDiscarded { + public: + MaybeDiscarded() = default; + MaybeDiscarded(MaybeDiscarded<T>&&) = default; + MaybeDiscarded(const MaybeDiscarded<T>&) = default; + + // Construct from raw pointers and |nullptr|. + MOZ_IMPLICIT MaybeDiscarded(T* aRawPtr) + : mId(aRawPtr ? aRawPtr->Id() : 0), mPtr(aRawPtr) {} + MOZ_IMPLICIT MaybeDiscarded(decltype(nullptr)) {} + + // Construct from |RefPtr<I>| + template <typename I, + typename = std::enable_if_t<std::is_convertible_v<I*, T*>>> + MOZ_IMPLICIT MaybeDiscarded(RefPtr<I>&& aPtr) + : mId(aPtr ? aPtr->Id() : 0), mPtr(std::move(aPtr)) {} + template <typename I, + typename = std::enable_if_t<std::is_convertible_v<I*, T*>>> + MOZ_IMPLICIT MaybeDiscarded(const RefPtr<I>& aPtr) + : mId(aPtr ? aPtr->Id() : 0), mPtr(aPtr) {} + + // Basic assignment operators. + MaybeDiscarded<T>& operator=(const MaybeDiscarded<T>&) = default; + MaybeDiscarded<T>& operator=(MaybeDiscarded<T>&&) = default; + MaybeDiscarded<T>& operator=(decltype(nullptr)) { + mId = 0; + mPtr = nullptr; + return *this; + } + MaybeDiscarded<T>& operator=(T* aRawPtr) { + mId = aRawPtr ? aRawPtr->Id() : 0; + mPtr = aRawPtr; + return *this; + } + template <typename I> + MaybeDiscarded<T>& operator=(const RefPtr<I>& aRhs) { + mId = aRhs ? aRhs->Id() : 0; + mPtr = aRhs; + return *this; + } + template <typename I> + MaybeDiscarded<T>& operator=(RefPtr<I>&& aRhs) { + mId = aRhs ? aRhs->Id() : 0; + mPtr = std::move(aRhs); + return *this; + } + + // Validate that the value is neither discarded nor null. + bool IsNullOrDiscarded() const { return !mPtr || mPtr->IsDiscarded(); } + bool IsDiscarded() const { return IsNullOrDiscarded() && !IsNull(); } + bool IsNull() const { return mId == 0; } + + explicit operator bool() const { return !IsNullOrDiscarded(); } + + // Extract the wrapped |T|. Must not be called on a discarded |T|. + T* get() const { + MOZ_DIAGNOSTIC_ASSERT(!IsDiscarded()); + return mPtr.get(); + } + already_AddRefed<T> forget() { + MOZ_DIAGNOSTIC_ASSERT(!IsDiscarded()); + return mPtr.forget(); + } + + T* operator->() const { + MOZ_ASSERT(!IsNull()); + return get(); + } + + // Like "get", but gets the "Canonical" version of the type. This method may + // only be called in the parent process. + auto get_canonical() const -> decltype(get()->Canonical()) { + if (get()) { + return get()->Canonical(); + } else { + return nullptr; + } + } + + // The ID for the context wrapped by this MaybeDiscarded. This ID comes from a + // remote process, and should generally only be used for logging. A + // BrowsingContext with this ID may not exist in the current process. + uint64_t ContextId() const { return mId; } + + // Tries to get the wrapped value, disregarding discarded status. + // This may return |nullptr| for a non-null |MaybeDiscarded|, in the case that + // the target is no longer available in this process. + T* GetMaybeDiscarded() const { return mPtr.get(); } + + // Clear the value to a discarded state with the given ID. + void SetDiscarded(uint64_t aId) { + mId = aId; + mPtr = nullptr; + } + + // Comparison operators required by IPDL + bool operator==(const MaybeDiscarded<T>& aRhs) const { + return mId == aRhs.mId && mPtr == aRhs.mPtr; + } + bool operator!=(const MaybeDiscarded<T>& aRhs) const { + return !operator==(aRhs); + } + + private: + uint64_t mId = 0; + RefPtr<T> mPtr; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_MaybeDiscarded_h diff --git a/dom/ipc/MemMapSnapshot.cpp b/dom/ipc/MemMapSnapshot.cpp new file mode 100644 index 0000000000..53accfdb1b --- /dev/null +++ b/dom/ipc/MemMapSnapshot.cpp @@ -0,0 +1,45 @@ +/* -*- 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 "MemMapSnapshot.h" + +#include "mozilla/AutoMemMap.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/Try.h" +#include "mozilla/ipc/FileDescriptor.h" + +namespace mozilla::ipc { + +Result<Ok, nsresult> MemMapSnapshot::Init(size_t aSize) { + MOZ_ASSERT(!mInitialized); + + if (NS_WARN_IF(!mMem.CreateFreezeable(aSize))) { + return Err(NS_ERROR_FAILURE); + } + if (NS_WARN_IF(!mMem.Map(aSize))) { + return Err(NS_ERROR_FAILURE); + } + + mInitialized = true; + return Ok(); +} + +Result<Ok, nsresult> MemMapSnapshot::Finalize(loader::AutoMemMap& aMem) { + MOZ_ASSERT(mInitialized); + + if (NS_WARN_IF(!mMem.Freeze())) { + return Err(NS_ERROR_FAILURE); + } + // TakeHandle resets mMem, so call max_size first. + size_t size = mMem.max_size(); + FileDescriptor memHandle(mMem.TakeHandle()); + MOZ_TRY(aMem.initWithHandle(memHandle, size)); + + mInitialized = false; + return Ok(); +} + +} // namespace mozilla::ipc diff --git a/dom/ipc/MemMapSnapshot.h b/dom/ipc/MemMapSnapshot.h new file mode 100644 index 0000000000..12b4d353ad --- /dev/null +++ b/dom/ipc/MemMapSnapshot.h @@ -0,0 +1,54 @@ +/* -*- 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 dom_ipc_MemMapSnapshot_h +#define dom_ipc_MemMapSnapshot_h + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "mozilla/RangedPtr.h" +#include "mozilla/Result.h" +#include "base/shared_memory.h" +#include "ErrorList.h" + +namespace mozilla { +namespace loader { +class AutoMemMap; +} + +namespace ipc { + +/** + * A helper class for creating a read-only snapshot of memory-mapped data. + * + * The Init() method initializes a read-write memory mapped region of the given + * size, which can be initialized with arbitrary data. The Finalize() method + * remaps that region as read-only (and backs it with a read-only file + * descriptor), and initializes an AutoMemMap with the new contents. + * + * The file descriptor for the resulting AutoMemMap can be shared among + * processes, to safely access a shared, read-only copy of the data snapshot. + */ +class MOZ_RAII MemMapSnapshot { + public: + Result<Ok, nsresult> Init(size_t aSize); + Result<Ok, nsresult> Finalize(loader::AutoMemMap& aMap); + + template <typename T> + RangedPtr<T> Get() { + MOZ_ASSERT(mInitialized); + return {static_cast<T*>(mMem.memory()), mMem.max_size() / sizeof(T)}; + } + + private: + base::SharedMemory mMem; + bool mInitialized = false; +}; + +} // namespace ipc +} // namespace mozilla + +#endif // dom_ipc_MemMapSnapshot_h diff --git a/dom/ipc/MemoryReportRequest.cpp b/dom/ipc/MemoryReportRequest.cpp new file mode 100644 index 0000000000..794351461d --- /dev/null +++ b/dom/ipc/MemoryReportRequest.cpp @@ -0,0 +1,170 @@ +/* -*- 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 "nsMemoryReporterManager.h" +#include "MemoryReportRequest.h" +#include "mozilla/ipc/FileDescriptor.h" +#include "mozilla/ipc/FileDescriptorUtils.h" + +using namespace mozilla::ipc; + +namespace mozilla::dom { + +MemoryReportRequestHost::MemoryReportRequestHost(uint32_t aGeneration) + : mGeneration(aGeneration), mSuccess(false) { + MOZ_COUNT_CTOR(MemoryReportRequestHost); + mReporterManager = nsMemoryReporterManager::GetOrCreate(); + NS_WARNING_ASSERTION(mReporterManager, "GetOrCreate failed"); +} + +void MemoryReportRequestHost::RecvReport(const MemoryReport& aReport) { + // Skip reports from older generations. We need to do this here since we + // could receive older reports from a subprocesses before it acknowledges + // a new request, and we only track one active request per process. + if (aReport.generation() != mGeneration) { + return; + } + + if (mReporterManager) { + mReporterManager->HandleChildReport(mGeneration, aReport); + } +} + +void MemoryReportRequestHost::Finish(uint32_t aGeneration) { + // Skip reports from older generations. See the comment in RecvReport. + if (mGeneration != aGeneration) { + return; + } + mSuccess = true; +} + +MemoryReportRequestHost::~MemoryReportRequestHost() { + MOZ_COUNT_DTOR(MemoryReportRequestHost); + + if (mReporterManager) { + mReporterManager->EndProcessReport(mGeneration, mSuccess); + mReporterManager = nullptr; + } +} + +NS_IMPL_ISUPPORTS(MemoryReportRequestClient, nsIRunnable) + +/* static */ void MemoryReportRequestClient::Start( + uint32_t aGeneration, bool aAnonymize, bool aMinimizeMemoryUsage, + const Maybe<FileDescriptor>& aDMDFile, const nsACString& aProcessString, + const ReportCallback& aReportCallback, + const FinishCallback& aFinishCallback) { + RefPtr<MemoryReportRequestClient> request = new MemoryReportRequestClient( + aGeneration, aAnonymize, aDMDFile, aProcessString, aReportCallback, + aFinishCallback); + + DebugOnly<nsresult> rv; + if (aMinimizeMemoryUsage) { + nsCOMPtr<nsIMemoryReporterManager> mgr = + do_GetService("@mozilla.org/memory-reporter-manager;1"); + rv = mgr->MinimizeMemoryUsage(request); + // mgr will eventually call actor->Run() + } else { + rv = request->Run(); + } + + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "actor operation failed"); +} + +MemoryReportRequestClient::MemoryReportRequestClient( + uint32_t aGeneration, bool aAnonymize, + const Maybe<FileDescriptor>& aDMDFile, const nsACString& aProcessString, + const ReportCallback& aReportCallback, + const FinishCallback& aFinishCallback) + : mGeneration(aGeneration), + mAnonymize(aAnonymize), + mProcessString(aProcessString), + mReportCallback(aReportCallback), + mFinishCallback(aFinishCallback) { + if (aDMDFile.isSome()) { + mDMDFile = aDMDFile.value(); + } +} + +MemoryReportRequestClient::~MemoryReportRequestClient() = default; + +class HandleReportCallback final : public nsIHandleReportCallback { + public: + using ReportCallback = typename MemoryReportRequestClient::ReportCallback; + + NS_DECL_ISUPPORTS + + explicit HandleReportCallback(uint32_t aGeneration, + const nsACString& aProcess, + const ReportCallback& aReportCallback) + : mGeneration(aGeneration), + mProcess(aProcess), + mReportCallback(aReportCallback) {} + + NS_IMETHOD Callback(const nsACString& aProcess, const nsACString& aPath, + int32_t aKind, int32_t aUnits, int64_t aAmount, + const nsACString& aDescription, + nsISupports* aUnused) override { + MemoryReport memreport(mProcess, nsCString(aPath), aKind, aUnits, aAmount, + mGeneration, nsCString(aDescription)); + mReportCallback(memreport); + return NS_OK; + } + + private: + ~HandleReportCallback() = default; + + uint32_t mGeneration; + const nsCString mProcess; + ReportCallback mReportCallback; +}; + +NS_IMPL_ISUPPORTS(HandleReportCallback, nsIHandleReportCallback) + +class FinishReportingCallback final : public nsIFinishReportingCallback { + public: + using FinishCallback = typename MemoryReportRequestClient::FinishCallback; + + NS_DECL_ISUPPORTS + + explicit FinishReportingCallback(uint32_t aGeneration, + const FinishCallback& aFinishCallback) + : mGeneration(aGeneration), mFinishCallback(aFinishCallback) {} + + NS_IMETHOD Callback(nsISupports* aUnused) override { + mFinishCallback(mGeneration); + return NS_OK; + } + + private: + ~FinishReportingCallback() = default; + + uint32_t mGeneration; + FinishCallback mFinishCallback; +}; + +NS_IMPL_ISUPPORTS(FinishReportingCallback, nsIFinishReportingCallback) + +NS_IMETHODIMP MemoryReportRequestClient::Run() { + nsCOMPtr<nsIMemoryReporterManager> mgr = + do_GetService("@mozilla.org/memory-reporter-manager;1"); + + // Run the reporters. The callback will turn each measurement into a + // MemoryReport. + RefPtr<HandleReportCallback> handleReport = + new HandleReportCallback(mGeneration, mProcessString, mReportCallback); + RefPtr<FinishReportingCallback> finishReporting = + new FinishReportingCallback(mGeneration, mFinishCallback); + + nsresult rv = mgr->GetReportsForThisProcessExtended( + handleReport, nullptr, mAnonymize, FileDescriptorToFILE(mDMDFile, "wb"), + finishReporting, nullptr); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "GetReportsForThisProcessExtended failed"); + return rv; +} + +} // namespace mozilla::dom diff --git a/dom/ipc/MemoryReportRequest.h b/dom/ipc/MemoryReportRequest.h new file mode 100644 index 0000000000..1e81a39bf6 --- /dev/null +++ b/dom/ipc/MemoryReportRequest.h @@ -0,0 +1,73 @@ +/* -*- 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_dom_MemoryReportRequest_h_ +#define mozilla_dom_MemoryReportRequest_h_ + +#include "mozilla/dom/MemoryReportTypes.h" +#include "mozilla/ipc/FileDescriptor.h" +#include "nsISupports.h" + +#include <functional> + +class nsMemoryReporterManager; + +namespace mozilla::dom { + +class MemoryReport; + +class MemoryReportRequestHost final { + public: + explicit MemoryReportRequestHost(uint32_t aGeneration); + ~MemoryReportRequestHost(); + + void RecvReport(const MemoryReport& aReport); + void Finish(uint32_t aGeneration); + + private: + const uint32_t mGeneration; + // Non-null if we haven't yet called EndProcessReport() on it. + RefPtr<nsMemoryReporterManager> mReporterManager; + bool mSuccess; +}; + +class MemoryReportRequestClient final : public nsIRunnable { + public: + using ReportCallback = std::function<void(const MemoryReport&)>; + using FinishCallback = std::function<void(const uint32_t&)>; + + NS_DECL_ISUPPORTS + + static void Start(uint32_t aGeneration, bool aAnonymize, + bool aMinimizeMemoryUsage, + const Maybe<mozilla::ipc::FileDescriptor>& aDMDFile, + const nsACString& aProcessString, + const ReportCallback& aReportCallback, + const FinishCallback& aFinishCallback); + + NS_IMETHOD Run() override; + + private: + MemoryReportRequestClient(uint32_t aGeneration, bool aAnonymize, + const Maybe<mozilla::ipc::FileDescriptor>& aDMDFile, + const nsACString& aProcessString, + const ReportCallback& aReportCallback, + const FinishCallback& aFinishCallback); + + private: + ~MemoryReportRequestClient(); + + uint32_t mGeneration; + bool mAnonymize; + mozilla::ipc::FileDescriptor mDMDFile; + nsCString mProcessString; + ReportCallback mReportCallback; + FinishCallback mFinishCallback; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_MemoryReportRequest_h_ diff --git a/dom/ipc/MemoryReportTypes.ipdlh b/dom/ipc/MemoryReportTypes.ipdlh new file mode 100644 index 0000000000..fb52e575f2 --- /dev/null +++ b/dom/ipc/MemoryReportTypes.ipdlh @@ -0,0 +1,22 @@ +/* -*- 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/. */ + +using struct mozilla::void_t from "mozilla/ipc/IPCCore.h"; + +namespace mozilla { +namespace dom { + +struct MemoryReport { + nsCString process; + nsCString path; + int32_t kind; + int32_t units; + int64_t amount; + uint32_t generation; + nsCString desc; +}; + +} +} diff --git a/dom/ipc/NativeThreadId.h b/dom/ipc/NativeThreadId.h new file mode 100644 index 0000000000..9193aefde1 --- /dev/null +++ b/dom/ipc/NativeThreadId.h @@ -0,0 +1,16 @@ +/* -*- 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_dom_NativeThreadId_h +#define mozilla_dom_NativeThreadId_h + +#include "nsExceptionHandler.h" + +namespace mozilla::dom { +typedef CrashReporter::ThreadId NativeThreadId; +} + +#endif diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl new file mode 100644 index 0000000000..bc126724f4 --- /dev/null +++ b/dom/ipc/PBrowser.ipdl @@ -0,0 +1,1023 @@ +/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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 PColorPicker; +include protocol PContent; +#ifdef ACCESSIBILITY +include protocol PDocAccessible; +#endif +include protocol PFilePicker; +include protocol PRemotePrintJob; +include protocol PPaymentRequest; +include protocol PSessionStore; +include protocol PWindowGlobal; +include protocol PBrowserBridge; +include protocol PVsync; + +include DOMTypes; +include NeckoChannelParams; +include WindowGlobalTypes; +include IPCBlob; +include IPCStream; +include IPCTransferable; +include URIParams; +include PPrintingTypes; +include PTabContext; +include PBackgroundSharedTypes; + +include "mozilla/AntiTrackingIPCUtils.h"; +include "mozilla/dom/BindingIPCUtils.h"; +include "mozilla/dom/CSPMessageUtils.h"; +include "mozilla/dom/DocShellMessageUtils.h"; +include "mozilla/dom/FilePickerMessageUtils.h"; +include "mozilla/dom/PermissionMessageUtils.h"; +include "mozilla/dom/ReferrerInfoUtils.h"; +include "mozilla/dom/TabMessageUtils.h"; +include "mozilla/GfxMessageUtils.h"; +include "mozilla/LayoutMessageUtils.h"; +include "mozilla/layers/LayersMessageUtils.h"; +include "mozilla/ipc/TransportSecurityInfoUtils.h"; +include "mozilla/ipc/URIUtils.h"; + +using struct nsID from "nsID.h"; +using mozilla::gfx::Matrix4x4 from "mozilla/gfx/Matrix.h"; +using mozilla::gfx::SurfaceFormat from "mozilla/gfx/Types.h"; +using mozilla::LayoutDeviceIntPoint from "Units.h"; +using mozilla::LayoutDevicePoint from "Units.h"; +using mozilla::ScreenIntCoord from "Units.h"; +using mozilla::ScreenIntMargin from "Units.h"; +using mozilla::ScreenIntPoint from "Units.h"; +using mozilla::ScreenRect from "Units.h"; +using struct mozilla::layers::ScrollableLayerGuid from "mozilla/layers/ScrollableLayerGuid.h"; +using struct mozilla::layers::ZoomConstraints from "mozilla/layers/ZoomConstraints.h"; +using struct mozilla::layers::DoubleTapToZoomMetrics from "mozilla/layers/DoubleTapToZoom.h"; +using mozilla::layers::LayersId from "mozilla/layers/LayersTypes.h"; +using mozilla::layers::GeckoContentController_TapType from "mozilla/layers/GeckoContentControllerTypes.h"; +using mozilla::layers::ScrollableLayerGuid::ViewID from "mozilla/layers/ScrollableLayerGuid.h"; +using struct mozilla::void_t from "mozilla/ipc/IPCCore.h"; +using mozilla::WindowsHandle from "mozilla/ipc/IPCTypes.h"; +using class mozilla::WidgetCompositionEvent from "ipc/nsGUIEventIPC.h"; +using struct mozilla::widget::IMENotification from "mozilla/widget/IMEData.h"; +using struct mozilla::widget::IMENotificationRequests from "mozilla/widget/IMEData.h"; +using struct mozilla::widget::IMEState from "mozilla/widget/IMEData.h"; +using struct mozilla::widget::InputContext from "mozilla/widget/IMEData.h"; +using struct mozilla::widget::InputContextAction from "mozilla/widget/IMEData.h"; +using mozilla::gfx::IntSize from "mozilla/gfx/Point.h"; +using mozilla::gfx::IntPoint from "mozilla/gfx/Point.h"; +using class mozilla::ContentCache from "ipc/nsGUIEventIPC.h"; +using class mozilla::WidgetKeyboardEvent from "ipc/nsGUIEventIPC.h"; +using class mozilla::WidgetMouseEvent from "ipc/nsGUIEventIPC.h"; +using class mozilla::WidgetWheelEvent from "ipc/nsGUIEventIPC.h"; +using class mozilla::WidgetDragEvent from "ipc/nsGUIEventIPC.h"; +using struct nsRect from "nsRect.h"; +using class mozilla::WidgetSelectionEvent from "ipc/nsGUIEventIPC.h"; +using class mozilla::WidgetTouchEvent from "ipc/nsGUIEventIPC.h"; +using struct mozilla::dom::RemoteDOMEvent from "mozilla/dom/TabMessageTypes.h"; +using struct mozilla::layers::TextureFactoryIdentifier from "mozilla/layers/CompositorTypes.h"; +using mozilla::layers::CompositorOptions from "mozilla/layers/CompositorOptions.h"; +using mozilla::CSSToScreenScale from "Units.h"; +using mozilla::CommandInt from "mozilla/EventForwards.h"; +using nsIWidget::TouchPointerState from "nsIWidget.h"; +using nsIWidget::TouchpadGesturePhase from "nsIWidget.h"; +using nsCursor from "nsIWidget.h"; +using struct LookAndFeelInt from "mozilla/widget/WidgetMessageUtils.h"; +using struct mozilla::DimensionRequest from "mozilla/widget/WidgetMessageUtils.h"; +using class mozilla::dom::MessagePort from "mozilla/dom/MessagePort.h"; +using class mozilla::dom::ipc::StructuredCloneData from "mozilla/dom/ipc/StructuredCloneData.h"; +using mozilla::dom::MaybeDiscardedWindowContext from "mozilla/dom/WindowContext.h"; +using mozilla::EventMessage from "mozilla/EventForwards.h"; +using nsEventStatus from "mozilla/EventForwards.h"; +using mozilla::Modifiers from "mozilla/EventForwards.h"; +using struct mozilla::widget::CandidateWindowPosition from "ipc/nsGUIEventIPC.h"; +using struct mozilla::FontRange from "ipc/nsGUIEventIPC.h"; +using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h"; +using mozilla::dom::EffectsInfo from "mozilla/dom/EffectsInfo.h"; +using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h"; +using mozilla::ScrollAxis from "mozilla/PresShellForwards.h"; +using mozilla::ScrollFlags from "mozilla/PresShellForwards.h"; +using struct InputFormData from "mozilla/dom/SessionStoreMessageUtils.h"; +using struct CollectedInputDataValue from "mozilla/dom/SessionStoreMessageUtils.h"; +using mozilla::ContentBlockingNotifier::StorageAccessPermissionGrantedReason from "mozilla/ContentBlockingNotifier.h"; +using mozilla::ContentBlockingNotifier::CanvasFingerprinter from "mozilla/ContentBlockingNotifier.h"; +using mozilla::dom::CallerType from "mozilla/dom/BindingDeclarations.h"; +using mozilla::dom::EmbedderElementEventType from "mozilla/dom/TabMessageTypes.h"; +using mozilla::IntrinsicSize from "nsIFrame.h"; +using mozilla::AspectRatio from "mozilla/AspectRatio.h"; +using mozilla::NativeKeyBindingsType from "mozilla/NativeKeyBindingsType.h"; +using mozilla::StyleImageRendering from "mozilla/ServoStyleConsts.h"; +[MoveOnly] using class mozilla::ipc::BigBuffer from "mozilla/ipc/BigBuffer.h"; +using nsIFilePicker::Mode from "nsIFilePicker.h"; + +namespace mozilla { +namespace dom { + +struct WebProgressData +{ + MaybeDiscardedBrowsingContext browsingContext; + uint32_t loadType; +}; + +struct RequestData +{ + nullable nsIURI requestURI; + nullable nsIURI originalRequestURI; + nsCString matchedList; +}; + +struct WebProgressStateChangeData +{ + bool isNavigating; + bool mayEnableCharacterEncodingMenu; + + // The following fields are only set when the aStateFlags param passed with + // this struct is |nsIWebProgress.STATE_STOP|. + nsString contentType; + nsString charset; + nullable nsIURI documentURI; +}; + +struct WebProgressLocationChangeData +{ + bool isNavigating; + bool isSyntheticDocument; + bool mayEnableCharacterEncodingMenu; + nsString contentType; + nsString title; + nsString charset; + nullable nsIURI documentURI; + nullable nsIPrincipal contentPrincipal; + nullable nsIPrincipal contentPartitionedPrincipal; + nullable nsIContentSecurityPolicy csp; + nullable nsIReferrerInfo referrerInfo; + uint64_t? requestContextID; +}; + +/** + * If creating the print preview document or updating it with new print + * settings fails, sheetCount will be zero. + */ +struct PrintPreviewResultInfo +{ + uint32_t sheetCount; + uint32_t totalPageCount; + bool isEmpty; + // Whether there's a selection in the previewed page, including its subframes. + bool hasSelection; + // Whether there's a selection in the previewed page, excluding its subframes. + bool hasSelfSelection; + // If present, indicates if the page should be printed landscape when true or + // portrait when false; + bool? printLandscape; + // The at-page specified page width or null when no width is provided. + float? pageWidth; + // The at-page specified page height or null when no height is provided. + float? pageHeight; +}; + +/** + * A PBrowser manages a maximal locally connected subtree of BrowsingContexts + * in a content process. + * + * See `dom/docs/Fission-IPC-Diagram.svg` for an overview of the DOM IPC + * actors. + */ +[NestedUpTo=inside_cpow] sync protocol PBrowser +{ + manager PContent; + + manages PColorPicker; + +#ifdef ACCESSIBILITY + manages PDocAccessible; +#endif + + manages PFilePicker; + manages PPaymentRequest; + manages PSessionStore; + manages PWindowGlobal; + manages PBrowserBridge; + manages PVsync; + +both: + async AsyncMessage(nsString aMessage, ClonedMessageData aData); + +parent: +#ifdef ACCESSIBILITY + /** + * Tell the parent process a new accessible document has been created. + * aParentDoc is the accessible document it was created in if any, and + * aParentAcc is the id of the accessible in that document the new document + * is a child of. + */ + async PDocAccessible(nullable PDocAccessible aParentDoc, uint64_t aParentAcc, + MaybeDiscardedBrowsingContext aBrowsingContext); +#endif + + async PPaymentRequest(); + + /** + * Create a new Vsync connection for our associated root widget + */ + async PVsync(); + + /** + * When the child process unsuppresses painting, we send the message for the + * parent process to start painting the new document, if it's still painting + * the old one. + */ + [Priority=control] async DidUnsuppressPainting(); + + async DidUnsuppressPaintingNormalPriority(); + + /** + * When child sends this message, parent should move focus to + * the next or previous focusable element or document. + */ + async MoveFocus(bool forward, bool forDocumentNavigation); + + /** + * Called by the child to inform the parent that links are dropped into + * content area. + * + * aLinks A flat array of url, name, and type for each link + */ + async DropLinks(nsString[] aLinks); + + sync SyncMessage(nsString aMessage, ClonedMessageData aData) + returns (StructuredCloneData[] retval); + + /** + * Notifies chrome that there is a focus change involving an editable + * object (input, textarea, document, contentEditable. etc.) + * + * contentCache Cache of content + * notification Whole data of the notification + * requests Requests of notification for IME of the native widget + */ + [Nested=inside_cpow] async NotifyIMEFocus(ContentCache contentCache, + IMENotification notification) + returns (IMENotificationRequests requests); + + /** + * Notifies chrome that there has been a change in text content + * One call can encompass both a delete and an insert operation + * Only called when NotifyIMEFocus returns PR_TRUE for mWantUpdates + * + * contentCache Cache of content + * notification Whole data of the notification + */ + [Nested=inside_cpow] async NotifyIMETextChange(ContentCache contentCache, + IMENotification notification); + + /** + * Notifies chrome that there is a IME compostion rect updated + * + * contentCache Cache of content + */ + [Nested=inside_cpow] async NotifyIMECompositionUpdate(ContentCache contentCache, + IMENotification notification); + + /** + * Notifies chrome that there has been a change in selection + * Only called when NotifyIMEFocus returns PR_TRUE for mWantUpdates + * + * contentCache Cache of content + * notification Whole data of the notification + */ + [Nested=inside_cpow] async NotifyIMESelection(ContentCache contentCache, + IMENotification notification); + + /** + * Notifies chrome of updating its content cache. + * This is useful if content is modified but we don't need to notify IME. + * + * contentCache Cache of content + */ + [Nested=inside_cpow] async UpdateContentCache(ContentCache contentCache); + + /** + * Notifies IME of mouse button event on a character in focused editor. + * + * Returns true if the mouse button event is consumed by IME. + */ + [Nested=inside_cpow] sync NotifyIMEMouseButtonEvent(IMENotification notification) + returns (bool consumedByIME); + + /** + * Notifies chrome to position change + * + * contentCache Cache of content + */ + [Nested=inside_cpow] async NotifyIMEPositionChange(ContentCache contentCache, + IMENotification notification); + + /** + * Requests chrome to commit or cancel composition of IME. + * + * cancel Set true if composition should be cancelled. + * compositionId Set the composition ID to requesting commit + * (stored in TextComposition). + * + * isCommitted Returns true if the request causes composition + * being committed synchronously. + * committedString Returns committed string. The may be non-empty + * string even if cancel is true because IME may + * try to restore selected string which was + * replaced with the composition. + */ + [Nested=inside_cpow] sync RequestIMEToCommitComposition(bool cancel, + uint32_t aCompositionId) + returns (bool isCommitted, nsString committedString); + + /** + * OnEventNeedingAckHandled() is called after a child process dispatches a + * composition event or a selection event which is sent from the parent + * process. + * + * message The message value of the handled event. + * compositionId The composition ID of handled composition event if + * the message is a composition event message. Otherwise, + * 0. + */ + [Nested=inside_cpow] async OnEventNeedingAckHandled(EventMessage message, + uint32_t compositionId); + + /** + * Request that the parent process move focus to the browser's frame. If + * canRaise is true, the window can be raised if it is inactive. + */ + async RequestFocus(bool canRaise, CallerType aCallerType); + + /** + * Sends a mouse wheel zoom change to the parent process, to be handled by + * the front end as needed. + */ + async WheelZoomChange(bool increase); + + /** + * Indicate, based on the current state, that some commands are enabled and + * some are disabled. + */ + async EnableDisableCommands(MaybeDiscardedBrowsingContext bc, + nsString action, + nsCString[] enabledCommands, + nsCString[] disabledCommands); + + [Nested=inside_cpow] sync GetInputContext() returns (IMEState state); + + [Nested=inside_cpow] async SetInputContext(InputContext context, + InputContextAction action); + + /** + * Set the native cursor. + * @param value + * The widget cursor to set. + * @param hasCustomCursor + * Whether there's any custom cursor represented by cursorData and + * company. + * @param customCursorData + * Serialized image data. + * @param width + * Width of the image. + * @param height + * Height of the image. + * @param resolutionX + * Resolution of the image X axis in dppx units. + * @param resolutionY + * Resolution of the image Y axis in dppx units. + * @param stride + * Stride used in the image data. + * @param format + * Image format, see gfx::SurfaceFormat for possible values. + * @param hotspotX + * Horizontal hotspot of the image, as specified by the css cursor property. + * @param hotspotY + * Vertical hotspot of the image, as specified by the css cursor property. + * @param force + * Invalidate any locally cached cursor settings and force an + * update. + */ + async SetCursor(nsCursor value, + bool hasCustomCursor, + BigBuffer? customCursorData, + uint32_t width, uint32_t height, + float resolutionX, float resolutionY, + uint32_t stride, SurfaceFormat format, + uint32_t hotspotX, uint32_t hotspotY, bool force); + + /** + * Used to set the current text of the status tooltip. + * Nowadays this is only used for link locations on hover. + */ + async SetLinkStatus(nsString status); + + /** + * Show/hide a tooltip when the mouse hovers over an element in the content + * document. + */ + async ShowTooltip(uint32_t x, uint32_t y, nsString tooltip, nsString direction); + async HideTooltip(); + + /** + * Create an asynchronous color picker on the parent side, + * but don't open it yet. + */ + async PColorPicker(nsString title, nsString initialColor, nsString[] defaultColors); + + async PFilePicker(nsString aTitle, Mode aMode); + + /** + * Tells the containing widget whether the given input block results in a + * swipe. Should be called in response to a WidgetWheelEvent that has + * mFlags.mCanTriggerSwipe set on it. + */ + async RespondStartSwipeEvent(uint64_t aInputBlockId, bool aStartSwipe); + + /** + * Look up dictionary by selected word for OSX + * + * @param aText The word to look up + * @param aFontRange Text decoration of aText + * @param aIsVertical true if vertical layout + */ + async LookUpDictionary(nsString aText, FontRange[] aFontRangeArray, + bool aIsVertical, LayoutDeviceIntPoint aPoint); + + async __delete__(); + + /** + * Send a reply of keyboard event to the parent. Then, parent can consider + * whether the event should kick a shortcut key or ignore. + * + * @param aEvent The event which was sent from the parent and handled + * in a remote process. + * @param aUUI The UUID which was generated when aEvent was sent to + * a remote process. + */ + async ReplyKeyEvent(WidgetKeyboardEvent aEvent, nsID aUUID); + + /** + * Retrieves edit commands for the key combination represented by aEvent. + * + * @param aType One of NativeKeyBindingsType. + * @param aEvent KeyboardEvent which represents a key combination. + * Note that this must be a trusted event. + * @return Array of edit commands which should be executed in + * editor of native applications. + */ + sync RequestNativeKeyBindings(uint32_t aType, WidgetKeyboardEvent aEvent) + returns (CommandInt[] commands); + + async SynthesizeNativeKeyEvent(int32_t aNativeKeyboardLayout, + int32_t aNativeKeyCode, + uint32_t aModifierFlags, + nsString aCharacters, + nsString aUnmodifiedCharacters, + uint64_t aObserverId); + async SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint, + uint32_t aNativeMessage, + int16_t aButton, + uint32_t aModifierFlags, + uint64_t aObserverId); + async SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint, + uint64_t aObserverId); + async SynthesizeNativeMouseScrollEvent(LayoutDeviceIntPoint aPoint, + uint32_t aNativeMessage, + double aDeltaX, + double aDeltaY, + double aDeltaZ, + uint32_t aModifierFlags, + uint32_t aAdditionalFlags, + uint64_t aObserverId); + async SynthesizeNativeTouchPoint(uint32_t aPointerId, + TouchPointerState aPointerState, + LayoutDeviceIntPoint aPoint, + double aPointerPressure, + uint32_t aPointerOrientation, + uint64_t aObserverId); + async SynthesizeNativeTouchPadPinch(TouchpadGesturePhase aEventPhase, + float aScale, + LayoutDeviceIntPoint aPoint, + int32_t aModifierFlags); + async SynthesizeNativeTouchTap(LayoutDeviceIntPoint aPoint, + bool aLongTap, + uint64_t aObserverId); + async ClearNativeTouchSequence(uint64_t aObserverId); + async SynthesizeNativePenInput(uint32_t aPointerId, + TouchPointerState aPointerState, + LayoutDeviceIntPoint aPoint, + double aPressure, + uint32_t aRotation, + int32_t aTiltX, + int32_t aTiltY, + int32_t aButton, + uint64_t aObserverId); + async SynthesizeNativeTouchpadDoubleTap(LayoutDeviceIntPoint aPoint, + uint32_t aModifierFlags); + async SynthesizeNativeTouchpadPan(TouchpadGesturePhase aEventPhase, + LayoutDeviceIntPoint aPoint, + double aDeltaX, double aDeltaY, + int32_t aModifierFlags, + uint64_t aObserverId); + + async LockNativePointer(); + async UnlockNativePointer(); + + async AccessKeyNotHandled(WidgetKeyboardEvent event); + + async RegisterProtocolHandler(nsString scheme, nullable nsIURI handlerURI, nsString title, + nullable nsIURI documentURI); + + async OnStateChange(WebProgressData aWebProgressData, + RequestData aRequestData, uint32_t aStateFlags, + nsresult aStatus, + WebProgressStateChangeData? aStateChangeData); + + async OnLocationChange(WebProgressData aWebProgressData, + RequestData aRequestData, nullable nsIURI aLocation, + uint32_t aFlags, bool aCanGoBack, + bool aCanGoForward, + WebProgressLocationChangeData? aLocationChangeData); + + // We only track information about total progress in the parent process. + // This value is throttled using nsBrowserStatusFilter, and records the full + // total progress for nsDocShells managed by this actor. + async OnProgressChange(int32_t aCurTotalProgress, int32_t aMaxTotalProgress); + + // Calls to OnStatusChange are throttled by nsBrowserStatusFilter, meaning + // they are only called with a status of `NS_OK`, and with no webProgress or + // request. + async OnStatusChange(nsString aMessage); + + async NotifyContentBlockingEvent(uint32_t aEvent, RequestData aRequestData, + bool aBlocked, nsCString aTrackingOrigin, + nsCString[] aTrackingFullHashes, + StorageAccessPermissionGrantedReason? aReason, + CanvasFingerprinter? aCanvasFingerprinter, + bool? aCanvasFingerprinterKnownText); + + async NavigationFinished(); + + async IntrinsicSizeOrRatioChanged(IntrinsicSize? aIntrinsicSize, + AspectRatio? aIntrinsicRatio); + + async ImageLoadComplete(nsresult aResult); + + /** + * Child informs the parent that a pointer lock has requested/released. + */ + async RequestPointerLock() returns (nsCString error); + async ReleasePointerLock(); + + /** + * Child informs the parent that a pointer capture has requested/released. + */ + async RequestPointerCapture(uint32_t aPointerId) returns (bool aSuccess); + async ReleasePointerCapture(uint32_t aPointerId); + +child: + async NativeSynthesisResponse(uint64_t aObserverId, nsCString aResponse); + async UpdateSHistory(); + async CloneDocumentTreeIntoSelf(MaybeDiscardedBrowsingContext aBc, PrintData aPrintData) returns(bool aSuccess); + async UpdateRemotePrintSettings(PrintData aPrintData); + + /** + * Parent informs the child to release all pointer capture. + */ + [Priority=input] async ReleaseAllPointerCapture(); + +parent: + + /** + * Child informs the parent that the content is ready to handle input + * events. This is sent when the BrowserChild is created. + */ + async RemoteIsReadyToHandleInputEvents(); + +child: + /** + * Parent informs the child of graphical effects that are being applied + * to the child browser. + */ + async UpdateEffects(EffectsInfo aEffects); + +parent: + + /** + * Sent by the child to the parent to inform it that an update to the + * dimensions has been requested. + * + * @param aRequest The requested change of inner or outer dimensions. + * @param aScale The scale at the time of the request. This is to allow + * the parent to recompute the dimensions in case of an + * ongoing scale change. + */ + async SetDimensions(DimensionRequest aRequest, double aScale); + + [Nested=inside_sync] sync DispatchWheelEvent(WidgetWheelEvent event); + [Nested=inside_sync] sync DispatchMouseEvent(WidgetMouseEvent event); + [Nested=inside_sync] sync DispatchKeyboardEvent(WidgetKeyboardEvent event); + [Nested=inside_sync] sync DispatchTouchEvent(WidgetTouchEvent event); + + async InvokeDragSession(IPCTransferableData[] transfers, uint32_t action, + BigBuffer? visualData, + uint32_t stride, SurfaceFormat format, + LayoutDeviceIntRect dragRect, + nullable nsIPrincipal principal, + nullable nsIContentSecurityPolicy csp, + CookieJarSettingsArgs cookieJarSettings, + MaybeDiscardedWindowContext sourceWindowContext, + MaybeDiscardedWindowContext sourceTopWindowContext); + + // After a compositor reset, it is necessary to reconnect each layers ID to + // the compositor of the widget that will render those layers. Note that + // this is sync so we can ensure that messages to the window compositor + // arrive before the BrowserChild attempts to use its cross-process compositor + // bridge. + sync EnsureLayersConnected() returns (CompositorOptions compositorOptions); + + /** + * This function is used to notify the parent that it should display a + * canvas permission prompt. + * + * @param aOrigin origin string of the document that is requesting access. + */ + async ShowCanvasPermissionPrompt(nsCString aOrigin, + bool aHideDoorHanger); + + sync SetSystemFont(nsCString aFontName); + sync GetSystemFont() returns (nsCString retval); + + /** + * Called once this PBrowser's OOP subdoc no longer blocks its + * embedding element's and embedding doc's 'load' events. + */ + async MaybeFireEmbedderLoadEvents(EmbedderElementEventType aFireEventAtEmbeddingElement); + + async ScrollRectIntoView(nsRect aRect, ScrollAxis aVertical, + ScrollAxis aHorizontal, ScrollFlags aScrollFlags, + int32_t aAppUnitsPerDevPixel); + + async ShowDynamicToolbar(); + +child: + /** + * Notify the remote browser that it has been Show()n on this side. This + * message is expected to trigger creation of the remote browser's "widget". + */ + async Show(ParentShowInfo parentInfo, OwnerShowInfo childInfo); + + /** + * Sending an activate message moves focus to the child. + */ + async Activate(uint64_t aActionId); + + async Deactivate(uint64_t aActionId); + + async ScrollbarPreferenceChanged(ScrollbarPreference pref); + + async InitRendering(TextureFactoryIdentifier textureFactoryIdentifier, + LayersId layersId, + CompositorOptions compositorOptions, + bool layersConnected); + + async CompositorOptionsChanged(CompositorOptions newOptions); + + async LoadURL(nsDocShellLoadState loadState, ParentShowInfo info); + + async CreateAboutBlankDocumentViewer(nullable nsIPrincipal principal, + nullable nsIPrincipal partitionedPrincipal); + + async ResumeLoad(uint64_t pendingSwitchID, ParentShowInfo info); + + [Compress=all] async UpdateDimensions(DimensionInfo dimensions); + + async SizeModeChanged(nsSizeMode sizeMode); + + async ChildToParentMatrix(Matrix4x4? aMatrix, + ScreenRect aRemoteDocumentRect); + + async UpdateRemoteStyle(StyleImageRendering aImageRendering); + + async DynamicToolbarMaxHeightChanged(ScreenIntCoord height); + + async DynamicToolbarOffsetChanged(ScreenIntCoord height); + + /** + * StopIMEStateManagement() is called when the process loses focus and + * should stop managing IME state. + */ + async StopIMEStateManagement(); + + /** + * When two consecutive mouse move events would be added to the message queue, + * they are 'compressed' by dumping the oldest one. + */ + [Compress, Priority=input] + async RealMouseMoveEvent(WidgetMouseEvent event, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId); + [Compress] + async NormalPriorityRealMouseMoveEvent(WidgetMouseEvent event, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId); + + /** + * But don't compress mousemove events for tests since every event is + * important for the test since synthesizing various input events may + * be faster than what the user operates same things. If you need to + * test the `Compress`, send mouse move events with setting `isSyntehsized` + * of `aEvent` of `EventUtils#syntehsizeMouse*()`. + */ + [Priority=input] + async RealMouseMoveEventForTests(WidgetMouseEvent event, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId); + async NormalPriorityRealMouseMoveEventForTests(WidgetMouseEvent event, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId); + + /** + * Mouse move events with |reason == eSynthesized| are sent via a separate + * message because they do not generate DOM 'mousemove' events, and the + * 'Compress' attribute on RealMouseMoveEvent() could result in a + * |reason == eReal| event being dropped in favour of an |eSynthesized| + * event, and thus a DOM 'mousemove' event to be lost. + */ + [Priority=input] + async SynthMouseMoveEvent(WidgetMouseEvent event, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId); + async NormalPrioritySynthMouseMoveEvent(WidgetMouseEvent event, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId); + + [Priority=input] + async RealMouseButtonEvent(WidgetMouseEvent event, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId); + async NormalPriorityRealMouseButtonEvent(WidgetMouseEvent event, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId); + + [Priority=input] + async RealMouseEnterExitWidgetEvent(WidgetMouseEvent event, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId); + async NormalPriorityRealMouseEnterExitWidgetEvent(WidgetMouseEvent event, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId); + + /** + * Send a keyboard event which reporesents a user input to a remote process. + * + * @param aEvent The event which user typed a key. + * @param aUUID A UUID which is generated in the parent at sending it. + * This must be specified when the child sends a reply + * event to the parent. + */ + [Priority=input] + async RealKeyEvent(WidgetKeyboardEvent aEvent, nsID aUUID); + async NormalPriorityRealKeyEvent(WidgetKeyboardEvent aEvent, nsID aUUID); + + [Priority=input] + async MouseWheelEvent(WidgetWheelEvent event, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId); + async NormalPriorityMouseWheelEvent(WidgetWheelEvent event, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId); + + [Priority=input] + async RealTouchEvent(WidgetTouchEvent aEvent, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId, + nsEventStatus aApzResponse); + async NormalPriorityRealTouchEvent(WidgetTouchEvent aEvent, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId, + nsEventStatus aApzResponse); + + [Priority=input] + async HandleTap(GeckoContentController_TapType aType, LayoutDevicePoint point, + Modifiers aModifiers, ScrollableLayerGuid aGuid, + uint64_t aInputBlockId, + DoubleTapToZoomMetrics? aMetrics); + async NormalPriorityHandleTap(GeckoContentController_TapType aType, LayoutDevicePoint point, + Modifiers aModifiers, ScrollableLayerGuid aGuid, + uint64_t aInputBlockId, + DoubleTapToZoomMetrics? aMetrics); + + [Compress, Priority=input] + async RealTouchMoveEvent(WidgetTouchEvent aEvent, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId, + nsEventStatus aApzResponse); + [Compress] + async NormalPriorityRealTouchMoveEvent(WidgetTouchEvent aEvent, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId, + nsEventStatus aApzResponse); + [Compress, Priority=input] + async RealTouchMoveEvent2(WidgetTouchEvent aEvent, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId, + nsEventStatus aApzResponse); + [Compress] + async NormalPriorityRealTouchMoveEvent2(WidgetTouchEvent aEvent, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId, + nsEventStatus aApzResponse); + + /* + * We disable the input event queue when there is an active dnd session. We + * don't need support RealDragEvent with input priority. + */ + async RealDragEvent(WidgetDragEvent aEvent, uint32_t aDragAction, + uint32_t aDropEffect, nullable nsIPrincipal aPrincipal, + nullable nsIContentSecurityPolicy csp); + + [Priority=input] async CompositionEvent(WidgetCompositionEvent event); + async NormalPriorityCompositionEvent(WidgetCompositionEvent event); + + [Priority=input] async SelectionEvent(WidgetSelectionEvent event); + async NormalPrioritySelectionEvent(WidgetSelectionEvent event); + + /** + * Dispatch eContentCommandInsertText event in the remote process. + */ + [Priority=input] async InsertText(nsString aStringToInsert); + async NormalPriorityInsertText(nsString aStringToInsert); + + /** + * Call PasteTransferable via a controller on the content process + * to handle the command content event, "pasteTransferable". + */ + async PasteTransferable(IPCTransferable aTransferable); + + async LoadRemoteScript(nsString aURL, bool aRunInGlobalScope); + + /** + * Sent by the chrome process when it no longer wants this remote + * <browser>. The child side cleans up in response, then + * finalizing its death by sending back __delete__() to the + * parent. + */ + async Destroy(); + + /** + * If aEnabled is true, tells the child to paint and upload layers to + * the compositor. If aEnabled is false, the child stops painting and + * clears the layers from the compositor. + * + * @param aEnabled + * True if the child should render and upload layers, false if the + * child should clear layers. + */ + async RenderLayers(bool aEnabled); + + /** + * Communicates the child that we want layers to be preserved even when the + * browser is inactive. + */ + async PreserveLayers(bool aPreserve); +child: + /** + * Notify the child that it shouldn't paint the offscreen displayport. + * This is useful to speed up interactive operations over async + * scrolling performance like resize, tabswitch, pageload. + * + * Each enable call must be matched with a disable call. The child + * will remain in the suppress mode as long as there's + * a single unmatched call. + */ + async SuppressDisplayport(bool aEnabled); + + /** + * Navigate by key (Tab/Shift+Tab/F6/Shift+f6). + */ + async NavigateByKey(bool aForward, bool aForDocumentNavigation); + + /** + * Tell the child that the UI resolution changed for the containing + * window. + * To avoid some sync messages from child to parent, we also send the dpi + * and default scale with the notification. + * If we don't know the dpi and default scale, we just pass in a negative + * value (-1) but in the majority of the cases this saves us from two + * sync requests from the child to the parent. + */ + async UIResolutionChanged(float dpi, int32_t rounding, double scale); + + /** + * Tell the child that the safe area of widget has changed. + * + */ + async SafeAreaInsetsChanged(ScreenIntMargin aSafeAreaInsets); + + /** + * Tell the browser that its frame loader has been swapped + * with another. + */ + async SwappedWithOtherRemoteLoader(IPCTabContext context); + + /** + * A potential accesskey was just pressed. Look for accesskey targets + * using the list of provided charCodes. + * + * @param event keyboard event + * @param isTrusted true if triggered by a trusted key event + */ + async HandleAccessKey(WidgetKeyboardEvent event, + uint32_t[] charCodes); + + /** + * Tell the child to create a print preview document in this browser, or + * to update the existing print preview document with new print settings. + * + * @param aPrintData The serialized print settings to use to layout the + * print preview document. + * @param aSourceBrowsingContext Optionally, the browsing context that + * contains the document from which the print preview is to be generated. + * This should only be passed on the first call. It should not be passed + * for any subsequent calls that are made to update the existing print + * preview document with a new print settings object. + */ + async PrintPreview(PrintData aPrintData, + MaybeDiscardedBrowsingContext aSourceBrowsingContext) returns (PrintPreviewResultInfo aInfo); + + /** + * Inform the print preview document that we're done with it. + */ + async ExitPrintPreview(); + + /** + * Tell the child to print the current page with the given settings. + * + * @param aBrowsingContext the browsing context to print. + * @param aPrintData the serialized settings to print with + */ + async Print(MaybeDiscardedBrowsingContext aBC, PrintData aPrintData); + + /** + * Update the child with the tab's current top-level native window handle. + * This is used by a11y objects who must expose their native window. + * + * @param aNewHandle The native window handle of the tab's top-level window. + */ + async UpdateNativeWindowHandle(uintptr_t aNewHandle); + + /** + * Tell the BrowserChild to allow scripts in the docshell to close the window. + */ + async AllowScriptsToClose(); + + async WillChangeProcess(); + +parent: + /** + * Fetches whether this window supports protected media, which is sent back in response. + */ + async IsWindowSupportingProtectedMedia(uint64_t aOuterWindowID) returns(bool isSupported); + + /** + * Fetches whether this window supports WebVR, which is sent back in response. + */ + async IsWindowSupportingWebVR(uint64_t aOuterWindowID) returns(bool isSupported); + + /** Records a history visit. */ + async VisitURI(nullable nsIURI aURI, nullable nsIURI aLastVisitedURI, uint32_t aFlags, uint64_t aBrowserId); + + /** Fetches the visited status for an array of URIs (Android-only). */ + async QueryVisitedState(nullable nsIURI[] aURIs); + + /** Create a session store for a browser child. */ + async PSessionStore(); + + /** + * Construct a new WindowGlobal for an existing global in the content process + */ + async NewWindowGlobal(ManagedEndpoint<PWindowGlobalParent> aEndpoint, + WindowGlobalInit aInit); + +/* + * FIXME: write protocol! + +state LIVE: + send LoadURL goto LIVE; +//etc. + send Destroy goto DYING; + +state DYING: + discard send blah; +// etc. + recv __delete__; + */ +}; + +} +} diff --git a/dom/ipc/PBrowserBridge.ipdl b/dom/ipc/PBrowserBridge.ipdl new file mode 100644 index 0000000000..c2af10d2a4 --- /dev/null +++ b/dom/ipc/PBrowserBridge.ipdl @@ -0,0 +1,134 @@ +/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* vim: set sw=4 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 PBrowser; +#ifdef ACCESSIBILITY +include protocol PDocAccessible; +#endif + +include DOMTypes; +include PPrintingTypes; + +include "mozilla/LayoutMessageUtils.h"; +include "mozilla/dom/BindingIPCUtils.h"; +include "mozilla/dom/DocShellMessageUtils.h"; +include "mozilla/dom/TabMessageUtils.h"; + +using class mozilla::WidgetMouseEvent from "ipc/nsGUIEventIPC.h"; +using mozilla::dom::EffectsInfo from "mozilla/dom/EffectsInfo.h"; +using mozilla::ScrollAxis from "mozilla/PresShellForwards.h"; +using mozilla::ScrollFlags from "mozilla/PresShellForwards.h"; +using struct nsRect from "nsRect.h"; +using mozilla::dom::CallerType from "mozilla/dom/BindingDeclarations.h"; +using nsIntRect from "nsRect.h"; +using mozilla::dom::EmbedderElementEventType from "mozilla/dom/TabMessageTypes.h"; +[RefCounted] using class nsDocShellLoadState from "nsDocShellLoadState.h"; +using mozilla::IntrinsicSize from "nsIFrame.h"; +using mozilla::AspectRatio from "mozilla/AspectRatio.h"; +using mozilla::StyleImageRendering from "mozilla/ServoStyleConsts.h"; + +namespace mozilla { +namespace dom { + +/** + * A PBrowserBridge connects an iframe/browser in a content process to the + * PBrowser that manages the embedded content. + * + * See `dom/docs/Fission-IPC-Diagram.svg` for an overview of the DOM IPC + * actors. + */ +async protocol PBrowserBridge { + manager PBrowser; + +child: + /** + * Request that the IPC child / Web parent process move focus to the + * browser's frame. If canRaise is true, the window can be raised if + * it is inactive. + */ + async RequestFocus(bool canRaise, CallerType aCallerType); + + /** + * When IPC parent / Web child sends this message, the IPC child / Web parent + * should move focus to the next or previous focusable element or document. + */ + async MoveFocus(bool forward, bool forDocumentNavigation); + + /** + * Called once this PBrowserBridge's OOP subdoc no longer blocks its + * embedding element's and embedding doc's 'load' events. + */ + async MaybeFireEmbedderLoadEvents(EmbedderElementEventType aFireEventAtEmbeddingElement); + + async ScrollRectIntoView(nsRect aRect, ScrollAxis aVertical, + ScrollAxis aHorizontal, ScrollFlags aScrollFlags, + int32_t aAppUnitsPerDevPixel); + + async SubFrameCrashed(); + + async IntrinsicSizeOrRatioChanged(IntrinsicSize? aIntrinsicSize, + AspectRatio? aIntrinsicRatio); + + async ImageLoadComplete(nsresult aResult); + +both: + + // Destroy the remote web browser due to the nsFrameLoader going away. + // Before initialization we sync-delete it from the child. After + // initialization we sync-delete it from the parent after BeginDestroy(). + async __delete__(); + +parent: + + async BeginDestroy(); + + // DocShell messaging. + async LoadURL(nsDocShellLoadState aLoadState); + async ResumeLoad(uint64_t aPendingSwitchID); + + // Out of process rendering. + async Show(OwnerShowInfo info); + async ScrollbarPreferenceChanged(ScrollbarPreference pref); + [Compress=all] async UpdateDimensions(nsIntRect rect, ScreenIntSize size); + async RenderLayers(bool aEnabled); + + async UpdateEffects(EffectsInfo aEffects); + async UpdateRemotePrintSettings(PrintData aPrintData); + + /** + * Navigate by key (Tab/Shift+Tab/F6/Shift+f6). + */ + async NavigateByKey(bool aForward, bool aForDocumentNavigation); + + /** + * Dispatch the given synthesized mousemove event to the child. + */ + async DispatchSynthesizedMouseEvent(WidgetMouseEvent event); + + /** + * Sending an activate message moves focus to the iframe. + */ + async Activate(uint64_t aActionId); + + async Deactivate(bool aWindowLowering, uint64_t aActionId); + + async UpdateRemoteStyle(StyleImageRendering aImageRendering); + + async WillChangeProcess(); + +#ifdef ACCESSIBILITY + /** + * Tell the parent the accessible for this iframe's embedder + * OuterDocAccessible. + * aDoc is the actor for the containing document. + * aID is the unique id of the embedder accessible within that document. + */ + async SetEmbedderAccessible(nullable PDocAccessible aDoc, uint64_t aID); +#endif +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/ipc/PColorPicker.ipdl b/dom/ipc/PColorPicker.ipdl new file mode 100644 index 0000000000..9690327d1d --- /dev/null +++ b/dom/ipc/PColorPicker.ipdl @@ -0,0 +1,28 @@ +/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* vim: set sw=4 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 PBrowser; + +namespace mozilla { +namespace dom { + +[ChildImpl=virtual, ParentImpl=virtual] +protocol PColorPicker +{ + manager PBrowser; + +parent: + async Open(); + +child: + async Update(nsString color); + + async __delete__(nsString color); +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl new file mode 100644 index 0000000000..b8155fd375 --- /dev/null +++ b/dom/ipc/PContent.ipdl @@ -0,0 +1,1957 @@ +/* -*- tab-width: 2; 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 PBackgroundStarter; +include protocol PBrowser; +include protocol PClipboardReadRequest; +include protocol PClipboardWriteRequest; +include protocol PCompositorManager; +include protocol PContentPermissionRequest; +include protocol PCycleCollectWithLogs; +include protocol PDocumentChannel; +include protocol PExtensions; +include protocol PExternalHelperApp; +include protocol PHandlerService; +include protocol PHal; +include protocol PHeapSnapshotTempFileHelper; +include protocol PProcessHangMonitor; +include protocol PImageBridge; +include protocol PRemotePrintJob; +include protocol PMedia; +include protocol PNecko; +include protocol PStreamFilter; +include protocol PGMPContent; +include protocol PGMPService; +include protocol PGMP; +#ifdef MOZ_WEBSPEECH +include protocol PSpeechSynthesis; +#endif +include protocol PTestShell; +include protocol PRemoteSpellcheckEngine; +include protocol PWebBrowserPersistDocument; +#ifdef MOZ_WEBRTC +include protocol PWebrtcGlobal; +#endif +include protocol PWindowGlobal; +include protocol PURLClassifier; +include protocol PURLClassifierLocal; +include protocol PVRManager; +include protocol PRemoteDecoderManager; +include protocol PProfiler; +include protocol PScriptCache; +include protocol PSessionStorageObserver; +include protocol PBenchmarkStorage; +include DOMTypes; +include WindowGlobalTypes; +include IPCBlob; +include IPCStream; +include IPCTransferable; +include PPrintingTypes; +include PTabContext; +include ProtocolTypes; +include PBackgroundSharedTypes; +include PContentPermission; +include GraphicsMessages; +include MemoryReportTypes; +include ClientIPCTypes; +include HangTypes; +include PrefsTypes; +include NeckoChannelParams; +include PSMIPCTypes; +include LookAndFeelTypes; + +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) +include protocol PSandboxTesting; +#endif + +include "ipc/MediaControlIPC.h"; +include "mozilla/AntiTrackingIPCUtils.h"; +include "mozilla/GfxMessageUtils.h"; +include "mozilla/dom/BindingIPCUtils.h"; +include "mozilla/dom/CSPMessageUtils.h"; +include "mozilla/dom/DocShellMessageUtils.h"; +include "mozilla/dom/FeaturePolicyUtils.h"; +include "mozilla/dom/MediaSessionIPCUtils.h"; +include "mozilla/dom/PageLoadEventUtils.h"; +include "mozilla/dom/ReferrerInfoUtils.h"; +include "mozilla/glean/GleanMetrics.h"; +include "mozilla/ipc/ByteBufUtils.h"; +include "mozilla/ipc/TransportSecurityInfoUtils.h"; +include "mozilla/ipc/URIUtils.h"; +include "mozilla/net/NeckoMessageUtils.h"; +include "mozilla/PermissionDelegateIPCUtils.h"; + +[RefCounted] using class nsIDOMGeoPosition from "nsGeoPositionIPCSerialiser.h"; +[RefCounted] using class nsIAlertNotification from "mozilla/AlertNotificationIPCSerializer.h"; + +using struct ChromePackage from "mozilla/chrome/RegistryMessageUtils.h"; +using struct SubstitutionMapping from "mozilla/chrome/RegistryMessageUtils.h"; +using struct OverrideMapping from "mozilla/chrome/RegistryMessageUtils.h"; +using base::ProcessId from "base/process.h"; +using struct IPC::Permission from "mozilla/net/NeckoMessageUtils.h"; +using mozilla::dom::NativeThreadId from "mozilla/dom/NativeThreadId.h"; +using mozilla::hal::ProcessPriority from "mozilla/HalTypes.h"; +using mozilla::gfx::IntSize from "mozilla/gfx/2D.h"; +using mozilla::gfx::SurfaceFormat from "mozilla/gfx/Types.h"; +using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h"; +using mozilla::dom::ContentParentId from "mozilla/dom/ipc/IdType.h"; +using mozilla::EventMessage from "mozilla/EventForwards.h"; +using mozilla::LayoutDeviceIntPoint from "Units.h"; +using mozilla::ImagePoint from "Units.h"; +using mozilla::ImageIntSize from "Units.h"; +using mozilla::widget::ThemeChangeKind from "mozilla/widget/WidgetMessageUtils.h"; +using class mozilla::dom::MessagePort from "mozilla/dom/MessagePort.h"; +using class mozilla::dom::ipc::StructuredCloneData from "mozilla/dom/ipc/StructuredCloneData.h"; +using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h"; +using struct mozilla::layers::TextureFactoryIdentifier from "mozilla/layers/CompositorTypes.h"; +using mozilla::layers::CompositorOptions from "mozilla/layers/CompositorOptions.h"; +using mozilla::layers::LayersId from "mozilla/layers/LayersTypes.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::DynamicScalarDefinition from "mozilla/TelemetryComms.h"; +using mozilla::Telemetry::ChildEventData from "mozilla/TelemetryComms.h"; + +#if defined(XP_WIN) +[MoveOnly] using mozilla::UntrustedModulesData from "mozilla/UntrustedModulesData.h"; +[MoveOnly] using mozilla::ModulePaths from "mozilla/UntrustedModulesData.h"; +[MoveOnly] using mozilla::ModulesMapResult from "mozilla/UntrustedModulesData.h"; +#endif // defined(XP_WIN) + +using mozilla::Telemetry::DiscardedData from "mozilla/TelemetryComms.h"; +using mozilla::glean::perf::PageLoadExtra from "mozilla/glean/GleanMetrics.h"; +[MoveOnly] using mozilla::CrossProcessMutexHandle from "mozilla/ipc/CrossProcessMutex.h"; +using mozilla::dom::MaybeDiscardedBrowsingContext from "mozilla/dom/BrowsingContext.h"; +using mozilla::dom::BrowsingContextTransaction from "mozilla/dom/BrowsingContext.h"; +using mozilla::dom::BrowsingContextInitializer from "mozilla/dom/BrowsingContext.h"; +using mozilla::dom::PermitUnloadResult from "nsIDocumentViewer.h"; +using mozilla::dom::MaybeDiscardedWindowContext from "mozilla/dom/WindowContext.h"; +using mozilla::dom::WindowContextTransaction from "mozilla/dom/WindowContext.h"; +[MoveOnly] using base::SharedMemoryHandle from "base/shared_memory.h"; +using gfxSparseBitSet from "gfxFontUtils.h"; +using FontVisibility from "gfxFontEntry.h"; +using mozilla::dom::MediaControlAction from "mozilla/dom/MediaControlKeySource.h"; +using mozilla::dom::MediaPlaybackState from "mozilla/dom/MediaPlaybackStatus.h"; +using mozilla::dom::MediaAudibleState from "mozilla/dom/MediaPlaybackStatus.h"; +using mozilla::dom::MediaMetadataBase from "mozilla/dom/MediaMetadata.h"; +using mozilla::dom::MediaSessionAction from "mozilla/dom/MediaSessionBinding.h"; +using mozilla::dom::MediaSessionPlaybackState from "mozilla/dom/MediaSessionBinding.h"; +using mozilla::dom::PositionState from "mozilla/dom/MediaSession.h"; +using mozilla::dom::ServiceWorkerShutdownState::Progress from "mozilla/dom/ServiceWorkerShutdownState.h"; +using mozilla::ContentBlockingNotifier::StorageAccessPermissionGrantedReason from "mozilla/ContentBlockingNotifier.h"; +using mozilla::ContentBlockingNotifier::BlockingDecision from "mozilla/ContentBlockingNotifier.h"; +using mozilla::StorageAccessAPIHelper::StorageAccessPromptChoices from "mozilla/StorageAccessAPIHelper.h"; +using mozilla::dom::JSActorMessageKind from "mozilla/dom/JSActor.h"; +using mozilla::dom::JSActorMessageMeta from "mozilla/dom/PWindowGlobal.h"; +using mozilla::PermissionDelegateHandler::DelegatedPermissionList from "mozilla/PermissionDelegateHandler.h"; +[RefCounted] using class nsILayoutHistoryState from "nsILayoutHistoryState.h"; +using class mozilla::dom::SessionHistoryInfo from "mozilla/dom/SessionHistoryEntry.h"; +using struct nsPoint from "nsPoint.h"; +using struct mozilla::dom::LoadingSessionHistoryInfo from "mozilla/dom/SessionHistoryEntry.h"; +using mozilla::media::MediaCodecsSupported from "MediaCodecsSupport.h"; +using mozilla::RemoteDecodeIn from "mozilla/RemoteDecoderManagerChild.h"; +using mozilla::dom::PerformanceTimingData from "mozilla/dom/PerformanceTiming.h"; +[RefCounted] using mozilla::dom::FeaturePolicy from "mozilla/dom/FeaturePolicy.h"; +using mozilla::dom::Wireframe from "mozilla/dom/DocumentBinding.h"; +using mozilla::PerfStats::MetricMask from "mozilla/PerfStats.h"; +[RefCounted] using class nsIX509Cert from "nsIX509Cert.h"; +using nsIDNSService::ResolverMode from "nsIDNSService.h"; +using mozilla::dom::UserActivation::Modifiers from "mozilla/dom/UserActivation.h"; + +union ChromeRegistryItem +{ + ChromePackage; + OverrideMapping; + SubstitutionMapping; +}; + +namespace mozilla { +namespace dom { + +// SetXPCOMProcessAttributes passes an array of font data to the child, +// but each platform needs different details so we have platform-specific +// versions of the SystemFontListEntry type: +#if defined(ANDROID) +// Used on Android to pass the list of fonts on the device +// to the child process +struct SystemFontListEntry { + nsCString familyName; + nsCString faceName; + nsCString filepath; + uint32_t weightRange; + uint32_t stretchRange; + uint32_t styleRange; + uint8_t index; + FontVisibility visibility; +}; +#elif defined(XP_MACOSX) +// Used on Mac OS X to pass the list of font families (not faces) +// from chrome to content processes. +// The entryType field distinguishes several types of font family +// record; see gfxMacPlatformFontList.h for values and meaning. +struct SystemFontListEntry { + nsCString familyName; + FontVisibility visibility; + uint8_t entryType; +}; +#else +// Used on Linux to pass list of font patterns from chrome to content. +// (Unused on Windows, but there needs to be a definition of the type.) +struct SystemFontListEntry { + nsCString pattern; + bool appFontFamily; +}; +#endif + +#ifdef MOZ_WIDGET_GTK +struct SystemFontOptions { + int32_t antialias; // cairo_antialias_t + int32_t subpixelOrder; // cairo_subpixel_order_t + int32_t hintStyle; // cairo_hint_style_t + int32_t lcdFilter; // cairo_lcd_filter_t +}; +#endif + +struct SystemFontList { + SystemFontListEntry[] entries; +#ifdef MOZ_WIDGET_GTK + SystemFontOptions options; +#endif +}; + +union SystemParameterValue { + bool; + float; +}; + +struct ClipboardCapabilities { + bool supportsSelectionClipboard; + bool supportsFindClipboard; + bool supportsSelectionCache; +}; + +union FileDescOrError { + FileDescriptor; + nsresult; +}; + +struct DomainPolicyClone +{ + bool active; + nullable nsIURI[] blocklist; + nullable nsIURI[] allowlist; + nullable nsIURI[] superBlocklist; + nullable nsIURI[] superAllowlist; +}; + +struct AndroidSystemInfo +{ + nsString device; + nsString manufacturer; + nsString release_version; + nsString hardware; + uint32_t sdk_version; + bool isTablet; +}; + +struct GetFilesResponseSuccess +{ + IPCBlob[] blobs; +}; + +struct GetFilesResponseFailure +{ + nsresult errorCode; +}; + +union GetFilesResponseResult +{ + GetFilesResponseSuccess; + GetFilesResponseFailure; +}; + +struct BlobURLRegistrationData +{ + nsCString url; + IPCBlob blob; + nullable nsIPrincipal principal; + nsCString partitionKey; + bool revoked; +}; + +struct JSWindowActorEventDecl +{ + nsString name; + bool capture; + bool systemGroup; + bool allowUntrusted; + bool? passive; + bool createActor; +}; + +struct JSWindowActorInfo +{ + nsCString name; + bool allFrames; + + // True if `url` is for ESM. + // False if `url` is for JSM or nothing. + bool isESModule; + + // This is to align with JSProcessActorInfo. + // This attribute isn't used for JSWindow Actors. + bool loadInDevToolsLoader; + + // The module of the url. + nsCString? url; + + JSWindowActorEventDecl[] events; + + // Observer notifications this actor listens to. + nsCString[] observers; + nsString[] matches; + nsCString[] remoteTypes; + nsString[] messageManagerGroups; +}; + +struct JSProcessActorInfo +{ + // The name of the actor. + nsCString name; + + // True if `url` is for ESM. + // False if `url` is for JSM or nothing. + bool isESModule; + + // True if the actor should be loaded in the distinct loader dedicated to DevTools. + bool loadInDevToolsLoader; + + // The module of the url. + nsCString? url; + + // Observer notifications this actor listens to. + nsCString[] observers; + nsCString[] remoteTypes; +}; + +struct GMPAPITags +{ + nsCString api; + nsCString[] tags; +}; + +struct GMPCapabilityData +{ + nsCString name; + nsCString version; + GMPAPITags[] capabilities; +}; + +struct L10nFileSourceDescriptor { + nsCString name; + nsCString metasource; + nsCString[] locales; + nsCString prePath; + nsCString[] index; +}; + +struct XPCOMInitData +{ + bool isOffline; + bool isConnected; + int32_t captivePortalState; + bool isLangRTL; + bool haveBidiKeyboards; + nsCString[] dictionaries; + ClipboardCapabilities clipboardCaps; + DomainPolicyClone domainPolicy; + nullable nsIURI userContentSheetURL; + GfxVarUpdate[] gfxNonDefaultVarUpdates; + ContentDeviceData contentDeviceData; + GfxInfoFeatureStatus[] gfxFeatureStatus; + nsCString[] appLocales; + nsCString[] requestedLocales; + L10nFileSourceDescriptor[] l10nFileSources; + DynamicScalarDefinition[] dynamicScalarDefs; + MetricMask perfStatsMask; + nsCString trrDomain; + ResolverMode trrMode; + // This is the value of network.trr.mode and can be diffrent than trrMode. + ResolverMode trrModeFromPref; +}; + +struct VisitedQueryResult +{ + nullable nsIURI uri; + bool visited; +}; + +struct StringBundleDescriptor +{ + nsCString bundleURL; + FileDescriptor mapFile; + uint32_t mapSize; +}; + +struct IPCURLClassifierFeature +{ + nsCString featureName; + nsCString[] tables; + nsCString exceptionHostList; +}; + +// Transport structure for Notifications API notifications +// (https://developer.mozilla.org/en-US/docs/Web/API/notification) instances +// used exclusively by the NotificationEvent PContent method. +struct NotificationEventData +{ + nsCString originSuffix; + nsCString scope; + nsString ID; + nsString title; + nsString dir; + nsString lang; + nsString body; + nsString tag; + nsString icon; + nsString data; + nsString behavior; +}; + +struct PostMessageData +{ + MaybeDiscardedBrowsingContext source; + nsString origin; + nsString targetOrigin; + nullable nsIURI targetOriginURI; + nullable nsIPrincipal callerPrincipal; + nullable nsIPrincipal subjectPrincipal; + nullable nsIURI callerURI; + bool isFromPrivateWindow; + nsCString scriptLocation; + uint64_t innerWindowId; +}; + +union SyncedContextInitializer +{ + BrowsingContextInitializer; + WindowContextInitializer; +}; + +union BlobURLDataRequestResult +{ + IPCBlob; + nsresult; +}; + +struct TextRecognitionQuad { + float confidence; + nsString string; + ImagePoint[] points; +}; + +struct TextRecognitionResult { + TextRecognitionQuad[] quads; +}; + +union TextRecognitionResultOrError { + TextRecognitionResult; + nsCString; +}; + +struct IPCImage { + BigBuffer data; + uint32_t stride; + SurfaceFormat format; + ImageIntSize size; +}; + +union PClipboardReadRequestOrError { + PClipboardReadRequest; + nsresult; +}; + +/** + * The PContent protocol is a top-level protocol between the UI process + * and a content process. There is exactly one PContentParent/PContentChild pair + * for each content process. + */ +[NestedUpTo=inside_cpow, NeedsOtherPid, ChildProc=Content] +sync protocol PContent +{ + manages PBrowser; + manages PClipboardReadRequest; + manages PClipboardWriteRequest; + manages PContentPermissionRequest; + manages PCycleCollectWithLogs; + manages PExtensions; + manages PExternalHelperApp; + manages PHal; + manages PHandlerService; + manages PHeapSnapshotTempFileHelper; + manages PRemotePrintJob; + manages PMedia; + manages PNecko; +#ifdef MOZ_WEBSPEECH + manages PSpeechSynthesis; +#endif + manages PTestShell; + manages PRemoteSpellcheckEngine; + manages PWebBrowserPersistDocument; +#ifdef MOZ_WEBRTC + manages PWebrtcGlobal; +#endif + manages PURLClassifier; + manages PURLClassifierLocal; + manages PScriptCache; + manages PSessionStorageObserver; + manages PBenchmarkStorage; + + // Depending on exactly how the new browser is being created, it might be + // created from either the child or parent process! + // + // The child creates the PBrowser as part of + // BrowserChild::BrowserFrameProvideWindow (which happens when the child's + // content calls window.open()), and the parent creates the PBrowser as part + // of ContentParent::CreateBrowser. + // + // When the parent constructs a PBrowser, the child trusts the attributes it + // receives from the parent. In that case, the context should be + // FrameIPCTabContext. + // + // When the child constructs a PBrowser, the parent doesn't trust the + // attributes it receives from the child. In this case, context must have + // type PopupIPCTabContext. The parent checks that if the opener is a + // browser element, the context is also for a browser element. + // + // If |sameTabGroupAs| is non-zero, the new tab should go in the same + // TabGroup as |sameTabGroupAs|. This parameter should always be zero + // for PBrowser messages sent from the child to the parent. + // + // Separate messages are used for the parent and child side constructors due + // to the differences in data and actor setup required. + // + // Keep the last 3 attributes in sync with GetProcessAttributes! +parent: + async ConstructPopupBrowser(ManagedEndpoint<PBrowserParent> browserEp, + ManagedEndpoint<PWindowGlobalParent> windowEp, + TabId tabId, IPCTabContext context, + WindowGlobalInit windowInit, + uint32_t chromeFlags); + + // TODO: Do I need to make this return something to watch for completion? + // Guess we'll see how we end up triggering the actual print, for preview + // this should be enough... + async CloneDocumentTreeInto(MaybeDiscardedBrowsingContext aSourceBc, + MaybeDiscardedBrowsingContext aTargetBc, + PrintData aPrintData); + + async UpdateRemotePrintSettings(MaybeDiscardedBrowsingContext aBc, + PrintData aPrintData); + + async PExtensions(); + +child: + async ConstructBrowser(ManagedEndpoint<PBrowserChild> browserEp, + ManagedEndpoint<PWindowGlobalChild> windowEp, + TabId tabId, + IPCTabContext context, + WindowGlobalInit windowInit, + uint32_t chromeFlags, ContentParentId cpId, + bool isForBrowser, bool isTopLevel); + +both: + // For parent->child, aBrowser must be non-null; aContext can + // be null to indicate the browser's current root document, or non-null + // to persist a subdocument. For child->parent, arguments are + // ignored and should be null. + async PWebBrowserPersistDocument(nullable PBrowser aBrowser, + MaybeDiscardedBrowsingContext aContext); + + async RawMessage(JSActorMessageMeta aMetadata, ClonedMessageData? aData, + ClonedMessageData? aStack); + +child: + async InitGMPService(Endpoint<PGMPServiceChild> service); + async InitProcessHangMonitor(Endpoint<PProcessHangMonitorChild> hangMonitor); + async InitProfiler(Endpoint<PProfilerChild> aEndpoint); + + // Give the content process its endpoints to the compositor. + async InitRendering( + Endpoint<PCompositorManagerChild> compositor, + Endpoint<PImageBridgeChild> imageBridge, + Endpoint<PVRManagerChild> vr, + Endpoint<PRemoteDecoderManagerChild> video, + uint32_t[] namespaces); + + // Re-create the rendering stack using the given endpoints. This is sent + // after the compositor process has crashed. The new endpoints may be to a + // newly launched GPU process, or the compositor thread of the UI process. + async ReinitRendering( + Endpoint<PCompositorManagerChild> compositor, + Endpoint<PImageBridgeChild> bridge, + Endpoint<PVRManagerChild> vr, + Endpoint<PRemoteDecoderManagerChild> video, + uint32_t[] namespaces); + + async NetworkLinkTypeChange(uint32_t type); + async SocketProcessCrashed(); + + // Re-create the rendering stack for a device reset. + async ReinitRenderingForDeviceReset(); + + /** + * Enable system-level sandboxing features, if available. Can + * usually only be performed zero or one times. The child may + * abnormally exit if this fails; the details are OS-specific. + */ + async SetProcessSandbox(FileDescriptor? aBroker); + + async RequestMemoryReport(uint32_t generation, + bool anonymize, + bool minimizeMemoryUsage, + FileDescriptor? DMDFile) + returns (uint32_t aGeneration); + +#if defined(XP_WIN) + /** + * Used by third-party modules telemetry (aka "untrusted modules" telemetry) + * to pull data from content processes. + */ + async GetUntrustedModulesData() returns (UntrustedModulesData? data); + + /** + * This method is used to notifty a child process to start + * processing module loading events in UntrustedModulesProcessor. + * This should be called when the parent process has gone idle. + */ + async UnblockUntrustedModulesThread(); +#endif // defined(XP_WIN) + + /** + * Communication between the PuppetBidiKeyboard and the actual + * BidiKeyboard hosted by the parent + */ + async BidiKeyboardNotify(bool isLangRTL, bool haveBidiKeyboards); + + /** + * Dump this process's GC and CC logs to the provided files. + * + * For documentation on the other args, see dumpGCAndCCLogsToFile in + * nsIMemoryInfoDumper.idl + */ + async PCycleCollectWithLogs(bool dumpAllTraces, + FileDescriptor gcLog, + FileDescriptor ccLog); + + async PTestShell(); + + async PScriptCache(FileDescOrError cacheFile, bool wantCacheData); + + async RegisterChrome(ChromePackage[] packages, SubstitutionMapping[] substitutions, + OverrideMapping[] overrides, nsCString locale, bool reset); + async RegisterChromeItem(ChromeRegistryItem item); + + async ClearImageCacheFromPrincipal(nullable nsIPrincipal aPrincipal); + + async ClearImageCacheFromBaseDomain(nsCString aBaseDomain); + + async ClearImageCache(bool privateLoader, bool chrome); + + async ClearStyleSheetCache(nullable nsIPrincipal? aForPrincipal, + nsCString? aBaseDomain); + + async SetOffline(bool offline); + async SetConnectivity(bool connectivity); + async SetCaptivePortalState(int32_t aState); + async SetTRRMode(ResolverMode aMode, ResolverMode aModeFromPref); + + async NotifyVisited(VisitedQueryResult[] uri); + + /** + * Tell the child that the system theme has changed, and that a repaint is + * necessary. + */ + async ThemeChanged(FullLookAndFeel lookAndFeelData, ThemeChangeKind aKind); + + async PreferenceUpdate(Pref pref); + async VarUpdate(GfxVarUpdate var); + + async UpdatePerfStatsCollectionMask(uint64_t aMask); + async CollectPerfStatsJSON() returns (nsCString aStats); + + async CollectScrollingMetrics() returns (uint32_t pixelsScrolled, uint32_t scrollDurationMS); + + async NotifyAlertsObserver(nsCString topic, nsString data); + + async GeolocationUpdate(nullable nsIDOMGeoPosition aPosition); + + async GeolocationError(uint16_t errorCode); + + async UpdateDictionaryList(nsCString[] dictionaries); + + async UpdateFontList(SystemFontList fontList); + + /** + * The shared font list has been updated by the parent, so child processes + * should globally reflow everything to pick up new character coverage etc. + * If aFullRebuild is true, child processes must discard and recreate + * their mappings to the shmem blocks, as those are no longer valid. + * This message has raised priority so that it will take precedence over + * vsync messages to the child. + */ + [Priority=mediumhigh] async RebuildFontList(bool aFullRebuild); + + /** + * The shared font list has been modified, potentially adding matches + * for src:local() names that were previously not known, so content + * may need to be reflowed. + */ + async FontListChanged(); + + /** + * The font list or prefs have been updated in such a way that we might need + * to do a reflow and maybe reframe. + */ + async ForceGlobalReflow(bool aNeedsReframe); + + /** + * A new shmem block has been added to the font list; the child process + * should map the new block and add to its index. + */ + async FontListShmBlockAdded(uint32_t aGeneration, uint32_t aIndex, + SharedMemoryHandle aHandle); + + async UpdateAppLocales(nsCString[] appLocales); + async UpdateRequestedLocales(nsCString[] requestedLocales); + + /** + * The system timezone has changed; the child process should ensure that + * calls to get the default timezone return the new value. + */ + async SystemTimezoneChanged(); + + async UpdateL10nFileSources(L10nFileSourceDescriptor[] sources); + + async RegisterStringBundles(StringBundleDescriptor[] stringBundles); + + async UpdateSharedData(FileDescriptor mapFile, uint32_t aSize, + IPCBlob[] blobs, + nsCString[] changedKeys); + + // nsIPermissionManager messages + async AddPermission(Permission permission); + async RemoveAllPermissions(); + + async FlushMemory(nsString reason); + + async ApplicationBackground(); + async ApplicationForeground(); + async GarbageCollect(); + async CycleCollect(); + async UnlinkGhosts(); + + /** + * Start accessibility engine in content process. + */ + async ActivateA11y(); + + /** + * Shutdown accessibility engine in content process (if not in use). + */ + async ShutdownA11y(); + + async AppInfo(nsCString version, nsCString buildID, nsCString name, nsCString UAName, + nsCString ID, nsCString vendor, nsCString sourceURL, nsCString updateURL); + + /** + * Send the remote type associated with the content process. + */ + async RemoteType(nsCString aRemoteType, nsCString aProfile); + + /** + * Send BlobURLRegistrationData to child process. + */ + async InitBlobURLs(BlobURLRegistrationData[] registrations); + + /** + * Send JS{Content, Window}ActorInfos to child process. + */ + async InitJSActorInfos(JSProcessActorInfo[] aContentInfos, JSWindowActorInfo[] aWindowInfos); + + /** + * Unregister a previously registered JSWindowActor in the child process. + */ + async UnregisterJSWindowActor(nsCString name); + + /** + * Unregister a previously registered JSProcessActor in the child process. + */ + async UnregisterJSProcessActor(nsCString name); + + async SetXPCOMProcessAttributes(XPCOMInitData xpcomInit, + StructuredCloneData initialData, + FullLookAndFeel lookAndFeeldata, + /* used on MacOSX/Linux/Android only: */ + SystemFontList systemFontList, + SharedMemoryHandle? sharedUASheetHandle, + uintptr_t sharedUASheetAddress, + SharedMemoryHandle[] sharedFontListBlocks, + bool aIsStartingUp); + + // Notify child that last-pb-context-exited notification was observed + async LastPrivateDocShellDestroyed(); + + async NotifyProcessPriorityChanged(ProcessPriority priority); + async MinimizeMemoryUsage(); + + /** + * Used to manage nsIStyleSheetService across processes. + */ + async LoadAndRegisterSheet(nullable nsIURI uri, uint32_t type); + async UnregisterSheet(nullable nsIURI uri, uint32_t type); + + /** + * Notify idle observers in the child + */ + async NotifyIdleObserver(uint64_t observerId, nsCString topic, nsString str); + + async InvokeDragSession(MaybeDiscardedWindowContext aSourceWindowContext, + MaybeDiscardedWindowContext aSourceTopWindowContext, + IPCTransferableData[] transfers, uint32_t action); + + async UpdateDragSession(IPCTransferableData[] transfers, EventMessage message); + + async EndDragSession(bool aDoneDrag, bool aUserCancelled, + LayoutDeviceIntPoint aDragEndPoint, + uint32_t aKeyModifiers, + uint32_t aDropEffect); + + async DomainSetChanged(uint32_t aSetType, uint32_t aChangeType, nullable nsIURI aDomain); + + /** + * Notify the child that it will soon be asked to shutdown. + * This is sent with high priority right before the normal shutdown. + */ + [Priority=control] async ShutdownConfirmedHP(); + + /** + * Notify the child to shutdown. The child will in turn call FinishShutdown + * and let the parent close the channel. + */ + async Shutdown(); + + async LoadProcessScript(nsString url); + + /** + * Requests a full native update of a native plugin child window. This is + * a Windows specific call. + */ + async UpdateWindow(uintptr_t aChildId); + + /** + * Notify the child that cache is emptied. + */ + async NotifyEmptyHTTPCache(); + + /** + * Send a `push` event without data to a service worker in the child. + */ + async Push(nsCString scope, nullable nsIPrincipal principal, nsString messageId); + + /** + * Send a `push` event with data to a service worker in the child. + */ + async PushWithData(nsCString scope, nullable nsIPrincipal principal, + nsString messageId, uint8_t[] data); + + /** + * Send a `pushsubscriptionchange` event to a service worker in the child. + */ + async PushSubscriptionChange(nsCString scope, nullable nsIPrincipal principal); + + async GetFilesResponse(nsID aID, GetFilesResponseResult aResult); + + async BlobURLRegistration(nsCString aURI, IPCBlob aBlob, + nullable nsIPrincipal aPrincipal, nsCString aPartitionKey); + + async BlobURLUnregistration(nsCString aURI); + + async GMPsChanged(GMPCapabilityData[] capabilities); + + + async ProvideAnonymousTemporaryFile(uint64_t aID, FileDescOrError aFD); + + async SetPermissionsWithKey(nsCString aPermissionKey, Permission[] aPermissions); + + async RefreshScreens(ScreenDetails[] aScreens); + + async ShareCodeCoverageMutex(CrossProcessMutexHandle handle); + async FlushCodeCoverageCounters() returns (bool unused); + + /* + * IPC message to enable the input event queue on the main thread of the + * content process. + */ + async SetInputEventQueueEnabled(); + + /* + * IPC message to flush the input event queue on the main thread of the + * content process. + * + * When the ContentParent stops sending the input event with input priority, + * there may be some pending events in the input event queue and normal + * event queue. Here is a possible scenario. + * R: Runnables. + * D: Enable the input priority event. + * E: Disable the input priority evnet. + * + * D E + * Normal Queue: R1 R2 R3 + * Input Queue: II I2 I3 + * + * To avoid the newly added normal events (e.g. R2, which may be an input + * event) preempt the pending input events (e.g. I1), or the newly added + * input events (e.g. I3) preempt the pending normal events (e.g. R2), we + * have to flush all pending events before enabling and disabling the input + * priority event. + * + * To flush the normal event queue and the input event queue, we use three + * IPC messages as the followings. + * FI: Flush the input queue. + * SI: Suspend the input queue. + * RI: Resume the input queue. + * + * Normal Queue: R1 FI RI R2 FI RI R3 + * Input Queue: II SI I2 SI I3 + * + * When the flush input request is processed before the other two requests, + * we consume all input events until the suspend request. After handling the + * suspend request, we stop consuming the input events until the resume + * request to make sure we consume all pending normal events. + * + * If we process the suspend request before the other two requests, we + * ignore the flush request and consume all pending normal events until the + * resume request. + */ + async FlushInputEventQueue(); + + /* + * IPC message to resume consuming the pending events in the input event + * queue. + */ + async ResumeInputEventQueue(); + + /* + * IPC message to suspend consuming the pending events in the input event + * queue. + */ + [Priority=input] async SuspendInputEventQueue(); + + /* + * IPC message to propagate dynamic scalar definitions, added after the + * content process is spawned, from the parent to the child. + * Dynamic scalar definitions added at the process startup are handled + * using the |TelemetryIPC::AddDynamicScalarDefinitions| functions. + */ + async AddDynamicScalars(DynamicScalarDefinition[] definitions); + + // This message is sent to content processes, and triggers the creation of a + // new HttpChannelChild that will be connected to the parent channel + // represented by registrarId. + // This is on PContent not PNecko, as PNecko may not be initialized yet. + // The returned loadInfo needs to be set on the channel - since the channel + // moved to a new process it now has different properties. + + async CrossProcessRedirect(RedirectToRealChannelArgs args, + Endpoint<PStreamFilterParent>[] aEndpoint) + returns (nsresult rv); + + /** + * This method is used to notifty content process to start delayed autoplay + * media via browsing context. + */ + async StartDelayedAutoplayMediaComponents(MaybeDiscardedBrowsingContext aContext); + + /** + * This method is used to dispatch MediaControlAction to content process in + * order to control media within a specific browsing context tree. + */ + async UpdateMediaControlAction(MaybeDiscardedBrowsingContext aContext, + MediaControlAction aAction); + + // Begin subscribing to a new BrowsingContextGroup, sending down the current + // value for every individual BrowsingContext. + async RegisterBrowsingContextGroup(uint64_t aGroupId, SyncedContextInitializer[] aInits); + + // The BrowsingContextGroup has been destroyed in the parent process. The + // content process won't destroy the group until it receives this message or + // during shutdown. + // + // When the content process receives this message, all contexts in the group + // should have already been destroyed. + async DestroyBrowsingContextGroup(uint64_t aGroupId); + +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) + // Initialize top-level actor for testing content process sandbox. + async InitSandboxTesting(Endpoint<PSandboxTestingChild> aEndpoint); +#endif + + async LoadURI(MaybeDiscardedBrowsingContext aContext, nsDocShellLoadState aLoadState, bool aSetNavigating) + returns (bool aSuccess); + + async InternalLoad(nsDocShellLoadState aLoadState); + + async DisplayLoadError(MaybeDiscardedBrowsingContext aContext, nsString aURI); + + async GoBack(MaybeDiscardedBrowsingContext aContext, int32_t? aCancelContentJSEpoch, bool aRequireUserInteraction, bool aUserActivation); + async GoForward(MaybeDiscardedBrowsingContext aContext, int32_t? aCancelContentJSEpoch, bool aRequireUserInteraction, bool aUserActivation); + async GoToIndex(MaybeDiscardedBrowsingContext aContext, int32_t aIndex, int32_t? aCancelContentJSEpoch, bool aUserActivation); + async Reload(MaybeDiscardedBrowsingContext aContext, uint32_t aReloadFlags); + async StopLoad(MaybeDiscardedBrowsingContext aContext, uint32_t aStopFlags); + + async OnAllowAccessFor(MaybeDiscardedBrowsingContext aParentContext, + nsCString aTrackingOrigin, + uint32_t aCookieBehavior, + StorageAccessPermissionGrantedReason aReason); + + async OnContentBlockingDecision(MaybeDiscardedBrowsingContext aContext, + BlockingDecision aReason, + uint32_t aRejectedReason); + + /** + * Abort orientationPendingPromises for documents in the child which + * are part of a BrowsingContextGroup. + */ + async AbortOrientationPendingPromises(MaybeDiscardedBrowsingContext aContext); + + async HistoryCommitIndexAndLength(MaybeDiscardedBrowsingContext aContext, + uint32_t aIndex, uint32_t aLength, + nsID aChangeID); + + async GetLayoutHistoryState(MaybeDiscardedBrowsingContext aContext) + returns (nullable nsILayoutHistoryState aState, Wireframe? aWireframe); + + async DispatchLocationChangeEvent(MaybeDiscardedBrowsingContext aContext); + + // Dispatches a "beforeunload" event to each in-process content window in the + // subtree beginning at `aStartingAt`, and returns the result as documented in + // the `PermitUnloadResult` enum. + async DispatchBeforeUnloadToSubtree(MaybeDiscardedBrowsingContext aStartingAt) + returns (PermitUnloadResult result); + + // Update the cached list of codec supported in the given process. + async UpdateMediaCodecsSupported(RemoteDecodeIn aLocation, MediaCodecsSupported aSupported); + + // Send the list of the supported mimetypes in the given process. GeckoView-specific + async DecoderSupportedMimeTypes(nsCString[] supportedTypes); + + // Used to initialize the global variable in content processes with the + // latched value in the parent process. See dom/LocalStorageCommon.h for more + // details. + async InitNextGenLocalStorageEnabled(bool enabled); + + async PRemotePrintJob(); + + async PClipboardReadRequest(nsCString[] aTypes); + +parent: + async SynchronizeLayoutHistoryState(MaybeDiscardedBrowsingContext aContext, + nullable nsILayoutHistoryState aState); + + async SessionHistoryEntryTitle(MaybeDiscardedBrowsingContext aContext, + nsString aTitle); + + async SessionHistoryEntryScrollRestorationIsManual(MaybeDiscardedBrowsingContext aContext, + bool aIsManual); + async SessionHistoryEntryScrollPosition(MaybeDiscardedBrowsingContext aContext, + int32_t aX, int32_t aY); + + async SessionHistoryEntryCacheKey(MaybeDiscardedBrowsingContext aContext, + uint32_t aCacheKey); + + async SessionHistoryEntryStoreWindowNameInContiguousEntries(MaybeDiscardedBrowsingContext aContext, + nsString aName); + + async SessionHistoryEntryWireframe(MaybeDiscardedBrowsingContext aContext, + Wireframe aWireframe); + + async GetLoadingSessionHistoryInfoFromParent(MaybeDiscardedBrowsingContext aContext) + returns (LoadingSessionHistoryInfo? aLoadingInfo); + + async RemoveFromBFCache(MaybeDiscardedBrowsingContext aContext); + + async InitBackground(Endpoint<PBackgroundStarterParent> aEndpoint); + + async CreateGMPService(); + + async InitStreamFilter(uint64_t channelId, nsString addonId) + returns (Endpoint<PStreamFilterChild> aEndpoint); + + async PRemoteSpellcheckEngine(); + + async InitCrashReporter(NativeThreadId tid); + + sync IsSecureURI(nullable nsIURI aURI, OriginAttributes aOriginAttributes) + returns (bool isSecureURI); + + async AccumulateMixedContentHSTS(nullable nsIURI aURI, bool aActive, + OriginAttributes aOriginAttributes); + + [Nested=inside_cpow] async PHal(); + + async PHeapSnapshotTempFileHelper(); + + async PNecko(); + +#ifdef MOZ_WEBSPEECH + async PSpeechSynthesis(); +#endif + + async PMedia(); + +#ifdef MOZ_WEBRTC + async PWebrtcGlobal(); +#endif + + async CreateAudioIPCConnection() returns (FileDescOrError fd); + + sync PURLClassifier(nullable nsIPrincipal principal) + returns (bool success); + + async PURLClassifierLocal(nullable nsIURI uri, IPCURLClassifierFeature[] features); + + async PSessionStorageObserver(); + + async PBenchmarkStorage(); + + // Services remoting + + async StartVisitedQueries(nullable nsIURI[] uri); + async SetURITitle(nullable nsIURI uri, nsString title); + + async LoadURIExternal(nullable nsIURI uri, + nullable nsIPrincipal triggeringPrincipal, + nullable nsIPrincipal redirectPrincipal, + MaybeDiscardedBrowsingContext browsingContext, + bool wasExternallyTriggered, + bool hasValidUserGestureActivation); + async ExtProtocolChannelConnectParent(uint64_t registrarId); + + sync SyncMessage(nsString aMessage, ClonedMessageData aData) + returns (StructuredCloneData[] retval); + + async ShowAlert(nullable nsIAlertNotification alert); + + async CloseAlert(nsString name, bool contextClosed); + + async DisableNotifications(nullable nsIPrincipal principal); + + async OpenNotificationSettings(nullable nsIPrincipal principal); + + async AddSecurityState(MaybeDiscardedWindowContext aContext, uint32_t aStateFlags); + + // Request that the ServiceWorkerManager in the parent process create a + // notification "click" or "close" event and dispatch it on the relevant + // ServiceWorker. This needs to happen because when a notification is + // created it is tied to a specific content process and when the user clicks + // on the notification, it will be that content process that is notified. + // However, even if the ServiceWorker lives in that process (it may no + // longer be in that process, or may have never lived there), the right/only + // way to talk through the ServiceWorker is through the parent. + // + // This happens on PContent because the ServiceWorkerManager lives on the + // main thread and bouncing this off of PBackground would be silly and + // complex. In the long run, the notification implementation will be + // overhauled to directly process the notification click/close and directly + // translate that to a ServiceWorker event. + async NotificationEvent(nsString type, NotificationEventData data); + + // Creates a helper for forwarding data from an nsExternalAppHandler + // running in the content process, to one running in the parent + // process. + // Bug 1574372 aims to run nsExternalAppHandler entirely in the + // parent so that we can remove this. + // + // Serializes the uri, loadInfo, contentType, referrer, contentDisposition + // headers and contentLength of the channel so that we can make them + // available to the parent instance via a nsIChannel helper. Also + // passes whether the original channel was an instance of nsIFileChannel. + // + // aContext is the BrowsingContext that initiated the load, and created the + // channel. + // + // Pass true for aForceSave to always save this content to disk, regardless of + // nsIMIMEInfo and other such influences. + // Pass true for aShouldCloseWindow to specify that aContext was opened specifically + // for this load, and should be closed once we've handled it. + async PExternalHelperApp(nullable nsIURI uri, + LoadInfoArgs loadInfoArgs, + nsCString aMimeContentType, + nsCString aContentDisposition, + uint32_t aContentDispositionHint, + nsString aContentDispositionFilename, + bool aForceSave, + int64_t aContentLength, + bool aWasFileChannel, + nullable nsIURI aReferrer, + MaybeDiscardedBrowsingContext aContext, + bool aShouldCloseWindow); + + async PHandlerService(); + + async AddGeolocationListener(bool highAccuracy); + async RemoveGeolocationListener(); + async SetGeolocationHigherAccuracy(bool enable); + + async ConsoleMessage(nsString message); + async ScriptErrorWithStack(nsString message, nsString sourceName, nsString sourceLine, + uint32_t lineNumber, uint32_t colNumber, uint32_t flags, + nsCString category, bool privateWindow, + bool fromChromeContext, ClonedMessageData stack); + + // Places the items within dataTransfer on the clipboard. + async SetClipboard(IPCTransferable aTransferable, + int32_t aWhichClipboard); + + // Given a list of supported types, returns the clipboard data for the + // first type that matches. + // aRequestingWindowContext is the window that is requesting the clipboard, + // which is used for content analysis. + sync GetClipboard(nsCString[] aTypes, int32_t aWhichClipboard, + MaybeDiscardedWindowContext aRequestingWindowContext) + returns (IPCTransferableData transferableData); + + // Returns a list of formats supported by the clipboard + sync GetExternalClipboardFormats(int32_t aWhichClipboard, bool aPlainTextOnly) returns (nsCString[] aTypes); + + // Requests getting data from clipboard. + async GetClipboardAsync(nsCString[] aTypes, int32_t aWhichClipboard, + MaybeDiscardedWindowContext aRequestingWindowContext, + nsIPrincipal aRequestingPrincipal) + returns (PClipboardReadRequestOrError aClipboardReadRequest); + + // Clears the clipboard. + async EmptyClipboard(int32_t aWhichClipboard); + + // Returns true if data of one of the specified types is on the clipboard. + sync ClipboardHasType(nsCString[] aTypes, int32_t aWhichClipboard) + returns (bool hasType); + + /** + * Notify the parent that the child has started a clipboard write request, + * and that the data will be sent over another IPC message once it is ready. + * @param aClipboardType + * The clipboard type defined in nsIClipboard. + */ + async PClipboardWriteRequest(int32_t aClipboardType); + + sync GetIconForExtension(nsCString aFileExt, uint32_t aIconSize) + returns (uint8_t[] bits); + + // Tell the parent that the child has gone idle for the first time. + async FirstIdle(); + + async CopyFavicon(nullable nsIURI oldURI, nullable nsIURI newURI, bool isPrivate); + + async FindImageText(IPCImage image, nsCString[] languages) + returns (TextRecognitionResultOrError result); + + // Graphics errors + async GraphicsError(nsCString aError); + + // Driver crash guards. aGuardType must be a member of CrashGuardType. + sync BeginDriverCrashGuard(uint32_t aGuardType) returns (bool crashDetected); + sync EndDriverCrashGuard(uint32_t aGuardType); + + async AddIdleObserver(uint64_t observerId, uint32_t idleTimeInS); + async RemoveIdleObserver(uint64_t observerId, uint32_t idleTimeInS); + + /** + * This message is only used on X11 platforms. + * + * Send a dup of the plugin process's X socket to the parent + * process. In theory, this scheme keeps the plugin's X resources + * around until after both the plugin process shuts down *and* the + * parent process closes the dup fd. This is used to prevent the + * parent process from crashing on X errors if, e.g., the plugin + * crashes *just before* a repaint and the parent process tries to + * use the newly-invalid surface. + */ + async BackUpXResources(FileDescriptor aXSocketFd); + + async RequestAnonymousTemporaryFile(uint64_t aID); + + /** + * Notifies the parent that the child needs no more ForceKill dump. + */ + [Priority=control] async NotifyShutdownSuccess(); + + /** + * Notifies the parent to continue shutting down after the child performs + * its shutdown tasks. + */ + async FinishShutdown(); + + async UpdateDropEffect(uint32_t aDragAction, uint32_t aDropEffect); + + /** + * Initiates an asynchronous request for permission for the + * provided principal. + * + * @param aRequests + * The array of permissions to request. + * @param aPrincipal + * The principal of the request. + * @param aTopLevelPrincipal + * The principal of the top level page the request comes from. + * @param tabId + * To identify which tab issues this request. + * + * NOTE: The principal is untrusted in the parent process. Only + * principals that can live in the content process should + * provided. + */ + async PContentPermissionRequest(PermissionRequest[] aRequests, + nullable nsIPrincipal aPrincipal, + nullable nsIPrincipal aTopLevelPrincipal, + bool aIsHandlingUserInput, + bool aMaybeUnsafePermissionDelegate, + TabId tabId); + + /** + * If the profiler is running when the process shuts down, this sends the + * profile data collected so far. + * + * @param aProfile + * This may contain an empty string (unknown issue), an error message + * starting with '*', or a profile as a stringified JSON object. + */ + async ShutdownProfile(nsCString aProfile); + + /** + * This sends any collected perf stats data on shutdown. + */ + async ShutdownPerfStats(nsCString aPerfStats); + + /** + * A shared font list (see gfx/thebes/SharedFontList.*) contains a list + * of shared-memory blocks that are used to store all the font list data. + * The font list created in the parent process is the only one that can + * create or store objects into the shared memory; content processes font + * lists have read-only access to it. + * + * To minimize the cost of record allocations, the shared font list + * bump-allocates new objects that it adds to the shared memory blocks + * (i.e. the records stored in the shared memory blocks are only ever + * appended, and never freed except when the entire font list is + * reconstructed). + * + * When initially created by the parent process, the font list may contain + * nothing except a header, and the list of the system's installed font + * family names. Additional data about the families (styled faces available + * and character coverage) is appended to the font list during the session + * as a given font is considered for use, because loading all data for all + * installed fonts during startup is too expensive/slow. + * + * During content process launch, a content process's first step in + * gaining access to the font list is to call GetFontListShmBlock, + * passing index zero in order to get access to the first block, which + * contains the font list header and the list of font-family records + * (which may be virtually all uninitialized at this time, containing + * nothing but the family names). Once a content process determines a + * font-family name it wants to use (e.g. from a CSS font-family list, or + * from preferences), if that Family record has not yet been initialized, + * it will call InitializeFamily (below) to have the parent process + * populate Face records in the shared memory with the family's styles. + * The content process can then pick the face with best style match from + * the available faces according to the CSS font matching algorithm, load + * its character map, then send the map to the parent process using + * SetCharacterMap (so that the parent process can share the map with all + * processes to avoid duplication of work). + * + * At some point, as the parent process adds data to the font list, a new + * shared-memory block will probably be needed. At that point the parent + * will create a new block and append it to its share memory block list. + * The new Block index will start to appear in Pointer records in the + * shared memory, and the content process's can then fetch those other + * blocks using this function as needed. + * + * @param aGeneration + * The font list has a Generation ID stored in its Header, and any time + * the parent process needs to reinitialize the list (because of a change + * in the available font repertoire) a new Generation ID is assigned. + * Content processes pass the Generation of the list they're using in + * all messages, so that the parent can recognize if they're out of date + * and safely ignore such messages. (When the parent rebuilds the list, + * it will notify all content processes, but they may still send a few + * messages that relate to the obsolete list before they have processed + * this notification.) + * @param aIndex + * (Zero-based) index of the shared-memory block to be mapped. + * In a typical case, there will be a handful of blocks altogether, so + * each content process only needs to make this request a few times. + * @returns aHandle + * Handle that can be used to construct a SharedMemory that maps the + * requested block of memory. + * If aGeneration does not match the parent's font list generation ID, or + * if requesting a block that does not exist (i.e. with aIndex greater + * than or equal to the number of blocks actually in existence), returns + * a null handle. + * + * This is a sync message because the content process needs font data in + * order to perform font-matching (e.g. during reflow), and cannot continue + * until it has mapped the font-list memory. + */ + sync GetFontListShmBlock(uint32_t aGeneration, uint32_t aIndex) + returns (SharedMemoryHandle aHandle); + + /** + * Ask the parent to initialize a given font family, so that face metadata + * will be available. Content processes will only call this for families + * where the Face data has not yet been populated, so it will generally be + * called no more than once per family. (It may not be needed at all, if + * the parent process has already initialized the families that content + * wants to use.) + * + * @param aGeneration + * Font-list generation, so requests relating to an obsolete list can be + * ignored (see comments for GetFontListShmBlock). + * @param aFamilyIndex + * The 0-based index of the Family within the font-list that a content + * process needs to use. + * @param aLoadCmaps + * If true, the parent should eagerly load character maps for the faces + * in the family. + * + * This is a sync message because the content process cannot complete its + * font-matching until the family is fully populated with Face records. + * If we make it async, content processes will reflow using fallbacks, + * and then have to reflow again once all the font information needed + * becomes available. + */ + sync InitializeFamily(uint32_t aGeneration, uint32_t aFamilyIndex, + bool aLoadCmaps); + + /** + * Record the character map of a given Face in the font list. + * + * @param aGeneration + * Font-list generation, so requests relating to an obsolete list can be + * ignored (see comments for GetFontListShmBlock). + * @param aFamilyIndex + * Index of the font family in the font list (see aAlias for which list). + * @param aAlias + * Whether aFamilyIndex refers to the Families() or AliasFamilies() list. + * @param aFaceIndex + * Index of the face within the family's Faces() list. + * @param aMap + * The character coverage map of the face. (This will be stored as a + * SharedBitSet record within the shared font list, and the Face record + * will be updated to reference it.) + */ + async SetCharacterMap(uint32_t aGeneration, uint32_t aFamilyIndex, bool aAlias, + uint32_t aFaceIndex, gfxSparseBitSet aMap); + + /** + * Ask the parent to set up the merged charmap for a family, to accelerate + * future fallback searches. + * aFamilyIndex may refer to an element in either Families() or AliasFamilies(), + * with aAlias determining which. + */ + async SetupFamilyCharMap(uint32_t aGeneration, uint32_t aFamilyIndex, bool aAlias); + + /** + * Ask the parent to try and complete the InitOtherFamilyNames task, because + * we're trying to look up a localized font name. This is a sync method so that + * the update will be available before the child continues reflow; however, it + * is possible the task will have timed-out in the parent and not actually + * completed during this call. + * + * @param aGeneration + * Font-list generation, so requests relating to an obsolete list can be + * ignored (see comments for GetFontListShmBlock). + * @param aDefer + * Parameter aDeferOtherFamilyNamesLoading to be passed to + * gfxPlatformFontList::InitOtherFamilyNames, to determine whether name + * loading should be deferred to a background task or run immediately. + * @param aLoaded + * Returns whether the font name loading process has completed. + * + * TODO: This is currently a sync message but can probably be made async, + * at the cost of an increased chance of some testcases failing because + * they depend on lazily-loaded font names. + */ + sync InitOtherFamilyNames(uint32_t aGeneration, bool aDefer) returns (bool aLoaded); + + /** + * Ask the parent to load all font character maps, as we need to do an + * exhaustive font-fallback search. This is done asynchronously; when it + * finishes, the parent will trigger global reflow so that font selection + * is re-done in all content, making use of the newly-loaded cmaps. + * Normally this will only happen once per browser session (unless the + * font list is rebuilt due to installation/removal of system fonts). + * + * @param aGeneration + * Font-list generation, so requests relating to an obsolete list can be + * ignored (see comments for GetFontListShmBlock). + * @param aStartIndex + * The family index to start from; the sender has determined that cmaps + * up to this point are already loaded. + */ + async StartCmapLoading(uint32_t aGeneration, uint32_t aStartIndex); + + /** + * Ask the parent for a specific hyphenation resource (identified by URI) + * as a shared memory block. + * + * This is a sync method because at the point where a content process finds + * that it requires a particular hyphenation dictionary, this is blocking + * reflow; making it async would require scheduling another reflow after + * the resource is available, and a possible layout "jump" as line-breaks + * change. Note that the content process retains a reference to each such + * resource it requests, so it will only make this call once per locale for + * which hyphenation data exists. + * + * @param aURI + * The URI (which currently must always point to an omnijar resource) + * for the required hyphenation dictionary. + * @param aHandle + * Returns the shmem handle to the resource (or an invalid shmem handle + * in case of failure). + * @param aLoaded + * Returns the size in bytes of the resource. + */ + sync GetHyphDict(nullable nsIURI aURI) returns (SharedMemoryHandle aHandle, uint32_t aSize); + + async CreateWindow(PBrowser aThisTab, + MaybeDiscardedBrowsingContext aParent, + PBrowser aNewTab, + uint32_t aChromeFlags, + bool aCalledFromJS, + bool aForPrinting, + bool aForWindowDotPrint, + nullable nsIURI aURIToLoad, + nsCString aFeatures, + Modifiers aModifiers, + nullable nsIPrincipal aTriggeringPrincipal, + nullable nsIContentSecurityPolicy aCsp, + nullable nsIReferrerInfo aReferrerInfo, + OriginAttributes aOriginAttributes) + returns (CreatedWindowInfo window); + + async CreateWindowInDifferentProcess( + PBrowser aThisTab, + MaybeDiscardedBrowsingContext aParent, + uint32_t aChromeFlags, + bool aCalledFromJS, + nullable nsIURI aURIToLoad, + nsCString aFeatures, + Modifiers aModifiers, + nsString aName, + nullable nsIPrincipal aTriggeringPrincipal, + nullable nsIContentSecurityPolicy aCsp, + nullable nsIReferrerInfo aReferrerInfo, + OriginAttributes aOriginAttributes); + + /** + * Tell the parent that a decoder's' benchmark has been completed. + * The result can then be stored in permanent storage. + */ + async NotifyBenchmarkResult(nsString aCodecName, uint32_t aDecodeFPS); + + /** + * Notify `push-message` observers without data in the parent. + */ + async NotifyPushObservers(nsCString scope, nullable nsIPrincipal principal, + nsString messageId); + + /** + * Notify `push-message` observers with data in the parent. + */ + async NotifyPushObserversWithData(nsCString scope, nullable nsIPrincipal principal, + nsString messageId, uint8_t[] data); + + /** + * Notify `push-subscription-change` observers in the parent. + */ + async NotifyPushSubscriptionChangeObservers(nsCString scope, + nullable nsIPrincipal principal); + + async GetFilesRequest(nsID aID, nsString aDirectory, bool aRecursiveFlag); + async DeleteGetFilesRequest(nsID aID); + + async StoreAndBroadcastBlobURLRegistration(nsCString url, IPCBlob blob, + nullable nsIPrincipal principal, nsCString aPartitionKey); + + async UnstoreAndBroadcastBlobURLUnregistration(nsCString url, nullable nsIPrincipal principal); + + /** + * Messages for communicating child Glean data to the parent process + */ + async RecordPageLoadEvent(PageLoadExtra event); + + /** + * Messages for communicating child Telemetry to the parent process + */ + async AccumulateChildHistograms(HistogramAccumulation[] accumulations); + async AccumulateChildKeyedHistograms(KeyedHistogramAccumulation[] accumulations); + async UpdateChildScalars(ScalarAction[] updates); + async UpdateChildKeyedScalars(KeyedScalarAction[] updates); + async RecordChildEvents(ChildEventData[] events); + async RecordDiscardedData(DiscardedData data); + + async AddMemoryReport(MemoryReport aReport); + + async BHRThreadHang(HangDetails aHangDetails); + + /* + * Adds a certificate exception for the given hostname and port. + */ + async AddCertException(nullable nsIX509Cert aCert, nsCString aHostName, + int32_t aPort, OriginAttributes aOriginAttributes, + bool aIsTemporary) + returns (nsresult success); + + /* + * Determines whether storage access can be granted automatically by the + * storage access API without showing a user prompt. + */ + async AutomaticStorageAccessPermissionCanBeGranted(nullable nsIPrincipal aPrincipal) + returns (bool success); + + /* + * A 3rd party tracking origin (aTrackingOrigin) has received the permission + * granted to have access to aGrantedOrigin when loaded by aParentWindowId. + */ + async StorageAccessPermissionGrantedForOrigin(uint64_t aTopLevelWindowId, + MaybeDiscardedBrowsingContext aParentContext, + nullable nsIPrincipal aTrackingPrincipal, + nsCString aTrackingOrigin, + int aAllowMode, + StorageAccessPermissionGrantedReason? aReason, + bool aFrameOnly) + returns (bool unused); + + async CompleteAllowAccessFor(MaybeDiscardedBrowsingContext aParentContext, + uint64_t aTopLevelWindowId, + nullable nsIPrincipal aTrackingPrincipal, + nsCString aTrackingOrigin, + uint32_t aCookieBehavior, + StorageAccessPermissionGrantedReason aReason) + returns (StorageAccessPromptChoices? choice); + + async SetAllowStorageAccessRequestFlag( + nullable nsIPrincipal aEmbeddingPrincipal, + nullable nsIURI aEmbeddedOrigin) + returns (bool success); + + async TestAllowStorageAccessRequestFlag( + nullable nsIPrincipal aEmbeddedPrincipal, + nullable nsIURI aEmbeddingOrigin) + returns (bool success); + + async StoreUserInteractionAsPermission(nullable nsIPrincipal aPrincipal); + + async TestCookiePermissionDecided(MaybeDiscardedBrowsingContext aContext, + nullable nsIPrincipal aPrincipal) + returns (bool? allowed); + + async TestStorageAccessPermission(nullable nsIPrincipal aEmbeddingPrincipal, + nsCString aEmbeddedOrigin) + returns (bool? allowed); + + /** + * When media element's controlled state changed in the content process, we + * have to notify the chrome process in order to update the status of the + * corresponding media controller, which is used to control all media in the + * certain tab. We would use the browsing context to find the corresponding + * controller. + */ + async NotifyMediaPlaybackChanged(MaybeDiscardedBrowsingContext aContext, + MediaPlaybackState aState); + + /** + * When media became audible or inaudible in content process, we have to + * notify chrome process in order to which tab is audible. + */ + async NotifyMediaAudibleChanged(MaybeDiscardedBrowsingContext aContext, + MediaAudibleState aState); + + /** + * When media enabled or disabled the Picture-in-Picture mode, we have to + * update that to the media controller in the chrome process. + */ + async NotifyPictureInPictureModeChanged( + MaybeDiscardedBrowsingContext aContext, bool aEnabled); + + /** + * This method is used to update media session's status when it's being + * created or destroyed. + */ + async NotifyMediaSessionUpdated(MaybeDiscardedBrowsingContext aContext, bool aIsCreated); + + /** + * This method is used to update media session's media metadata whenever its + * metadata is being updated. + */ + async NotifyUpdateMediaMetadata(MaybeDiscardedBrowsingContext aContext, + MediaMetadataBase? aMetadata); + + /** + * This method is used to update media session's playback state whenever its + * playback state is changed. + */ + async NotifyMediaSessionPlaybackStateChanged( + MaybeDiscardedBrowsingContext aContext, + MediaSessionPlaybackState aMetadata); + + /** + * This method is used to update media session's supported media session + * action when the action becomes supported or unsupported. + */ + async NotifyMediaSessionSupportedActionChanged( + MaybeDiscardedBrowsingContext aContext, + MediaSessionAction aAction, + bool aEnabled); + + /** + * This method is used to notify the media controller in chrome process that + * the media element in the browsing context entered fullscreen. + */ + async NotifyMediaFullScreenState( + MaybeDiscardedBrowsingContext aContext, + bool aIsInFullScreen); + + /** + * This method is used to update media session's position state whenever its + * position state is being updated. + */ + async NotifyPositionStateChanged( + MaybeDiscardedBrowsingContext aContext, + PositionState aState); + + /** + * This method will make canonical browsing context to update the count of + * callers which want to keep the page from being suspended even if the page + * is inactive. + */ + async AddOrRemovePageAwakeRequest(MaybeDiscardedBrowsingContext aContext, + bool aShouldAddCount); + +#if defined(XP_WIN) + /** + * Due to sandboxing, a child process's UntrustedModulesProcessor cannot + * obtain enough information about a DLL file to determine its + * trustworthiness. This API asks the chrome process to perform that + * evaluation. + */ + async GetModulesTrust(ModulePaths aModPaths, bool aRunAtNormalPriority) + returns (ModulesMapResult? modMapResult); +#endif // defined(XP_WIN) + + /** + * Used to route shutdown diagnostic info from the content process + * ServiceWorkers to the parent process' ServiceWorkerManager's + * ServiceWorkerShutdownBlocker. (The only other actor chain available + * for this would be very convoluted and create ordering problems). + */ + async ReportServiceWorkerShutdownProgress(uint32_t aShutdownStateId, + Progress aProgress); + + /** + * Whenever a document is updating the OrientationLock, we need to + * reject the orientationPendingPromises in other processes. + */ + async AbortOtherOrientationPendingPromises(MaybeDiscardedBrowsingContext aContext); + + async HistoryReload(MaybeDiscardedBrowsingContext aContext, uint32_t aReloadFlags); + + async NotifyOnHistoryReload(MaybeDiscardedBrowsingContext aContext, + bool aForceReload) + returns (bool canReload, nsDocShellLoadState? loadState, + bool? reloadActiveEntry); + + async HistoryCommit(MaybeDiscardedBrowsingContext aContext, + uint64_t aLoadID, nsID aChangeID, uint32_t aLoadType, + bool aPersist, bool aCloneEntryChildren, + bool aChannelExpired, uint32_t aCacheKey); + + async HistoryGo(MaybeDiscardedBrowsingContext aContext, int32_t aOffset, + uint64_t aHistoryEpoch, bool aRequireUserInteraction, + bool aUserActivation) returns(int32_t? requestedIndex); + + async BlobURLDataRequest(nsCString aBlobURL, + nullable nsIPrincipal aTriggeringPrincipal, + nullable nsIPrincipal aLoadingPrincipal, + OriginAttributes aOriginAttributes, + uint64_t aInnerWindowId, + nsCString aPartitionKey) + returns (BlobURLDataRequestResult aResult); + + async SetActiveSessionHistoryEntry(MaybeDiscardedBrowsingContext context, + nsPoint? previousScrollPosition, + SessionHistoryInfo info, uint32_t loadType, + uint32_t updatedCacheKey, nsID changeID); + async ReplaceActiveSessionHistoryEntry( + MaybeDiscardedBrowsingContext context, SessionHistoryInfo info); + + async RemoveDynEntriesFromActiveSessionHistoryEntry( + MaybeDiscardedBrowsingContext aContext); + + async RemoveFromSessionHistory( + MaybeDiscardedBrowsingContext aContext, nsID changeID); + + // Called when a nsDocShellLoadState which was received over IPC is + // destroyed in the content process to clean up pending state left behind + // tracking the load state in the parent process. + [LazySend] async CleanupPendingLoadState(uint64_t aLoadIdentifier); + +both: + async ScriptError(nsString message, nsString sourceName, nsString sourceLine, + uint32_t lineNumber, uint32_t colNumber, uint32_t flags, + nsCString category, bool privateWindow, uint64_t innerWindowId, + bool fromChromeContext); + + /** + * Used in fission to report timing data when the parent window is in + * another process. Child frame will send data to its ContentParent which + * will then identify the ContentParent for the innerWindowId and pass + * the data to the correct process. + * loadInfo is passed in order to enforce same-origin security checks + * aData must be non-null. + */ + async ReportFrameTimingData(LoadInfoArgs loadInfo, nsString entryName, + nsString initiatorType, + UniquePtr<PerformanceTimingData> aData); + + async CommitBrowsingContextTransaction(MaybeDiscardedBrowsingContext aContext, + BrowsingContextTransaction aTransaction, + uint64_t aEpoch); + + async AsyncMessage(nsString aMessage, ClonedMessageData aData); + + /** + * Notify `push-subscription-modified` observers in the parent and child. + */ + async NotifyPushSubscriptionModifiedObservers(nsCString scope, + nullable nsIPrincipal principal); + + /** + * Send a Push error message to all service worker clients in the parent or + * child. + */ + async PushError(nsCString scope, nullable nsIPrincipal principal, nsString message, + uint32_t flags); + + /** + * Creates a new BrowsingContext, initialized with the values provided in + * `BrowsingContextInitializer`. + * + * This message may only be sent to the parent in limited situations. If the + * new BrowsingContext has a parent window, it must be owned by the + * embedding process, otherwise it must be owned by the opener, if set. + */ + [LazySend] async CreateBrowsingContext(uint64_t aGroupId, BrowsingContextInitializer aInit); + + /** + * If aDoDiscard is true, discards the passed-in BrowsingContext. If the + * BrowsingContext has already been discarded, this message does nothing. + * If the receiver is the parent process, resolves when all content + * processes have flagged the BrowsingContext as discarded, and if the + * receiver is a child process, resolves when that child process has flagged + * the BrowsingContext as discarded. + */ + async DiscardBrowsingContext(MaybeDiscardedBrowsingContext aContext, bool aDoDiscard) + returns (uint64_t unused); + + async AdjustWindowFocus(MaybeDiscardedBrowsingContext aContext, + bool aIsVisible, uint64_t aActionId); + async WindowClose(MaybeDiscardedBrowsingContext aContext, + bool aTrustedCaller); + async WindowFocus(MaybeDiscardedBrowsingContext aContext, + CallerType aCallerType, uint64_t aActionId); + async WindowBlur(MaybeDiscardedBrowsingContext aContext, + CallerType aCallerType); + async RaiseWindow(MaybeDiscardedBrowsingContext aContext, CallerType aCallerType, uint64_t aActionId); + async ClearFocus(MaybeDiscardedBrowsingContext aContext); + async SetFocusedBrowsingContext(MaybeDiscardedBrowsingContext aContext, uint64_t aActionId); + async SetActiveBrowsingContext(MaybeDiscardedBrowsingContext aContext, uint64_t aActionId); + async UnsetActiveBrowsingContext(MaybeDiscardedBrowsingContext aContext, uint64_t aActionId); + async SetFocusedElement(MaybeDiscardedBrowsingContext aContext, bool aNeedsFocus); + async FinalizeFocusOuter(MaybeDiscardedBrowsingContext aContext, bool aCanFocus, + CallerType aCallerType); +parent: + [LazySend] async InsertNewFocusActionId(uint64_t aActionId); + async BlurToParent(MaybeDiscardedBrowsingContext aFocusedBrowsingContext, + MaybeDiscardedBrowsingContext aBrowsingContextToClear, + MaybeDiscardedBrowsingContext aAncestorBrowsingContextToFocus, + bool aIsLeavingDocument, bool aAdjustWidget, + bool aBrowsingContextToClearHandled, + bool aAncestorBrowsingContextToFocusHandled, uint64_t aActionId); +child: + async BlurToChild(MaybeDiscardedBrowsingContext aFocusedBrowsingContext, + MaybeDiscardedBrowsingContext aBrowsingContextToClear, + MaybeDiscardedBrowsingContext aAncestorBrowsingContextToFocus, + bool aIsLeavingDocument, bool aAdjustWidget, uint64_t aActionId); + async SetupFocusedAndActive(MaybeDiscardedBrowsingContext aFocusedBrowsingContext, + uint64_t aActionIdForFocused, + MaybeDiscardedBrowsingContext aActiveBrowsingContext, + uint64_t aActionId); + async ReviseActiveBrowsingContext(uint64_t aOldActionId, + MaybeDiscardedBrowsingContext aActiveBrowsingContext, + uint64_t aNewActionId); + async ReviseFocusedBrowsingContext(uint64_t aOldActionId, + MaybeDiscardedBrowsingContext aFocusedBrowsingContext, + uint64_t aNewActionId); +both: + async MaybeExitFullscreen(MaybeDiscardedBrowsingContext aContext); + async WindowPostMessage(MaybeDiscardedBrowsingContext aContext, + ClonedOrErrorMessageData aMessage, + PostMessageData aData); + + async CommitWindowContextTransaction(MaybeDiscardedWindowContext aContext, + WindowContextTransaction aTransaction, + uint64_t aEpoch); + +child: + // NOTE: These methods are only needed on the child, as the parent + // WindowContext is managed using the PWindowGlobal actor's lifecycle. + [LazySend] async CreateWindowContext(WindowContextInitializer aInit); + async DiscardWindowContext(uint64_t aContextId) returns (bool unused); + +parent: + // Temporary (bug 1641989) conduit for Glean data in content processes. + // Sent from time-to-time to limit the amount of data vulnerable to loss. + // Buffer contains bincoded Rust structs. + async FOGData(ByteBuf buf); + +child: + // Temporary (bug 1641989) conduit for Glean data in content processes. + // Tells the child to flush any pending data. Used in tests and ping + // assembly. Buffer contains bincoded Rust structs. + async FlushFOGData() returns (ByteBuf buf); + +parent: + async SetContainerFeaturePolicy(MaybeDiscardedBrowsingContext aContainerContext, + nullable FeaturePolicy aContainerFeaturePolicy); + + // Obtain an icon from the system widget toolkit, in nsIconDecoder + // format. Not supported (or needed) on all platforms; see the + // implementation in ContentParent::RecvGetSystemIcon for details. + async GetSystemIcon(nullable nsIURI aURI) returns (nsresult aResult, ByteBuf? aData); + +#ifdef FUZZING_SNAPSHOT + // Used by the child process to signal that it is ready to start fuzzing. + // This can in particular be used to wait for a particular event in a + // test document before taking the snapshot and starting e.g. IPC fuzzing. + async SignalFuzzingReady(); +#endif +}; + +} +} diff --git a/dom/ipc/PContentPermission.ipdlh b/dom/ipc/PContentPermission.ipdlh new file mode 100644 index 0000000000..a70b5bca94 --- /dev/null +++ b/dom/ipc/PContentPermission.ipdlh @@ -0,0 +1,19 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +namespace mozilla { +namespace dom { + +struct PermissionRequest { + nsCString type; + nsString[] options; +}; + +struct PermissionChoice { + nsCString type; + nsString choice; +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/ipc/PContentPermissionRequest.ipdl b/dom/ipc/PContentPermissionRequest.ipdl new file mode 100644 index 0000000000..0ce5acbe95 --- /dev/null +++ b/dom/ipc/PContentPermissionRequest.ipdl @@ -0,0 +1,29 @@ +/* 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 PContentPermission; + +include "nsContentPermissionHelper.h"; + +namespace mozilla { +namespace dom { + +[ManualDealloc, ChildImpl="RemotePermissionRequest", ParentImpl=virtual] +protocol PContentPermissionRequest +{ + manager PContent; + +parent: + async prompt(); + async Destroy(); + +child: + async NotifyResult(bool allow, PermissionChoice[] choices); + async __delete__(); +}; + + +} // namespace dom +} // namespace mozilla diff --git a/dom/ipc/PCycleCollectWithLogs.ipdl b/dom/ipc/PCycleCollectWithLogs.ipdl new file mode 100644 index 0000000000..9ecfe3da4e --- /dev/null +++ b/dom/ipc/PCycleCollectWithLogs.ipdl @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PContent; + +namespace mozilla { +namespace dom { + +[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual] +protocol PCycleCollectWithLogs { + manager PContent; + +parent: + async CloseGCLog(); + async CloseCCLog(); + + async __delete__(); +}; + +} +} diff --git a/dom/ipc/PFilePicker.ipdl b/dom/ipc/PFilePicker.ipdl new file mode 100644 index 0000000000..0af98fd01b --- /dev/null +++ b/dom/ipc/PFilePicker.ipdl @@ -0,0 +1,48 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* vim: set sw=4 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 PBrowser; + +include IPCBlob; + +include "mozilla/dom/FilePickerMessageUtils.h"; + +using struct mozilla::void_t from "mozilla/ipc/IPCCore.h"; +using nsIFilePicker::CaptureTarget from "nsIFilePicker.h"; +using nsIFilePicker::ResultCode from "nsIFilePicker.h"; + +namespace mozilla { +namespace dom { + +struct InputBlobs { IPCBlob[] blobs; }; +struct InputDirectory { nsString directoryPath; }; +union MaybeInputData +{ + InputBlobs; + InputDirectory; + void_t; +}; + +[ChildImpl=virtual] +protocol PFilePicker +{ + manager PBrowser; + +parent: + async Open(int16_t selectedType, bool addToRecentDocs, nsString defaultFile, + nsString defaultExtension, nsString[] filters, nsString[] filterNames, + nsString[] rawFilters, nsString displayDirectory, + nsString displaySpecialDirectory, nsString okButtonLabel, + CaptureTarget capture); + + async Close(); + +child: + async __delete__(MaybeInputData data, ResultCode result); +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/ipc/PInProcess.ipdl b/dom/ipc/PInProcess.ipdl new file mode 100644 index 0000000000..4c2d7ba548 --- /dev/null +++ b/dom/ipc/PInProcess.ipdl @@ -0,0 +1,33 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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 PExtensions; +include protocol PSessionStore; +include protocol PWindowGlobal; + +include DOMTypes; + +namespace mozilla { +namespace dom { + +/** + * PInProcess is intended for use as an alternative actor manager to PContent + * for async actors which want to be used uniformly in both Content->Chrome and + * Chrome->Chrome circumstances. + * + * `mozilla::dom::InProcess{Parent, Child}::Singleton()` should be used to get + * an instance of this actor. + */ +[ChildProc=Parent] +async protocol PInProcess +{ + manages PExtensions; + manages PSessionStore; + manages PWindowGlobal; +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/ipc/PJSOracle.ipdl b/dom/ipc/PJSOracle.ipdl new file mode 100644 index 0000000000..519c579aa4 --- /dev/null +++ b/dom/ipc/PJSOracle.ipdl @@ -0,0 +1,21 @@ +/* -*- 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 PJSValidator; + +namespace mozilla { +namespace dom { + +// PJSOracle is a top-level actor which manages PJSValidator +[ChildProc=Utility] +async protocol PJSOracle { + manages PJSValidator; + +child: + async PJSValidator(); +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/ipc/PJSValidator.ipdl b/dom/ipc/PJSValidator.ipdl new file mode 100644 index 0000000000..7d369c5a21 --- /dev/null +++ b/dom/ipc/PJSValidator.ipdl @@ -0,0 +1,32 @@ +/* -*- 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 PJSOracle; + +using mozilla::net::OpaqueResponseBlocker::ValidatorResult from "mozilla/net/OpaqueResponseUtils.h"; + +namespace mozilla { +namespace dom { + +async protocol PJSValidator { + manager PJSOracle; + +child: + [ReplyPriority=control] + async IsOpaqueResponseAllowed() returns (Shmem? aMem, ValidatorResult aResult); + + async OnDataAvailable(Shmem aData); + + // aContentCharset, aHintCharset and aDocumentCharset + // are needed to determine the decoder for the received data + async OnStopRequest(nsresult aReason, nsCString aContentCharset, + nsString aHintCharset, + nsString aDocumentCharset); + + async __delete__(); +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/ipc/PProcessHangMonitor.ipdl b/dom/ipc/PProcessHangMonitor.ipdl new file mode 100644 index 0000000000..290f9b157b --- /dev/null +++ b/dom/ipc/PProcessHangMonitor.ipdl @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* 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/. */ + +// ParamTraits stuff for nsIRemoteTab::NavigationType +include "mozilla/dom/TabMessageUtils.h"; +using nsIRemoteTab::NavigationType from "nsIRemoteTab.h"; + +using base::ProcessId from "base/process.h"; +using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h"; +using nsIThread::QoSPriority from "nsIThread.h"; + +namespace mozilla { + +struct SlowScriptData +{ + TabId tabId; + nsCString filename; + nsString addonId; + double duration; +}; + +[ChildImpl=virtual, ParentImpl=virtual, ChildProc=Content] +protocol PProcessHangMonitor +{ +parent: + async HangEvidence(SlowScriptData data); + async ClearHang(); + +child: + async TerminateScript(); + async RequestContentJSInterrupt(); + + async BeginStartingDebugger(); + async EndStartingDebugger(); + + async PaintWhileInterruptingJS(TabId tabId); + async UnloadLayersWhileInterruptingJS(TabId tabId); + + async CancelContentJSExecutionIfRunning( + TabId tabId, NavigationType aNavigationType, + int32_t aNavigationIndex, nsCString? aNavigationURI, int32_t aEpoch); + + // For MacOS QoS use + async SetMainThreadQoSPriority(QoSPriority qosPriority); + +}; + +} // namespace mozilla diff --git a/dom/ipc/PTabContext.ipdlh b/dom/ipc/PTabContext.ipdlh new file mode 100644 index 0000000000..7a917cd232 --- /dev/null +++ b/dom/ipc/PTabContext.ipdlh @@ -0,0 +1,47 @@ +/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* vim: set sw=4 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 "mozilla/dom/TabMessageUtils.h"; + +include protocol PBrowser; + +using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h"; + +namespace mozilla { +namespace dom { + +// An IPCTabContext which corresponds to a PBrowser opened by a child when it +// receives window.open(). +struct PopupIPCTabContext +{ + PBrowser opener; + uint64_t chromeOuterWindowID; +}; + +// An IPCTabContext which corresponds to an app, browser, or normal frame. +struct FrameIPCTabContext +{ + uint64_t chromeOuterWindowID; + + // Maximum number of touch points on the screen. + uint32_t maxTouchPoints; +}; + +// IPCTabContext is an analog to mozilla::dom::TabContext. Both specify an +// iframe/PBrowser's own and containing app-ids and tell you whether the +// iframe/PBrowser is a browser frame. But only IPCTabContext is allowed to +// travel over IPC. +// +// We need IPCTabContext (specifically, PopupIPCTabContext) to prevent a +// privilege escalation attack by a compromised child process. +union IPCTabContext +{ + PopupIPCTabContext; + FrameIPCTabContext; +}; + +} +} diff --git a/dom/ipc/PURLClassifier.ipdl b/dom/ipc/PURLClassifier.ipdl new file mode 100644 index 0000000000..d4eb5480aa --- /dev/null +++ b/dom/ipc/PURLClassifier.ipdl @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* 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 PURLClassifierInfo; + +namespace mozilla { +namespace dom { + +[ManualDealloc] +protocol PURLClassifier +{ + manager PContent; + +child: + async __delete__(ClassifierInfo? info, nsresult errorCode); +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/ipc/PURLClassifierInfo.ipdlh b/dom/ipc/PURLClassifierInfo.ipdlh new file mode 100644 index 0000000000..e4e198fa4c --- /dev/null +++ b/dom/ipc/PURLClassifierInfo.ipdlh @@ -0,0 +1,15 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +namespace mozilla { +namespace dom { + +struct ClassifierInfo { + nsCString list; + nsCString provider; + nsCString fullhash; +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/ipc/PURLClassifierLocal.ipdl b/dom/ipc/PURLClassifierLocal.ipdl new file mode 100644 index 0000000000..a551fc779c --- /dev/null +++ b/dom/ipc/PURLClassifierLocal.ipdl @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* 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 PURLClassifierInfo; + +include "mozilla/ipc/URIUtils.h"; +include "mozilla/dom/URLClassifierParent.h"; +include "mozilla/dom/URLClassifierChild.h"; + +[RefCounted] using class nsIURI from "nsIURI.h"; + +namespace mozilla { +namespace dom { + +struct URLClassifierLocalResult +{ + nullable nsIURI uri; + nsCString featureName; + nsCString matchingList; +}; + +[ManualDealloc, ChildImpl="URLClassifierLocalChild", ParentImpl="URLClassifierLocalParent"] +protocol PURLClassifierLocal +{ + manager PContent; + +child: + async __delete__(URLClassifierLocalResult[] results); +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/ipc/PVsync.ipdl b/dom/ipc/PVsync.ipdl new file mode 100644 index 0000000000..10d8ab4760 --- /dev/null +++ b/dom/ipc/PVsync.ipdl @@ -0,0 +1,40 @@ +/* -*- 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 PBackground; +include protocol PBrowser; +include "mozilla/layers/LayersMessageUtils.h"; + +using class mozilla::TimeStamp from "mozilla/TimeStamp.h"; +using mozilla::VsyncEvent from "mozilla/VsyncDispatcher.h"; + +namespace mozilla { +namespace dom { + +/* + * The PVsync is a sub-protocol in PBackground or PBrowser and it is used to + * notify the vsync event from chrome to content process. It also provides the + * interfaces for content to observe/unobserve vsync event notifications. + */ +async protocol PVsync +{ + manager PBackground or PBrowser; + +child: + // Send vsync event and vsync rate from chrome to content process. + [Compress, Priority=vsync] async Notify(VsyncEvent aVsync, float aVsyncRate); + +parent: + // Content process use these messages to acquire the vsync event. + async Observe(); + async Unobserve(); + + // This message is never sent. Each PVsync actor will stay alive as long as + // its PBackground or PBrowser manager. + async __delete__(); +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/ipc/PWindowGlobal.ipdl b/dom/ipc/PWindowGlobal.ipdl new file mode 100644 index 0000000000..a063852f56 --- /dev/null +++ b/dom/ipc/PWindowGlobal.ipdl @@ -0,0 +1,227 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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 "mozilla/dom/DocShellMessageUtils.h"; +include "mozilla/dom/FeaturePolicyUtils.h"; +include "mozilla/dom/IdentityCredentialSerializationHelpers.h"; +include "mozilla/dom/PermissionMessageUtils.h"; +include "mozilla/dom/SessionStoreMessageUtils.h"; +include "mozilla/ipc/TransportSecurityInfoUtils.h"; +include "mozilla/ipc/URIUtils.h"; + +include protocol PBrowser; +include protocol PInProcess; +include protocol PBrowserBridge; + +include DOMTypes; +include ClientIPCTypes; +include IPCIdentityCredential; +include NeckoChannelParams; +include SessionStoreTypes; + +include "mozilla/layers/LayersMessageUtils.h"; + +using mozilla::dom::JSActorMessageKind from "mozilla/dom/JSActor.h"; +using mozilla::gfx::IntRect from "mozilla/gfx/Rect.h"; +[MoveOnly] using mozilla::gfx::PaintFragment from "mozilla/gfx/CrossProcessPaint.h"; +using nscolor from "nsColor.h"; +using mozilla::dom::XPCOMPermitUnloadAction from "nsIDocumentViewer.h"; +using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h"; +using mozilla::layers::LayersId from "mozilla/layers/LayersTypes.h"; +[RefCounted] using class nsITransportSecurityInfo from "nsITransportSecurityInfo.h"; +using mozilla::UseCounters from "mozilla/UseCounter.h"; +using mozilla::dom::MaybeDiscardedWindowContext from "mozilla/dom/WindowContext.h"; +[RefCounted] using mozilla::dom::FeaturePolicy from "mozilla/dom/FeaturePolicy.h"; +[RefCounted] using mozilla::dom::SessionStoreRestoreData from "mozilla/dom/SessionStoreRestoreData.h"; +using mozilla::dom::IdentityCredentialRequestOptions from "mozilla/dom/IdentityCredentialBinding.h"; + +namespace mozilla { +namespace dom { + +struct JSActorMessageMeta { + nsCString actorName; + nsString messageName; + uint64_t queryId; + JSActorMessageKind kind; +}; + +struct IPCWebShareData +{ + nsCString title; + nsCString text; + nullable nsIURI url; +}; + +/** + * A PWindowGlobal actor has a lifetime matching that of a single Window Global, + * specifically a |nsGlobalWindowInner|. These actors will form a parent/child + * link either between the chrome/content process, or will be in-process, for + * documents which are loaded in the chrome process. + */ +async protocol PWindowGlobal +{ + manager PBrowser or PInProcess; + +child: + async __delete__(); + + async MakeFrameLocal(MaybeDiscardedBrowsingContext aFrameContext, + uint64_t aSwitchId); + async MakeFrameRemote(MaybeDiscardedBrowsingContext aFrameContext, + ManagedEndpoint<PBrowserBridgeChild> aEndpoint, + TabId aTabId, LayersId aLayersId) returns (bool success); + + async DrawSnapshot(IntRect? aRect, float aScale, nscolor aBackgroundColor, + uint32_t aFlags) returns (PaintFragment retval); + + async DispatchSecurityPolicyViolation(nsString aViolationEventJSON); + + async SaveStorageAccessPermissionGranted(); + + async AddBlockedFrameNodeByClassifier(MaybeDiscardedBrowsingContext aNode); + + /** + * Request from UI to reset the scaling zoom that is controlled by APZ. + */ + async ResetScalingZoom(); + + async SetContainerFeaturePolicy(nullable FeaturePolicy aContainerFeaturePolicy); + + async RestoreDocShellState(DocShellRestoreState aState) + returns (bool success); + + async RestoreTabContent(nullable SessionStoreRestoreData aData) returns (bool success); + +both: + async RawMessage(JSActorMessageMeta aMetadata, ClonedMessageData? aData, + ClonedMessageData? aStack); + +parent: + // Load the given URI load state into the current owner process of the given + // BrowsingContext. aTargetBC must be in the same BrowsingContextGroup as this + // window global. + async LoadURI(MaybeDiscardedBrowsingContext aTargetBC, + nsDocShellLoadState aLoadState, bool aSetNavigating); + + async InternalLoad(nsDocShellLoadState aLoadState); + + /// Update the URI of the document in this WindowGlobal. + [LazySend] async UpdateDocumentURI(nsIURI aUri); + + // We expose frameAncestors to web-extensions and they extract URIs from the + // principals collected. In order to be compatible with that API, we need to + // update the document's principal. This is only allowed if the principals are + // `equals` to each other. + [LazySend] async UpdateDocumentPrincipal(nullable nsIPrincipal aPrincipal, + nullable nsIPrincipal aStoragePrincipal); + + // Update document's `documentHasLoaded` bit in this WindowGlobal. + [LazySend] async UpdateDocumentHasLoaded(bool aDocumentHasLoaded); + + // Update document's 'documentHasUserInteracted' bit in this WindowGlobal. + [LazySend] async UpdateDocumentHasUserInteracted(bool aDocumentHasUserInteracted); + + // Update document's sandbox flags in this WindowGlobal. + [LazySend] async UpdateSandboxFlags(uint32_t aSandboxFlags); + + // Update document csp's fields in this WindowGlobal. + [LazySend] async UpdateDocumentCspSettings(bool aBlockAllMixedContent, bool aUpgradeInsecureRequests); + + // Update document's cookie settings in this WindowGlobal. + [LazySend] async UpdateCookieJarSettings(CookieJarSettingsArgs cookieJarSettings); + + // Update the title of the document in this WindowGlobal. + [LazySend] async UpdateDocumentTitle(nsString aTitle); + + [LazySend] async UpdateDocumentSecurityInfo(nullable nsITransportSecurityInfo aSecurityInfo); + + // Update the document's HTTPS-Only Mode flags in this WindowGlobal. + [LazySend] async UpdateHttpsOnlyStatus(uint32_t aHttpsOnlyStatus); + + /// Send down initial document bit to the parent. + [LazySend] async SetIsInitialDocument(bool aIsInitialDocument); + + // Attempts to perform a "Web Share". + async Share(IPCWebShareData aData) returns (nsresult rv); + + // Get content blocking events from the parent process. + async GetContentBlockingEvents() returns (uint32_t events); + + // Send the ClientInfo associated with a top-level document load. + [LazySend] async SetClientInfo(IPCClientInfo aClientInfo); + + // Checks whether any "beforeunload" event listener in the document subtree + // wants to block unload, and prompts the user to allow if any does (depending + // on the action specified, using nsIDocumentViewer::PermitUnloadAction + // values). The sender is responsible for checking documents in its own + // process, and passing true for `aHasInProcessBlocker` if any exist. Windows + // hosted outside of the caller process will be checked automatically. + async CheckPermitUnload(bool aHasInProcessBlocker, XPCOMPermitUnloadAction aAction) + returns (bool permitUnload); + + /** + * Informs the parent process that the document in aTop should expect to + * receive page use counter contributions from the document in this + * WindowGlobal. + */ + async ExpectPageUseCounters(MaybeDiscardedWindowContext aTop); + + /** + * Accumulates use counter data from the document in this WindowGlobal into + * the document previously passed into the ExpectPageUseCounters call. + */ + async AccumulatePageUseCounters(UseCounters aUseCounters); + + async RequestRestoreTabContent(); + + // Add the flags in aOnFlags to the current BFCache status and remove the + // flags in aOffFlags from the current BFCache status. See the BFCacheStatus + // enum for the valid flags. + async UpdateBFCacheStatus(uint32_t aOnFlags, uint32_t aOffFlags); + + // Signal whether the first connection is added (aIsAdded = true) or + // the last connection is removed (aIsAdded = false). + async UpdateActivePeerConnectionStatus(bool aIsAdded); + + /** + * Used to notify the parent when there's a change in the number of requests + * in the loadgroup. If there are no requests this will be set to Nothing(). + * If there is one request this will be set to the ID of that request, if it + * implements nsIIdentChannel. If there are more than one requests this will + * be set to 0. + * Note that some requests are ignored (eg. favicon loads). + */ + async SetSingleChannelId(uint64_t? singleChannelId); + + async SetDocumentDomain(nsIURI aDomain); + + async Destroy(); + + async ReloadWithHttpsOnlyException(); + + // Used by the Credential Manager API and FedCM to keep the discovery of + // a credential abstracted from the content process. This is required because + // credentialed requests that are specifically not partitioned are made and + // the results must not enter the child process until the user consents via + // purpose built UI. + async DiscoverIdentityCredentialFromExternalSource(IdentityCredentialRequestOptions aOptions) + returns (IPCIdentityCredential? identityCredential); + + async GetStorageAccessPermission() returns(uint32_t permission_action); + + + async SetCookies(nsCString baseDomain, + OriginAttributes attrs, + nullable nsIURI host, + bool fromHttp, + CookieStruct[] cookies); + +child: + async NotifyPermissionChange(nsCString type, uint32_t permission); +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/ipc/PageLoadEventUtils.h b/dom/ipc/PageLoadEventUtils.h new file mode 100644 index 0000000000..243386ca6c --- /dev/null +++ b/dom/ipc/PageLoadEventUtils.h @@ -0,0 +1,52 @@ +/* -*- 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_dom_page_load_event_utils_h__ +#define mozilla_dom_page_load_event_utils_h__ + +#include "ipc/IPCMessageUtils.h" +#include "mozilla/glean/GleanMetrics.h" + +namespace IPC { + +template <> +struct ParamTraits<mozilla::glean::perf::PageLoadExtra> { + typedef mozilla::glean::perf::PageLoadExtra paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.fcpTime); + WriteParam(aWriter, aParam.lcpTime); + WriteParam(aWriter, aParam.jsExecTime); + WriteParam(aWriter, aParam.loadTime); + WriteParam(aWriter, aParam.loadType); + WriteParam(aWriter, aParam.responseTime); + WriteParam(aWriter, aParam.httpVer); + WriteParam(aWriter, aParam.redirectCount); + WriteParam(aWriter, aParam.redirectTime); + WriteParam(aWriter, aParam.sameOriginNav); + WriteParam(aWriter, aParam.trrDomain); + WriteParam(aWriter, aParam.dnsLookupTime); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &aResult->fcpTime) && + ReadParam(aReader, &aResult->lcpTime) && + ReadParam(aReader, &aResult->jsExecTime) && + ReadParam(aReader, &aResult->loadTime) && + ReadParam(aReader, &aResult->loadType) && + ReadParam(aReader, &aResult->responseTime) && + ReadParam(aReader, &aResult->httpVer) && + ReadParam(aReader, &aResult->redirectCount) && + ReadParam(aReader, &aResult->redirectTime) && + ReadParam(aReader, &aResult->sameOriginNav) && + ReadParam(aReader, &aResult->trrDomain) && + ReadParam(aReader, &aResult->dnsLookupTime); + } +}; + +} // namespace IPC + +#endif // mozilla_dom_page_load_event_utils_h__ diff --git a/dom/ipc/PermissionMessageUtils.cpp b/dom/ipc/PermissionMessageUtils.cpp new file mode 100644 index 0000000000..19d232a7bc --- /dev/null +++ b/dom/ipc/PermissionMessageUtils.cpp @@ -0,0 +1,51 @@ +/* -*- 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 "mozilla/dom/PermissionMessageUtils.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/ipc/PBackgroundSharedTypes.h" +#include "nsCOMPtr.h" +#include "nsIPrincipal.h" + +namespace mozilla::ipc { + +void IPDLParamTraits<nsIPrincipal*>::Write(IPC::MessageWriter* aWriter, + IProtocol* aActor, + nsIPrincipal* aParam) { + Maybe<PrincipalInfo> info; + if (aParam) { + info.emplace(); + nsresult rv = PrincipalToPrincipalInfo(aParam, info.ptr()); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); + } + + WriteIPDLParam(aWriter, aActor, info); +} + +bool IPDLParamTraits<nsIPrincipal*>::Read(IPC::MessageReader* aReader, + IProtocol* aActor, + RefPtr<nsIPrincipal>* aResult) { + Maybe<PrincipalInfo> info; + if (!ReadIPDLParam(aReader, aActor, &info)) { + return false; + } + + if (info.isNothing()) { + return true; + } + + auto principalOrErr = PrincipalInfoToPrincipal(info.ref()); + + if (NS_WARN_IF(principalOrErr.isErr())) { + return false; + } + + nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap(); + *aResult = principal; + return true; +} + +} // namespace mozilla::ipc diff --git a/dom/ipc/PermissionMessageUtils.h b/dom/ipc/PermissionMessageUtils.h new file mode 100644 index 0000000000..7a32b9e930 --- /dev/null +++ b/dom/ipc/PermissionMessageUtils.h @@ -0,0 +1,38 @@ +/* -*- 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_dom_permission_message_utils_h__ +#define mozilla_dom_permission_message_utils_h__ + +#include "mozilla/ipc/IPDLParamTraits.h" +#include "ipc/IPCMessageUtils.h" +#include "nsCOMPtr.h" +#include "nsIPrincipal.h" + +namespace mozilla::ipc { + +template <> +struct IPDLParamTraits<nsIPrincipal*> { + static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor, + nsIPrincipal* aParam); + static bool Read(IPC::MessageReader* aReader, IProtocol* aActor, + RefPtr<nsIPrincipal>* aResult); + + // Overload to support deserializing nsCOMPtr<nsIPrincipal> directly. + static bool Read(IPC::MessageReader* aReader, IProtocol* aActor, + nsCOMPtr<nsIPrincipal>* aResult) { + RefPtr<nsIPrincipal> result; + if (!Read(aReader, aActor, &result)) { + return false; + } + *aResult = std::move(result); + return true; + } +}; + +} // namespace mozilla::ipc + +#endif // mozilla_dom_permission_message_utils_h__ diff --git a/dom/ipc/PreallocatedProcessManager.cpp b/dom/ipc/PreallocatedProcessManager.cpp new file mode 100644 index 0000000000..b96521492f --- /dev/null +++ b/dom/ipc/PreallocatedProcessManager.cpp @@ -0,0 +1,440 @@ +/* -*- 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 "mozilla/PreallocatedProcessManager.h" + +#include "mozilla/AppShutdown.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Preferences.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/StaticPrefs_dom.h" +#include "nsIPropertyBag2.h" +#include "ProcessPriorityManager.h" +#include "nsServiceManagerUtils.h" +#include "nsIXULRuntime.h" +#include "nsTArray.h" +#include "prsystem.h" + +using namespace mozilla::hal; +using namespace mozilla::dom; + +namespace mozilla { +/** + * This singleton class implements the static methods on + * PreallocatedProcessManager. + */ +class PreallocatedProcessManagerImpl final : public nsIObserver { + friend class PreallocatedProcessManager; + + public: + static PreallocatedProcessManagerImpl* Singleton(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + // See comments on PreallocatedProcessManager for these methods. + void AddBlocker(ContentParent* aParent); + void RemoveBlocker(ContentParent* aParent); + already_AddRefed<ContentParent> Take(const nsACString& aRemoteType); + void Erase(ContentParent* aParent); + + private: + static const char* const kObserverTopics[]; + + static StaticRefPtr<PreallocatedProcessManagerImpl> sSingleton; + + PreallocatedProcessManagerImpl(); + ~PreallocatedProcessManagerImpl(); + PreallocatedProcessManagerImpl(const PreallocatedProcessManagerImpl&) = + delete; + + const PreallocatedProcessManagerImpl& operator=( + const PreallocatedProcessManagerImpl&) = delete; + + void Init(); + + bool CanAllocate(); + void AllocateAfterDelay(bool aStartup = false); + void AllocateOnIdle(); + void AllocateNow(); + + void RereadPrefs(); + void Enable(uint32_t aProcesses); + void Disable(); + void CloseProcesses(); + + bool IsEmpty() const { return mPreallocatedProcesses.IsEmpty(); } + static bool IsShutdown() { + return AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed); + } + bool IsEnabled() { return mEnabled && !IsShutdown(); } + + bool mEnabled; + uint32_t mNumberPreallocs; + AutoTArray<RefPtr<ContentParent>, 3> mPreallocatedProcesses; + // Even if we have multiple PreallocatedProcessManagerImpls, we'll have + // one blocker counter + static uint32_t sNumBlockers; + TimeStamp mBlockingStartTime; +}; + +/* static */ +uint32_t PreallocatedProcessManagerImpl::sNumBlockers = 0; + +const char* const PreallocatedProcessManagerImpl::kObserverTopics[] = { + "memory-pressure", + "profile-change-teardown", + NS_XPCOM_SHUTDOWN_OBSERVER_ID, +}; + +/* static */ +StaticRefPtr<PreallocatedProcessManagerImpl> + PreallocatedProcessManagerImpl::sSingleton; + +/* static */ +PreallocatedProcessManagerImpl* PreallocatedProcessManagerImpl::Singleton() { + MOZ_ASSERT(NS_IsMainThread()); + if (!sSingleton) { + sSingleton = new PreallocatedProcessManagerImpl; + sSingleton->Init(); + ClearOnShutdown(&sSingleton); + } + return sSingleton; + // PreallocatedProcessManagers live until shutdown +} + +NS_IMPL_ISUPPORTS(PreallocatedProcessManagerImpl, nsIObserver) + +PreallocatedProcessManagerImpl::PreallocatedProcessManagerImpl() + : mEnabled(false), mNumberPreallocs(1) {} + +PreallocatedProcessManagerImpl::~PreallocatedProcessManagerImpl() { + // Note: mPreallocatedProcesses may not be null, but all processes should + // be dead (IsDead==true). We block Erase() when our observer sees + // shutdown starting. +} + +void PreallocatedProcessManagerImpl::Init() { + Preferences::AddStrongObserver(this, "dom.ipc.processPrelaunch.enabled"); + // We have to respect processCount at all time. This is especially important + // for testing. + Preferences::AddStrongObserver(this, "dom.ipc.processCount"); + // A StaticPref, but we need to adjust the number of preallocated processes + // if the value goes up or down, so we need to run code on change. + Preferences::AddStrongObserver(this, + "dom.ipc.processPrelaunch.fission.number"); + + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + MOZ_ASSERT(os); + for (auto topic : kObserverTopics) { + os->AddObserver(this, topic, /* ownsWeak */ false); + } + RereadPrefs(); +} + +NS_IMETHODIMP +PreallocatedProcessManagerImpl::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) { + if (!strcmp("nsPref:changed", aTopic)) { + // The only other observer we registered was for our prefs. + RereadPrefs(); + } else if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, aTopic) || + !strcmp("profile-change-teardown", aTopic)) { + Preferences::RemoveObserver(this, "dom.ipc.processPrelaunch.enabled"); + Preferences::RemoveObserver(this, "dom.ipc.processCount"); + Preferences::RemoveObserver(this, + "dom.ipc.processPrelaunch.fission.number"); + + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + MOZ_ASSERT(os); + for (auto topic : kObserverTopics) { + os->RemoveObserver(this, topic); + } + } else if (!strcmp("memory-pressure", aTopic)) { + CloseProcesses(); + } else { + MOZ_ASSERT_UNREACHABLE("Unknown topic"); + } + + return NS_OK; +} + +void PreallocatedProcessManagerImpl::RereadPrefs() { + if (mozilla::BrowserTabsRemoteAutostart() && + Preferences::GetBool("dom.ipc.processPrelaunch.enabled")) { + int32_t number = 1; + if (mozilla::FissionAutostart()) { + number = StaticPrefs::dom_ipc_processPrelaunch_fission_number(); + // limit preallocated processes on low-mem machines + PRUint64 bytes = PR_GetPhysicalMemorySize(); + if (bytes > 0 && + bytes <= + StaticPrefs::dom_ipc_processPrelaunch_lowmem_mb() * 1024 * 1024) { + number = 1; + } + } + if (number >= 0) { + Enable(number); + // We have one prealloc queue for all types except File now + if (static_cast<uint64_t>(number) < mPreallocatedProcesses.Length()) { + CloseProcesses(); + } + } + } else { + Disable(); + } +} + +already_AddRefed<ContentParent> PreallocatedProcessManagerImpl::Take( + const nsACString& aRemoteType) { + if (!IsEnabled()) { + return nullptr; + } + RefPtr<ContentParent> process; + if (!IsEmpty()) { + process = mPreallocatedProcesses.ElementAt(0); + mPreallocatedProcesses.RemoveElementAt(0); + + // Don't set the priority to FOREGROUND here, since it may not have + // finished starting + + // We took a preallocated process. Let's try to start up a new one + // soon. + ContentParent* last = mPreallocatedProcesses.SafeLastElement(nullptr); + // There could be a launching process that isn't the last, but that's + // ok (and unlikely) + if (!last || !last->IsLaunching()) { + AllocateAfterDelay(); + } + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("Use prealloc process %p%s, %lu available", process.get(), + process->IsLaunching() ? " (still launching)" : "", + (unsigned long)mPreallocatedProcesses.Length())); + } + if (process && !process->IsLaunching()) { + ProcessPriorityManager::SetProcessPriority(process, + PROCESS_PRIORITY_FOREGROUND); + } // else this will get set by the caller when they call InitInternal() + + return process.forget(); +} + +void PreallocatedProcessManagerImpl::Erase(ContentParent* aParent) { + (void)mPreallocatedProcesses.RemoveElement(aParent); +} + +void PreallocatedProcessManagerImpl::Enable(uint32_t aProcesses) { + mNumberPreallocs = aProcesses; + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("Enabling preallocation: %u", aProcesses)); + if (mEnabled || IsShutdown()) { + return; + } + + mEnabled = true; + AllocateAfterDelay(/* aStartup */ true); +} + +void PreallocatedProcessManagerImpl::AddBlocker(ContentParent* aParent) { + if (sNumBlockers == 0) { + mBlockingStartTime = TimeStamp::Now(); + } + sNumBlockers++; +} + +void PreallocatedProcessManagerImpl::RemoveBlocker(ContentParent* aParent) { + // This used to assert that the blocker existed, but preallocated + // processes aren't blockers anymore because it's not useful and + // interferes with async launch, and it's simpler if content + // processes don't need to remember whether they were preallocated. + + MOZ_DIAGNOSTIC_ASSERT(sNumBlockers > 0); + sNumBlockers--; + if (sNumBlockers == 0) { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("Blocked preallocation for %fms", + (TimeStamp::Now() - mBlockingStartTime).ToMilliseconds())); + PROFILER_MARKER_TEXT("Process", DOM, + MarkerTiming::IntervalUntilNowFrom(mBlockingStartTime), + "Blocked preallocation"); + if (IsEmpty()) { + AllocateAfterDelay(); + } + } +} + +bool PreallocatedProcessManagerImpl::CanAllocate() { + return IsEnabled() && sNumBlockers == 0 && + mPreallocatedProcesses.Length() < mNumberPreallocs && !IsShutdown() && + (FissionAutostart() || + !ContentParent::IsMaxProcessCountReached(DEFAULT_REMOTE_TYPE)); +} + +void PreallocatedProcessManagerImpl::AllocateAfterDelay(bool aStartup) { + if (!IsEnabled()) { + return; + } + long delay = aStartup ? StaticPrefs::dom_ipc_processPrelaunch_startupDelayMs() + : StaticPrefs::dom_ipc_processPrelaunch_delayMs(); + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("Starting delayed process start, delay=%ld", delay)); + NS_DelayedDispatchToCurrentThread( + NewRunnableMethod("PreallocatedProcessManagerImpl::AllocateOnIdle", this, + &PreallocatedProcessManagerImpl::AllocateOnIdle), + delay); +} + +void PreallocatedProcessManagerImpl::AllocateOnIdle() { + if (!IsEnabled()) { + return; + } + + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("Starting process allocate on idle")); + NS_DispatchToCurrentThreadQueue( + NewRunnableMethod("PreallocatedProcessManagerImpl::AllocateNow", this, + &PreallocatedProcessManagerImpl::AllocateNow), + EventQueuePriority::Idle); +} + +void PreallocatedProcessManagerImpl::AllocateNow() { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("Trying to start process now")); + if (!CanAllocate()) { + if (IsEnabled() && IsEmpty() && sNumBlockers > 0) { + // If it's too early to allocate a process let's retry later. + AllocateAfterDelay(); + } + return; + } + + RefPtr<ContentParent> process = ContentParent::MakePreallocProcess(); + mPreallocatedProcesses.AppendElement(process); + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("Preallocated = %lu of %d processes", + (unsigned long)mPreallocatedProcesses.Length(), mNumberPreallocs)); + + RefPtr<PreallocatedProcessManagerImpl> self(this); + process->LaunchSubprocessAsync(PROCESS_PRIORITY_PREALLOC) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [self, this, process](const RefPtr<ContentParent>&) { + if (process->IsDead()) { + Erase(process); + // Process died in startup (before we could add it). If it + // dies after this, MarkAsDead() will Erase() this entry. + // Shouldn't be in the sBrowserContentParents, so we don't need + // RemoveFromList(). We won't try to kick off a new + // preallocation here, to avoid possible looping if something is + // causing them to consistently fail; if everything is ok on the + // next allocation request we'll kick off creation. + } else { + // Continue prestarting processes if needed + if (CanAllocate()) { + if (mPreallocatedProcesses.Length() < mNumberPreallocs) { + AllocateOnIdle(); + } + } else if (!IsEnabled()) { + // if this has a remote type set, it's been allocated for use + // already + if (process->mRemoteType == PREALLOC_REMOTE_TYPE) { + // This will Erase() it + process->ShutDownProcess( + ContentParent::SEND_SHUTDOWN_MESSAGE); + } + } + } + }, + [self, this, process]() { Erase(process); }); +} + +void PreallocatedProcessManagerImpl::Disable() { + if (!mEnabled) { + return; + } + + mEnabled = false; + CloseProcesses(); +} + +void PreallocatedProcessManagerImpl::CloseProcesses() { + while (!IsEmpty()) { + RefPtr<ContentParent> process(mPreallocatedProcesses.ElementAt(0)); + mPreallocatedProcesses.RemoveElementAt(0); + process->ShutDownProcess(ContentParent::SEND_SHUTDOWN_MESSAGE); + // drop ref and let it free + } + + // Make sure to also clear out the recycled E10S process cache, as it's also + // controlled by the same preference, and can be cleaned up due to memory + // pressure. + if (RefPtr<ContentParent> recycled = + ContentParent::sRecycledE10SProcess.forget()) { + recycled->MaybeBeginShutDown(); + } +} + +inline PreallocatedProcessManagerImpl* +PreallocatedProcessManager::GetPPMImpl() { + if (PreallocatedProcessManagerImpl::IsShutdown()) { + return nullptr; + } + return PreallocatedProcessManagerImpl::Singleton(); +} + +/* static */ +bool PreallocatedProcessManager::Enabled() { + if (auto impl = GetPPMImpl()) { + return impl->IsEnabled(); + } + return false; +} + +/* static */ +void PreallocatedProcessManager::AddBlocker(const nsACString& aRemoteType, + ContentParent* aParent) { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("AddBlocker: %s %p (sNumBlockers=%d)", + PromiseFlatCString(aRemoteType).get(), aParent, + PreallocatedProcessManagerImpl::sNumBlockers)); + if (auto impl = GetPPMImpl()) { + impl->AddBlocker(aParent); + } +} + +/* static */ +void PreallocatedProcessManager::RemoveBlocker(const nsACString& aRemoteType, + ContentParent* aParent) { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("RemoveBlocker: %s %p (sNumBlockers=%d)", + PromiseFlatCString(aRemoteType).get(), aParent, + PreallocatedProcessManagerImpl::sNumBlockers)); + if (auto impl = GetPPMImpl()) { + impl->RemoveBlocker(aParent); + } +} + +/* static */ +already_AddRefed<ContentParent> PreallocatedProcessManager::Take( + const nsACString& aRemoteType) { + if (auto impl = GetPPMImpl()) { + return impl->Take(aRemoteType); + } + return nullptr; +} + +/* static */ +void PreallocatedProcessManager::Erase(ContentParent* aParent) { + if (auto impl = GetPPMImpl()) { + impl->Erase(aParent); + } +} + +} // namespace mozilla diff --git a/dom/ipc/PreallocatedProcessManager.h b/dom/ipc/PreallocatedProcessManager.h new file mode 100644 index 0000000000..9dea534dbd --- /dev/null +++ b/dom/ipc/PreallocatedProcessManager.h @@ -0,0 +1,73 @@ +/* -*- 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_PreallocatedProcessManager_h +#define mozilla_PreallocatedProcessManager_h + +#include "base/basictypes.h" +#include "mozilla/AlreadyAddRefed.h" +#include "nsStringFwd.h" + +namespace mozilla { +namespace dom { +class ContentParent; +} // namespace dom + +/** + * This class manages a ContentParent that it starts up ahead of any particular + * need. You can then call Take() to get this process and use it. Since we + * already started it up, it should be ready for use faster than if you'd + * created the process when you needed it. + * + * This class watches the dom.ipc.processPrelaunch.enabled pref. If it changes + * from false to true, it preallocates a process. If it changes from true to + * false, it kills the preallocated process, if any. + * + * We don't expect this pref to flip between true and false in production, but + * flipping the pref is important for tests. + */ +class PreallocatedProcessManagerImpl; + +class PreallocatedProcessManager final { + typedef mozilla::dom::ContentParent ContentParent; + + public: + static PreallocatedProcessManagerImpl* GetPPMImpl(); + + static bool Enabled(); + + /** + * Before first paint we don't want to allocate any processes in the + * background. To avoid that, the PreallocatedProcessManager won't start up + * any processes while there is a blocker active. + */ + static void AddBlocker(const nsACString& aRemoteType, ContentParent* aParent); + static void RemoveBlocker(const nsACString& aRemoteType, + ContentParent* aParent); + + /** + * Take a preallocated process, if we have one. If we don't have a + * preallocated process to return, this returns null. + * + * If we use a preallocated process, it will schedule the start of + * another on Idle (AllocateOnIdle()). + */ + static already_AddRefed<ContentParent> Take(const nsACString& aRemoteType); + + /** + * Note that a process was shut down, and should no longer be tracked as a + * preallocated process. + */ + static void Erase(ContentParent* aParent); + + private: + PreallocatedProcessManager(); + DISALLOW_EVIL_CONSTRUCTORS(PreallocatedProcessManager); +}; + +} // namespace mozilla + +#endif // defined mozilla_PreallocatedProcessManager_h diff --git a/dom/ipc/PrefsTypes.ipdlh b/dom/ipc/PrefsTypes.ipdlh new file mode 100644 index 0000000000..403c7aaf42 --- /dev/null +++ b/dom/ipc/PrefsTypes.ipdlh @@ -0,0 +1,32 @@ +/* -*- 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/. */ + +using struct mozilla::null_t from "mozilla/ipc/IPCCore.h"; + +namespace mozilla { +namespace dom { + +union PrefValue { + nsCString; + int32_t; + bool; +}; + +// This serialization form mirrors that used in mozilla::Pref in +// Preferences.cpp. The two should be kept in sync, e.g. if something is added +// to one it should also be added to the other. +// +// Note: there is no need to pass the isSticky attribute because that's an +// immutable attribute obtained from file at startup. +struct Pref { + nsCString name; + bool isLocked; + bool isSanitized; + PrefValue? defaultValue; + PrefValue? userValue; +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/ipc/ProcessActor.cpp b/dom/ipc/ProcessActor.cpp new file mode 100644 index 0000000000..b036f625c2 --- /dev/null +++ b/dom/ipc/ProcessActor.cpp @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/dom/ProcessActor.h" + +#include "nsContentUtils.h" +#include "mozilla/ContentBlockingAllowList.h" +#include "mozilla/Logging.h" +#include "mozilla/dom/JSActorService.h" +#include "mozilla/dom/JSProcessActorParent.h" +#include "mozilla/dom/JSProcessActorChild.h" +#include "mozilla/dom/JSProcessActorProtocol.h" + +namespace mozilla::dom { + +already_AddRefed<JSActorProtocol> ProcessActor::MatchingJSActorProtocol( + JSActorService* aActorSvc, const nsACString& aName, ErrorResult& aRv) { + RefPtr<JSProcessActorProtocol> proto = + aActorSvc->GetJSProcessActorProtocol(aName); + if (!proto) { + aRv.ThrowNotFoundError(nsPrintfCString("No such JSProcessActor '%s'", + PromiseFlatCString(aName).get())); + return nullptr; + } + + if (!proto->Matches(GetRemoteType(), aRv)) { + MOZ_ASSERT(aRv.Failed()); + return nullptr; + } + MOZ_ASSERT(!aRv.Failed()); + return proto.forget(); +} + +} // namespace mozilla::dom diff --git a/dom/ipc/ProcessActor.h b/dom/ipc/ProcessActor.h new file mode 100644 index 0000000000..a80300b672 --- /dev/null +++ b/dom/ipc/ProcessActor.h @@ -0,0 +1,36 @@ +/* -*- 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_dom_ProcessActor_h +#define mozilla_dom_ProcessActor_h + +#include "mozilla/dom/JSActorManager.h" +#include "nsStringFwd.h" + +namespace mozilla { +class ErrorResult; + +namespace dom { + +class JSActorProtocol; +class JSActorService; + +// Common base class for Content{Parent, Child} and InProcess{Parent, Child}. +class ProcessActor : public JSActorManager { + protected: + virtual ~ProcessActor() = default; + + already_AddRefed<JSActorProtocol> MatchingJSActorProtocol( + JSActorService* aActorSvc, const nsACString& aName, + ErrorResult& aRv) final; + + virtual const nsACString& GetRemoteType() const = 0; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_ProcessActor_h diff --git a/dom/ipc/ProcessHangMonitor.cpp b/dom/ipc/ProcessHangMonitor.cpp new file mode 100644 index 0000000000..1dcba24406 --- /dev/null +++ b/dom/ipc/ProcessHangMonitor.cpp @@ -0,0 +1,1399 @@ +/* -*- 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 "mozilla/ProcessHangMonitor.h" +#include "mozilla/ProcessHangMonitorIPC.h" + +#include "jsapi.h" +#include "xpcprivate.h" + +#include "mozilla/Atomics.h" +#include "mozilla/BackgroundHangMonitor.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/dom/CancelContentJSOptionsBinding.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/ProcessChild.h" +#include "mozilla/ipc/TaskFactory.h" +#include "mozilla/Monitor.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticMonitor.h" +#include "mozilla/StaticPrefs_browser.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Unused.h" +#include "mozilla/WeakPtr.h" + +#include "MainThreadUtils.h" +#include "nsExceptionHandler.h" +#include "nsFrameLoader.h" +#include "nsIHangReport.h" +#include "nsIRemoteTab.h" +#include "nsNetUtil.h" +#include "nsQueryObject.h" +#include "nsThreadUtils.h" + +#include "base/task.h" +#include "base/thread.h" + +#ifdef XP_WIN +// For IsDebuggerPresent() +# include <windows.h> +#endif + +#ifdef XP_MACOSX +// for qos controls +# include <sys/qos.h> +#endif + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::ipc; + +/* + * Basic architecture: + * + * Each process has its own ProcessHangMonitor singleton. This singleton exists + * as long as there is at least one content process in the system. Each content + * process has a HangMonitorChild and the chrome process has one + * HangMonitorParent per process. Each process (including the chrome process) + * runs a hang monitoring thread. The PHangMonitor actors are bound to this + * thread so that they never block on the main thread. + * + * When the content process detects a hang, it posts a task to its hang thread, + * which sends an IPC message to the hang thread in the parent. The parent + * cancels any ongoing CPOW requests and then posts a runnable to the main + * thread that notifies Firefox frontend code of the hang. The frontend code is + * passed an nsIHangReport, which can be used to terminate the hang. + * + * If the user chooses to terminate a script, a task is posted to the chrome + * process's hang monitoring thread, which sends an IPC message to the hang + * thread in the content process. That thread sets a flag to indicate that JS + * execution should be terminated the next time it hits the interrupt + * callback. A similar scheme is used for debugging slow scripts. If a content + * process or plug-in needs to be terminated, the chrome process does so + * directly, without messaging the content process. + */ + +namespace { + +/* Child process objects */ + +class HangMonitorChild : public PProcessHangMonitorChild, + public BackgroundHangAnnotator { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_MAIN_THREAD( + HangMonitorChild, override) + + void Bind(Endpoint<PProcessHangMonitorChild>&& aEndpoint); + + using SlowScriptAction = ProcessHangMonitor::SlowScriptAction; + SlowScriptAction NotifySlowScript(nsIBrowserChild* aBrowserChild, + const char* aFileName, + const nsString& aAddonId, + const double aDuration); + void NotifySlowScriptAsync(TabId aTabId, const nsCString& aFileName, + const nsString& aAddonId, const double aDuration); + + bool IsDebuggerStartupComplete(); + + void ClearHang(); + void ClearHangAsync(); + void ClearPaintWhileInterruptingJS(); + + // MaybeStartPaintWhileInterruptingJS will notify the background hang monitor + // of activity if this is the first time calling it since + // ClearPaintWhileInterruptingJS. It should be callable from any thread, but + // you must be holding mMonitor if using it off the main thread, since it + // could race with ClearPaintWhileInterruptingJS. + void MaybeStartPaintWhileInterruptingJS(); + + mozilla::ipc::IPCResult RecvTerminateScript() override; + mozilla::ipc::IPCResult RecvRequestContentJSInterrupt() override; + mozilla::ipc::IPCResult RecvBeginStartingDebugger() override; + mozilla::ipc::IPCResult RecvEndStartingDebugger() override; + + mozilla::ipc::IPCResult RecvPaintWhileInterruptingJS( + const TabId& aTabId) override; + + mozilla::ipc::IPCResult RecvUnloadLayersWhileInterruptingJS( + const TabId& aTabId) override; + + mozilla::ipc::IPCResult RecvCancelContentJSExecutionIfRunning( + const TabId& aTabId, const nsIRemoteTab::NavigationType& aNavigationType, + const int32_t& aNavigationIndex, + const mozilla::Maybe<nsCString>& aNavigationURI, + const int32_t& aEpoch) override; + + mozilla::ipc::IPCResult RecvSetMainThreadQoSPriority( + const nsIThread::QoSPriority& aQoSPriority) override; + + void ActorDestroy(ActorDestroyReason aWhy) override; + + bool InterruptCallback(); + void Shutdown(); + + static HangMonitorChild* Get() MOZ_REQUIRES(sMainThreadCapability) { + return sInstance; + } + + static void CreateAndBind(ProcessHangMonitor* aMonitor, + Endpoint<PProcessHangMonitorChild>&& aEndpoint); + + void Dispatch(already_AddRefed<nsIRunnable> aRunnable) { + mHangMonitor->Dispatch(std::move(aRunnable)); + } + bool IsOnThread() { return mHangMonitor->IsOnThread(); } + + void AnnotateHang(BackgroundHangAnnotations& aAnnotations) override; + + protected: + friend class mozilla::ProcessHangMonitor; + + private: + explicit HangMonitorChild(ProcessHangMonitor* aMonitor); + ~HangMonitorChild() override; + + void ShutdownOnThread(); + + static StaticRefPtr<HangMonitorChild> sInstance + MOZ_GUARDED_BY(sMainThreadCapability); + + const RefPtr<ProcessHangMonitor> mHangMonitor; + +#ifdef XP_MACOSX + // On macOS, the pthread_t is required to start a QoS class override. As we + // can't recover this from a PRThread*, we need to record it when the + // HangMonitorChild is initially created on the main thread. + const pthread_t mMainPThread; +#endif + + Monitor mMonitor; + + // Main thread-only. + bool mSentReport; + + // These fields must be accessed with mMonitor held. + bool mTerminateScript MOZ_GUARDED_BY(mMonitor); + bool mStartDebugger MOZ_GUARDED_BY(mMonitor); + bool mFinishedStartingDebugger MOZ_GUARDED_BY(mMonitor); + + // this variable is used to paint/unload layers + // if not set, no action required + // true means, we will paint. false - unload layers + Maybe<bool> mPaintWhileInterruptingJS MOZ_GUARDED_BY(mMonitor); + TabId mPaintWhileInterruptingJSTab MOZ_GUARDED_BY(mMonitor); + bool mCancelContentJS MOZ_GUARDED_BY(mMonitor); + TabId mCancelContentJSTab MOZ_GUARDED_BY(mMonitor); + nsIRemoteTab::NavigationType mCancelContentJSNavigationType + MOZ_GUARDED_BY(mMonitor); + int32_t mCancelContentJSNavigationIndex MOZ_GUARDED_BY(mMonitor); + mozilla::Maybe<nsCString> mCancelContentJSNavigationURI + MOZ_GUARDED_BY(mMonitor); + int32_t mCancelContentJSEpoch MOZ_GUARDED_BY(mMonitor); + bool mShutdownDone MOZ_GUARDED_BY(mMonitor); + + JSContext* mContext; // const after constructor + + // This field is only accessed on the hang thread. + bool mIPCOpen; + + // Allows us to ensure we NotifyActivity only once, allowing + // either thread to do so. + Atomic<bool> mPaintWhileInterruptingJSActive; +}; + +StaticRefPtr<HangMonitorChild> HangMonitorChild::sInstance; + +/* Parent process objects */ + +class HangMonitorParent; + +class HangMonitoredProcess final : public nsIHangReport { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + HangMonitoredProcess(HangMonitorParent* aActor, ContentParent* aContentParent) + : mActor(aActor), mContentParent(aContentParent) {} + + NS_DECL_NSIHANGREPORT + + // Called when a content process shuts down. + void Clear() { + mContentParent = nullptr; + mActor = nullptr; + } + + /** + * Sets the information associated with this hang: this includes the tab ID, + * filename, duration, and an add-on ID if it was caused by an add-on. + * + * @param aDumpId The ID of a minidump taken when the hang occurred + */ + void SetSlowScriptData(const SlowScriptData& aSlowScriptData, + const nsAString& aDumpId) { + mSlowScriptData = aSlowScriptData; + mDumpId = aDumpId; + } + + void ClearHang() { + mSlowScriptData = SlowScriptData(); + mDumpId.Truncate(); + } + + private: + ~HangMonitoredProcess() = default; + + // Everything here is main thread-only. + HangMonitorParent* mActor; + ContentParent* mContentParent; + SlowScriptData mSlowScriptData; + nsAutoString mDumpId; +}; + +class HangMonitorParent : public PProcessHangMonitorParent { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_MAIN_THREAD( + HangMonitorParent, override) + + explicit HangMonitorParent(ProcessHangMonitor* aMonitor); + + void Bind(Endpoint<PProcessHangMonitorParent>&& aEndpoint); + + mozilla::ipc::IPCResult RecvHangEvidence( + const SlowScriptData& aSlowScriptData) override; + mozilla::ipc::IPCResult RecvClearHang() override; + + void ActorDestroy(ActorDestroyReason aWhy) override; + + void SetProcess(HangMonitoredProcess* aProcess) { mProcess = aProcess; } + + void Shutdown(); + + void PaintWhileInterruptingJS(dom::BrowserParent* aTab); + + void UnloadLayersWhileInterruptingJS(dom::BrowserParent* aTab); + void CancelContentJSExecutionIfRunning( + dom::BrowserParent* aBrowserParent, + nsIRemoteTab::NavigationType aNavigationType, + const dom::CancelContentJSOptions& aCancelContentJSOptions); + + void SetMainThreadQoSPriority(nsIThread::QoSPriority aQoSPriority); + + void TerminateScript(); + void BeginStartingDebugger(); + void EndStartingDebugger(); + + nsresult Dispatch(already_AddRefed<nsIRunnable> aRunnable) { + return mHangMonitor->Dispatch(std::move(aRunnable)); + } + bool IsOnThread() { return mHangMonitor->IsOnThread(); } + + private: + ~HangMonitorParent() override = default; + + void SendHangNotification(const SlowScriptData& aSlowScriptData, + const nsString& aBrowserDumpId); + + void ClearHangNotification(); + + void PaintOrUnloadLayersWhileInterruptingJSOnThread(bool aPaint, + TabId aTabId); + void CancelContentJSExecutionIfRunningOnThread( + TabId aTabId, nsIRemoteTab::NavigationType aNavigationType, + int32_t aNavigationIndex, nsIURI* aNavigationURI, int32_t aEpoch); + +#ifdef XP_MACOSX + void SetMainThreadQoSPriorityOnThread(nsIThread::QoSPriority aQoSPriority); +#endif + + void ShutdownOnThread(); + + const RefPtr<ProcessHangMonitor> mHangMonitor; + + // This field is only accessed on the hang thread. + bool mIPCOpen; + + Monitor mMonitor; + + // MainThread only + RefPtr<HangMonitoredProcess> mProcess; + + // Must be accessed with mMonitor held. + bool mShutdownDone MOZ_GUARDED_BY(mMonitor); + mozilla::ipc::TaskFactory<HangMonitorParent> mMainThreadTaskFactory + MOZ_GUARDED_BY(mMonitor); +}; + +} // namespace + +/* HangMonitorChild implementation */ + +HangMonitorChild::HangMonitorChild(ProcessHangMonitor* aMonitor) + : mHangMonitor(aMonitor), +#ifdef XP_MACOSX + mMainPThread(pthread_self()), +#endif + mMonitor("HangMonitorChild lock"), + mSentReport(false), + mTerminateScript(false), + mStartDebugger(false), + mFinishedStartingDebugger(false), + mCancelContentJS(false), + mCancelContentJSNavigationType(nsIRemoteTab::NAVIGATE_BACK), + mCancelContentJSNavigationIndex(0), + mCancelContentJSEpoch(0), + mShutdownDone(false), + mIPCOpen(true), + mPaintWhileInterruptingJSActive(false) { + ReleaseAssertIsOnMainThread(); + MOZ_ASSERT(!sInstance); + + mContext = danger::GetJSContext(); +} + +HangMonitorChild::~HangMonitorChild() { + ReleaseAssertIsOnMainThread(); + MOZ_ASSERT(sInstance != this); +} + +void HangMonitorChild::CreateAndBind( + ProcessHangMonitor* aMonitor, + Endpoint<PProcessHangMonitorChild>&& aEndpoint) { + ReleaseAssertIsOnMainThread(); + MOZ_ASSERT(!sInstance); + + sInstance = new HangMonitorChild(aMonitor); + + BackgroundHangMonitor::RegisterAnnotator(*sInstance); + + aMonitor->Dispatch(NewRunnableMethod<Endpoint<PProcessHangMonitorChild>&&>( + "HangMonitorChild::Bind", sInstance.get(), &HangMonitorChild::Bind, + std::move(aEndpoint))); +} + +bool HangMonitorChild::InterruptCallback() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + if (StaticPrefs::dom_abort_script_on_child_shutdown() && + mozilla::ipc::ProcessChild::ExpectingShutdown()) { + // We preserve chrome JS from cancel, but not extension content JS. + if (!nsContentUtils::IsCallerChrome()) { + NS_WARNING( + "HangMonitorChild::InterruptCallback: ExpectingShutdown, " + "canceling content JS execution.\n"); + return false; + } + return true; + } + + // Don't start painting if we're not in a good place to run script. We run + // chrome script during layout and such, and it wouldn't be good to interrupt + // painting code from there. + if (!nsContentUtils::IsSafeToRunScript()) { + return true; + } + + Maybe<bool> paintWhileInterruptingJS; + TabId paintWhileInterruptingJSTab; + + { + MonitorAutoLock lock(mMonitor); + paintWhileInterruptingJS = mPaintWhileInterruptingJS; + paintWhileInterruptingJSTab = mPaintWhileInterruptingJSTab; + + mPaintWhileInterruptingJS.reset(); + } + + if (paintWhileInterruptingJS.isSome()) { + RefPtr<BrowserChild> browserChild = + BrowserChild::FindBrowserChild(paintWhileInterruptingJSTab); + if (browserChild) { + js::AutoAssertNoContentJS nojs(mContext); + if (paintWhileInterruptingJS.value()) { + browserChild->PaintWhileInterruptingJS(); + } else { + browserChild->UnloadLayersWhileInterruptingJS(); + } + } + } + + // Only handle the interrupt for cancelling content JS if we have a + // non-privileged script (i.e. not part of Gecko or an add-on). + JS::Rooted<JSObject*> global(mContext, JS::CurrentGlobalOrNull(mContext)); + nsIPrincipal* principal = xpc::GetObjectPrincipal(global); + if (principal && (principal->IsSystemPrincipal() || + principal->GetIsAddonOrExpandedAddonPrincipal())) { + return true; + } + + nsCOMPtr<nsPIDOMWindowInner> win = xpc::WindowOrNull(global); + if (!win) { + return true; + } + + bool cancelContentJS; + TabId cancelContentJSTab; + nsIRemoteTab::NavigationType cancelContentJSNavigationType; + int32_t cancelContentJSNavigationIndex; + mozilla::Maybe<nsCString> cancelContentJSNavigationURI; + int32_t cancelContentJSEpoch; + + { + MonitorAutoLock lock(mMonitor); + cancelContentJS = mCancelContentJS; + cancelContentJSTab = mCancelContentJSTab; + cancelContentJSNavigationType = mCancelContentJSNavigationType; + cancelContentJSNavigationIndex = mCancelContentJSNavigationIndex; + cancelContentJSNavigationURI = std::move(mCancelContentJSNavigationURI); + cancelContentJSEpoch = mCancelContentJSEpoch; + + mCancelContentJS = false; + } + + if (cancelContentJS) { + js::AutoAssertNoContentJS nojs(mContext); + + RefPtr<BrowserChild> browserChild = + BrowserChild::FindBrowserChild(cancelContentJSTab); + RefPtr<BrowserChild> browserChildFromWin = BrowserChild::GetFrom(win); + if (!browserChild || !browserChildFromWin) { + return true; + } + + TabId tabIdFromWin = browserChildFromWin->GetTabId(); + if (tabIdFromWin != cancelContentJSTab) { + // The currently-executing content JS doesn't belong to the tab that + // requested cancellation of JS. Just return and let the JS continue. + return true; + } + + nsresult rv; + nsCOMPtr<nsIURI> uri; + + if (cancelContentJSNavigationURI) { + rv = NS_NewURI(getter_AddRefs(uri), cancelContentJSNavigationURI.value()); + if (NS_FAILED(rv)) { + return true; + } + } + + bool canCancel; + rv = browserChild->CanCancelContentJS(cancelContentJSNavigationType, + cancelContentJSNavigationIndex, uri, + cancelContentJSEpoch, &canCancel); + if (NS_SUCCEEDED(rv) && canCancel) { + // Don't add this page to the BF cache, since we're cancelling its JS. + if (Document* doc = win->GetExtantDoc()) { + doc->DisallowBFCaching(); + } + + return false; + } + } + + return true; +} + +void HangMonitorChild::AnnotateHang(BackgroundHangAnnotations& aAnnotations) { + if (mPaintWhileInterruptingJSActive) { + aAnnotations.AddAnnotation(u"PaintWhileInterruptingJS"_ns, true); + } +} + +void HangMonitorChild::Shutdown() { + ReleaseAssertIsOnMainThread(); + + BackgroundHangMonitor::UnregisterAnnotator(*this); + + { + MonitorAutoLock lock(mMonitor); + while (!mShutdownDone) { + mMonitor.Wait(); + } + } + + MOZ_ASSERT(sInstance == this); + sInstance = nullptr; +} + +void HangMonitorChild::ShutdownOnThread() { + MOZ_RELEASE_ASSERT(IsOnThread()); + + MonitorAutoLock lock(mMonitor); + mShutdownDone = true; + mMonitor.Notify(); +} + +void HangMonitorChild::ActorDestroy(ActorDestroyReason aWhy) { + MOZ_RELEASE_ASSERT(IsOnThread()); + + mIPCOpen = false; + + // We use a task here to ensure that IPDL is finished with this + // HangMonitorChild before it gets deleted on the main thread. + Dispatch(NewNonOwningRunnableMethod("HangMonitorChild::ShutdownOnThread", + this, + &HangMonitorChild::ShutdownOnThread)); +} + +mozilla::ipc::IPCResult HangMonitorChild::RecvTerminateScript() { + MOZ_RELEASE_ASSERT(IsOnThread()); + + MonitorAutoLock lock(mMonitor); + mTerminateScript = true; + return IPC_OK(); +} + +mozilla::ipc::IPCResult HangMonitorChild::RecvRequestContentJSInterrupt() { + MOZ_RELEASE_ASSERT(IsOnThread()); + + // In order to cancel JS execution on shutdown, we expect that + // ProcessChild::NotifiedImpendingShutdown has been called before. + if (mozilla::ipc::ProcessChild::ExpectingShutdown()) { + CrashReporter::AppendToCrashReportAnnotation( + CrashReporter::Annotation::IPCShutdownState, + "HangMonitorChild::RecvRequestContentJSInterrupt (expected)"_ns); + } else { + CrashReporter::AppendToCrashReportAnnotation( + CrashReporter::Annotation::IPCShutdownState, + "HangMonitorChild::RecvRequestContentJSInterrupt (unexpected)"_ns); + } + JS_RequestInterruptCallback(mContext); + return IPC_OK(); +} + +mozilla::ipc::IPCResult HangMonitorChild::RecvBeginStartingDebugger() { + MOZ_RELEASE_ASSERT(IsOnThread()); + + MonitorAutoLock lock(mMonitor); + mStartDebugger = true; + return IPC_OK(); +} + +mozilla::ipc::IPCResult HangMonitorChild::RecvEndStartingDebugger() { + MOZ_RELEASE_ASSERT(IsOnThread()); + + MonitorAutoLock lock(mMonitor); + mFinishedStartingDebugger = true; + return IPC_OK(); +} + +mozilla::ipc::IPCResult HangMonitorChild::RecvPaintWhileInterruptingJS( + const TabId& aTabId) { + MOZ_RELEASE_ASSERT(IsOnThread()); + + { + MonitorAutoLock lock(mMonitor); + MaybeStartPaintWhileInterruptingJS(); + mPaintWhileInterruptingJS = Some(true); + mPaintWhileInterruptingJSTab = aTabId; + } + + JS_RequestInterruptCallback(mContext); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult HangMonitorChild::RecvUnloadLayersWhileInterruptingJS( + const TabId& aTabId) { + MOZ_RELEASE_ASSERT(IsOnThread()); + + { + MonitorAutoLock lock(mMonitor); + MaybeStartPaintWhileInterruptingJS(); + mPaintWhileInterruptingJS = Some(false); + mPaintWhileInterruptingJSTab = aTabId; + } + + JS_RequestInterruptCallback(mContext); + + return IPC_OK(); +} + +void HangMonitorChild::MaybeStartPaintWhileInterruptingJS() { + mPaintWhileInterruptingJSActive = true; +} + +void HangMonitorChild::ClearPaintWhileInterruptingJS() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + MOZ_RELEASE_ASSERT(XRE_IsContentProcess()); + mPaintWhileInterruptingJSActive = false; +} + +mozilla::ipc::IPCResult HangMonitorChild::RecvCancelContentJSExecutionIfRunning( + const TabId& aTabId, const nsIRemoteTab::NavigationType& aNavigationType, + const int32_t& aNavigationIndex, + const mozilla::Maybe<nsCString>& aNavigationURI, const int32_t& aEpoch) { + MOZ_RELEASE_ASSERT(IsOnThread()); + + { + MonitorAutoLock lock(mMonitor); + mCancelContentJS = true; + mCancelContentJSTab = aTabId; + mCancelContentJSNavigationType = aNavigationType; + mCancelContentJSNavigationIndex = aNavigationIndex; + mCancelContentJSNavigationURI = aNavigationURI; + mCancelContentJSEpoch = aEpoch; + } + + JS_RequestInterruptCallback(mContext); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult HangMonitorChild::RecvSetMainThreadQoSPriority( + const nsIThread::QoSPriority& aQoSPriority) { + MOZ_RELEASE_ASSERT(IsOnThread()); + +#ifdef XP_MACOSX + // If the new priority is the background (low) priority, we can tell the OS to + // put the main thread on low-power cores. Alternately, if we are changing + // from the background to a higher priority, we change the main thread back to + // the |user-interactive| state, defined in MacOS's QoS documentation as + // reserved for main threads. + qos_class_t qosClass = aQoSPriority == nsIThread::QOS_PRIORITY_LOW + ? QOS_CLASS_BACKGROUND + : QOS_CLASS_USER_INTERACTIVE; + + // We can't directly set the main thread's QoS class from off-main-thread. + // However, we can start a QoS class override to raise the QoS, then dispatch + // a runnable to set the QoS class and clear the override once complete. + pthread_override_t qosOverride = + pthread_override_qos_class_start_np(mMainPThread, qosClass, 0); + if (NS_FAILED(NS_DispatchToMainThread(NS_NewRunnableFunction( + "HangMonitorChild::RecvSetMainThreadQoSPriority", + [qosClass, qosOverride] { + pthread_set_qos_class_self_np(qosClass, 0); + if (qosOverride) { + pthread_override_qos_class_end_np(qosOverride); + } + })))) { + // If we fail to dispatch, go ahead and end the override anyway. + pthread_override_qos_class_end_np(qosOverride); + } +#endif + + return IPC_OK(); +} + +void HangMonitorChild::Bind(Endpoint<PProcessHangMonitorChild>&& aEndpoint) { + MOZ_RELEASE_ASSERT(IsOnThread()); + + DebugOnly<bool> ok = aEndpoint.Bind(this); + MOZ_ASSERT(ok); +} + +void HangMonitorChild::NotifySlowScriptAsync(TabId aTabId, + const nsCString& aFileName, + const nsString& aAddonId, + const double aDuration) { + if (mIPCOpen) { + Unused << SendHangEvidence( + SlowScriptData(aTabId, aFileName, aAddonId, aDuration)); + } +} + +HangMonitorChild::SlowScriptAction HangMonitorChild::NotifySlowScript( + nsIBrowserChild* aBrowserChild, const char* aFileName, + const nsString& aAddonId, const double aDuration) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + mSentReport = true; + + { + MonitorAutoLock lock(mMonitor); + + if (mTerminateScript) { + mTerminateScript = false; + return SlowScriptAction::Terminate; + } + + if (mStartDebugger) { + mStartDebugger = false; + return SlowScriptAction::StartDebugger; + } + } + + TabId id; + if (aBrowserChild) { + RefPtr<BrowserChild> browserChild = + static_cast<BrowserChild*>(aBrowserChild); + id = browserChild->GetTabId(); + } + nsAutoCString filename(aFileName); + + Dispatch(NewNonOwningRunnableMethod<TabId, nsCString, nsString, double>( + "HangMonitorChild::NotifySlowScriptAsync", this, + &HangMonitorChild::NotifySlowScriptAsync, id, filename, aAddonId, + aDuration)); + return SlowScriptAction::Continue; +} + +bool HangMonitorChild::IsDebuggerStartupComplete() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + MonitorAutoLock lock(mMonitor); + + if (mFinishedStartingDebugger) { + mFinishedStartingDebugger = false; + return true; + } + + return false; +} + +void HangMonitorChild::ClearHang() { + MOZ_ASSERT(NS_IsMainThread()); + + if (mSentReport) { + // bounce to background thread + Dispatch(NewNonOwningRunnableMethod("HangMonitorChild::ClearHangAsync", + this, + &HangMonitorChild::ClearHangAsync)); + + MonitorAutoLock lock(mMonitor); + mSentReport = false; + mTerminateScript = false; + mStartDebugger = false; + mFinishedStartingDebugger = false; + } +} + +void HangMonitorChild::ClearHangAsync() { + MOZ_RELEASE_ASSERT(IsOnThread()); + + // bounce back to parent on background thread + if (mIPCOpen) { + Unused << SendClearHang(); + } +} + +/* HangMonitorParent implementation */ + +HangMonitorParent::HangMonitorParent(ProcessHangMonitor* aMonitor) + : mHangMonitor(aMonitor), + mIPCOpen(true), + mMonitor("HangMonitorParent lock"), + mShutdownDone(false), + mMainThreadTaskFactory(this) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); +} + +void HangMonitorParent::Shutdown() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + MonitorAutoLock lock(mMonitor); + + if (mProcess) { + mProcess->Clear(); + mProcess = nullptr; + } + + nsresult rv = Dispatch( + NewNonOwningRunnableMethod("HangMonitorParent::ShutdownOnThread", this, + &HangMonitorParent::ShutdownOnThread)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + while (!mShutdownDone) { + mMonitor.Wait(); + } +} + +void HangMonitorParent::ShutdownOnThread() { + MOZ_RELEASE_ASSERT(IsOnThread()); + + // mIPCOpen is only written from this thread, so need need to take the lock + // here. We'd be shooting ourselves in the foot, because ActorDestroy takes + // it. + if (mIPCOpen) { + Close(); + } + + MonitorAutoLock lock(mMonitor); + mShutdownDone = true; + mMonitor.Notify(); +} + +void HangMonitorParent::PaintWhileInterruptingJS(dom::BrowserParent* aTab) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + if (StaticPrefs::browser_tabs_remote_force_paint()) { + TabId id = aTab->GetTabId(); + Dispatch(NewNonOwningRunnableMethod<bool, TabId>( + "HangMonitorParent::PaintOrUnloadLayersWhileInterruptingJSOnThread ", + this, + &HangMonitorParent::PaintOrUnloadLayersWhileInterruptingJSOnThread, + true, id)); + } +} + +void HangMonitorParent::UnloadLayersWhileInterruptingJS( + dom::BrowserParent* aTab) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + TabId id = aTab->GetTabId(); + Dispatch(NewNonOwningRunnableMethod<bool, TabId>( + "HangMonitorParent::PaintOrUnloadLayersWhileInterruptingJSOnThread ", + this, &HangMonitorParent::PaintOrUnloadLayersWhileInterruptingJSOnThread, + false, id)); +} + +void HangMonitorParent::PaintOrUnloadLayersWhileInterruptingJSOnThread( + const bool aPaint, TabId aTabId) { + MOZ_RELEASE_ASSERT(IsOnThread()); + + if (mIPCOpen) { + if (aPaint) { + Unused << SendPaintWhileInterruptingJS(aTabId); + } else { + Unused << SendUnloadLayersWhileInterruptingJS(aTabId); + } + } +} + +void HangMonitorParent::CancelContentJSExecutionIfRunning( + dom::BrowserParent* aBrowserParent, + nsIRemoteTab::NavigationType aNavigationType, + const dom::CancelContentJSOptions& aCancelContentJSOptions) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + if (!aBrowserParent->CanCancelContentJS(aNavigationType, + aCancelContentJSOptions.mIndex, + aCancelContentJSOptions.mUri)) { + return; + } + + TabId id = aBrowserParent->GetTabId(); + Dispatch(NewNonOwningRunnableMethod<TabId, nsIRemoteTab::NavigationType, + int32_t, nsIURI*, int32_t>( + "HangMonitorParent::CancelContentJSExecutionIfRunningOnThread", this, + &HangMonitorParent::CancelContentJSExecutionIfRunningOnThread, id, + aNavigationType, aCancelContentJSOptions.mIndex, + aCancelContentJSOptions.mUri, aCancelContentJSOptions.mEpoch)); +} + +void HangMonitorParent::CancelContentJSExecutionIfRunningOnThread( + TabId aTabId, nsIRemoteTab::NavigationType aNavigationType, + int32_t aNavigationIndex, nsIURI* aNavigationURI, int32_t aEpoch) { + MOZ_RELEASE_ASSERT(IsOnThread()); + + mozilla::Maybe<nsCString> spec; + if (aNavigationURI) { + nsAutoCString tmp; + nsresult rv = aNavigationURI->GetSpec(tmp); + if (NS_SUCCEEDED(rv)) { + spec.emplace(tmp); + } + } + + if (mIPCOpen) { + Unused << SendCancelContentJSExecutionIfRunning( + aTabId, aNavigationType, aNavigationIndex, spec, aEpoch); + } +} + +void HangMonitorParent::SetMainThreadQoSPriority( + nsIThread::QoSPriority aQoSPriority) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); +#ifdef XP_MACOSX // Should not be using outside of MacOS. + + Dispatch(NewNonOwningRunnableMethod<nsIThread::QoSPriority>( + "HangMonitorParent::SetMainThreadQoSPriorityOnThread", this, + &HangMonitorParent::SetMainThreadQoSPriorityOnThread, aQoSPriority)); +#endif +} + +#ifdef XP_MACOSX +void HangMonitorParent::SetMainThreadQoSPriorityOnThread( + nsIThread::QoSPriority aQoSPriority) { + MOZ_RELEASE_ASSERT(IsOnThread()); + if (mIPCOpen) { + Unused << SendSetMainThreadQoSPriority(aQoSPriority); + } +} +#endif + +void HangMonitorParent::ActorDestroy(ActorDestroyReason aWhy) { + MOZ_RELEASE_ASSERT(IsOnThread()); + mIPCOpen = false; +} + +void HangMonitorParent::Bind(Endpoint<PProcessHangMonitorParent>&& aEndpoint) { + MOZ_RELEASE_ASSERT(IsOnThread()); + + DebugOnly<bool> ok = aEndpoint.Bind(this); + MOZ_ASSERT(ok); +} + +void HangMonitorParent::SendHangNotification( + const SlowScriptData& aSlowScriptData, const nsString& aBrowserDumpId) { + // chrome process, main thread + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + nsString dumpId; + + // We already have a full minidump; go ahead and use it. + dumpId = aBrowserDumpId; + + mProcess->SetSlowScriptData(aSlowScriptData, dumpId); + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + observerService->NotifyObservers(mProcess, "process-hang-report", nullptr); +} + +void HangMonitorParent::ClearHangNotification() { + // chrome process, main thread + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + observerService->NotifyObservers(mProcess, "clear-hang-report", nullptr); + + mProcess->ClearHang(); +} + +mozilla::ipc::IPCResult HangMonitorParent::RecvHangEvidence( + const SlowScriptData& aSlowScriptData) { + // chrome process, background thread + MOZ_RELEASE_ASSERT(IsOnThread()); + + if (!StaticPrefs::dom_ipc_reportProcessHangs()) { + return IPC_OK(); + } + +#ifdef XP_WIN + // Don't report hangs if we're debugging the process. You can comment this + // line out for testing purposes. + if (IsDebuggerPresent()) { + return IPC_OK(); + } +#endif + + // Before we wake up the browser main thread we want to take a + // browser minidump. + nsAutoString crashId; + + mHangMonitor->InitiateCPOWTimeout(); + + MonitorAutoLock lock(mMonitor); + + NS_DispatchToMainThread(mMainThreadTaskFactory.NewRunnableMethod( + &HangMonitorParent::SendHangNotification, aSlowScriptData, crashId)); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult HangMonitorParent::RecvClearHang() { + // chrome process, background thread + MOZ_RELEASE_ASSERT(IsOnThread()); + + if (!StaticPrefs::dom_ipc_reportProcessHangs()) { + return IPC_OK(); + } + + mHangMonitor->InitiateCPOWTimeout(); + + MonitorAutoLock lock(mMonitor); + + NS_DispatchToMainThread(mMainThreadTaskFactory.NewRunnableMethod( + &HangMonitorParent::ClearHangNotification)); + + return IPC_OK(); +} + +void HangMonitorParent::TerminateScript() { + MOZ_RELEASE_ASSERT(IsOnThread()); + + if (mIPCOpen) { + Unused << SendTerminateScript(); + } +} + +void HangMonitorParent::BeginStartingDebugger() { + MOZ_RELEASE_ASSERT(IsOnThread()); + + if (mIPCOpen) { + Unused << SendBeginStartingDebugger(); + } +} + +void HangMonitorParent::EndStartingDebugger() { + MOZ_RELEASE_ASSERT(IsOnThread()); + + if (mIPCOpen) { + Unused << SendEndStartingDebugger(); + } +} + +/* HangMonitoredProcess implementation */ + +NS_IMPL_ISUPPORTS(HangMonitoredProcess, nsIHangReport) + +NS_IMETHODIMP +HangMonitoredProcess::GetHangDuration(double* aHangDuration) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + *aHangDuration = mSlowScriptData.duration(); + return NS_OK; +} + +NS_IMETHODIMP +HangMonitoredProcess::GetScriptBrowser(Element** aBrowser) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + TabId tabId = mSlowScriptData.tabId(); + if (!mContentParent) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsTArray<PBrowserParent*> tabs; + mContentParent->ManagedPBrowserParent(tabs); + for (size_t i = 0; i < tabs.Length(); i++) { + BrowserParent* tp = BrowserParent::GetFrom(tabs[i]); + if (tp->GetTabId() == tabId) { + RefPtr<Element> node = tp->GetOwnerElement(); + node.forget(aBrowser); + return NS_OK; + } + } + + *aBrowser = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +HangMonitoredProcess::GetScriptFileName(nsACString& aFileName) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + aFileName = mSlowScriptData.filename(); + return NS_OK; +} + +NS_IMETHODIMP +HangMonitoredProcess::GetAddonId(nsAString& aAddonId) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + aAddonId = mSlowScriptData.addonId(); + return NS_OK; +} + +NS_IMETHODIMP +HangMonitoredProcess::TerminateScript() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + if (!mActor) { + return NS_ERROR_UNEXPECTED; + } + + ProcessHangMonitor::Get()->Dispatch( + NewNonOwningRunnableMethod("HangMonitorParent::TerminateScript", mActor, + &HangMonitorParent::TerminateScript)); + return NS_OK; +} + +NS_IMETHODIMP +HangMonitoredProcess::BeginStartingDebugger() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + if (!mActor) { + return NS_ERROR_UNEXPECTED; + } + + ProcessHangMonitor::Get()->Dispatch(NewNonOwningRunnableMethod( + "HangMonitorParent::BeginStartingDebugger", mActor, + &HangMonitorParent::BeginStartingDebugger)); + return NS_OK; +} + +NS_IMETHODIMP +HangMonitoredProcess::EndStartingDebugger() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + if (!mActor) { + return NS_ERROR_UNEXPECTED; + } + + ProcessHangMonitor::Get()->Dispatch(NewNonOwningRunnableMethod( + "HangMonitorParent::EndStartingDebugger", mActor, + &HangMonitorParent::EndStartingDebugger)); + return NS_OK; +} + +NS_IMETHODIMP +HangMonitoredProcess::IsReportForBrowserOrChildren(nsFrameLoader* aFrameLoader, + bool* aResult) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + MOZ_RELEASE_ASSERT(XRE_IsParentProcess()); + + if (!mActor) { + *aResult = false; + return NS_OK; + } + + NS_ENSURE_STATE(aFrameLoader); + + AutoTArray<RefPtr<BrowsingContext>, 10> bcs; + bcs.AppendElement(aFrameLoader->GetExtantBrowsingContext()); + while (!bcs.IsEmpty()) { + RefPtr<BrowsingContext> bc = bcs[bcs.Length() - 1]; + bcs.RemoveLastElement(); + if (!bc) { + continue; + } + if (mContentParent == bc->Canonical()->GetContentParent()) { + *aResult = true; + return NS_OK; + } + bc->GetChildren(bcs); + } + + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP +HangMonitoredProcess::UserCanceled() { return NS_OK; } + +NS_IMETHODIMP +HangMonitoredProcess::GetChildID(uint64_t* aChildID) { + if (!mContentParent) { + return NS_ERROR_NOT_AVAILABLE; + } + *aChildID = mContentParent->ChildID(); + return NS_OK; +} + +static bool InterruptCallback(JSContext* cx) { + AssertIsOnMainThread(); + if (HangMonitorChild* child = HangMonitorChild::Get()) { + return child->InterruptCallback(); + } + + return true; +} + +ProcessHangMonitor* ProcessHangMonitor::sInstance; + +ProcessHangMonitor::ProcessHangMonitor() : mCPOWTimeout(false) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + if (XRE_IsContentProcess()) { + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + obs->AddObserver(this, "xpcom-shutdown", false); + } + + if (NS_FAILED(NS_NewNamedThread("ProcessHangMon", getter_AddRefs(mThread)))) { + mThread = nullptr; + } +#ifdef XP_MACOSX + // On MacOS, ensure the priority is high enough to handle dispatches at + // high cpu load. USER_INITIATED class threads are prioritized just below + // the main thread. + mThread->Dispatch(NS_NewRunnableFunction( + "ProcessHangMonitor::SetPriority", + [] { pthread_set_qos_class_self_np(QOS_CLASS_USER_INITIATED, 0); })); +#endif +} + +ProcessHangMonitor::~ProcessHangMonitor() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + MOZ_ASSERT(sInstance == this); + sInstance = nullptr; + + mThread->Shutdown(); + mThread = nullptr; +} + +ProcessHangMonitor* ProcessHangMonitor::GetOrCreate() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + if (!sInstance) { + sInstance = new ProcessHangMonitor(); + } + return sInstance; +} + +NS_IMPL_ISUPPORTS(ProcessHangMonitor, nsIObserver) + +NS_IMETHODIMP +ProcessHangMonitor::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + ReleaseAssertIsOnMainThread(); + if (!strcmp(aTopic, "xpcom-shutdown")) { + if (RefPtr<HangMonitorChild> child = HangMonitorChild::Get()) { + child->Shutdown(); + } + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + obs->RemoveObserver(this, "xpcom-shutdown"); + } + return NS_OK; +} + +ProcessHangMonitor::SlowScriptAction ProcessHangMonitor::NotifySlowScript( + nsIBrowserChild* aBrowserChild, const char* aFileName, + const nsString& aAddonId, const double aDuration) { + ReleaseAssertIsOnMainThread(); + return HangMonitorChild::Get()->NotifySlowScript(aBrowserChild, aFileName, + aAddonId, aDuration); +} + +bool ProcessHangMonitor::IsDebuggerStartupComplete() { + ReleaseAssertIsOnMainThread(); + return HangMonitorChild::Get()->IsDebuggerStartupComplete(); +} + +bool ProcessHangMonitor::ShouldTimeOutCPOWs() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + if (mCPOWTimeout) { + mCPOWTimeout = false; + return true; + } + return false; +} + +void ProcessHangMonitor::InitiateCPOWTimeout() { + MOZ_RELEASE_ASSERT(IsOnThread()); + mCPOWTimeout = true; +} + +static already_AddRefed<PProcessHangMonitorParent> CreateHangMonitorParent( + ContentParent* aContentParent, + Endpoint<PProcessHangMonitorParent>&& aEndpoint) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + ProcessHangMonitor* monitor = ProcessHangMonitor::GetOrCreate(); + RefPtr<HangMonitorParent> parent = new HangMonitorParent(monitor); + + auto* process = new HangMonitoredProcess(parent, aContentParent); + parent->SetProcess(process); + + monitor->Dispatch( + NewNonOwningRunnableMethod<Endpoint<PProcessHangMonitorParent>&&>( + "HangMonitorParent::Bind", parent, &HangMonitorParent::Bind, + std::move(aEndpoint))); + + return parent.forget(); +} + +void mozilla::CreateHangMonitorChild( + Endpoint<PProcessHangMonitorChild>&& aEndpoint) { + ReleaseAssertIsOnMainThread(); + + JSContext* cx = danger::GetJSContext(); + JS_AddInterruptCallback(cx, InterruptCallback); + + ProcessHangMonitor* monitor = ProcessHangMonitor::GetOrCreate(); + HangMonitorChild::CreateAndBind(monitor, std::move(aEndpoint)); +} + +nsresult ProcessHangMonitor::Dispatch(already_AddRefed<nsIRunnable> aRunnable) { + return mThread->Dispatch(std::move(aRunnable), + nsIEventTarget::NS_DISPATCH_NORMAL); +} + +bool ProcessHangMonitor::IsOnThread() { + bool on; + return NS_SUCCEEDED(mThread->IsOnCurrentThread(&on)) && on; +} + +/* static */ +already_AddRefed<PProcessHangMonitorParent> ProcessHangMonitor::AddProcess( + ContentParent* aContentParent) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + if (!StaticPrefs::dom_ipc_processHangMonitor_AtStartup()) { + return nullptr; + } + + Endpoint<PProcessHangMonitorParent> parent; + Endpoint<PProcessHangMonitorChild> child; + nsresult rv; + rv = PProcessHangMonitor::CreateEndpoints(&parent, &child); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, "PProcessHangMonitor::CreateEndpoints failed"); + return nullptr; + } + + if (!aContentParent->SendInitProcessHangMonitor(std::move(child))) { + MOZ_ASSERT(false); + return nullptr; + } + + return CreateHangMonitorParent(aContentParent, std::move(parent)); +} + +/* static */ +void ProcessHangMonitor::RemoveProcess(PProcessHangMonitorParent* aParent) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + auto parent = static_cast<HangMonitorParent*>(aParent); + parent->Shutdown(); +} + +/* static */ +void ProcessHangMonitor::ClearHang() { + AssertIsOnMainThread(); + if (HangMonitorChild* child = HangMonitorChild::Get()) { + child->ClearHang(); + } +} + +/* static */ +void ProcessHangMonitor::PaintWhileInterruptingJS( + PProcessHangMonitorParent* aParent, dom::BrowserParent* aTab) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + auto* parent = static_cast<HangMonitorParent*>(aParent); + parent->PaintWhileInterruptingJS(aTab); +} + +/* static */ +void ProcessHangMonitor::UnloadLayersWhileInterruptingJS( + PProcessHangMonitorParent* aParent, dom::BrowserParent* aTab) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + auto* parent = static_cast<HangMonitorParent*>(aParent); + parent->UnloadLayersWhileInterruptingJS(aTab); +} + +/* static */ +void ProcessHangMonitor::ClearPaintWhileInterruptingJS() { + ReleaseAssertIsOnMainThread(); + MOZ_RELEASE_ASSERT(XRE_IsContentProcess()); + + if (HangMonitorChild* child = HangMonitorChild::Get()) { + child->ClearPaintWhileInterruptingJS(); + } +} + +/* static */ +void ProcessHangMonitor::MaybeStartPaintWhileInterruptingJS() { + ReleaseAssertIsOnMainThread(); + MOZ_RELEASE_ASSERT(XRE_IsContentProcess()); + + if (HangMonitorChild* child = HangMonitorChild::Get()) { + child->MaybeStartPaintWhileInterruptingJS(); + } +} + +/* static */ +void ProcessHangMonitor::CancelContentJSExecutionIfRunning( + PProcessHangMonitorParent* aParent, dom::BrowserParent* aBrowserParent, + nsIRemoteTab::NavigationType aNavigationType, + const dom::CancelContentJSOptions& aCancelContentJSOptions) { + ReleaseAssertIsOnMainThread(); + auto* parent = static_cast<HangMonitorParent*>(aParent); + parent->CancelContentJSExecutionIfRunning(aBrowserParent, aNavigationType, + aCancelContentJSOptions); +} + +/* static */ +void ProcessHangMonitor::SetMainThreadQoSPriority( + PProcessHangMonitorParent* aParent, nsIThread::QoSPriority aQoSPriority) { + ReleaseAssertIsOnMainThread(); + auto* parent = static_cast<HangMonitorParent*>(aParent); + parent->SetMainThreadQoSPriority(aQoSPriority); +} diff --git a/dom/ipc/ProcessHangMonitor.h b/dom/ipc/ProcessHangMonitor.h new file mode 100644 index 0000000000..9e4ce5758d --- /dev/null +++ b/dom/ipc/ProcessHangMonitor.h @@ -0,0 +1,97 @@ +/* -*- 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_ProcessHangMonitor_h +#define mozilla_ProcessHangMonitor_h + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Atomics.h" +#include "nsCOMPtr.h" +#include "nsIObserver.h" +#include "nsIRemoteTab.h" +#include "nsIThread.h" +#include "nsStringFwd.h" + +class nsIRunnable; +class nsIBrowserChild; +class nsIThread; + +namespace mozilla { + +namespace dom { +class ContentParent; +class BrowserParent; +struct CancelContentJSOptions; +} // namespace dom + +class PProcessHangMonitorParent; + +class ProcessHangMonitor final : public nsIObserver { + private: + ProcessHangMonitor(); + virtual ~ProcessHangMonitor(); + + public: + static ProcessHangMonitor* Get() { return sInstance; } + static ProcessHangMonitor* GetOrCreate(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + static already_AddRefed<PProcessHangMonitorParent> AddProcess( + dom::ContentParent* aContentParent); + static void RemoveProcess(PProcessHangMonitorParent* aParent); + + static void ClearHang(); + + static void PaintWhileInterruptingJS(PProcessHangMonitorParent* aParent, + dom::BrowserParent* aTab); + + static void UnloadLayersWhileInterruptingJS( + PProcessHangMonitorParent* aParent, dom::BrowserParent* aTab); + + static void ClearPaintWhileInterruptingJS(); + static void MaybeStartPaintWhileInterruptingJS(); + + static void CancelContentJSExecutionIfRunning( + PProcessHangMonitorParent* aParent, dom::BrowserParent* aTab, + nsIRemoteTab::NavigationType aNavigationType, + const dom::CancelContentJSOptions& aCancelContentJSOptions); + + static void SetMainThreadQoSPriority(PProcessHangMonitorParent* aParent, + nsIThread::QoSPriority aQoSPriority); + + enum SlowScriptAction { + Continue, + Terminate, + StartDebugger, + }; + SlowScriptAction NotifySlowScript(nsIBrowserChild* aBrowserChild, + const char* aFileName, + const nsString& aAddonId, + const double aDuration); + + void NotifyPluginHang(uint32_t aPluginId); + + bool IsDebuggerStartupComplete(); + + void InitiateCPOWTimeout(); + bool ShouldTimeOutCPOWs(); + + nsresult Dispatch(already_AddRefed<nsIRunnable> aRunnable); + bool IsOnThread(); + + private: + static ProcessHangMonitor* sInstance; + + Atomic<bool> mCPOWTimeout; + + nsCOMPtr<nsIThread> mThread; +}; + +} // namespace mozilla + +#endif // mozilla_ProcessHangMonitor_h diff --git a/dom/ipc/ProcessHangMonitorIPC.h b/dom/ipc/ProcessHangMonitorIPC.h new file mode 100644 index 0000000000..c40ffa7d3b --- /dev/null +++ b/dom/ipc/ProcessHangMonitorIPC.h @@ -0,0 +1,28 @@ +/* -*- 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_ProcessHangMonitorIPC_h +#define mozilla_ProcessHangMonitorIPC_h + +#include "base/task.h" +#include "base/thread.h" + +#include "mozilla/PProcessHangMonitor.h" +#include "mozilla/PProcessHangMonitorParent.h" +#include "mozilla/PProcessHangMonitorChild.h" + +namespace mozilla { + +namespace dom { +class ContentParent; +} // namespace dom + +void CreateHangMonitorChild( + ipc::Endpoint<PProcessHangMonitorChild>&& aEndpoint); + +} // namespace mozilla + +#endif // mozilla_ProcessHangMonitorIPC_h diff --git a/dom/ipc/ProcessIsolation.cpp b/dom/ipc/ProcessIsolation.cpp new file mode 100644 index 0000000000..f0c6dd8863 --- /dev/null +++ b/dom/ipc/ProcessIsolation.cpp @@ -0,0 +1,1139 @@ +/* -*- 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/dom/ProcessIsolation.h" + +#include "mozilla/Assertions.h" +#include "mozilla/dom/BrowsingContextGroup.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/RemoteType.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/extensions/WebExtensionPolicy.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/ContentPrincipal.h" +#include "mozilla/ExtensionPolicyService.h" +#include "mozilla/Logging.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/PermissionManager.h" +#include "mozilla/Preferences.h" +#include "mozilla/RefPtr.h" +#include "mozilla/StaticPrefs_browser.h" +#include "mozilla/StaticPrefs_fission.h" +#include "mozilla/StaticPtr.h" +#include "nsAboutProtocolUtils.h" +#include "nsDocShell.h" +#include "nsError.h" +#include "nsIChromeRegistry.h" +#include "nsIHttpChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIProtocolHandler.h" +#include "nsIXULRuntime.h" +#include "nsNetUtil.h" +#include "nsServiceManagerUtils.h" +#include "nsSHistory.h" +#include "nsURLHelper.h" + +namespace mozilla::dom { + +mozilla::LazyLogModule gProcessIsolationLog{"ProcessIsolation"}; + +namespace { + +// Strategy used to determine whether or not a particular site should load into +// a webIsolated content process. The particular strategy chosen is controlled +// by the `fission.webContentIsolationStrategy` pref, which must hold one of the +// following values. +enum class WebContentIsolationStrategy : uint32_t { + // All web content is loaded into a shared `web` content process. This is + // similar to the non-Fission behaviour, however remote subframes may still + // be used for sites with special isolation behaviour, such as extension or + // mozillaweb content processes. + IsolateNothing = 0, + // Web content is always isolated into its own `webIsolated` content process + // based on site-origin, and will only load in a shared `web` content process + // if site-origin could not be determined. + IsolateEverything = 1, + // Only isolates web content loaded by sites which are considered "high + // value". A site is considered "high value" if it has been granted a + // `highValue*` permission by the permission manager, which is done in + // response to certain actions. + IsolateHighValue = 2, +}; + +/** + * Helper class for caching the result of splitting prefs which are represented + * as a comma-separated list of strings. + */ +struct CommaSeparatedPref { + public: + explicit constexpr CommaSeparatedPref(nsLiteralCString aPrefName) + : mPrefName(aPrefName) {} + + void OnChange() { + if (mValues) { + mValues->Clear(); + nsAutoCString prefValue; + if (NS_SUCCEEDED(Preferences::GetCString(mPrefName.get(), prefValue))) { + for (const auto& value : + nsCCharSeparatedTokenizer(prefValue, ',').ToRange()) { + mValues->EmplaceBack(value); + } + } + } + } + + const nsTArray<nsCString>& Get() { + if (!mValues) { + mValues = new nsTArray<nsCString>; + Preferences::RegisterCallbackAndCall( + [](const char*, void* aData) { + static_cast<CommaSeparatedPref*>(aData)->OnChange(); + }, + mPrefName, this); + RunOnShutdown([this] { + delete this->mValues; + this->mValues = nullptr; + }); + } + return *mValues; + } + + auto begin() { return Get().cbegin(); } + auto end() { return Get().cend(); } + + private: + nsLiteralCString mPrefName; + nsTArray<nsCString>* MOZ_OWNING_REF mValues = nullptr; +}; + +CommaSeparatedPref sSeparatedMozillaDomains{ + "browser.tabs.remote.separatedMozillaDomains"_ns}; + +/** + * Certain URIs have special isolation behaviour, and need to be loaded within + * specific process types. + */ +enum class IsolationBehavior { + // This URI loads web content and should be treated as a content load, being + // isolated based on the response principal if enabled. + WebContent, + // Forcibly load in a process with the "web" remote type. This will ignore the + // response principal completely. + // This is generally reserved for internal documents which are loaded in + // content, but not in the privilegedabout content process. + ForceWebRemoteType, + // Load this URI in the privileged about content process. + PrivilegedAbout, + // Load this URI in the extension process. + Extension, + // Load this URI in the file content process. + File, + // Load this URI in the priviliged mozilla content process. + PrivilegedMozilla, + // Load this URI explicitly in the parent process. + Parent, + // Load this URI wherever the browsing context is currently loaded. This is + // generally used for error pages. + Anywhere, + // May only be returned for subframes. Inherits the remote type of the parent + // document which is embedding this document. + Inherit, + // Special case for the `about:reader` URI which should be loaded in the same + // process which would be used for the "url" query parameter. + AboutReader, + // There was a fatal error, and the load should be aborted. + Error, +}; + +/** + * Returns a static string with the name of the given isolation behaviour. For + * use in logging code. + */ +static const char* IsolationBehaviorName(IsolationBehavior aBehavior) { + switch (aBehavior) { + case IsolationBehavior::WebContent: + return "WebContent"; + case IsolationBehavior::ForceWebRemoteType: + return "ForceWebRemoteType"; + case IsolationBehavior::PrivilegedAbout: + return "PrivilegedAbout"; + case IsolationBehavior::Extension: + return "Extension"; + case IsolationBehavior::File: + return "File"; + case IsolationBehavior::PrivilegedMozilla: + return "PrivilegedMozilla"; + case IsolationBehavior::Parent: + return "Parent"; + case IsolationBehavior::Anywhere: + return "Anywhere"; + case IsolationBehavior::Inherit: + return "Inherit"; + case IsolationBehavior::AboutReader: + return "AboutReader"; + case IsolationBehavior::Error: + return "Error"; + default: + return "Unknown"; + } +} + +/** + * Returns a static string with the name of the given worker kind. For use in + * logging code. + */ +static const char* WorkerKindName(WorkerKind aWorkerKind) { + switch (aWorkerKind) { + case WorkerKindDedicated: + return "Dedicated"; + case WorkerKindShared: + return "Shared"; + case WorkerKindService: + return "Service"; + default: + return "Unknown"; + } +} + +/** + * Check if a given URI has specialized process isolation behaviour, such as + * needing to be loaded within a specific type of content process. + * + * When handling a navigation, this method will be called twice: first with the + * channel's creation URI, and then it will be called with a result principal's + * URI. + */ +static IsolationBehavior IsolationBehaviorForURI(nsIURI* aURI, bool aIsSubframe, + bool aForChannelCreationURI) { + nsAutoCString scheme; + MOZ_ALWAYS_SUCCEEDS(aURI->GetScheme(scheme)); + + if (scheme == "chrome"_ns) { + // `chrome://` URIs are always loaded in the parent process, unless they + // have opted in to loading in a content process. This is currently only + // done in tests. + // + // FIXME: These flags should be removed from `chrome` URIs at some point. + nsCOMPtr<nsIXULChromeRegistry> chromeReg = + do_GetService("@mozilla.org/chrome/chrome-registry;1"); + bool mustLoadRemotely = false; + if (NS_SUCCEEDED(chromeReg->MustLoadURLRemotely(aURI, &mustLoadRemotely)) && + mustLoadRemotely) { + return IsolationBehavior::ForceWebRemoteType; + } + bool canLoadRemotely = false; + if (NS_SUCCEEDED(chromeReg->CanLoadURLRemotely(aURI, &canLoadRemotely)) && + canLoadRemotely) { + return IsolationBehavior::Anywhere; + } + return IsolationBehavior::Parent; + } + + if (scheme == "about"_ns) { + nsAutoCString path; + MOZ_ALWAYS_SUCCEEDS(NS_GetAboutModuleName(aURI, path)); + + // The `about:blank` and `about:srcdoc` pages are loaded by normal web + // content, and should be allocated processes based on their simple content + // principals. + if (path == "blank"_ns || path == "srcdoc"_ns) { + MOZ_ASSERT(NS_IsContentAccessibleAboutURI(aURI)); + return IsolationBehavior::WebContent; + } + + MOZ_ASSERT(!NS_IsContentAccessibleAboutURI(aURI)); + // If we're loading an `about:reader` URI, perform isolation based on the + // principal of the URI being loaded. + if (path == "reader"_ns && aForChannelCreationURI) { + return IsolationBehavior::AboutReader; + } + + // Otherwise, we're going to be loading an about: page. Consult the module. + nsCOMPtr<nsIAboutModule> aboutModule; + if (NS_FAILED(NS_GetAboutModule(aURI, getter_AddRefs(aboutModule))) || + !aboutModule) { + // If we don't know of an about: module for this load, it's going to end + // up being a network error. Allow the load to finish as normal. + return IsolationBehavior::WebContent; + } + + // NOTE: about modules can be implemented in JS, so this may run script, and + // therefore can spuriously fail. + uint32_t flags = 0; + if (NS_FAILED(aboutModule->GetURIFlags(aURI, &flags))) { + NS_WARNING( + "nsIAboutModule::GetURIFlags unexpectedly failed. Abort the load"); + return IsolationBehavior::Error; + } + + if (flags & nsIAboutModule::URI_MUST_LOAD_IN_EXTENSION_PROCESS) { + return IsolationBehavior::Extension; + } + + if (flags & nsIAboutModule::URI_MUST_LOAD_IN_CHILD) { + if (flags & nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS) { + return IsolationBehavior::PrivilegedAbout; + } + return IsolationBehavior::ForceWebRemoteType; + } + + if (flags & nsIAboutModule::URI_CAN_LOAD_IN_CHILD) { + return IsolationBehavior::Anywhere; + } + + return IsolationBehavior::Parent; + } + + // If the test-only `dataUriInDefaultWebProcess` pref is enabled, dump all + // `data:` URIs in a "web" content process, rather than loading them in + // content processes based on their precursor origins. + if (StaticPrefs::browser_tabs_remote_dataUriInDefaultWebProcess() && + scheme == "data"_ns) { + return IsolationBehavior::ForceWebRemoteType; + } + + // Make sure to unwrap nested URIs before we early return for channel creation + // URI. The checks past this point are intended to operate on the principal, + // which has it's origin constructed from the innermost URI. + nsCOMPtr<nsIURI> inner; + if (nsCOMPtr<nsINestedURI> nested = do_QueryInterface(aURI); + nested && NS_SUCCEEDED(nested->GetInnerURI(getter_AddRefs(inner)))) { + return IsolationBehaviorForURI(inner, aIsSubframe, aForChannelCreationURI); + } + + // If we're doing the initial check based on the channel creation URI, stop + // here as we want to only perform the following checks on the true channel + // result principal. + if (aForChannelCreationURI) { + return IsolationBehavior::WebContent; + } + + // Protocols used by Thunderbird to display email messages. + if (scheme == "imap"_ns || scheme == "mailbox"_ns || scheme == "news"_ns || + scheme == "nntp"_ns || scheme == "snews"_ns) { + return IsolationBehavior::Parent; + } + + // There is more handling for extension content processes in the caller, but + // they should load in an extension content process unless we're loading a + // subframe. + if (scheme == "moz-extension"_ns) { + if (aIsSubframe) { + // As a temporary measure, extension iframes must be loaded within the + // same process as their parent document. + return IsolationBehavior::Inherit; + } + return IsolationBehavior::Extension; + } + + if (scheme == "file"_ns) { + return IsolationBehavior::File; + } + + // Check if the URI is listed as a privileged mozilla content process. + if (scheme == "https"_ns && + StaticPrefs:: + browser_tabs_remote_separatePrivilegedMozillaWebContentProcess()) { + nsAutoCString host; + if (NS_SUCCEEDED(aURI->GetAsciiHost(host))) { + for (const auto& separatedDomain : sSeparatedMozillaDomains) { + // If the domain exactly matches our host, or our host ends with "." + + // separatedDomain, we consider it matching. + if (separatedDomain == host || + (separatedDomain.Length() < host.Length() && + host.CharAt(host.Length() - separatedDomain.Length() - 1) == '.' && + StringEndsWith(host, separatedDomain))) { + return IsolationBehavior::PrivilegedMozilla; + } + } + } + } + + nsCOMPtr<nsIScriptSecurityManager> secMan = + nsContentUtils::GetSecurityManager(); + bool inFileURIAllowList = false; + if (NS_SUCCEEDED(secMan->InFileURIAllowlist(aURI, &inFileURIAllowList)) && + inFileURIAllowList) { + return IsolationBehavior::File; + } + + return IsolationBehavior::WebContent; +} + +/** + * Helper method for logging the origin of a principal as a string. + */ +static nsAutoCString OriginString(nsIPrincipal* aPrincipal) { + nsAutoCString origin; + aPrincipal->GetOrigin(origin); + return origin; +} + +/** + * Trim the OriginAttributes from aPrincipal, and use it to create a + * OriginSuffix string appropriate to use within a remoteType string. + */ +static nsAutoCString OriginSuffixForRemoteType(nsIPrincipal* aPrincipal) { + nsAutoCString originSuffix; + OriginAttributes attrs = aPrincipal->OriginAttributesRef(); + attrs.StripAttributes(OriginAttributes::STRIP_FIRST_PARTY_DOMAIN | + OriginAttributes::STRIP_PARITION_KEY); + attrs.CreateSuffix(originSuffix); + return originSuffix; +} + +/** + * Given an about:reader URI, extract the "url" query parameter, and use it to + * construct a principal which should be used for process selection. + */ +static already_AddRefed<BasePrincipal> GetAboutReaderURLPrincipal( + nsIURI* aURI, const OriginAttributes& aAttrs) { +#ifdef DEBUG + MOZ_ASSERT(aURI->SchemeIs("about")); + nsAutoCString path; + MOZ_ALWAYS_SUCCEEDS(NS_GetAboutModuleName(aURI, path)); + MOZ_ASSERT(path == "reader"_ns); +#endif + + nsAutoCString query; + MOZ_ALWAYS_SUCCEEDS(aURI->GetQuery(query)); + + // Extract the "url" parameter from the `about:reader`'s query parameters, + // and recover a content principal from it. + nsAutoString readerSpec; + if (URLParams::Extract(query, u"url"_ns, readerSpec)) { + nsCOMPtr<nsIURI> readerUri; + if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(readerUri), readerSpec))) { + return BasePrincipal::CreateContentPrincipal(readerUri, aAttrs); + } + } + return nullptr; +} + +/** + * Returns `true` if loads for this site should be isolated on a per-site basis. + * If `aTopBC` is nullptr, this is being called to check if a shared or service + * worker should be isolated. + */ +static bool ShouldIsolateSite(nsIPrincipal* aPrincipal, + bool aUseRemoteSubframes) { + // If Fission is disabled, we never want to isolate. We check the toplevel BC + // if it's available, or the global pref if checking for shared or service + // workers. + if (!aUseRemoteSubframes) { + return false; + } + + // non-content principals currently can't have webIsolated remote types + // assigned to them, so should not be isolated. + if (!aPrincipal->GetIsContentPrincipal()) { + return false; + } + + switch (WebContentIsolationStrategy( + StaticPrefs::fission_webContentIsolationStrategy())) { + case WebContentIsolationStrategy::IsolateNothing: + MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, + ("Not isolating '%s' as isolation is disabled", + OriginString(aPrincipal).get())); + return false; + case WebContentIsolationStrategy::IsolateEverything: + MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, + ("Isolating '%s' as isolation is enabled for all sites", + OriginString(aPrincipal).get())); + return true; + case WebContentIsolationStrategy::IsolateHighValue: { + RefPtr<PermissionManager> perms = PermissionManager::GetInstance(); + if (NS_WARN_IF(!perms)) { + // If we somehow have no permission manager, fall back to the safest + // option, and try to isolate. + MOZ_ASSERT_UNREACHABLE("Permission manager is missing"); + return true; + } + + static constexpr nsLiteralCString kHighValuePermissions[] = { + mozilla::dom::kHighValueCOOPPermission, + mozilla::dom::kHighValueHasSavedLoginPermission, + mozilla::dom::kHighValueIsLoggedInPermission, + }; + + for (const auto& type : kHighValuePermissions) { + uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION; + if (NS_SUCCEEDED(perms->TestPermissionFromPrincipal(aPrincipal, type, + &permission)) && + permission == nsIPermissionManager::ALLOW_ACTION) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, + ("Isolating '%s' due to high-value permission '%s'", + OriginString(aPrincipal).get(), type.get())); + return true; + } + } + MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, + ("Not isolating '%s' as it is not high-value", + OriginString(aPrincipal).get())); + return false; + } + default: + // An invalid pref value was used. Fall back to the safest option and + // isolate everything. + NS_WARNING("Invalid pref value for fission.webContentIsolationStrategy"); + MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, + ("Isolating '%s' due to unknown strategy pref value", + OriginString(aPrincipal).get())); + return true; + } +} + +static Result<nsCString, nsresult> SpecialBehaviorRemoteType( + IsolationBehavior aBehavior, const nsACString& aCurrentRemoteType, + WindowGlobalParent* aParentWindow) { + switch (aBehavior) { + case IsolationBehavior::ForceWebRemoteType: + return {WEB_REMOTE_TYPE}; + case IsolationBehavior::PrivilegedAbout: + // The privileged about: content process cannot be disabled, as it + // causes various actors to break. + return {PRIVILEGEDABOUT_REMOTE_TYPE}; + case IsolationBehavior::Extension: + if (ExtensionPolicyService::GetSingleton().UseRemoteExtensions()) { + return {EXTENSION_REMOTE_TYPE}; + } + return {NOT_REMOTE_TYPE}; + case IsolationBehavior::File: + if (StaticPrefs::browser_tabs_remote_separateFileUriProcess()) { + return {FILE_REMOTE_TYPE}; + } + return {WEB_REMOTE_TYPE}; + case IsolationBehavior::PrivilegedMozilla: + return {PRIVILEGEDMOZILLA_REMOTE_TYPE}; + case IsolationBehavior::Parent: + return {NOT_REMOTE_TYPE}; + case IsolationBehavior::Anywhere: + return {nsCString(aCurrentRemoteType)}; + case IsolationBehavior::Inherit: + MOZ_DIAGNOSTIC_ASSERT(aParentWindow); + return {nsCString(aParentWindow->GetRemoteType())}; + + case IsolationBehavior::Error: + return Err(NS_ERROR_UNEXPECTED); + + default: + MOZ_ASSERT_UNREACHABLE(); + return Err(NS_ERROR_UNEXPECTED); + } +} + +enum class WebProcessType { + Web, + WebIsolated, + WebCoopCoep, +}; + +} // namespace + +Result<NavigationIsolationOptions, nsresult> IsolationOptionsForNavigation( + CanonicalBrowsingContext* aTopBC, WindowGlobalParent* aParentWindow, + nsIURI* aChannelCreationURI, nsIChannel* aChannel, + const nsACString& aCurrentRemoteType, bool aHasCOOPMismatch, + bool aForNewTab, uint32_t aLoadStateLoadType, + const Maybe<uint64_t>& aChannelId, + const Maybe<nsCString>& aRemoteTypeOverride) { + // Get the final principal, used to select which process to load into. + nsCOMPtr<nsIPrincipal> resultPrincipal; + nsresult rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal( + aChannel, getter_AddRefs(resultPrincipal)); + if (NS_FAILED(rv)) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Error, + ("failed to get channel result principal")); + return Err(rv); + } + + MOZ_LOG( + gProcessIsolationLog, LogLevel::Verbose, + ("IsolationOptionsForNavigation principal:%s, uri:%s, parentUri:%s", + OriginString(resultPrincipal).get(), + aChannelCreationURI->GetSpecOrDefault().get(), + aParentWindow ? aParentWindow->GetDocumentURI()->GetSpecOrDefault().get() + : "")); + + // If we're loading a null principal, we can't easily make a process + // selection decision off ot it. Instead, we'll use our null principal's + // precursor principal to make process selection decisions. + bool isNullPrincipalPrecursor = false; + nsCOMPtr<nsIPrincipal> resultOrPrecursor(resultPrincipal); + if (nsCOMPtr<nsIPrincipal> precursor = + resultOrPrecursor->GetPrecursorPrincipal()) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, + ("using null principal precursor origin %s", + OriginString(precursor).get())); + resultOrPrecursor = precursor; + isNullPrincipalPrecursor = true; + } + + NavigationIsolationOptions options; + options.mReplaceBrowsingContext = aHasCOOPMismatch; + + // Check if this load has an explicit remote type override. This is used to + // perform an about:blank load within a specific content process. + if (aRemoteTypeOverride) { + MOZ_DIAGNOSTIC_ASSERT( + NS_IsAboutBlank(aChannelCreationURI), + "Should only have aRemoteTypeOverride for about:blank URIs"); + if (NS_WARN_IF(!resultPrincipal->GetIsNullPrincipal())) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Error, + ("invalid remote type override on non-null principal")); + return Err(NS_ERROR_DOM_SECURITY_ERR); + } + + MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, + ("using remote type override (%s) for load", + aRemoteTypeOverride->get())); + options.mRemoteType = *aRemoteTypeOverride; + return options; + } + + // First, check for any special cases which should be handled using the + // channel creation URI, and handle them. + auto behavior = IsolationBehaviorForURI(aChannelCreationURI, aParentWindow, + /* aForChannelCreationURI */ true); + MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, + ("Channel Creation Isolation Behavior: %s", + IsolationBehaviorName(behavior))); + + // In the about:reader special case, we want to fetch the relevant information + // from the URI, an then treat it as a normal web content load. + if (behavior == IsolationBehavior::AboutReader) { + if (RefPtr<BasePrincipal> readerURIPrincipal = GetAboutReaderURLPrincipal( + aChannelCreationURI, resultOrPrecursor->OriginAttributesRef())) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, + ("using about:reader's url origin %s", + OriginString(readerURIPrincipal).get())); + resultOrPrecursor = readerURIPrincipal; + } + behavior = IsolationBehavior::WebContent; + // If loading an about:reader page in a BrowsingContext which shares a + // BrowsingContextGroup with other toplevel documents, replace the + // BrowsingContext to destroy any references. + // + // With SHIP we can apply this to all about:reader loads, but otherwise + // do it at least where there are opener/group relationships. + if (mozilla::SessionHistoryInParent() || + aTopBC->Group()->Toplevels().Length() > 1) { + options.mReplaceBrowsingContext = true; + } + } + + // If we're running in a test which is requesting that system-triggered + // about:blank documents load within the current process, override the + // behaviour for loads which meet the requirements. + if (StaticPrefs::browser_tabs_remote_systemTriggeredAboutBlankAnywhere() && + NS_IsAboutBlank(aChannelCreationURI)) { + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); + if (loadInfo->TriggeringPrincipal()->IsSystemPrincipal() && + resultOrPrecursor->GetIsNullPrincipal()) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, + ("Forcing system-principal triggered about:blank load to " + "complete in the current process")); + behavior = IsolationBehavior::Anywhere; + } + } + +#ifdef MOZ_WIDGET_ANDROID + // If we're loading an error page on android, it must complete within the same + // process as the errored page load would complete in due to code expecting + // that behavior. See bug 1673763. + if (aLoadStateLoadType == LOAD_ERROR_PAGE) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, + ("Forcing error page load to complete in the current process")); + behavior = IsolationBehavior::Anywhere; + } +#endif + + // If we're loading for a specific extension, we'll need to perform a + // BCG-switching load to get our toplevel extension window in the correct + // BrowsingContextGroup. + if (auto* addonPolicy = + BasePrincipal::Cast(resultOrPrecursor)->AddonPolicy()) { + if (aParentWindow) { + // As a temporary measure, extension iframes must be loaded within the + // same process as their parent document. + MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, + ("Loading extension subframe in same process as parent")); + behavior = IsolationBehavior::Inherit; + } else { + MOZ_LOG( + gProcessIsolationLog, LogLevel::Verbose, + ("Found extension frame with addon policy. Will use group id %" PRIx64 + " (currentId: %" PRIx64 ")", + addonPolicy->GetBrowsingContextGroupId(), aTopBC->Group()->Id())); + behavior = IsolationBehavior::Extension; + if (aTopBC->Group()->Id() != addonPolicy->GetBrowsingContextGroupId()) { + options.mReplaceBrowsingContext = true; + options.mSpecificGroupId = addonPolicy->GetBrowsingContextGroupId(); + } + } + } + + // Do a second run of `GetIsolationBehavior`, this time using the + // principal's URI to handle additional special cases such as the file and + // privilegedmozilla content process. + if (behavior == IsolationBehavior::WebContent) { + if (resultOrPrecursor->IsSystemPrincipal()) { + // We're loading something with a system principal which isn't caught in + // one of our other edge-cases. If the load started in the parent process, + // and it's safe for it to end in the parent process, we should finish the + // load there. + bool isUIResource = false; + if (aCurrentRemoteType.IsEmpty() && + (aChannelCreationURI->SchemeIs("about") || + (NS_SUCCEEDED(NS_URIChainHasFlags( + aChannelCreationURI, nsIProtocolHandler::URI_IS_UI_RESOURCE, + &isUIResource)) && + isUIResource))) { + behavior = IsolationBehavior::Parent; + } else { + // In general, we don't want to load documents with a system principal + // in a content process, however we need to in some cases, such as when + // loading blob: URLs created by system code. We can force the load to + // finish in a content process instead. + behavior = IsolationBehavior::ForceWebRemoteType; + } + } else if (nsCOMPtr<nsIURI> principalURI = resultOrPrecursor->GetURI()) { + behavior = IsolationBehaviorForURI(principalURI, aParentWindow, + /* aForChannelCreationURI */ false); + } + } + + // If we're currently loaded in the extension process, and are going to switch + // to some other remote type, make sure we leave the extension's BCG which we + // may have entered earlier to separate extension and non-extension BCGs from + // each-other. + if (!aParentWindow && aCurrentRemoteType == EXTENSION_REMOTE_TYPE && + behavior != IsolationBehavior::Extension && + behavior != IsolationBehavior::Anywhere) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, + ("Forcing BC replacement to leave extension BrowsingContextGroup " + "%" PRIx64 " on navigation", + aTopBC->Group()->Id())); + options.mReplaceBrowsingContext = true; + } + + // We don't want to load documents with sandboxed null principals, like + // `data:` URIs, in the parent process, even if they were created by a + // document which would otherwise be loaded in the parent process. + if (behavior == IsolationBehavior::Parent && isNullPrincipalPrecursor) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Debug, + ("Ensuring sandboxed null-principal load doesn't occur in the " + "parent process")); + behavior = IsolationBehavior::ForceWebRemoteType; + } + + MOZ_LOG( + gProcessIsolationLog, LogLevel::Debug, + ("Using IsolationBehavior %s for %s (original uri %s)", + IsolationBehaviorName(behavior), OriginString(resultOrPrecursor).get(), + aChannelCreationURI->GetSpecOrDefault().get())); + + // Check if we can put the previous document into the BFCache. + if (mozilla::BFCacheInParent() && nsSHistory::GetMaxTotalViewers() > 0 && + !aForNewTab && !aParentWindow && !aTopBC->HadOriginalOpener() && + behavior != IsolationBehavior::Parent && + (ExtensionPolicyService::GetSingleton().UseRemoteExtensions() || + behavior != IsolationBehavior::Extension) && + !aCurrentRemoteType.IsEmpty() && + aTopBC->GetHasLoadedNonInitialDocument() && + (aLoadStateLoadType == LOAD_NORMAL || + aLoadStateLoadType == LOAD_HISTORY || aLoadStateLoadType == LOAD_LINK || + aLoadStateLoadType == LOAD_STOP_CONTENT || + aLoadStateLoadType == LOAD_STOP_CONTENT_AND_REPLACE) && + (!aTopBC->GetActiveSessionHistoryEntry() || + aTopBC->GetActiveSessionHistoryEntry()->GetSaveLayoutStateFlag())) { + if (nsCOMPtr<nsIURI> uri = aTopBC->GetCurrentURI()) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, + ("current uri: %s", uri->GetSpecOrDefault().get())); + } + options.mTryUseBFCache = + aTopBC->AllowedInBFCache(aChannelId, aChannelCreationURI); + if (options.mTryUseBFCache) { + options.mReplaceBrowsingContext = true; + options.mActiveSessionHistoryEntry = + aTopBC->GetActiveSessionHistoryEntry(); + } + } + + // If the load has any special remote type handling, do so at this point. + if (behavior != IsolationBehavior::WebContent) { + MOZ_TRY_VAR( + options.mRemoteType, + SpecialBehaviorRemoteType(behavior, aCurrentRemoteType, aParentWindow)); + + if (options.mRemoteType != aCurrentRemoteType && + (options.mRemoteType.IsEmpty() || aCurrentRemoteType.IsEmpty())) { + options.mReplaceBrowsingContext = true; + } + + MOZ_LOG( + gProcessIsolationLog, LogLevel::Debug, + ("Selecting specific remote type (%s) due to a special case isolation " + "behavior %s", + options.mRemoteType.get(), IsolationBehaviorName(behavior))); + return options; + } + + // At this point we're definitely not going to be loading in the parent + // process anymore, so we're definitely going to be replacing BrowsingContext + // if we're in the parent process. + if (aCurrentRemoteType.IsEmpty()) { + MOZ_ASSERT(!aParentWindow); + options.mReplaceBrowsingContext = true; + } + + nsAutoCString siteOriginNoSuffix; + MOZ_TRY(resultOrPrecursor->GetSiteOriginNoSuffix(siteOriginNoSuffix)); + + // Check if we've already loaded a document with the given principal in some + // content process. We want to finish the load in the same process in that + // case. + // + // The exception to that is with extension loads and the system principal, + // where we may have multiple documents with the same principal in different + // processes. Those have been handled above, and will not be reaching here. + // + // If we're doing a replace load or opening a new tab, we won't be staying in + // the same BrowsingContextGroup, so ignore this step. + if (!options.mReplaceBrowsingContext && !aForNewTab) { + // Helper for efficiently determining if a given origin is same-site. This + // will attempt to do a fast equality check, and will only fall back to + // computing the site-origin for content principals. + auto principalIsSameSite = [&](nsIPrincipal* aDocumentPrincipal) -> bool { + // If we're working with a null principal with a precursor, compare + // precursors, as `resultOrPrecursor` has already been stripped to its + // precursor. + nsCOMPtr<nsIPrincipal> documentPrincipal(aDocumentPrincipal); + if (nsCOMPtr<nsIPrincipal> precursor = + documentPrincipal->GetPrecursorPrincipal()) { + documentPrincipal = precursor; + } + + // First, attempt to use `Equals` to compare principals, and if that + // fails compare siteOrigins. Only compare siteOrigin for content + // principals, as non-content principals will never have siteOrigin != + // origin. + nsAutoCString documentSiteOrigin; + return resultOrPrecursor->Equals(documentPrincipal) || + (documentPrincipal->GetIsContentPrincipal() && + resultOrPrecursor->GetIsContentPrincipal() && + NS_SUCCEEDED(documentPrincipal->GetSiteOriginNoSuffix( + documentSiteOrigin)) && + documentSiteOrigin == siteOriginNoSuffix); + }; + + // XXX: Consider also checking in-flight process switches to see if any have + // matching principals? + AutoTArray<RefPtr<BrowsingContext>, 8> contexts; + aTopBC->Group()->GetToplevels(contexts); + while (!contexts.IsEmpty()) { + auto bc = contexts.PopLastElement(); + for (const auto& wc : bc->GetWindowContexts()) { + WindowGlobalParent* wgp = wc->Canonical(); + + // Check if this WindowGlobalParent has the given resultPrincipal, and + // if it does, we need to load in that process. + if (!wgp->GetRemoteType().IsEmpty() && + principalIsSameSite(wgp->DocumentPrincipal())) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Debug, + ("Found existing frame with matching principal " + "(remoteType:(%s), origin:%s)", + PromiseFlatCString(wgp->GetRemoteType()).get(), + OriginString(wgp->DocumentPrincipal()).get())); + options.mRemoteType = wgp->GetRemoteType(); + return options; + } + + // Also enumerate over this WindowContexts' subframes. + contexts.AppendElements(wc->Children()); + } + } + } + + nsAutoCString originSuffix = OriginSuffixForRemoteType(resultOrPrecursor); + + WebProcessType webProcessType = WebProcessType::Web; + if (ShouldIsolateSite(resultOrPrecursor, aTopBC->UseRemoteSubframes())) { + webProcessType = WebProcessType::WebIsolated; + } + + // Check if we should be loading in a webCOOP+COEP remote type due to our COOP + // status. + nsILoadInfo::CrossOriginOpenerPolicy coop = + nsILoadInfo::OPENER_POLICY_UNSAFE_NONE; + if (aParentWindow) { + coop = aTopBC->GetOpenerPolicy(); + } else if (nsCOMPtr<nsIHttpChannelInternal> httpChannel = + do_QueryInterface(aChannel)) { + MOZ_ALWAYS_SUCCEEDS(httpChannel->GetCrossOriginOpenerPolicy(&coop)); + } + if (coop == + nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP) { + webProcessType = WebProcessType::WebCoopCoep; + + // If we're changing BrowsingContext, and are going to end up within a + // webCOOP+COEP group, ensure we use a cross-origin isolated BCG ID. + if (options.mReplaceBrowsingContext) { + MOZ_ASSERT(!options.mSpecificGroupId, + "overriding previously-specified BCG ID"); + options.mSpecificGroupId = BrowsingContextGroup::CreateId( + /* aPotentiallyCrossOriginIsolated */ true); + } + } + + switch (webProcessType) { + case WebProcessType::Web: + options.mRemoteType = WEB_REMOTE_TYPE; + break; + case WebProcessType::WebIsolated: + options.mRemoteType = + FISSION_WEB_REMOTE_TYPE "="_ns + siteOriginNoSuffix + originSuffix; + break; + case WebProcessType::WebCoopCoep: + options.mRemoteType = + WITH_COOP_COEP_REMOTE_TYPE "="_ns + siteOriginNoSuffix + originSuffix; + break; + } + return options; +} + +Result<WorkerIsolationOptions, nsresult> IsolationOptionsForWorker( + nsIPrincipal* aPrincipal, WorkerKind aWorkerKind, + const nsACString& aCurrentRemoteType, bool aUseRemoteSubframes) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, + ("IsolationOptionsForWorker principal:%s, kind:%s, current:%s", + OriginString(aPrincipal).get(), WorkerKindName(aWorkerKind), + PromiseFlatCString(aCurrentRemoteType).get())); + + MOZ_ASSERT(NS_IsMainThread()); + MOZ_RELEASE_ASSERT( + aWorkerKind == WorkerKindService || aWorkerKind == WorkerKindShared, + "Unexpected remote worker kind"); + + if (aWorkerKind == WorkerKindService && + !aPrincipal->GetIsContentPrincipal()) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, + ("Rejecting service worker with non-content principal")); + return Err(NS_ERROR_UNEXPECTED); + } + + if (aPrincipal->GetIsExpandedPrincipal()) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, + ("Rejecting remote worker with expanded principal")); + return Err(NS_ERROR_UNEXPECTED); + } + + // In some cases, such as for null principals without precursors, we will want + // to load a shared worker in a process based on the current process. This is + // not done for service workers - process selection for those should function + // the same in all processes. + // + // We only allow the current remote type to be used if it is not a COOP+COEP + // remote type, in order to avoid loading a shared worker in one of these + // processes. Currently process selection for workers occurs before response + // headers are available, so we will never select to load a shared worker in a + // COOP+COEP content process. + nsCString preferredRemoteType = DEFAULT_REMOTE_TYPE; + if (aWorkerKind == WorkerKind::WorkerKindShared && + !StringBeginsWith(aCurrentRemoteType, + WITH_COOP_COEP_REMOTE_TYPE_PREFIX)) { + preferredRemoteType = aCurrentRemoteType; + } + + WorkerIsolationOptions options; + + // If we're loading a null principal, we can't easily make a process + // selection decision off ot it. Instead, we'll use our null principal's + // precursor principal to make process selection decisions. + bool isNullPrincipalPrecursor = false; + nsCOMPtr<nsIPrincipal> resultOrPrecursor(aPrincipal); + if (nsCOMPtr<nsIPrincipal> precursor = + resultOrPrecursor->GetPrecursorPrincipal()) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Verbose, + ("using null principal precursor origin %s", + OriginString(precursor).get())); + resultOrPrecursor = precursor; + isNullPrincipalPrecursor = true; + } + + IsolationBehavior behavior = IsolationBehavior::WebContent; + if (resultOrPrecursor->GetIsContentPrincipal()) { + nsCOMPtr<nsIURI> uri = resultOrPrecursor->GetURI(); + behavior = IsolationBehaviorForURI(uri, /* aIsSubframe */ false, + /* aForChannelCreationURI */ false); + } else if (resultOrPrecursor->IsSystemPrincipal()) { + MOZ_ASSERT(aWorkerKind == WorkerKindShared); + + // Allow system principal shared workers to load within either the + // parent process or privilegedabout process, depending on the + // responsible process. + if (preferredRemoteType == NOT_REMOTE_TYPE) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Debug, + ("Loading system principal shared worker in parent process")); + behavior = IsolationBehavior::Parent; + } else if (preferredRemoteType == PRIVILEGEDABOUT_REMOTE_TYPE) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Debug, + ("Loading system principal shared worker in privilegedabout " + "process")); + behavior = IsolationBehavior::PrivilegedAbout; + } else { + MOZ_LOG(gProcessIsolationLog, LogLevel::Warning, + ("Cannot load system-principal shared worker in " + "non-privilegedabout content process")); + return Err(NS_ERROR_UNEXPECTED); + } + } else { + MOZ_ASSERT(resultOrPrecursor->GetIsNullPrincipal()); + MOZ_ASSERT(aWorkerKind == WorkerKindShared); + + if (preferredRemoteType == NOT_REMOTE_TYPE) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Debug, + ("Ensuring precursorless null principal shared worker loads in a " + "content process")); + behavior = IsolationBehavior::ForceWebRemoteType; + } else { + MOZ_LOG(gProcessIsolationLog, LogLevel::Debug, + ("Loading precursorless null principal shared worker within " + "current remotetype: (%s)", + preferredRemoteType.get())); + behavior = IsolationBehavior::Anywhere; + } + } + + if (behavior == IsolationBehavior::Parent && isNullPrincipalPrecursor) { + MOZ_LOG(gProcessIsolationLog, LogLevel::Debug, + ("Ensuring sandboxed null-principal shared worker doesn't load in " + "the parent process")); + behavior = IsolationBehavior::ForceWebRemoteType; + } + + if (behavior != IsolationBehavior::WebContent) { + MOZ_TRY_VAR( + options.mRemoteType, + SpecialBehaviorRemoteType(behavior, preferredRemoteType, nullptr)); + + MOZ_LOG( + gProcessIsolationLog, LogLevel::Debug, + ("Selecting specific %s worker remote type (%s) due to a special case " + "isolation behavior %s", + WorkerKindName(aWorkerKind), options.mRemoteType.get(), + IsolationBehaviorName(behavior))); + return options; + } + + // If we should be isolating this site, we can determine the correct fission + // remote type from the principal's site-origin. + if (ShouldIsolateSite(resultOrPrecursor, aUseRemoteSubframes)) { + nsAutoCString siteOriginNoSuffix; + MOZ_TRY(resultOrPrecursor->GetSiteOriginNoSuffix(siteOriginNoSuffix)); + nsAutoCString originSuffix = OriginSuffixForRemoteType(resultOrPrecursor); + + nsCString prefix = aWorkerKind == WorkerKindService + ? SERVICEWORKER_REMOTE_TYPE + : FISSION_WEB_REMOTE_TYPE; + options.mRemoteType = prefix + "="_ns + siteOriginNoSuffix + originSuffix; + + MOZ_LOG(gProcessIsolationLog, LogLevel::Debug, + ("Isolating web content %s worker in remote type (%s)", + WorkerKindName(aWorkerKind), options.mRemoteType.get())); + } else { + options.mRemoteType = WEB_REMOTE_TYPE; + + MOZ_LOG(gProcessIsolationLog, LogLevel::Debug, + ("Loading web content %s worker in shared web remote type", + WorkerKindName(aWorkerKind))); + } + return options; +} + +void AddHighValuePermission(nsIPrincipal* aResultPrincipal, + const nsACString& aPermissionType) { + RefPtr<PermissionManager> perms = PermissionManager::GetInstance(); + if (NS_WARN_IF(!perms)) { + return; + } + + // We can't act on non-content principals, so if the load was sandboxed, try + // to use the unsandboxed precursor principal to add the highValue permission. + nsCOMPtr<nsIPrincipal> resultOrPrecursor(aResultPrincipal); + if (!aResultPrincipal->GetIsContentPrincipal()) { + resultOrPrecursor = aResultPrincipal->GetPrecursorPrincipal(); + if (!resultOrPrecursor) { + return; + } + } + + // Use the site-origin principal as we want to add the permission for the + // entire site, rather than a specific subdomain, as process isolation acts on + // a site granularity. + nsAutoCString siteOrigin; + if (NS_FAILED(resultOrPrecursor->GetSiteOrigin(siteOrigin))) { + return; + } + + nsCOMPtr<nsIPrincipal> sitePrincipal = + BasePrincipal::CreateContentPrincipal(siteOrigin); + if (!sitePrincipal || !sitePrincipal->GetIsContentPrincipal()) { + return; + } + + MOZ_LOG(dom::gProcessIsolationLog, LogLevel::Verbose, + ("Adding %s Permission for site '%s'", aPermissionType.BeginReading(), + siteOrigin.get())); + + uint32_t expiration = 0; + if (aPermissionType.Equals(mozilla::dom::kHighValueCOOPPermission)) { + expiration = StaticPrefs::fission_highValue_coop_expiration(); + } else if (aPermissionType.Equals( + mozilla::dom::kHighValueHasSavedLoginPermission) || + aPermissionType.Equals( + mozilla::dom::kHighValueIsLoggedInPermission)) { + expiration = StaticPrefs::fission_highValue_login_expiration(); + } else { + MOZ_ASSERT_UNREACHABLE("Unknown permission type"); + } + + // XXX: Would be nice if we could use `TimeStamp` here, but there's + // unfortunately no convenient way to recover a time in milliseconds since the + // unix epoch from `TimeStamp`. + int64_t expirationTime = + (PR_Now() / PR_USEC_PER_MSEC) + (int64_t(expiration) * PR_MSEC_PER_SEC); + Unused << perms->AddFromPrincipal( + sitePrincipal, aPermissionType, nsIPermissionManager::ALLOW_ACTION, + nsIPermissionManager::EXPIRE_TIME, expirationTime); +} + +void AddHighValuePermission(const nsACString& aOrigin, + const nsACString& aPermissionType) { + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + nsCOMPtr<nsIPrincipal> principal; + nsresult rv = + ssm->CreateContentPrincipalFromOrigin(aOrigin, getter_AddRefs(principal)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + AddHighValuePermission(principal, aPermissionType); +} + +bool IsIsolateHighValueSiteEnabled() { + return mozilla::FissionAutostart() && + WebContentIsolationStrategy( + StaticPrefs::fission_webContentIsolationStrategy()) == + WebContentIsolationStrategy::IsolateHighValue; +} + +} // namespace mozilla::dom diff --git a/dom/ipc/ProcessIsolation.h b/dom/ipc/ProcessIsolation.h new file mode 100644 index 0000000000..e9703a8d7a --- /dev/null +++ b/dom/ipc/ProcessIsolation.h @@ -0,0 +1,111 @@ +/* -*- 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_dom_ProcessIsolation_h +#define mozilla_dom_ProcessIsolation_h + +#include <stdint.h> + +#include "mozilla/Logging.h" +#include "mozilla/dom/RemoteType.h" +#include "mozilla/dom/SessionHistoryEntry.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "nsString.h" +#include "nsIPrincipal.h" +#include "nsIURI.h" + +namespace mozilla::dom { + +class CanonicalBrowsingContext; +class WindowGlobalParent; + +extern mozilla::LazyLogModule gProcessIsolationLog; + +constexpr nsLiteralCString kHighValueCOOPPermission = "highValueCOOP"_ns; +constexpr nsLiteralCString kHighValueHasSavedLoginPermission = + "highValueHasSavedLogin"_ns; +constexpr nsLiteralCString kHighValueIsLoggedInPermission = + "highValueIsLoggedIn"_ns; + +// NavigationIsolationOptions is passed through the methods to store the state +// of the possible process and/or browsing context change. +struct NavigationIsolationOptions { + nsCString mRemoteType; + bool mReplaceBrowsingContext = false; + uint64_t mSpecificGroupId = 0; + bool mTryUseBFCache = false; + RefPtr<SessionHistoryEntry> mActiveSessionHistoryEntry; +}; + +/** + * Given a specific channel, determines which process the navigation should + * complete in, and whether or not to perform a BrowsingContext-replace load + * or enter the BFCache. + * + * This method will always return a `NavigationIsolationOptions` even if the + * current remote type is compatible. Compatibility with the current process + * should be checked at the call-site. An error should only be returned in + * exceptional circumstances, and should lead to the load being cancelled. + * + * This method is only intended for use with document navigations. + */ +Result<NavigationIsolationOptions, nsresult> IsolationOptionsForNavigation( + CanonicalBrowsingContext* aTopBC, WindowGlobalParent* aParentWindow, + nsIURI* aChannelCreationURI, nsIChannel* aChannel, + const nsACString& aCurrentRemoteType, bool aHasCOOPMismatch, + bool aForNewTab, uint32_t aLoadStateLoadType, + const Maybe<uint64_t>& aChannelId, + const Maybe<nsCString>& aRemoteTypeOverride); + +// WorkerIsolationOptions is passed back to the RemoteWorkerManager to store the +// destination process information for remote worker loads. +struct WorkerIsolationOptions { + nsCString mRemoteType; +}; + +/** + * Given a specific worker principal and kind, determines which process the + * remote worker load should complete in. + * + * This method is only intended for use with remote workers. + */ +Result<WorkerIsolationOptions, nsresult> IsolationOptionsForWorker( + nsIPrincipal* aPrincipal, WorkerKind aWorkerKind, + const nsACString& aCurrentRemoteType, bool aUseRemoteSubframes); + +/** + * Adds a `highValue` permission to the permissions database, and make loads of + * that origin isolated. + * + * The 'aPermissionType' parameter indicates why the site is treated as a high + * value site. The possible values are: + * + * kHighValueCOOPPermission + * Called when a document request responds with a + * `Cross-Origin-Opener-Policy` header. + * + * kHighValueHasSavedLoginPermission + * Called for sites that have an associated login saved in the password + * manager. + * + * kHighValueIsLoggedInPermission + * Called when we detect a form with a password is submitted. + */ +void AddHighValuePermission(nsIPrincipal* aResultPrincipal, + const nsACString& aPermissionType); + +void AddHighValuePermission(const nsACString& aOrigin, + const nsACString& aPermissionType); + +/** + * Returns true when fission is enabled and the + * `fission.webContentIsolationStrategy` pref is set to `IsolateHighValue`. + */ +bool IsIsolateHighValueSiteEnabled(); + +} // namespace mozilla::dom + +#endif diff --git a/dom/ipc/ProcessPriorityManager.cpp b/dom/ipc/ProcessPriorityManager.cpp new file mode 100644 index 0000000000..2c54b43295 --- /dev/null +++ b/dom/ipc/ProcessPriorityManager.cpp @@ -0,0 +1,1067 @@ +/* -*- 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 "ProcessPriorityManager.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/BrowserHost.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/Hal.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Preferences.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/ProfilerState.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StaticPrefs_threads.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Unused.h" +#include "mozilla/Logging.h" +#include "nsPrintfCString.h" +#include "nsXULAppAPI.h" +#include "nsFrameLoader.h" +#include "nsINamed.h" +#include "nsIObserverService.h" +#include "StaticPtr.h" +#include "nsIObserver.h" +#include "nsITimer.h" +#include "nsIPropertyBag2.h" +#include "nsComponentManagerUtils.h" +#include "nsCRT.h" +#include "nsTHashSet.h" +#include "nsQueryObject.h" +#include "nsTHashMap.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::hal; + +#ifdef XP_WIN +# include <process.h> +# define getpid _getpid +#else +# include <unistd.h> +#endif + +#ifdef LOG +# undef LOG +#endif + +// Use LOGP inside a ParticularProcessPriorityManager method; use LOG +// everywhere else. LOGP prints out information about the particular process +// priority manager. +// +// (Wow, our logging story is a huge mess.) + +// #define ENABLE_LOGGING 1 + +#if defined(ANDROID) && defined(ENABLE_LOGGING) +# include <android/log.h> +# define LOG(fmt, ...) \ + __android_log_print(ANDROID_LOG_INFO, "Gecko:ProcessPriorityManager", fmt, \ + ##__VA_ARGS__) +# define LOGP(fmt, ...) \ + __android_log_print( \ + ANDROID_LOG_INFO, "Gecko:ProcessPriorityManager", \ + "[%schild-id=%" PRIu64 ", pid=%d] " fmt, NameWithComma().get(), \ + static_cast<uint64_t>(ChildID()), Pid(), ##__VA_ARGS__) + +#elif defined(ENABLE_LOGGING) +# define LOG(fmt, ...) \ + printf("ProcessPriorityManager - " fmt "\n", ##__VA_ARGS__) +# define LOGP(fmt, ...) \ + printf("ProcessPriorityManager[%schild-id=%" PRIu64 ", pid=%d] - " fmt \ + "\n", \ + NameWithComma().get(), static_cast<uint64_t>(ChildID()), Pid(), \ + ##__VA_ARGS__) +#else +static LogModule* GetPPMLog() { + static LazyLogModule sLog("ProcessPriorityManager"); + return sLog; +} +# define LOG(fmt, ...) \ + MOZ_LOG(GetPPMLog(), LogLevel::Debug, \ + ("ProcessPriorityManager - " fmt, ##__VA_ARGS__)) +# define LOGP(fmt, ...) \ + MOZ_LOG(GetPPMLog(), LogLevel::Debug, \ + ("ProcessPriorityManager[%schild-id=%" PRIu64 ", pid=%d] - " fmt, \ + NameWithComma().get(), static_cast<uint64_t>(ChildID()), Pid(), \ + ##__VA_ARGS__)) +#endif + +namespace geckoprofiler::markers { +struct SubProcessPriorityChange { + static constexpr Span<const char> MarkerTypeName() { + return MakeStringSpan("subprocessprioritychange"); + } + static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter, + int32_t aPid, + const ProfilerString8View& aPreviousPriority, + const ProfilerString8View& aNewPriority) { + aWriter.IntProperty("pid", aPid); + aWriter.StringProperty("Before", aPreviousPriority); + aWriter.StringProperty("After", aNewPriority); + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; + schema.AddKeyFormat("pid", MS::Format::Integer); + schema.AddKeyFormat("Before", MS::Format::String); + schema.AddKeyFormat("After", MS::Format::String); + schema.SetAllLabels( + "priority of child {marker.data.pid}:" + " {marker.data.Before} -> {marker.data.After}"); + return schema; + } +}; + +struct SubProcessPriority { + static constexpr Span<const char> MarkerTypeName() { + return MakeStringSpan("subprocesspriority"); + } + static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter, + int32_t aPid, + const ProfilerString8View& aPriority, + const ProfilingState& aProfilingState) { + aWriter.IntProperty("pid", aPid); + aWriter.StringProperty("Priority", aPriority); + aWriter.StringProperty("Marker cause", + ProfilerString8View::WrapNullTerminatedString( + ProfilingStateToString(aProfilingState))); + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; + schema.AddKeyFormat("pid", MS::Format::Integer); + schema.AddKeyFormat("Priority", MS::Format::String); + schema.AddKeyFormat("Marker cause", MS::Format::String); + schema.SetAllLabels( + "priority of child {marker.data.pid}: {marker.data.Priority}"); + return schema; + } +}; +} // namespace geckoprofiler::markers + +namespace { + +class ParticularProcessPriorityManager; + +/** + * This singleton class does the work to implement the process priority manager + * in the main process. This class may not be used in child processes. (You + * can call StaticInit, but it won't do anything, and GetSingleton() will + * return null.) + * + * ProcessPriorityManager::CurrentProcessIsForeground() and + * ProcessPriorityManager::AnyProcessHasHighPriority() which can be called in + * any process, are handled separately, by the ProcessPriorityManagerChild + * class. + */ +class ProcessPriorityManagerImpl final : public nsIObserver, + public nsSupportsWeakReference { + public: + /** + * If we're in the main process, get the ProcessPriorityManagerImpl + * singleton. If we're in a child process, return null. + */ + static ProcessPriorityManagerImpl* GetSingleton(); + + static void StaticInit(); + static bool PrefsEnabled(); + static void SetProcessPriorityIfEnabled(int aPid, ProcessPriority aPriority); + static bool TestMode(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + /** + * This function implements ProcessPriorityManager::SetProcessPriority. + */ + void SetProcessPriority(ContentParent* aContentParent, + ProcessPriority aPriority); + + /** + * If a magic testing-only pref is set, notify the observer service on the + * given topic with the given data. This is used for testing + */ + void FireTestOnlyObserverNotification(const char* aTopic, + const nsACString& aData); + + /** + * This must be called by a ParticularProcessPriorityManager when it changes + * its priority. + */ + void NotifyProcessPriorityChanged( + ParticularProcessPriorityManager* aParticularManager, + hal::ProcessPriority aOldPriority); + + void BrowserPriorityChanged(CanonicalBrowsingContext* aBC, bool aPriority); + void BrowserPriorityChanged(BrowserParent* aBrowserParent, bool aPriority); + + void ResetPriority(ContentParent* aContentParent); + + private: + static bool sPrefListenersRegistered; + static bool sInitialized; + static StaticRefPtr<ProcessPriorityManagerImpl> sSingleton; + + static void PrefChangedCallback(const char* aPref, void* aClosure); + + ProcessPriorityManagerImpl(); + ~ProcessPriorityManagerImpl(); + ProcessPriorityManagerImpl(const ProcessPriorityManagerImpl&) = delete; + + const ProcessPriorityManagerImpl& operator=( + const ProcessPriorityManagerImpl&) = delete; + + void Init(); + + already_AddRefed<ParticularProcessPriorityManager> + GetParticularProcessPriorityManager(ContentParent* aContentParent); + + void ObserveContentParentDestroyed(nsISupports* aSubject); + + nsTHashMap<uint64_t, RefPtr<ParticularProcessPriorityManager> > + mParticularManagers; + + /** Contains the PIDs of child processes holding high-priority wakelocks */ + nsTHashSet<uint64_t> mHighPriorityChildIDs; +}; + +/** + * This singleton class implements the parts of the process priority manager + * that are available from all processes. + */ +class ProcessPriorityManagerChild final : public nsIObserver { + public: + static void StaticInit(); + static ProcessPriorityManagerChild* Singleton(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + bool CurrentProcessIsForeground(); + + private: + static StaticRefPtr<ProcessPriorityManagerChild> sSingleton; + + ProcessPriorityManagerChild(); + ~ProcessPriorityManagerChild() = default; + ProcessPriorityManagerChild(const ProcessPriorityManagerChild&) = delete; + + const ProcessPriorityManagerChild& operator=( + const ProcessPriorityManagerChild&) = delete; + + void Init(); + + hal::ProcessPriority mCachedPriority; +}; + +/** + * This class manages the priority of one particular process. It is + * main-process only. + */ +class ParticularProcessPriorityManager final : public WakeLockObserver, + public nsITimerCallback, + public nsINamed, + public nsSupportsWeakReference { + ~ParticularProcessPriorityManager(); + + public: + explicit ParticularProcessPriorityManager(ContentParent* aContentParent); + + NS_DECL_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + + virtual void Notify(const WakeLockInformation& aInfo) override; + void Init(); + + int32_t Pid() const; + uint64_t ChildID() const; + + /** + * Used in logging, this method returns the ContentParent's name followed by + * ", ". If we can't get the ContentParent's name for some reason, it + * returns an empty string. + * + * The reference returned here is guaranteed to be live until the next call + * to NameWithComma() or until the ParticularProcessPriorityManager is + * destroyed, whichever comes first. + */ + const nsAutoCString& NameWithComma(); + + ProcessPriority CurrentPriority(); + ProcessPriority ComputePriority(); + + enum TimeoutPref { + BACKGROUND_PERCEIVABLE_GRACE_PERIOD, + BACKGROUND_GRACE_PERIOD, + }; + + void ScheduleResetPriority(TimeoutPref aTimeoutPref); + void ResetPriority(); + void ResetPriorityNow(); + void SetPriorityNow(ProcessPriority aPriority); + + void BrowserPriorityChanged(BrowserParent* aBrowserParent, bool aPriority); + + void ShutDown(); + + NS_IMETHOD GetName(nsACString& aName) override { + aName.AssignLiteral("ParticularProcessPriorityManager"); + return NS_OK; + } + + private: + void FireTestOnlyObserverNotification(const char* aTopic, const char* aData); + + bool IsHoldingWakeLock(const nsAString& aTopic); + + ContentParent* mContentParent; + uint64_t mChildID; + ProcessPriority mPriority; + bool mHoldsCPUWakeLock; + bool mHoldsHighPriorityWakeLock; + bool mHoldsPlayingAudioWakeLock; + bool mHoldsPlayingVideoWakeLock; + + /** + * Used to implement NameWithComma(). + */ + nsAutoCString mNameWithComma; + + nsCOMPtr<nsITimer> mResetPriorityTimer; + + // This hashtable contains the list of high priority TabIds for this process. + nsTHashSet<uint64_t> mHighPriorityBrowserParents; +}; + +/* static */ +bool ProcessPriorityManagerImpl::sInitialized = false; +/* static */ +bool ProcessPriorityManagerImpl::sPrefListenersRegistered = false; +/* static */ +StaticRefPtr<ProcessPriorityManagerImpl> ProcessPriorityManagerImpl::sSingleton; + +NS_IMPL_ISUPPORTS(ProcessPriorityManagerImpl, nsIObserver, + nsISupportsWeakReference); + +/* static */ +void ProcessPriorityManagerImpl::PrefChangedCallback(const char* aPref, + void* aClosure) { + StaticInit(); + if (!PrefsEnabled() && sSingleton) { + sSingleton = nullptr; + sInitialized = false; + } +} + +/* static */ +bool ProcessPriorityManagerImpl::PrefsEnabled() { + return StaticPrefs::dom_ipc_processPriorityManager_enabled(); +} + +/* static */ +void ProcessPriorityManagerImpl::SetProcessPriorityIfEnabled( + int aPid, ProcessPriority aPriority) { + // The preference doesn't disable the process priority manager, but only its + // effect. This way the IPCs still happen and can be used to collect telemetry + // about CPU use. + if (PrefsEnabled()) { + hal::SetProcessPriority(aPid, aPriority); + } +} + +/* static */ +bool ProcessPriorityManagerImpl::TestMode() { + return StaticPrefs::dom_ipc_processPriorityManager_testMode(); +} + +/* static */ +void ProcessPriorityManagerImpl::StaticInit() { + if (sInitialized) { + return; + } + + // The process priority manager is main-process only. + if (!XRE_IsParentProcess()) { + sInitialized = true; + return; + } + + // Run StaticInit() again if the pref changes. We don't expect this to + // happen in normal operation, but it happens during testing. + if (!sPrefListenersRegistered) { + sPrefListenersRegistered = true; + Preferences::RegisterCallback(PrefChangedCallback, + "dom.ipc.processPriorityManager.enabled"); + } + + sInitialized = true; + + sSingleton = new ProcessPriorityManagerImpl(); + sSingleton->Init(); + ClearOnShutdown(&sSingleton); +} + +/* static */ +ProcessPriorityManagerImpl* ProcessPriorityManagerImpl::GetSingleton() { + if (!sSingleton) { + StaticInit(); + } + + return sSingleton; +} + +ProcessPriorityManagerImpl::ProcessPriorityManagerImpl() { + MOZ_ASSERT(XRE_IsParentProcess()); +} + +ProcessPriorityManagerImpl::~ProcessPriorityManagerImpl() = default; + +void ProcessPriorityManagerImpl::Init() { + LOG("Starting up. This is the parent process."); + + // The parent process's priority never changes; set it here and then forget + // about it. We'll manage only subprocesses' priorities using the process + // priority manager. + SetProcessPriorityIfEnabled(getpid(), PROCESS_PRIORITY_PARENT_PROCESS); + + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + if (os) { + os->AddObserver(this, "ipc:content-shutdown", /* ownsWeak */ true); + } +} + +NS_IMETHODIMP +ProcessPriorityManagerImpl::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + nsDependentCString topic(aTopic); + if (topic.EqualsLiteral("ipc:content-shutdown")) { + ObserveContentParentDestroyed(aSubject); + } else { + MOZ_ASSERT(false); + } + + return NS_OK; +} + +already_AddRefed<ParticularProcessPriorityManager> +ProcessPriorityManagerImpl::GetParticularProcessPriorityManager( + ContentParent* aContentParent) { + // If this content parent is already being shut down, there's no + // need to adjust its priority. + if (aContentParent->IsDead()) { + return nullptr; + } + + const uint64_t cpId = aContentParent->ChildID(); + return mParticularManagers.WithEntryHandle(cpId, [&](auto&& entry) { + if (!entry) { + entry.Insert(new ParticularProcessPriorityManager(aContentParent)); + entry.Data()->Init(); + } + return do_AddRef(entry.Data()); + }); +} + +void ProcessPriorityManagerImpl::SetProcessPriority( + ContentParent* aContentParent, ProcessPriority aPriority) { + MOZ_ASSERT(aContentParent); + if (RefPtr pppm = GetParticularProcessPriorityManager(aContentParent)) { + pppm->SetPriorityNow(aPriority); + } +} + +void ProcessPriorityManagerImpl::ObserveContentParentDestroyed( + nsISupports* aSubject) { + nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject); + NS_ENSURE_TRUE_VOID(props); + + uint64_t childID = CONTENT_PROCESS_ID_UNKNOWN; + props->GetPropertyAsUint64(u"childID"_ns, &childID); + NS_ENSURE_TRUE_VOID(childID != CONTENT_PROCESS_ID_UNKNOWN); + + if (auto entry = mParticularManagers.Lookup(childID)) { + entry.Data()->ShutDown(); + mHighPriorityChildIDs.Remove(childID); + entry.Remove(); + } +} + +void ProcessPriorityManagerImpl::NotifyProcessPriorityChanged( + ParticularProcessPriorityManager* aParticularManager, + ProcessPriority aOldPriority) { + ProcessPriority newPriority = aParticularManager->CurrentPriority(); + + if (newPriority >= PROCESS_PRIORITY_FOREGROUND_HIGH && + aOldPriority < PROCESS_PRIORITY_FOREGROUND_HIGH) { + mHighPriorityChildIDs.Insert(aParticularManager->ChildID()); + } else if (newPriority < PROCESS_PRIORITY_FOREGROUND_HIGH && + aOldPriority >= PROCESS_PRIORITY_FOREGROUND_HIGH) { + mHighPriorityChildIDs.Remove(aParticularManager->ChildID()); + } +} + +static nsCString BCToString(dom::CanonicalBrowsingContext* aBC) { + nsCOMPtr<nsIURI> uri = aBC->GetCurrentURI(); + return nsPrintfCString("id=%" PRIu64 " uri=%s active=%d pactive=%d", + aBC->Id(), + uri ? uri->GetSpecOrDefault().get() : "(no uri)", + aBC->IsActive(), aBC->IsPriorityActive()); +} + +void ProcessPriorityManagerImpl::BrowserPriorityChanged( + dom::CanonicalBrowsingContext* aBC, bool aPriority) { + MOZ_ASSERT(aBC->IsTop()); + + LOG("BrowserPriorityChanged(%s, %d)\n", BCToString(aBC).get(), aPriority); + + bool alreadyActive = aBC->IsPriorityActive(); + if (alreadyActive == aPriority) { + return; + } + + Telemetry::ScalarAdd( + Telemetry::ScalarID::DOM_CONTENTPROCESS_OS_PRIORITY_CHANGE_CONSIDERED, 1); + + aBC->SetPriorityActive(aPriority); + + aBC->PreOrderWalk([&](BrowsingContext* aContext) { + CanonicalBrowsingContext* canonical = aContext->Canonical(); + LOG("PreOrderWalk for %p: %p -> %p, %p\n", aBC, canonical, + canonical->GetContentParent(), canonical->GetBrowserParent()); + if (ContentParent* cp = canonical->GetContentParent()) { + if (RefPtr pppm = GetParticularProcessPriorityManager(cp)) { + if (auto* bp = canonical->GetBrowserParent()) { + pppm->BrowserPriorityChanged(bp, aPriority); + } + } + } + }); +} + +void ProcessPriorityManagerImpl::BrowserPriorityChanged( + BrowserParent* aBrowserParent, bool aPriority) { + LOG("BrowserPriorityChanged(bp=%p, %d)\n", aBrowserParent, aPriority); + + if (RefPtr pppm = + GetParticularProcessPriorityManager(aBrowserParent->Manager())) { + Telemetry::ScalarAdd( + Telemetry::ScalarID::DOM_CONTENTPROCESS_OS_PRIORITY_CHANGE_CONSIDERED, + 1); + pppm->BrowserPriorityChanged(aBrowserParent, aPriority); + } +} + +void ProcessPriorityManagerImpl::ResetPriority(ContentParent* aContentParent) { + if (RefPtr pppm = GetParticularProcessPriorityManager(aContentParent)) { + pppm->ResetPriority(); + } +} + +NS_IMPL_ISUPPORTS(ParticularProcessPriorityManager, nsITimerCallback, + nsISupportsWeakReference, nsINamed); + +ParticularProcessPriorityManager::ParticularProcessPriorityManager( + ContentParent* aContentParent) + : mContentParent(aContentParent), + mChildID(aContentParent->ChildID()), + mPriority(PROCESS_PRIORITY_UNKNOWN), + mHoldsCPUWakeLock(false), + mHoldsHighPriorityWakeLock(false), + mHoldsPlayingAudioWakeLock(false), + mHoldsPlayingVideoWakeLock(false) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_RELEASE_ASSERT(!aContentParent->IsDead()); + LOGP("Creating ParticularProcessPriorityManager."); + // Our static analysis doesn't allow capturing ref-counted pointers in + // lambdas, so we need to hide it in a uintptr_t. This is safe because this + // lambda will be destroyed in ~ParticularProcessPriorityManager(). + uintptr_t self = reinterpret_cast<uintptr_t>(this); + profiler_add_state_change_callback( + AllProfilingStates(), + [self](ProfilingState aProfilingState) { + const ParticularProcessPriorityManager* selfPtr = + reinterpret_cast<const ParticularProcessPriorityManager*>(self); + PROFILER_MARKER("Subprocess Priority", OTHER, + MarkerThreadId::MainThread(), SubProcessPriority, + selfPtr->Pid(), + ProfilerString8View::WrapNullTerminatedString( + ProcessPriorityToString(selfPtr->mPriority)), + aProfilingState); + }, + self); +} + +void ParticularProcessPriorityManager::Init() { + RegisterWakeLockObserver(this); + + // This process may already hold the CPU lock; for example, our parent may + // have acquired it on our behalf. + mHoldsCPUWakeLock = IsHoldingWakeLock(u"cpu"_ns); + mHoldsHighPriorityWakeLock = IsHoldingWakeLock(u"high-priority"_ns); + mHoldsPlayingAudioWakeLock = IsHoldingWakeLock(u"audio-playing"_ns); + mHoldsPlayingVideoWakeLock = IsHoldingWakeLock(u"video-playing"_ns); + + LOGP( + "Done starting up. mHoldsCPUWakeLock=%d, " + "mHoldsHighPriorityWakeLock=%d, mHoldsPlayingAudioWakeLock=%d, " + "mHoldsPlayingVideoWakeLock=%d", + mHoldsCPUWakeLock, mHoldsHighPriorityWakeLock, mHoldsPlayingAudioWakeLock, + mHoldsPlayingVideoWakeLock); +} + +bool ParticularProcessPriorityManager::IsHoldingWakeLock( + const nsAString& aTopic) { + WakeLockInformation info; + GetWakeLockInfo(aTopic, &info); + return info.lockingProcesses().Contains(ChildID()); +} + +ParticularProcessPriorityManager::~ParticularProcessPriorityManager() { + LOGP("Destroying ParticularProcessPriorityManager."); + + profiler_remove_state_change_callback(reinterpret_cast<uintptr_t>(this)); + + ShutDown(); +} + +/* virtual */ +void ParticularProcessPriorityManager::Notify( + const WakeLockInformation& aInfo) { + if (!mContentParent) { + // We've been shut down. + return; + } + + bool* dest = nullptr; + if (aInfo.topic().EqualsLiteral("cpu")) { + dest = &mHoldsCPUWakeLock; + } else if (aInfo.topic().EqualsLiteral("high-priority")) { + dest = &mHoldsHighPriorityWakeLock; + } else if (aInfo.topic().EqualsLiteral("audio-playing")) { + dest = &mHoldsPlayingAudioWakeLock; + } else if (aInfo.topic().EqualsLiteral("video-playing")) { + dest = &mHoldsPlayingVideoWakeLock; + } + + if (dest) { + bool thisProcessLocks = aInfo.lockingProcesses().Contains(ChildID()); + if (thisProcessLocks != *dest) { + *dest = thisProcessLocks; + LOGP( + "Got wake lock changed event. " + "Now mHoldsCPUWakeLock=%d, mHoldsHighPriorityWakeLock=%d, " + "mHoldsPlayingAudioWakeLock=%d, mHoldsPlayingVideoWakeLock=%d", + mHoldsCPUWakeLock, mHoldsHighPriorityWakeLock, + mHoldsPlayingAudioWakeLock, mHoldsPlayingVideoWakeLock); + ResetPriority(); + } + } +} + +uint64_t ParticularProcessPriorityManager::ChildID() const { + // We have to cache mContentParent->ChildID() instead of getting it from the + // ContentParent each time because after ShutDown() is called, mContentParent + // is null. If we didn't cache ChildID(), then we wouldn't be able to run + // LOGP() after ShutDown(). + return mChildID; +} + +int32_t ParticularProcessPriorityManager::Pid() const { + return mContentParent ? mContentParent->Pid() : -1; +} + +const nsAutoCString& ParticularProcessPriorityManager::NameWithComma() { + mNameWithComma.Truncate(); + if (!mContentParent) { + return mNameWithComma; // empty string + } + + nsAutoString name; + mContentParent->FriendlyName(name); + if (name.IsEmpty()) { + return mNameWithComma; // empty string + } + + CopyUTF16toUTF8(name, mNameWithComma); + mNameWithComma.AppendLiteral(", "); + return mNameWithComma; +} + +void ParticularProcessPriorityManager::ResetPriority() { + ProcessPriority processPriority = ComputePriority(); + if (mPriority == PROCESS_PRIORITY_UNKNOWN || mPriority > processPriority) { + // Apps set at a perceivable background priority are often playing media. + // Most media will have short gaps while changing tracks between songs, + // switching videos, etc. Give these apps a longer grace period so they + // can get their next track started, if there is one, before getting + // downgraded. + if (mPriority == PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE) { + ScheduleResetPriority(BACKGROUND_PERCEIVABLE_GRACE_PERIOD); + } else { + ScheduleResetPriority(BACKGROUND_GRACE_PERIOD); + } + return; + } + + SetPriorityNow(processPriority); +} + +void ParticularProcessPriorityManager::ResetPriorityNow() { + SetPriorityNow(ComputePriority()); +} + +void ParticularProcessPriorityManager::ScheduleResetPriority( + TimeoutPref aTimeoutPref) { + if (mResetPriorityTimer) { + LOGP("ScheduleResetPriority bailing; the timer is already running."); + return; + } + + uint32_t timeout = 0; + switch (aTimeoutPref) { + case BACKGROUND_PERCEIVABLE_GRACE_PERIOD: + timeout = StaticPrefs:: + dom_ipc_processPriorityManager_backgroundPerceivableGracePeriodMS(); + break; + case BACKGROUND_GRACE_PERIOD: + timeout = + StaticPrefs::dom_ipc_processPriorityManager_backgroundGracePeriodMS(); + break; + default: + MOZ_ASSERT(false, "Unrecognized timeout pref"); + break; + } + + LOGP("Scheduling reset timer to fire in %dms.", timeout); + NS_NewTimerWithCallback(getter_AddRefs(mResetPriorityTimer), this, timeout, + nsITimer::TYPE_ONE_SHOT); +} + +NS_IMETHODIMP +ParticularProcessPriorityManager::Notify(nsITimer* aTimer) { + LOGP("Reset priority timer callback; about to ResetPriorityNow."); + ResetPriorityNow(); + mResetPriorityTimer = nullptr; + return NS_OK; +} + +ProcessPriority ParticularProcessPriorityManager::CurrentPriority() { + return mPriority; +} + +ProcessPriority ParticularProcessPriorityManager::ComputePriority() { + if (!mHighPriorityBrowserParents.IsEmpty() || + mContentParent->GetRemoteType() == EXTENSION_REMOTE_TYPE || + mHoldsPlayingAudioWakeLock) { + return PROCESS_PRIORITY_FOREGROUND; + } + + if (mHoldsCPUWakeLock || mHoldsHighPriorityWakeLock || + mHoldsPlayingVideoWakeLock) { + return PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE; + } + + return PROCESS_PRIORITY_BACKGROUND; +} + +#ifdef XP_MACOSX +// Method used for setting QoS levels on background main threads. +static bool PriorityUsesLowPowerMainThread( + const hal::ProcessPriority& aPriority) { + return aPriority == hal::PROCESS_PRIORITY_BACKGROUND || + aPriority == hal::PROCESS_PRIORITY_PREALLOC; +} +// Method reduces redundancy in pref check while addressing the edge case +// where a pref is flipped to false during active browser use. +static bool PrefsUseLowPriorityThreads() { + return StaticPrefs::threads_use_low_power_enabled() && + StaticPrefs::threads_lower_mainthread_priority_in_background_enabled(); +} +#endif + +void ParticularProcessPriorityManager::SetPriorityNow( + ProcessPriority aPriority) { + if (aPriority == PROCESS_PRIORITY_UNKNOWN) { + MOZ_ASSERT(false); + return; + } + + LOGP("Changing priority from %s to %s (cp=%p).", + ProcessPriorityToString(mPriority), ProcessPriorityToString(aPriority), + mContentParent); + + if (!mContentParent || mPriority == aPriority) { + return; + } + + PROFILER_MARKER( + "Subprocess Priority", OTHER, + MarkerOptions(MarkerThreadId::MainThread(), MarkerStack::Capture()), + SubProcessPriorityChange, this->Pid(), + ProfilerString8View::WrapNullTerminatedString( + ProcessPriorityToString(mPriority)), + ProfilerString8View::WrapNullTerminatedString( + ProcessPriorityToString(aPriority))); + + ProcessPriority oldPriority = mPriority; + + mPriority = aPriority; + + // We skip incrementing the DOM_CONTENTPROCESS_OS_PRIORITY_RAISED if we're + // transitioning from the PROCESS_PRIORITY_UNKNOWN level, which is where + // we initialize at. + if (oldPriority < mPriority && oldPriority != PROCESS_PRIORITY_UNKNOWN) { + Telemetry::ScalarAdd( + Telemetry::ScalarID::DOM_CONTENTPROCESS_OS_PRIORITY_RAISED, 1); + } else if (oldPriority > mPriority) { + Telemetry::ScalarAdd( + Telemetry::ScalarID::DOM_CONTENTPROCESS_OS_PRIORITY_LOWERED, 1); + } + + ProcessPriorityManagerImpl::SetProcessPriorityIfEnabled(Pid(), mPriority); + + if (oldPriority != mPriority) { + ProcessPriorityManagerImpl::GetSingleton()->NotifyProcessPriorityChanged( + this, oldPriority); + +#ifdef XP_MACOSX + // In cases where we have low-power threads enabled (such as on MacOS) we + // can go ahead and put the main thread in the background here. If the new + // priority is the background priority, we can tell the OS to put the main + // thread on low-power cores. Alternately, if we are changing from the + // background to a higher priority, we change the main thread back to its + // normal state. + // + // The messages for this will be relayed using the ProcessHangMonitor such + // that the priority can be raised even if the main thread is unresponsive. + if (PriorityUsesLowPowerMainThread(mPriority) != + (PriorityUsesLowPowerMainThread(oldPriority))) { + if (PriorityUsesLowPowerMainThread(mPriority) && + PrefsUseLowPriorityThreads()) { + mContentParent->SetMainThreadQoSPriority(nsIThread::QOS_PRIORITY_LOW); + } else if (PriorityUsesLowPowerMainThread(oldPriority)) { + // In the event that the user changes prefs while tabs are in the + // background, we still want to have the ability to put the main thread + // back in the foreground to keep tabs from being stuck in the + // background priority. + mContentParent->SetMainThreadQoSPriority( + nsIThread::QOS_PRIORITY_NORMAL); + } + } +#endif + + Unused << mContentParent->SendNotifyProcessPriorityChanged(mPriority); + } + + FireTestOnlyObserverNotification("process-priority-set", + ProcessPriorityToString(mPriority)); +} + +void ParticularProcessPriorityManager::BrowserPriorityChanged( + BrowserParent* aBrowserParent, bool aPriority) { + MOZ_ASSERT(aBrowserParent); + + if (!aPriority) { + mHighPriorityBrowserParents.Remove(aBrowserParent->GetTabId()); + } else { + mHighPriorityBrowserParents.Insert(aBrowserParent->GetTabId()); + } + + ResetPriority(); +} + +void ParticularProcessPriorityManager::ShutDown() { + LOGP("shutdown for %p (mContentParent %p)", this, mContentParent); + + // Unregister our wake lock observer if ShutDown hasn't been called. (The + // wake lock observer takes raw refs, so we don't want to take chances here!) + // We don't call UnregisterWakeLockObserver unconditionally because the code + // will print a warning if it's called unnecessarily. + if (mContentParent) { + UnregisterWakeLockObserver(this); + } + + if (mResetPriorityTimer) { + mResetPriorityTimer->Cancel(); + mResetPriorityTimer = nullptr; + } + + mContentParent = nullptr; +} + +void ProcessPriorityManagerImpl::FireTestOnlyObserverNotification( + const char* aTopic, const nsACString& aData) { + if (!TestMode()) { + return; + } + + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + NS_ENSURE_TRUE_VOID(os); + + nsPrintfCString topic("process-priority-manager:TEST-ONLY:%s", aTopic); + + LOG("Notifying observer %s, data %s", topic.get(), + PromiseFlatCString(aData).get()); + os->NotifyObservers(nullptr, topic.get(), NS_ConvertUTF8toUTF16(aData).get()); +} + +void ParticularProcessPriorityManager::FireTestOnlyObserverNotification( + const char* aTopic, const char* aData) { + MOZ_ASSERT(aData, "Pass in data"); + + if (!ProcessPriorityManagerImpl::TestMode()) { + return; + } + + nsAutoCString data(nsPrintfCString("%" PRIu64, ChildID())); + data.Append(':'); + data.AppendASCII(aData); + + // ProcessPriorityManagerImpl::GetSingleton() is guaranteed not to return + // null, since ProcessPriorityManagerImpl is the only class which creates + // ParticularProcessPriorityManagers. + + ProcessPriorityManagerImpl::GetSingleton()->FireTestOnlyObserverNotification( + aTopic, data); +} + +StaticRefPtr<ProcessPriorityManagerChild> + ProcessPriorityManagerChild::sSingleton; + +/* static */ +void ProcessPriorityManagerChild::StaticInit() { + if (!sSingleton) { + sSingleton = new ProcessPriorityManagerChild(); + sSingleton->Init(); + ClearOnShutdown(&sSingleton); + } +} + +/* static */ +ProcessPriorityManagerChild* ProcessPriorityManagerChild::Singleton() { + StaticInit(); + return sSingleton; +} + +NS_IMPL_ISUPPORTS(ProcessPriorityManagerChild, nsIObserver) + +ProcessPriorityManagerChild::ProcessPriorityManagerChild() { + if (XRE_IsParentProcess()) { + mCachedPriority = PROCESS_PRIORITY_PARENT_PROCESS; + } else { + mCachedPriority = PROCESS_PRIORITY_UNKNOWN; + } +} + +void ProcessPriorityManagerChild::Init() { + // The process priority should only be changed in child processes; don't even + // bother listening for changes if we're in the main process. + if (!XRE_IsParentProcess()) { + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + NS_ENSURE_TRUE_VOID(os); + os->AddObserver(this, "ipc:process-priority-changed", /* weak = */ false); + } +} + +NS_IMETHODIMP +ProcessPriorityManagerChild::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + MOZ_ASSERT(!strcmp(aTopic, "ipc:process-priority-changed")); + + nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject); + NS_ENSURE_TRUE(props, NS_OK); + + int32_t priority = static_cast<int32_t>(PROCESS_PRIORITY_UNKNOWN); + props->GetPropertyAsInt32(u"priority"_ns, &priority); + NS_ENSURE_TRUE(ProcessPriority(priority) != PROCESS_PRIORITY_UNKNOWN, NS_OK); + + mCachedPriority = static_cast<ProcessPriority>(priority); + + return NS_OK; +} + +bool ProcessPriorityManagerChild::CurrentProcessIsForeground() { + return mCachedPriority == PROCESS_PRIORITY_UNKNOWN || + mCachedPriority >= PROCESS_PRIORITY_FOREGROUND; +} + +} // namespace + +namespace mozilla { + +/* static */ +void ProcessPriorityManager::Init() { + ProcessPriorityManagerImpl::StaticInit(); + ProcessPriorityManagerChild::StaticInit(); +} + +/* static */ +void ProcessPriorityManager::SetProcessPriority(ContentParent* aContentParent, + ProcessPriority aPriority) { + MOZ_ASSERT(aContentParent); + MOZ_ASSERT(aContentParent->Pid() != -1); + + ProcessPriorityManagerImpl* singleton = + ProcessPriorityManagerImpl::GetSingleton(); + if (singleton) { + singleton->SetProcessPriority(aContentParent, aPriority); + } +} + +/* static */ +bool ProcessPriorityManager::CurrentProcessIsForeground() { + return ProcessPriorityManagerChild::Singleton()->CurrentProcessIsForeground(); +} + +/* static */ +void ProcessPriorityManager::BrowserPriorityChanged( + CanonicalBrowsingContext* aBC, bool aPriority) { + if (auto* singleton = ProcessPriorityManagerImpl::GetSingleton()) { + singleton->BrowserPriorityChanged(aBC, aPriority); + } +} + +/* static */ +void ProcessPriorityManager::BrowserPriorityChanged( + BrowserParent* aBrowserParent, bool aPriority) { + MOZ_ASSERT(aBrowserParent); + + ProcessPriorityManagerImpl* singleton = + ProcessPriorityManagerImpl::GetSingleton(); + if (!singleton) { + return; + } + singleton->BrowserPriorityChanged(aBrowserParent, aPriority); +} + +/* static */ +void ProcessPriorityManager::RemoteBrowserFrameShown( + nsFrameLoader* aFrameLoader) { + ProcessPriorityManagerImpl* singleton = + ProcessPriorityManagerImpl::GetSingleton(); + if (!singleton) { + return; + } + + BrowserParent* bp = BrowserParent::GetFrom(aFrameLoader); + NS_ENSURE_TRUE_VOID(bp); + + MOZ_ASSERT(XRE_IsParentProcess()); + + // Ignore calls that aren't from a Browser. + if (!aFrameLoader->OwnerIsMozBrowserFrame()) { + return; + } + + singleton->ResetPriority(bp->Manager()); +} + +} // namespace mozilla diff --git a/dom/ipc/ProcessPriorityManager.h b/dom/ipc/ProcessPriorityManager.h new file mode 100644 index 0000000000..8153a60993 --- /dev/null +++ b/dom/ipc/ProcessPriorityManager.h @@ -0,0 +1,95 @@ +/* -*- 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_ProcessPriorityManager_h_ +#define mozilla_ProcessPriorityManager_h_ + +#include "mozilla/HalTypes.h" + +class nsFrameLoader; + +namespace mozilla { +namespace dom { +class BrowserParent; +class CanonicalBrowsingContext; +class ContentParent; +} // namespace dom + +/** + * This class sets the priority of subprocesses in response to explicit + * requests and events in the system. + * + * A process's priority changes e.g. when it goes into the background via + * mozbrowser's setVisible(false). Process priority affects CPU scheduling and + * also which processes get killed when we run out of memory. + * + * After you call Initialize(), the only thing you probably have to do is call + * SetProcessPriority on processes immediately after creating them in order to + * set their initial priority. The ProcessPriorityManager takes care of the + * rest. + */ +class ProcessPriorityManager final { + public: + /** + * Initialize the ProcessPriorityManager machinery, causing the + * ProcessPriorityManager to actively manage the priorities of all + * subprocesses. You should call this before creating any subprocesses. + * + * You should also call this function even if you're in a child process, + * since it will initialize ProcessPriorityManagerChild. + */ + static void Init(); + + /** + * Set the process priority of a given ContentParent's process. + * + * Note that because this method takes a ContentParent*, you can only set the + * priority of your subprocesses. In fact, because we don't support nested + * content processes (bug 761935), you can only call this method from the + * main process. + * + * It probably only makes sense to call this function immediately after a + * process is created. At this point, the process priority manager doesn't + * have enough context about the processs to know what its priority should + * be. + * + * Eventually whatever priority you set here can and probably will be + * overwritten by the process priority manager. + */ + static void SetProcessPriority(dom::ContentParent* aContentParent, + hal::ProcessPriority aPriority); + + /** + * Returns true iff this process's priority is FOREGROUND*. + * + * Note that because process priorities are set in the main process, it's + * possible for this method to return a stale value. So be careful about + * what you use this for. + */ + static bool CurrentProcessIsForeground(); + + /** + * Updates the contents of mHighPriorityBrowserParents to keep track of + * the list of TabIds for this process that are high priority. + */ + static void BrowserPriorityChanged(dom::CanonicalBrowsingContext* aBC, + bool aPriority); + static void BrowserPriorityChanged(dom::BrowserParent* aBrowserParent, + bool aPriority); + + static void RemoteBrowserFrameShown(nsFrameLoader* aFrameLoader); + + private: + ProcessPriorityManager(); + ProcessPriorityManager(const ProcessPriorityManager&) = delete; + + const ProcessPriorityManager& operator=(const ProcessPriorityManager&) = + delete; +}; + +} // namespace mozilla + +#endif diff --git a/dom/ipc/PropertyBagUtils.cpp b/dom/ipc/PropertyBagUtils.cpp new file mode 100644 index 0000000000..6c1b3e8fea --- /dev/null +++ b/dom/ipc/PropertyBagUtils.cpp @@ -0,0 +1,261 @@ +/* -*- 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 "PropertyBagUtils.h" + +#include "mozilla/SimpleEnumerator.h" +#include "mozilla/dom/DOMTypes.h" +#include "nsCOMPtr.h" +#include "nsHashPropertyBag.h" +#include "nsID.h" +#include "nsIProperty.h" +#include "nsIURI.h" +#include "nsVariant.h" + +using namespace IPC; +using namespace mozilla::dom; + +namespace mozilla::ipc { + +void IPDLParamTraits<nsIVariant*>::Write(MessageWriter* aWriter, + IProtocol* aActor, + nsIVariant* aParam) { + IDPLVariant variant; + + variant.type() = aParam->GetDataType(); + + switch (variant.type()) { + case nsIDataType::VTYPE_INT8: + case nsIDataType::VTYPE_UINT8: + case nsIDataType::VTYPE_CHAR: { + uint8_t value; + MOZ_ALWAYS_SUCCEEDS(aParam->GetAsUint8(&value)); + variant.data() = value; + break; + } + case nsIDataType::VTYPE_WCHAR: + case nsIDataType::VTYPE_INT16: { + int16_t value; + MOZ_ALWAYS_SUCCEEDS(aParam->GetAsInt16(&value)); + variant.data() = value; + break; + } + case nsIDataType::VTYPE_UINT16: { + uint16_t value; + MOZ_ALWAYS_SUCCEEDS(aParam->GetAsUint16(&value)); + variant.data() = value; + break; + } + case nsIDataType::VTYPE_INT32: { + int32_t value; + MOZ_ALWAYS_SUCCEEDS(aParam->GetAsInt32(&value)); + variant.data() = value; + break; + } + case nsIDataType::VTYPE_UINT32: { + uint32_t value; + MOZ_ALWAYS_SUCCEEDS(aParam->GetAsUint32(&value)); + variant.data() = value; + break; + } + case nsIDataType::VTYPE_FLOAT: { + float value; + MOZ_ALWAYS_SUCCEEDS(aParam->GetAsFloat(&value)); + variant.data() = value; + break; + } + case nsIDataType::VTYPE_DOUBLE: { + double value; + MOZ_ALWAYS_SUCCEEDS(aParam->GetAsDouble(&value)); + variant.data() = value; + break; + } + case nsIDataType::VTYPE_BOOL: { + bool value; + MOZ_ALWAYS_SUCCEEDS(aParam->GetAsBool(&value)); + variant.data() = value; + break; + } + case nsIDataType::VTYPE_ID: { + nsID value; + MOZ_ALWAYS_SUCCEEDS(aParam->GetAsID(&value)); + variant.data() = value; + break; + } + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: { + nsString value; + MOZ_ALWAYS_SUCCEEDS(aParam->GetAsAString(value)); + variant.data() = value; + break; + } + case nsIDataType::VTYPE_CSTRING: + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_STRING_SIZE_IS: + case nsIDataType::VTYPE_UTF8STRING: { + nsCString value; + MOZ_ALWAYS_SUCCEEDS(aParam->GetAsACString(value)); + variant.data() = value; + break; + } + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: { + nsIID* iid; + nsCOMPtr<nsISupports> value; + MOZ_ALWAYS_SUCCEEDS(aParam->GetAsInterface(&iid, getter_AddRefs(value))); + free(iid); + // We only accept nsIURI and nsIPrincipal interface types, patch welcome. + if (nsCOMPtr<nsIURI> uri = do_QueryInterface(value)) { + variant.data() = uri; + } else if (nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(value)) { + variant.data() = principal; + } else if (value) { + variant.type() = nsIDataType::VTYPE_EMPTY; + variant.data() = false; // because we need something. + } else { + // Let's pretend like we had a null URI, though how do we know + // it wasn't a null principal? + variant.data() = (nsIURI*)nullptr; + } + break; + } + case nsIDataType::VTYPE_VOID: + case nsIDataType::VTYPE_EMPTY: + variant.data() = false; // because we need something. + break; + default: + MOZ_CRASH("Non handled variant type, patch welcome"); + break; + } + WriteIPDLParam(aWriter, aActor, variant); +} + +bool IPDLParamTraits<nsIVariant*>::Read(MessageReader* aReader, + IProtocol* aActor, + RefPtr<nsIVariant>* aResult) { + IDPLVariant value; + if (!ReadIPDLParam(aReader, aActor, &value)) { + return false; + } + + auto variant = MakeRefPtr<nsVariant>(); + + switch (value.type()) { + case nsIDataType::VTYPE_INT8: + case nsIDataType::VTYPE_UINT8: + if (value.type() == nsIDataType::VTYPE_INT8) { + variant->SetAsInt8(value.data().get_uint8_t()); + } else { + variant->SetAsUint8(value.data().get_uint8_t()); + } + break; + case nsIDataType::VTYPE_INT16: + variant->SetAsInt16(value.data().get_int16_t()); + break; + case nsIDataType::VTYPE_INT32: + variant->SetAsInt32(value.data().get_int32_t()); + break; + case nsIDataType::VTYPE_UINT16: + variant->SetAsUint16(value.data().get_uint16_t()); + break; + case nsIDataType::VTYPE_UINT32: + variant->SetAsUint32(value.data().get_uint32_t()); + break; + case nsIDataType::VTYPE_FLOAT: + variant->SetAsFloat(value.data().get_float()); + break; + case nsIDataType::VTYPE_DOUBLE: + variant->SetAsDouble(value.data().get_double()); + break; + case nsIDataType::VTYPE_BOOL: + variant->SetAsBool(value.data().get_bool()); + break; + case nsIDataType::VTYPE_CHAR: + variant->SetAsChar(value.data().get_uint8_t()); + break; + case nsIDataType::VTYPE_WCHAR: + variant->SetAsWChar(value.data().get_int16_t()); + break; + case nsIDataType::VTYPE_ID: + variant->SetAsID(value.data().get_nsID()); + break; + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + variant->SetAsAString(value.data().get_nsString()); + break; + case nsIDataType::VTYPE_CSTRING: + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_STRING_SIZE_IS: + variant->SetAsACString(value.data().get_nsCString()); + break; + case nsIDataType::VTYPE_UTF8STRING: + variant->SetAsAUTF8String(value.data().get_nsCString()); + break; + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + if (value.data().type() == IPDLVariantValue::TnsIURI) { + variant->SetAsISupports(value.data().get_nsIURI()); + } else if (value.data().type() == IPDLVariantValue::TnsIPrincipal) { + variant->SetAsISupports(value.data().get_nsIPrincipal()); + } else { + MOZ_CRASH("Unexpected interface type"); + } + break; + case nsIDataType::VTYPE_VOID: + variant->SetAsVoid(); + break; + case nsIDataType::VTYPE_EMPTY: + break; + default: + MOZ_CRASH("Non handled variant type, patch welcome"); + return false; + } + *aResult = std::move(variant); + return true; +} + +void IPDLParamTraits<nsIPropertyBag2*>::Write(MessageWriter* aWriter, + IProtocol* aActor, + nsIPropertyBag2* aParam) { + // We send a nsIPropertyBag as an array of IPDLProperty + nsTArray<IPDLProperty> bag; + + nsCOMPtr<nsISimpleEnumerator> enumerator; + if (aParam && + NS_SUCCEEDED(aParam->GetEnumerator(getter_AddRefs(enumerator)))) { + for (auto& property : SimpleEnumerator<nsIProperty>(enumerator)) { + nsString name; + nsCOMPtr<nsIVariant> value; + MOZ_ALWAYS_SUCCEEDS(property->GetName(name)); + MOZ_ALWAYS_SUCCEEDS(property->GetValue(getter_AddRefs(value))); + bag.AppendElement(IPDLProperty{name, value}); + } + } + WriteIPDLParam(aWriter, aActor, bag); +} + +bool IPDLParamTraits<nsIPropertyBag2*>::Read(MessageReader* aReader, + IProtocol* aActor, + RefPtr<nsIPropertyBag2>* aResult) { + nsTArray<IPDLProperty> bag; + if (!ReadIPDLParam(aReader, aActor, &bag)) { + return false; + } + + auto properties = MakeRefPtr<nsHashPropertyBag>(); + + for (auto& entry : bag) { + nsCOMPtr<nsIVariant> variant = std::move(entry.value()); + MOZ_ALWAYS_SUCCEEDS( + properties->SetProperty(std::move(entry.name()), variant)); + } + *aResult = std::move(properties); + return true; +} + +} // namespace mozilla::ipc diff --git a/dom/ipc/PropertyBagUtils.h b/dom/ipc/PropertyBagUtils.h new file mode 100644 index 0000000000..7eb94b46a7 --- /dev/null +++ b/dom/ipc/PropertyBagUtils.h @@ -0,0 +1,38 @@ +/* -*- 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 IPC_PropertyBagUtils_h +#define IPC_PropertyBagUtils_h + +#include "mozilla/ipc/IPDLParamTraits.h" +#include "nsIPropertyBag2.h" +#include "nsIVariant.h" + +namespace mozilla::ipc { + +/** + * Limited nsIVariant support. Not all types are implemented and only + * nsIURI is implemented with nsIVariant::GetAsInterface. + */ +template <> +struct IPDLParamTraits<nsIVariant*> { + static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor, + nsIVariant* aParam); + static bool Read(IPC::MessageReader* aReader, IProtocol* aActor, + RefPtr<nsIVariant>* aResult); +}; + +template <> +struct IPDLParamTraits<nsIPropertyBag2*> { + static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor, + nsIPropertyBag2* aParam); + static bool Read(IPC::MessageReader* aReader, IProtocol* aActor, + RefPtr<nsIPropertyBag2>* aResult); +}; + +} // namespace mozilla::ipc + +#endif // mozilla_ipc_URIUtils_h diff --git a/dom/ipc/RefMessageBodyService.cpp b/dom/ipc/RefMessageBodyService.cpp new file mode 100644 index 0000000000..e340443a53 --- /dev/null +++ b/dom/ipc/RefMessageBodyService.cpp @@ -0,0 +1,161 @@ +/* -*- 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 "RefMessageBodyService.h" + +#include <cstdint> +#include <cstdlib> +#include "mozilla/ErrorResult.h" +#include "mozilla/RefPtr.h" +#include "mozilla/dom/ipc/StructuredCloneData.h" +#include "nsBaseHashtable.h" +#include "nsDebug.h" + +namespace mozilla::dom { + +// Guards sService and its members. +StaticMutex sRefMessageBodyServiceMutex; + +// Raw pointer because the service is kept alive by other objects. +// See the CTOR and the DTOR of this object. +RefMessageBodyService* sService; + +// static +already_AddRefed<RefMessageBodyService> RefMessageBodyService::GetOrCreate() { + StaticMutexAutoLock lock(sRefMessageBodyServiceMutex); + + RefPtr<RefMessageBodyService> service = GetOrCreateInternal(lock); + return service.forget(); +} + +// static +RefMessageBodyService* RefMessageBodyService::GetOrCreateInternal( + const StaticMutexAutoLock& aProofOfLock) { + if (!sService) { + sService = new RefMessageBodyService(aProofOfLock); + } + return sService; +} + +RefMessageBodyService::RefMessageBodyService( + const StaticMutexAutoLock& aProofOfLock) { + MOZ_DIAGNOSTIC_ASSERT(sService == nullptr); +} + +RefMessageBodyService::~RefMessageBodyService() { + StaticMutexAutoLock lock(sRefMessageBodyServiceMutex); + MOZ_DIAGNOSTIC_ASSERT(sService == this); + sService = nullptr; +} + +const nsID RefMessageBodyService::Register( + already_AddRefed<RefMessageBody> aBody, ErrorResult& aRv) { + RefPtr<RefMessageBody> body = aBody; + MOZ_ASSERT(body); + + nsID uuid = {}; + aRv = nsID::GenerateUUIDInPlace(uuid); + if (NS_WARN_IF(aRv.Failed())) { + return nsID(); + } + + StaticMutexAutoLock lock(sRefMessageBodyServiceMutex); + GetOrCreateInternal(lock)->mMessages.InsertOrUpdate(uuid, std::move(body)); + return uuid; +} + +already_AddRefed<RefMessageBody> RefMessageBodyService::Steal(const nsID& aID) { + StaticMutexAutoLock lock(sRefMessageBodyServiceMutex); + if (!sService) { + return nullptr; + } + + RefPtr<RefMessageBody> body; + sService->mMessages.Remove(aID, getter_AddRefs(body)); + + return body.forget(); +} + +already_AddRefed<RefMessageBody> RefMessageBodyService::GetAndCount( + const nsID& aID) { + StaticMutexAutoLock lock(sRefMessageBodyServiceMutex); + if (!sService) { + return nullptr; + } + + RefPtr<RefMessageBody> body = sService->mMessages.Get(aID); + if (!body) { + return nullptr; + } + + ++body->mCount; + + MOZ_ASSERT_IF(body->mMaxCount.isSome(), + body->mCount <= body->mMaxCount.value()); + if (body->mMaxCount.isSome() && body->mCount >= body->mMaxCount.value()) { + sService->mMessages.Remove(aID); + } + + return body.forget(); +} + +void RefMessageBodyService::SetMaxCount(const nsID& aID, uint32_t aMaxCount) { + StaticMutexAutoLock lock(sRefMessageBodyServiceMutex); + if (!sService) { + return; + } + + RefPtr<RefMessageBody> body = sService->mMessages.Get(aID); + if (!body) { + return; + } + + MOZ_ASSERT(body->mMaxCount.isNothing()); + body->mMaxCount.emplace(aMaxCount); + + MOZ_ASSERT(body->mCount <= body->mMaxCount.value()); + if (body->mCount >= body->mMaxCount.value()) { + sService->mMessages.Remove(aID); + } +} + +void RefMessageBodyService::ForgetPort(const nsID& aPortID) { + StaticMutexAutoLock lock(sRefMessageBodyServiceMutex); + if (!sService) { + return; + } + + for (auto iter = sService->mMessages.Iter(); !iter.Done(); iter.Next()) { + if (iter.UserData()->PortID() == aPortID) { + iter.Remove(); + } + } +} + +RefMessageBody::RefMessageBody(const nsID& aPortID, + UniquePtr<ipc::StructuredCloneData>&& aCloneData) + : mPortID(aPortID), + mMutex("RefMessageBody::mMutex"), + mCloneData(std::move(aCloneData)), + mMaxCount(Nothing()), + mCount(0) {} + +RefMessageBody::~RefMessageBody() = default; + +void RefMessageBody::Read(JSContext* aCx, JS::MutableHandle<JS::Value> aValue, + const JS::CloneDataPolicy& aCloneDataPolicy, + ErrorResult& aRv) { + MutexAutoLock lock(mMutex); + mCloneData->Read(aCx, aValue, aCloneDataPolicy, aRv); +} + +bool RefMessageBody::TakeTransferredPortsAsSequence( + Sequence<OwningNonNull<mozilla::dom::MessagePort>>& aPorts) { + MOZ_ASSERT(mMaxCount.isNothing()); + return mCloneData->TakeTransferredPortsAsSequence(aPorts); +} + +} // namespace mozilla::dom diff --git a/dom/ipc/RefMessageBodyService.h b/dom/ipc/RefMessageBodyService.h new file mode 100644 index 0000000000..6cca2a836e --- /dev/null +++ b/dom/ipc/RefMessageBodyService.h @@ -0,0 +1,137 @@ +/* -*- 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_dom_RefMessageBodyService_h +#define mozilla_dom_RefMessageBodyService_h + +#include <cstdint> +#include "js/TypeDecls.h" +#include "mozilla/Maybe.h" +#include "mozilla/Mutex.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/UniquePtr.h" +#include "nsHashKeys.h" +#include "nsID.h" +#include "nsISupports.h" +#include "nsRefPtrHashtable.h" + +namespace JS { +class CloneDataPolicy; +} // namespace JS + +namespace mozilla { + +class ErrorResult; +template <class T> +class OwningNonNull; + +namespace dom { + +class MessagePort; +template <typename T> +class Sequence; + +namespace ipc { +class StructuredCloneData; +} + +/** + * At the time a BroadcastChannel or MessagePort sends messages, we don't know + * which process is going to receive it. Because of this, we need to check if + * the message is able to cross the process boundary. + * If the message contains objects such as SharedArrayBuffers, WASM modules or + * ImageBitmaps, it can be delivered on the current process only. + * Instead of sending the whole message via IPC, we send a unique ID, while the + * message is kept alive by RefMessageBodyService, on the current process using + * a ref-counted RefMessageBody object. + * When the receiver obtains the message ID, it checks if the local + * RefMessageBodyService knows that ID. If yes, the sender and the receiver are + * on the same process and the delivery can be completed; if not, a + * messageerror event has to be dispatched instead. + * + * For MessagePort communication is 1-to-1 and because of this, the + * receiver takes ownership of the message (RefMessageBodyService::Steal()). + * If the receiver port is on a different process, RefMessageBodyService will + * return a nullptr and a messageerror event will be dispatched. + + * For BroadcastChannel, the life-time of a message is a bit different than for + * MessagePort. It has a 1-to-many communication and we could have multiple + * receivers. Each one needs to deliver the message without taking full + * ownership of it. + * In order to support this feature, BroadcastChannel needs to call + * RefMessageBodyService::SetMaxCount() to inform how many ports are allowed to + * retrieve the current message, on the current process. Receivers on other + * processes are not kept in consideration because they will not be able to + * retrieve the message from RefMessageBodyService. When the last allowed + * port has called RefMessageBodyService::GetAndCount(), the message is + * released. + */ +class RefMessageBody final { + friend class RefMessageBodyService; + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RefMessageBody) + + RefMessageBody(const nsID& aPortID, + UniquePtr<ipc::StructuredCloneData>&& aCloneData); + + const nsID& PortID() const { return mPortID; } + + void Read(JSContext* aCx, JS::MutableHandle<JS::Value> aValue, + const JS::CloneDataPolicy& aCloneDataPolicy, ErrorResult& aRv); + + // This method can be called only if the RefMessageBody is not supposed to be + // ref-counted (see mMaxCount). + bool TakeTransferredPortsAsSequence( + Sequence<OwningNonNull<mozilla::dom::MessagePort>>& aPorts); + + private: + ~RefMessageBody(); + + const nsID mPortID; + + // In case the RefMessageBody is shared and refcounted (see mCount/mMaxCount), + // we must enforce that the reading does not happen simultaneously on + // different threads. + Mutex mMutex MOZ_UNANNOTATED; + + UniquePtr<ipc::StructuredCloneData> mCloneData; + + // When mCount reaches mMaxCount, this object is released by the service. + Maybe<uint32_t> mMaxCount; + uint32_t mCount; +}; + +class RefMessageBodyService final { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RefMessageBodyService) + + static already_AddRefed<RefMessageBodyService> GetOrCreate(); + + void ForgetPort(const nsID& aPortID); + + const nsID Register(already_AddRefed<RefMessageBody> aBody, ErrorResult& aRv); + + already_AddRefed<RefMessageBody> Steal(const nsID& aID); + + already_AddRefed<RefMessageBody> GetAndCount(const nsID& aID); + + void SetMaxCount(const nsID& aID, uint32_t aMaxCount); + + private: + explicit RefMessageBodyService(const StaticMutexAutoLock& aProofOfLock); + ~RefMessageBodyService(); + + static RefMessageBodyService* GetOrCreateInternal( + const StaticMutexAutoLock& aProofOfLock); + + nsRefPtrHashtable<nsIDHashKey, RefMessageBody> mMessages; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_RefMessageBodyService_h diff --git a/dom/ipc/ReferrerInfoUtils.cpp b/dom/ipc/ReferrerInfoUtils.cpp new file mode 100644 index 0000000000..a2728e342c --- /dev/null +++ b/dom/ipc/ReferrerInfoUtils.cpp @@ -0,0 +1,54 @@ +/* -*- 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 "mozilla/dom/ReferrerInfoUtils.h" + +#include "ipc/IPCMessageUtilsSpecializations.h" +#include "nsSerializationHelper.h" +#include "nsString.h" + +namespace IPC { + +void ParamTraits<nsIReferrerInfo*>::Write(MessageWriter* aWriter, + nsIReferrerInfo* aParam) { + bool isNull = !aParam; + WriteParam(aWriter, isNull); + if (isNull) { + return; + } + nsAutoCString infoString; + nsresult rv = NS_SerializeToString(aParam, infoString); + if (NS_FAILED(rv)) { + MOZ_CRASH("Unable to serialize referrer info."); + return; + } + WriteParam(aWriter, infoString); +} + +bool ParamTraits<nsIReferrerInfo*>::Read(MessageReader* aReader, + RefPtr<nsIReferrerInfo>* aResult) { + bool isNull; + if (!ReadParam(aReader, &isNull)) { + return false; + } + if (isNull) { + *aResult = nullptr; + return true; + } + nsAutoCString infoString; + if (!ReadParam(aReader, &infoString)) { + return false; + } + nsCOMPtr<nsISupports> iSupports; + nsresult rv = NS_DeserializeObject(infoString, getter_AddRefs(iSupports)); + NS_ENSURE_SUCCESS(rv, false); + nsCOMPtr<nsIReferrerInfo> referrerInfo = do_QueryInterface(iSupports); + NS_ENSURE_TRUE(referrerInfo, false); + *aResult = ToRefPtr(std::move(referrerInfo)); + return true; +} + +} // namespace IPC diff --git a/dom/ipc/ReferrerInfoUtils.h b/dom/ipc/ReferrerInfoUtils.h new file mode 100644 index 0000000000..9a17f9d148 --- /dev/null +++ b/dom/ipc/ReferrerInfoUtils.h @@ -0,0 +1,24 @@ +/* -*- 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_dom_referrer_info_utils_h__ +#define mozilla_dom_referrer_info_utils_h__ + +#include "ipc/IPCMessageUtils.h" +#include "nsCOMPtr.h" +#include "nsIReferrerInfo.h" + +namespace IPC { + +template <> +struct ParamTraits<nsIReferrerInfo*> { + static void Write(MessageWriter* aWriter, nsIReferrerInfo* aParam); + static bool Read(MessageReader* aReader, RefPtr<nsIReferrerInfo>* aResult); +}; + +} // namespace IPC + +#endif // mozilla_dom_referrer_info_utils_h__ diff --git a/dom/ipc/RemoteBrowser.cpp b/dom/ipc/RemoteBrowser.cpp new file mode 100644 index 0000000000..9fabb8e67f --- /dev/null +++ b/dom/ipc/RemoteBrowser.cpp @@ -0,0 +1,31 @@ +/* -*- 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 "RemoteBrowser.h" + +#include "nsFrameLoader.h" +#include "nsFrameLoaderOwner.h" +#include "nsQueryObject.h" + +namespace mozilla::dom { + +RemoteBrowser* RemoteBrowser::GetFrom(nsFrameLoader* aFrameLoader) { + if (!aFrameLoader) { + return nullptr; + } + return aFrameLoader->GetRemoteBrowser(); +} + +RemoteBrowser* RemoteBrowser::GetFrom(nsIContent* aContent) { + RefPtr<nsFrameLoaderOwner> loaderOwner = do_QueryObject(aContent); + if (!loaderOwner) { + return nullptr; + } + RefPtr<nsFrameLoader> frameLoader = loaderOwner->GetFrameLoader(); + return GetFrom(frameLoader); +} + +} // namespace mozilla::dom diff --git a/dom/ipc/RemoteBrowser.h b/dom/ipc/RemoteBrowser.h new file mode 100644 index 0000000000..7ea0b669fd --- /dev/null +++ b/dom/ipc/RemoteBrowser.h @@ -0,0 +1,72 @@ +/* -*- 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_dom_ipc_RemoteBrowser_h +#define mozilla_dom_ipc_RemoteBrowser_h + +#include "mozilla/dom/ipc/IdType.h" +#include "mozilla/layers/LayersTypes.h" +#include "nsISupports.h" +#include "nsRect.h" +#include "Units.h" + +class nsDocShellLoadState; +class nsFrameLoader; +class nsILoadContext; +class nsIContent; + +namespace mozilla::dom { + +class BrowserHost; +class BrowserBridgeHost; +class BrowsingContext; +class EffectsInfo; +class OwnerShowInfo; + +/** + * An interface to control a browser hosted in another process. + * + * This is used by nsFrameLoader to abstract between hosting a top-level remote + * browser in the chrome process and hosting an OOP-iframe in a content process. + * + * There are two concrete implementations that are used depending on whether + * the nsFrameLoader is in the chrome or content process. A chrome process + * nsFrameLoader will use BrowserHost, and a content process nsFrameLoader will + * use BrowserBridgeHost. + */ +class RemoteBrowser : public nsISupports { + public: + using LayersId = mozilla::layers::LayersId; + + static RemoteBrowser* GetFrom(nsFrameLoader* aFrameLoader); + static RemoteBrowser* GetFrom(nsIContent* aContent); + + // Try to cast this RemoteBrowser to a BrowserHost, may return null + virtual BrowserHost* AsBrowserHost() = 0; + // Try to cast this RemoteBrowser to a BrowserBridgeHost, may return null + virtual BrowserBridgeHost* AsBrowserBridgeHost() = 0; + + virtual TabId GetTabId() const = 0; + virtual LayersId GetLayersId() const = 0; + virtual BrowsingContext* GetBrowsingContext() const = 0; + virtual nsILoadContext* GetLoadContext() const = 0; + virtual bool CanRecv() const = 0; + + virtual void LoadURL(nsDocShellLoadState* aLoadState) = 0; + virtual void ResumeLoad(uint64_t aPendingSwitchId) = 0; + virtual void DestroyStart() = 0; + virtual void DestroyComplete() = 0; + + virtual bool Show(const OwnerShowInfo&) = 0; + virtual void UpdateDimensions(const nsIntRect& aRect, + const ScreenIntSize& aSize) = 0; + + virtual void UpdateEffects(EffectsInfo aInfo) = 0; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_ipc_RemoteBrowser_h diff --git a/dom/ipc/RemoteType.h b/dom/ipc/RemoteType.h new file mode 100644 index 0000000000..43183c7dee --- /dev/null +++ b/dom/ipc/RemoteType.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_dom_RemoteType_h +#define mozilla_dom_RemoteType_h + +#include "nsString.h" +#include "nsReadableUtils.h" + +// These must match the similar ones in E10SUtils.sys.mjs and ProcInfo.h and +// ChromeUtils.webidl Process names as reported by about:memory are defined in +// ContentChild:RecvRemoteType. Add your value there too or it will be called +// "Web Content". +#define PREALLOC_REMOTE_TYPE "prealloc"_ns +#define WEB_REMOTE_TYPE "web"_ns +#define FILE_REMOTE_TYPE "file"_ns +#define EXTENSION_REMOTE_TYPE "extension"_ns +#define PRIVILEGEDABOUT_REMOTE_TYPE "privilegedabout"_ns +#define PRIVILEGEDMOZILLA_REMOTE_TYPE "privilegedmozilla"_ns + +#define DEFAULT_REMOTE_TYPE WEB_REMOTE_TYPE + +// These must start with the WEB_REMOTE_TYPE above. +#define FISSION_WEB_REMOTE_TYPE "webIsolated"_ns +#define WITH_COOP_COEP_REMOTE_TYPE "webCOOP+COEP"_ns +#define WITH_COOP_COEP_REMOTE_TYPE_PREFIX "webCOOP+COEP="_ns +#define SERVICEWORKER_REMOTE_TYPE "webServiceWorker"_ns + +// Remote type value used to represent being non-remote. +#define NOT_REMOTE_TYPE VoidCString() + +#endif // mozilla_dom_RemoteType_h diff --git a/dom/ipc/RemoteWebProgressRequest.cpp b/dom/ipc/RemoteWebProgressRequest.cpp new file mode 100644 index 0000000000..72c80b3e6b --- /dev/null +++ b/dom/ipc/RemoteWebProgressRequest.cpp @@ -0,0 +1,270 @@ +/* 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 "RemoteWebProgressRequest.h" + +#include "nsIURI.h" + +namespace mozilla::dom { + +NS_IMPL_ISUPPORTS(RemoteWebProgressRequest, nsIRequest, nsIChannel, + nsIClassifiedChannel) + +// nsIChannel methods + +NS_IMETHODIMP RemoteWebProgressRequest::GetOriginalURI(nsIURI** aOriginalURI) { + NS_ENSURE_ARG_POINTER(aOriginalURI); + NS_ADDREF(*aOriginalURI = mOriginalURI); + return NS_OK; +} + +NS_IMETHODIMP RemoteWebProgressRequest::SetOriginalURI(nsIURI* aOriginalURI) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::GetURI(nsIURI** aURI) { + NS_ENSURE_ARG_POINTER(aURI); + NS_ADDREF(*aURI = mURI); + return NS_OK; +} + +NS_IMETHODIMP RemoteWebProgressRequest::GetOwner(nsISupports** aOwner) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::SetOwner(nsISupports* aOwner) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::GetNotificationCallbacks( + nsIInterfaceRequestor** aNotificationCallbacks) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::SetNotificationCallbacks( + nsIInterfaceRequestor* aNotificationCallbacks) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::GetSecurityInfo( + nsITransportSecurityInfo** aSecurityInfo) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::GetContentType( + nsACString& aContentType) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::SetContentType( + const nsACString& aContentType) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::GetContentCharset( + nsACString& aContentCharset) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::SetContentCharset( + const nsACString& aContentCharset) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::GetContentLength( + int64_t* aContentLength) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::SetContentLength( + int64_t aContentLength) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::Open(nsIInputStream** _retval) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::AsyncOpen( + nsIStreamListener* aListener) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::GetContentDisposition( + uint32_t* aContentDisposition) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::SetContentDisposition( + uint32_t aContentDisposition) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::GetContentDispositionFilename( + nsAString& aContentDispositionFilename) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::SetContentDispositionFilename( + const nsAString& aContentDispositionFilename) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::GetContentDispositionHeader( + nsACString& aContentDispositionHeader) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::GetLoadInfo(nsILoadInfo** aLoadInfo) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::SetLoadInfo(nsILoadInfo* aLoadInfo) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::GetIsDocument(bool* aIsDocument) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +// nsIClassifiedChannel methods + +NS_IMETHODIMP RemoteWebProgressRequest::SetMatchedInfo( + const nsACString& aList, const nsACString& aProvider, + const nsACString& aFullHash) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::GetMatchedList( + nsACString& aMatchedList) { + aMatchedList = mMatchedList; + return NS_OK; +} + +NS_IMETHODIMP RemoteWebProgressRequest::GetMatchedProvider( + nsACString& aMatchedProvider) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::GetMatchedFullHash( + nsACString& aMatchedFullHash) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::SetMatchedTrackingInfo( + const nsTArray<nsCString>& aLists, const nsTArray<nsCString>& aFullHashes) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::GetMatchedTrackingLists( + nsTArray<nsCString>& aLists) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::GetMatchedTrackingFullHashes( + nsTArray<nsCString>& aFullHashes) { + return NS_ERROR_NOT_IMPLEMENTED; +} +// nsIRequest methods + +NS_IMETHODIMP RemoteWebProgressRequest::GetName(nsACString& aName) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::IsPending(bool* _retval) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::GetStatus(nsresult* aStatus) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::SetCanceledReason( + const nsACString& aReason) { + return SetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP RemoteWebProgressRequest::GetCanceledReason(nsACString& aReason) { + return GetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP RemoteWebProgressRequest::CancelWithReason( + nsresult aStatus, const nsACString& aReason) { + return CancelWithReasonImpl(aStatus, aReason); +} + +NS_IMETHODIMP RemoteWebProgressRequest::Cancel(nsresult aStatus) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::GetCanceled(bool* aCanceled) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::Suspend(void) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::Resume(void) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::GetLoadGroup( + nsILoadGroup** aLoadGroup) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::SetLoadGroup(nsILoadGroup* aLoadGroup) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::GetLoadFlags(nsLoadFlags* aLoadFlags) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::GetTRRMode( + nsIRequest::TRRMode* aTRRMode) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::SetTRRMode( + nsIRequest::TRRMode aTRRMode) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgressRequest::SetLoadFlags(nsLoadFlags aLoadFlags) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +RemoteWebProgressRequest::IsThirdPartyTrackingResource( + bool* aIsTrackingResource) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +RemoteWebProgressRequest::IsThirdPartySocialTrackingResource( + bool* aIsThirdPartySocialTrackingResource) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +RemoteWebProgressRequest::GetClassificationFlags( + uint32_t* aClassificationFlags) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +RemoteWebProgressRequest::GetFirstPartyClassificationFlags( + uint32_t* aClassificationFlags) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +RemoteWebProgressRequest::GetThirdPartyClassificationFlags( + uint32_t* aClassificationFlags) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +} // namespace mozilla::dom diff --git a/dom/ipc/RemoteWebProgressRequest.h b/dom/ipc/RemoteWebProgressRequest.h new file mode 100644 index 0000000000..cef1cb76f6 --- /dev/null +++ b/dom/ipc/RemoteWebProgressRequest.h @@ -0,0 +1,36 @@ +/* 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_dom_RemoteWebProgressRequest_h +#define mozilla_dom_RemoteWebProgressRequest_h + +#include "nsIChannel.h" +#include "nsIClassifiedChannel.h" + +namespace mozilla::dom { + +class RemoteWebProgressRequest final : public nsIChannel, + public nsIClassifiedChannel { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSICHANNEL + NS_DECL_NSICLASSIFIEDCHANNEL + NS_DECL_NSIREQUEST + + RemoteWebProgressRequest(nsIURI* aURI, nsIURI* aOriginalURI, + const nsACString& aMatchedList) + : mURI(aURI), mOriginalURI(aOriginalURI), mMatchedList(aMatchedList) {} + + protected: + ~RemoteWebProgressRequest() = default; + + private: + nsCOMPtr<nsIURI> mURI; + nsCOMPtr<nsIURI> mOriginalURI; + nsCString mMatchedList; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_RemoteWebProgressRequest_h diff --git a/dom/ipc/ServiceWorkerConfiguration.ipdlh b/dom/ipc/ServiceWorkerConfiguration.ipdlh new file mode 100644 index 0000000000..16e0d46eab --- /dev/null +++ b/dom/ipc/ServiceWorkerConfiguration.ipdlh @@ -0,0 +1,18 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* vim: set sw=4 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 ServiceWorkerRegistrarTypes; + +namespace mozilla { +namespace dom { + +struct ServiceWorkerConfiguration +{ + ServiceWorkerRegistrationData[] serviceWorkerRegistrations; +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/ipc/SharedMap.cpp b/dom/ipc/SharedMap.cpp new file mode 100644 index 0000000000..dc6602d377 --- /dev/null +++ b/dom/ipc/SharedMap.cpp @@ -0,0 +1,467 @@ +/* -*- 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 "SharedMap.h" +#include "SharedMapChangeEvent.h" + +#include "MemMapSnapshot.h" +#include "ScriptPreloader-inl.h" + +#include "mozilla/dom/AutoEntryScript.h" +#include "mozilla/dom/BlobImpl.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ContentProcessMessageManager.h" +#include "mozilla/dom/IPCBlobUtils.h" +#include "mozilla/dom/RootedDictionary.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/IOBuffers.h" +#include "mozilla/ScriptPreloader.h" +#include "mozilla/Try.h" + +using namespace mozilla::loader; + +namespace mozilla { + +using namespace ipc; + +namespace dom::ipc { + +// Align to size of uintptr_t here, to be safe. It's probably not strictly +// necessary, though. +constexpr size_t kStructuredCloneAlign = sizeof(uintptr_t); + +static inline void AlignTo(size_t* aOffset, size_t aAlign) { + if (auto mod = *aOffset % aAlign) { + *aOffset += aAlign - mod; + } +} + +SharedMap::SharedMap() = default; + +SharedMap::SharedMap(nsIGlobalObject* aGlobal, const FileDescriptor& aMapFile, + size_t aMapSize, nsTArray<RefPtr<BlobImpl>>&& aBlobs) + : DOMEventTargetHelper(aGlobal), mBlobImpls(std::move(aBlobs)) { + mMapFile.reset(new FileDescriptor(aMapFile)); + mMapSize = aMapSize; +} + +bool SharedMap::Has(const nsACString& aName) { + Unused << MaybeRebuild(); + return mEntries.Contains(aName); +} + +void SharedMap::Get(JSContext* aCx, const nsACString& aName, + JS::MutableHandle<JS::Value> aRetVal, ErrorResult& aRv) { + auto res = MaybeRebuild(); + if (res.isErr()) { + aRv.Throw(res.unwrapErr()); + return; + } + + Entry* entry = mEntries.Get(aName); + if (!entry) { + aRetVal.setNull(); + return; + } + + entry->Read(aCx, aRetVal, aRv); +} + +void SharedMap::Entry::Read(JSContext* aCx, + JS::MutableHandle<JS::Value> aRetVal, + ErrorResult& aRv) { + if (mData.is<StructuredCloneData>()) { + // We have a temporary buffer for a key that was changed after the last + // snapshot. Just decode it directly. + auto& holder = mData.as<StructuredCloneData>(); + holder.Read(aCx, aRetVal, aRv); + return; + } + + // We have a pointer to a shared memory region containing our structured + // clone data. Create a temporary buffer to decode that data, and then + // discard it so that we don't keep a separate process-local copy around any + // longer than necessary. + StructuredCloneData holder; + if (!holder.CopyExternalData(Data(), Size())) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + if (mBlobCount) { + holder.BlobImpls().AppendElements(Blobs()); + } + holder.Read(aCx, aRetVal, aRv); +} + +FileDescriptor SharedMap::CloneMapFile() const { + if (mMap.initialized()) { + return mMap.cloneHandle(); + } + return *mMapFile; +} + +void SharedMap::Update(const FileDescriptor& aMapFile, size_t aMapSize, + nsTArray<RefPtr<BlobImpl>>&& aBlobs, + nsTArray<nsCString>&& aChangedKeys) { + MOZ_DIAGNOSTIC_ASSERT(!mWritable); + + mMap.reset(); + if (mMapFile) { + *mMapFile = aMapFile; + } else { + mMapFile.reset(new FileDescriptor(aMapFile)); + } + mMapSize = aMapSize; + mEntries.Clear(); + mEntryArray.reset(); + + mBlobImpls = std::move(aBlobs); + + AutoEntryScript aes(GetParentObject(), "SharedMap change event"); + JSContext* cx = aes.cx(); + + RootedDictionary<MozSharedMapChangeEventInit> init(cx); + if (!init.mChangedKeys.SetCapacity(aChangedKeys.Length(), fallible)) { + NS_WARNING("Failed to dispatch SharedMap change event"); + return; + } + for (auto& key : aChangedKeys) { + Unused << init.mChangedKeys.AppendElement(NS_ConvertUTF8toUTF16(key), + fallible); + } + + RefPtr<SharedMapChangeEvent> event = + SharedMapChangeEvent::Constructor(this, u"change"_ns, init); + event->SetTrusted(true); + + DispatchEvent(*event); +} + +const nsTArray<SharedMap::Entry*>& SharedMap::EntryArray() const { + if (mEntryArray.isNothing()) { + MaybeRebuild(); + + mEntryArray.emplace(mEntries.Count()); + auto& array = mEntryArray.ref(); + for (auto& entry : mEntries) { + array.AppendElement(entry.GetWeak()); + } + } + + return mEntryArray.ref(); +} + +const nsString SharedMap::GetKeyAtIndex(uint32_t aIndex) const { + return NS_ConvertUTF8toUTF16(EntryArray()[aIndex]->Name()); +} + +bool SharedMap::GetValueAtIndex(JSContext* aCx, uint32_t aIndex, + JS::MutableHandle<JS::Value> aResult) const { + ErrorResult rv; + EntryArray()[aIndex]->Read(aCx, aResult, rv); + if (rv.MaybeSetPendingException(aCx)) { + return false; + } + return true; +} + +void SharedMap::Entry::TakeData(StructuredCloneData&& aHolder) { + mData = AsVariant(std::move(aHolder)); + + mSize = Holder().Data().Size(); + mBlobCount = Holder().BlobImpls().Length(); +} + +void SharedMap::Entry::ExtractData(char* aDestPtr, uint32_t aNewOffset, + uint16_t aNewBlobOffset) { + if (mData.is<StructuredCloneData>()) { + char* ptr = aDestPtr; + Holder().Data().ForEachDataChunk([&](const char* aData, size_t aSize) { + memcpy(ptr, aData, aSize); + ptr += aSize; + return true; + }); + MOZ_ASSERT(uint32_t(ptr - aDestPtr) == mSize); + } else { + memcpy(aDestPtr, Data(), mSize); + } + + mData = AsVariant(aNewOffset); + mBlobOffset = aNewBlobOffset; +} + +Result<Ok, nsresult> SharedMap::MaybeRebuild() { + if (!mMapFile) { + return Ok(); + } + + // This function maps a shared memory region created by Serialize() and reads + // its header block to build a new mEntries hashtable of its contents. + // + // The entries created by this function contain a pointer to this SharedMap + // instance, and the offsets and sizes of their structured clone data within + // its shared memory region. When needed, that structured clone data is + // retrieved directly as indexes into the SharedMap's shared memory region. + + MOZ_TRY(mMap.initWithHandle(*mMapFile, mMapSize)); + mMapFile.reset(); + + // We should be able to pass this range as an initializer list or an immediate + // param, but gcc currently chokes on that if optimization is enabled, and + // initializes everything to 0. + Range<uint8_t> range(&mMap.get<uint8_t>()[0], mMap.size()); + InputBuffer buffer(range); + + uint32_t count; + buffer.codeUint32(count); + + for (uint32_t i = 0; i < count; i++) { + auto entry = MakeUnique<Entry>(*this); + entry->Code(buffer); + + // This buffer was created at runtime, during this session, so any errors + // indicate memory corruption, and are fatal. + MOZ_RELEASE_ASSERT(!buffer.error()); + + // Note: While the order of evaluation of the arguments to Put doesn't + // matter for this (the actual move will only happen within Put), to be + // clear about this, we call entry->Name() before calling Put. + const auto& name = entry->Name(); + mEntries.InsertOrUpdate(name, std::move(entry)); + } + + return Ok(); +} + +void SharedMap::MaybeRebuild() const { + Unused << const_cast<SharedMap*>(this)->MaybeRebuild(); +} + +WritableSharedMap::WritableSharedMap() { + mWritable = true; + // Serialize the initial empty contents of the map immediately so that we + // always have a file descriptor to send to callers of CloneMapFile(). + Unused << Serialize(); + MOZ_RELEASE_ASSERT(mMap.initialized()); +} + +SharedMap* WritableSharedMap::GetReadOnly() { + if (!mReadOnly) { + nsTArray<RefPtr<BlobImpl>> blobs(mBlobImpls.Clone()); + mReadOnly = + new SharedMap(ContentProcessMessageManager::Get()->GetParentObject(), + CloneMapFile(), MapSize(), std::move(blobs)); + } + return mReadOnly; +} + +Result<Ok, nsresult> WritableSharedMap::Serialize() { + // Serializes a new snapshot of the map, initializes a new read-only shared + // memory region with its contents, and updates all entries to point to that + // new snapshot. + // + // The layout of the snapshot is as follows: + // + // - A header containing a uint32 count field containing the number of + // entries in the map, followed by that number of serialized entry headers, + // as produced by Entry::Code. + // + // - A data block containing structured clone data for each of the entries' + // values. This data is referenced by absolute byte offsets from the start + // of the shared memory region, encoded in each of the entry header values. + // Each entry's data is aligned to kStructuredCloneAlign, and therefore may + // have alignment padding before it. + // + // This serialization format is decoded by the MaybeRebuild() method of + // read-only SharedMap() instances, and used to populate their mEntries + // hashtables. + // + // Writable instances never read the header blocks, but instead directly + // update their Entry instances to point to the appropriate offsets in the + // shared memory region created by this function. + + uint32_t count = mEntries.Count(); + + size_t dataSize = 0; + size_t headerSize = sizeof(count); + size_t blobCount = 0; + + for (const auto& entry : mEntries.Values()) { + headerSize += entry->HeaderSize(); + blobCount += entry->BlobCount(); + + dataSize += entry->Size(); + AlignTo(&dataSize, kStructuredCloneAlign); + } + + size_t offset = headerSize; + AlignTo(&offset, kStructuredCloneAlign); + + OutputBuffer header; + header.codeUint32(count); + + MemMapSnapshot mem; + MOZ_TRY(mem.Init(offset + dataSize)); + + auto ptr = mem.Get<char>(); + + // We need to build the new array of blobs before we overwrite the existing + // one, since previously-serialized entries will store their blob references + // as indexes into our blobs array. + nsTArray<RefPtr<BlobImpl>> blobImpls(blobCount); + + for (const auto& entry : mEntries.Values()) { + AlignTo(&offset, kStructuredCloneAlign); + + size_t blobOffset = blobImpls.Length(); + if (entry->BlobCount()) { + blobImpls.AppendElements(entry->Blobs()); + } + + entry->ExtractData(&ptr[offset], offset, blobOffset); + entry->Code(header); + + offset += entry->Size(); + } + + mBlobImpls = std::move(blobImpls); + + // FIXME: We should create a separate OutputBuffer class which can encode to + // a static memory region rather than dynamically allocating and then + // copying. + MOZ_ASSERT(header.cursor() == headerSize); + memcpy(ptr.get(), header.Get(), header.cursor()); + + // We've already updated offsets at this point. We need this to succeed. + mMap.reset(); + MOZ_RELEASE_ASSERT(mem.Finalize(mMap).isOk()); + + return Ok(); +} + +void WritableSharedMap::SendTo(ContentParent* aParent) const { + nsTArray<IPCBlob> blobs(mBlobImpls.Length()); + + for (auto& blobImpl : mBlobImpls) { + nsresult rv = IPCBlobUtils::Serialize(blobImpl, *blobs.AppendElement()); + if (NS_WARN_IF(NS_FAILED(rv))) { + continue; + } + } + + Unused << aParent->SendUpdateSharedData(CloneMapFile(), mMap.size(), blobs, + mChangedKeys); +} + +void WritableSharedMap::BroadcastChanges() { + if (mChangedKeys.IsEmpty()) { + return; + } + + if (!Serialize().isOk()) { + return; + } + + nsTArray<ContentParent*> parents; + ContentParent::GetAll(parents); + for (auto& parent : parents) { + SendTo(parent); + } + + if (mReadOnly) { + nsTArray<RefPtr<BlobImpl>> blobImpls(mBlobImpls.Clone()); + mReadOnly->Update(CloneMapFile(), mMap.size(), std::move(blobImpls), + std::move(mChangedKeys)); + } + + mChangedKeys.Clear(); +} + +void WritableSharedMap::Delete(const nsACString& aName) { + if (mEntries.Remove(aName)) { + KeyChanged(aName); + } +} + +void WritableSharedMap::Set(JSContext* aCx, const nsACString& aName, + JS::Handle<JS::Value> aValue, ErrorResult& aRv) { + StructuredCloneData holder; + + holder.Write(aCx, aValue, aRv); + if (aRv.Failed()) { + return; + } + + if (!holder.InputStreams().IsEmpty()) { + aRv.Throw(NS_ERROR_INVALID_ARG); + return; + } + + Entry* entry = mEntries.GetOrInsertNew(aName, *this, aName); + entry->TakeData(std::move(holder)); + + KeyChanged(aName); +} + +void WritableSharedMap::Flush() { BroadcastChanges(); } + +void WritableSharedMap::IdleFlush() { + mPendingFlush = false; + Flush(); +} + +nsresult WritableSharedMap::KeyChanged(const nsACString& aName) { + if (!mChangedKeys.ContainsSorted(aName)) { + mChangedKeys.InsertElementSorted(aName); + } + mEntryArray.reset(); + + if (!mPendingFlush) { + MOZ_TRY(NS_DispatchToCurrentThreadQueue( + NewRunnableMethod("WritableSharedMap::IdleFlush", this, + &WritableSharedMap::IdleFlush), + EventQueuePriority::Idle)); + mPendingFlush = true; + } + return NS_OK; +} + +JSObject* SharedMap::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return MozSharedMap_Binding::Wrap(aCx, this, aGivenProto); +} + +JSObject* WritableSharedMap::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return MozWritableSharedMap_Binding::Wrap(aCx, this, aGivenProto); +} + +/* static */ +already_AddRefed<SharedMapChangeEvent> SharedMapChangeEvent::Constructor( + EventTarget* aEventTarget, const nsAString& aType, + const MozSharedMapChangeEventInit& aInit) { + RefPtr<SharedMapChangeEvent> event = new SharedMapChangeEvent(aEventTarget); + + bool trusted = event->Init(aEventTarget); + event->InitEvent(aType, aInit.mBubbles, aInit.mCancelable); + event->SetTrusted(trusted); + event->SetComposed(aInit.mComposed); + + event->mChangedKeys = aInit.mChangedKeys; + + return event.forget(); +} + +NS_IMPL_CYCLE_COLLECTION_INHERITED(WritableSharedMap, SharedMap, mReadOnly) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WritableSharedMap) +NS_INTERFACE_MAP_END_INHERITING(SharedMap) + +NS_IMPL_ADDREF_INHERITED(WritableSharedMap, SharedMap) +NS_IMPL_RELEASE_INHERITED(WritableSharedMap, SharedMap) + +} // namespace dom::ipc +} // namespace mozilla diff --git a/dom/ipc/SharedMap.h b/dom/ipc/SharedMap.h new file mode 100644 index 0000000000..0a5b686c62 --- /dev/null +++ b/dom/ipc/SharedMap.h @@ -0,0 +1,361 @@ +/* -*- 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 dom_ipc_SharedMap_h +#define dom_ipc_SharedMap_h + +#include "mozilla/dom/MozSharedMapBinding.h" + +#include "mozilla/AutoMemMap.h" +#include "mozilla/dom/ipc/StructuredCloneData.h" +#include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/Maybe.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Variant.h" +#include "nsClassHashtable.h" +#include "nsTArray.h" + +class nsIGlobalObject; + +namespace mozilla::dom { + +class ContentParent; + +namespace ipc { + +/** + * Together, the SharedMap and WritableSharedMap classes allow sharing a + * dynamically-updated, shared-memory key-value store across processes. + * + * The maps may only ever be updated in the parent process, via + * WritableSharedMap instances. When that map changes, its entire contents are + * serialized into a contiguous shared memory buffer, and broadcast to all child + * processes, which in turn update their entire map contents wholesale. + * + * Keys are arbitrary UTF-8 strings (currently exposed to JavaScript as UTF-16), + * and values are structured clone buffers. Values are eagerly encoded whenever + * they are updated, and lazily decoded each time they're read. + * + * Updates are batched. Rather than each key change triggering an immediate + * update, combined updates are broadcast after a delay. Changes are flushed + * immediately any time a new process is created. Additionally, any time a key + * is changed, a flush task is scheduled for the next time the event loop + * becomes idle. Changes can be flushed immediately by calling the flush() + * method. + * + * + * Whenever a read-only SharedMap is updated, it dispatches a "change" event. + * The event contains a "changedKeys" property with a list of all keys which + * were changed in the last update batch. Change events are never dispatched to + * WritableSharedMap instances. + */ +class SharedMap : public DOMEventTargetHelper { + using FileDescriptor = mozilla::ipc::FileDescriptor; + + public: + SharedMap(); + + SharedMap(nsIGlobalObject* aGlobal, const FileDescriptor&, size_t, + nsTArray<RefPtr<BlobImpl>>&& aBlobs); + + // Returns true if the map contains the given (UTF-8) key. + bool Has(const nsACString& name); + + // If the map contains the given (UTF-8) key, decodes and returns a new copy + // of its value. Otherwise returns null. + void Get(JSContext* cx, const nsACString& name, + JS::MutableHandle<JS::Value> aRetVal, ErrorResult& aRv); + + // Conversion helpers for WebIDL callers + bool Has(const nsAString& aName) { return Has(NS_ConvertUTF16toUTF8(aName)); } + + void Get(JSContext* aCx, const nsAString& aName, + JS::MutableHandle<JS::Value> aRetVal, ErrorResult& aRv) { + return Get(aCx, NS_ConvertUTF16toUTF8(aName), aRetVal, aRv); + } + + /** + * WebIDL iterator glue. + */ + uint32_t GetIterableLength() const { return EntryArray().Length(); } + + /** + * These functions return the key or value, respectively, at the given index. + * The index *must* be less than the value returned by GetIterableLength(), or + * the program will crash. + */ + const nsString GetKeyAtIndex(uint32_t aIndex) const; + bool GetValueAtIndex(JSContext* aCx, uint32_t aIndex, + JS::MutableHandle<JS::Value> aResult) const; + + /** + * Returns a copy of the read-only file descriptor which backs the shared + * memory region for this map. The file descriptor may be passed between + * processes, and used to update corresponding instances in child processes. + */ + FileDescriptor CloneMapFile() const; + + /** + * Returns the size of the memory mapped region that backs this map. Must be + * passed to the SharedMap() constructor or Update() method along with the + * descriptor returned by CloneMapFile() in order to initialize or update a + * child SharedMap. + */ + size_t MapSize() const { return mMap.size(); } + + /** + * Updates this instance to reflect the contents of the shared memory region + * in the given map file, and broadcasts a change event for the given set of + * changed (UTF-8-encoded) keys. + */ + void Update(const FileDescriptor& aMapFile, size_t aMapSize, + nsTArray<RefPtr<BlobImpl>>&& aBlobs, + nsTArray<nsCString>&& aChangedKeys); + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + protected: + ~SharedMap() override = default; + + class Entry { + public: + Entry(Entry&&) = delete; + + explicit Entry(SharedMap& aMap, const nsACString& aName = ""_ns) + : mMap(aMap), mName(aName), mData(AsVariant(uint32_t(0))) {} + + ~Entry() = default; + + /** + * Encodes or decodes this entry into or from the given OutputBuffer or + * InputBuffer. + */ + template <typename Buffer> + void Code(Buffer& buffer) { + DebugOnly<size_t> startOffset = buffer.cursor(); + + buffer.codeString(mName); + buffer.codeUint32(DataOffset()); + buffer.codeUint32(mSize); + buffer.codeUint16(mBlobOffset); + buffer.codeUint16(mBlobCount); + + MOZ_ASSERT(buffer.cursor() == startOffset + HeaderSize()); + } + + /** + * Returns the size that this entry will take up in the map header. This + * must be equal to the number of bytes encoded by Code(). + */ + size_t HeaderSize() const { + return (sizeof(uint16_t) + mName.Length() + sizeof(DataOffset()) + + sizeof(mSize) + sizeof(mBlobOffset) + sizeof(mBlobCount)); + } + + /** + * Updates the value of this entry to the given structured clone data, of + * which it takes ownership. The passed StructuredCloneData object must not + * be used after this call. + */ + void TakeData(StructuredCloneData&&); + + /** + * This is called while building a new snapshot of the SharedMap. aDestPtr + * must point to a buffer within the new snapshot with Size() bytes reserved + * for it, and `aNewOffset` must be the offset of that buffer from the start + * of the snapshot's memory region. + * + * This function copies the raw structured clone data for the entry's value + * to the new buffer, and updates its internal state for use with the new + * data. Its offset is updated to aNewOffset, and any StructuredCloneData + * object it holds is destroyed. + * + * After this call, the entry is only valid in reference to the new + * snapshot, and must not be accessed again until the SharedMap mMap has + * been updated to point to it. + */ + void ExtractData(char* aDestPtr, uint32_t aNewOffset, + uint16_t aNewBlobOffset); + + // Returns the UTF-8-encoded name of the entry, which is used as its key in + // the map. + const nsCString& Name() const { return mName; } + + // Decodes the entry's value into the current Realm of the given JS context + // and puts the result in aRetVal on success. + void Read(JSContext* aCx, JS::MutableHandle<JS::Value> aRetVal, + ErrorResult& aRv); + + // Returns the byte size of the entry's raw structured clone data. + uint32_t Size() const { return mSize; } + + private: + // Returns a pointer to the entry value's structured clone data within the + // SharedMap's mapped memory region. This is *only* valid shen mData + // contains a uint32_t. + const char* Data() const { return mMap.Data() + DataOffset(); } + + // Returns the offset of the entry value's structured clone data within the + // SharedMap's mapped memory region. This is *only* valid shen mData + // contains a uint32_t. + uint32_t& DataOffset() { return mData.as<uint32_t>(); } + const uint32_t& DataOffset() const { return mData.as<uint32_t>(); } + + public: + uint16_t BlobOffset() const { return mBlobOffset; } + uint16_t BlobCount() const { return mBlobCount; } + + Span<const RefPtr<BlobImpl>> Blobs() { + if (mData.is<StructuredCloneData>()) { + return mData.as<StructuredCloneData>().BlobImpls(); + } + return {&mMap.mBlobImpls[mBlobOffset], BlobCount()}; + } + + private: + // Returns the temporary StructuredCloneData object containing the entry's + // value. This is *only* value when mData contains a StructuredCloneDAta + // object. + const StructuredCloneData& Holder() const { + return mData.as<StructuredCloneData>(); + } + + SharedMap& mMap; + + // The entry's (UTF-8 encoded) name, which serves as its key in the map. + nsCString mName; + + /** + * This member provides a reference to the entry's structured clone data. + * Its type varies depending on the state of the entry: + * + * - For entries which have been snapshotted into a shared memory region, + * this is a uint32_t offset into the parent SharedMap's Data() buffer. + * + * - For entries which have been changed in a WritableSharedMap instance, + * but not serialized to a shared memory snapshot yet, this is a + * StructuredCloneData instance, containing a process-local copy of the + * data. This will be discarded the next time the map is serialized, and + * replaced with a buffer offset, as described above. + */ + Variant<uint32_t, StructuredCloneData> mData; + + // The size, in bytes, of the entry's structured clone data. + uint32_t mSize = 0; + + uint16_t mBlobOffset = 0; + uint16_t mBlobCount = 0; + }; + + const nsTArray<Entry*>& EntryArray() const; + + nsTArray<RefPtr<BlobImpl>> mBlobImpls; + + // Rebuilds the entry hashtable mEntries from the values serialized in the + // current snapshot, if necessary. The hashtable is rebuilt lazily after + // construction and after every Update() call, so this function must be called + // before any attempt to access mEntries. + Result<Ok, nsresult> MaybeRebuild(); + void MaybeRebuild() const; + + // Note: This header is included by WebIDL binding headers, and therefore + // can't include "windows.h". Since FileDescriptor.h does include "windows.h" + // on Windows, we can only forward declare FileDescriptor, and can't include + // it as an inline member. + UniquePtr<FileDescriptor> mMapFile; + // The size of the memory-mapped region backed by mMapFile, in bytes. + size_t mMapSize = 0; + + mutable nsClassHashtable<nsCStringHashKey, Entry> mEntries; + mutable Maybe<nsTArray<Entry*>> mEntryArray; + + // Manages the memory mapping of the current snapshot. This is initialized + // lazily after each SharedMap construction or updated, based on the values in + // mMapFile and mMapSize. + loader::AutoMemMap mMap; + + bool mWritable = false; + + // Returns a pointer to the beginning of the memory mapped snapshot. Entry + // offsets are relative to this pointer, and Entry objects access their + // structured clone data by indexing this pointer. + char* Data() { return mMap.get<char>().get(); } +}; + +class WritableSharedMap final : public SharedMap { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(WritableSharedMap, SharedMap) + + WritableSharedMap(); + + // Sets the value of the given (UTF-8 encoded) key to a structured clone + // snapshot of the given value. + void Set(JSContext* cx, const nsACString& name, JS::Handle<JS::Value> value, + ErrorResult& aRv); + + // Deletes the given (UTF-8 encoded) key from the map. + void Delete(const nsACString& name); + + // Conversion helpers for WebIDL callers + void Set(JSContext* aCx, const nsAString& aName, JS::Handle<JS::Value> aValue, + ErrorResult& aRv) { + return Set(aCx, NS_ConvertUTF16toUTF8(aName), aValue, aRv); + } + + void Delete(const nsAString& aName) { + return Delete(NS_ConvertUTF16toUTF8(aName)); + } + + // Flushes any queued changes to a new snapshot, and broadcasts it to all + // child SharedMap instances. + void Flush(); + + // Sends the current set of shared map data to the given content process. + void SendTo(ContentParent* aContentParent) const; + + /** + * Returns the read-only SharedMap instance corresponding to this + * WritableSharedMap for use in the parent process. + */ + SharedMap* GetReadOnly(); + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + protected: + ~WritableSharedMap() override = default; + + private: + // The set of (UTF-8 encoded) keys which have changed, or been deleted, since + // the last snapshot. + nsTArray<nsCString> mChangedKeys; + + RefPtr<SharedMap> mReadOnly; + + bool mPendingFlush = false; + + // Creates a new snapshot of the map, and updates all Entry instance to + // reference its data. + Result<Ok, nsresult> Serialize(); + + void IdleFlush(); + + // If there have been any changes since the last snapshot, creates a new + // serialization and broadcasts it to all child SharedMap instances. + void BroadcastChanges(); + + // Marks the given (UTF-8 encoded) key as having changed. This adds it to + // mChangedKeys, if not already present, and schedules a flush for the next + // time the event loop is idle. + nsresult KeyChanged(const nsACString& aName); +}; + +} // namespace ipc +} // namespace mozilla::dom + +#endif // dom_ipc_SharedMap_h diff --git a/dom/ipc/SharedMapChangeEvent.h b/dom/ipc/SharedMapChangeEvent.h new file mode 100644 index 0000000000..06bc53933b --- /dev/null +++ b/dom/ipc/SharedMapChangeEvent.h @@ -0,0 +1,46 @@ +/* -*- 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 dom_ipc_SharedMapChangeEvent_h +#define dom_ipc_SharedMapChangeEvent_h + +#include "mozilla/dom/MozSharedMapBinding.h" + +#include "mozilla/dom/Event.h" +#include "nsTArray.h" + +namespace mozilla::dom::ipc { + +class SharedMapChangeEvent final : public Event { + public: + NS_INLINE_DECL_REFCOUNTING_INHERITED(SharedMapChangeEvent, Event) + + JSObject* WrapObjectInternal(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override { + return MozSharedMapChangeEvent_Binding::Wrap(aCx, this, aGivenProto); + } + + static already_AddRefed<SharedMapChangeEvent> Constructor( + EventTarget* aEventTarget, const nsAString& aType, + const MozSharedMapChangeEventInit& aInit); + + void GetChangedKeys(nsTArray<nsString>& aChangedKeys) const { + aChangedKeys.AppendElements(mChangedKeys); + } + + protected: + ~SharedMapChangeEvent() override = default; + + private: + explicit SharedMapChangeEvent(EventTarget* aEventTarget) + : Event(aEventTarget, nullptr, nullptr) {} + + nsTArray<nsString> mChangedKeys; +}; + +} // namespace mozilla::dom::ipc + +#endif // dom_ipc_SharedMapChangeEvent_h diff --git a/dom/ipc/SharedMessageBody.cpp b/dom/ipc/SharedMessageBody.cpp new file mode 100644 index 0000000000..e09ff2fd55 --- /dev/null +++ b/dom/ipc/SharedMessageBody.cpp @@ -0,0 +1,298 @@ +/* -*- 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 "SharedMessageBody.h" +#include "mozilla/dom/File.h" +#include "mozilla/dom/MessagePort.h" +#include "mozilla/dom/RefMessageBodyService.h" +#include "mozilla/dom/PMessagePort.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "xpcpublic.h" + +namespace mozilla { + +using namespace ipc; + +namespace dom { + +SharedMessageBody::SharedMessageBody( + StructuredCloneHolder::TransferringSupport aSupportsTransferring, + const Maybe<nsID>& aAgentClusterId) + : mRefDataId(Nothing()), + mSupportsTransferring(aSupportsTransferring), + mAgentClusterId(aAgentClusterId) {} + +void SharedMessageBody::Write(JSContext* aCx, JS::Handle<JS::Value> aValue, + JS::Handle<JS::Value> aTransfers, nsID& aPortID, + RefMessageBodyService* aRefMessageBodyService, + ErrorResult& aRv) { + MOZ_ASSERT(!mCloneData && !mRefData); + MOZ_ASSERT(aRefMessageBodyService); + + JS::CloneDataPolicy cloneDataPolicy; + // During a writing, we don't know the destination, so we assume it is part of + // the same agent cluster. + cloneDataPolicy.allowIntraClusterClonableSharedObjects(); + + nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx); + MOZ_ASSERT(global); + + if (global->IsSharedMemoryAllowed()) { + cloneDataPolicy.allowSharedMemoryObjects(); + } + + mCloneData = MakeUnique<ipc::StructuredCloneData>( + JS::StructuredCloneScope::UnknownDestination, mSupportsTransferring); + mCloneData->Write(aCx, aValue, aTransfers, cloneDataPolicy, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + if (mCloneData->CloneScope() == JS::StructuredCloneScope::DifferentProcess) { + return; + } + + MOZ_ASSERT(mCloneData->CloneScope() == JS::StructuredCloneScope::SameProcess); + RefPtr<RefMessageBody> refData = + new RefMessageBody(aPortID, std::move(mCloneData)); + + mRefDataId.emplace(aRefMessageBodyService->Register(refData.forget(), aRv)); +} + +void SharedMessageBody::Read(JSContext* aCx, + JS::MutableHandle<JS::Value> aValue, + RefMessageBodyService* aRefMessageBodyService, + SharedMessageBody::ReadMethod aReadMethod, + ErrorResult& aRv) { + MOZ_ASSERT(aRefMessageBodyService); + + if (mCloneData) { + // Use a default cloneDataPolicy here, because SharedArrayBuffers and WASM + // are not supported. + return mCloneData->Read(aCx, aValue, JS::CloneDataPolicy(), aRv); + } + + JS::CloneDataPolicy cloneDataPolicy; + + nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx); + MOZ_ASSERT(global); + + // Clones within the same agent cluster are allowed to use shared array + // buffers and WASM modules. + if (mAgentClusterId.isSome()) { + Maybe<nsID> agentClusterId = global->GetAgentClusterId(); + if (agentClusterId.isSome() && + mAgentClusterId.value().Equals(agentClusterId.value())) { + cloneDataPolicy.allowIntraClusterClonableSharedObjects(); + } + } + + if (global->IsSharedMemoryAllowed()) { + cloneDataPolicy.allowSharedMemoryObjects(); + } + + MOZ_ASSERT(!mRefData); + MOZ_ASSERT(mRefDataId.isSome()); + + if (aReadMethod == SharedMessageBody::StealRefMessageBody) { + mRefData = aRefMessageBodyService->Steal(mRefDataId.value()); + } else { + MOZ_ASSERT(aReadMethod == SharedMessageBody::KeepRefMessageBody); + mRefData = aRefMessageBodyService->GetAndCount(mRefDataId.value()); + } + + if (!mRefData) { + aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); + return; + } + + mRefData->Read(aCx, aValue, cloneDataPolicy, aRv); +} + +bool SharedMessageBody::TakeTransferredPortsAsSequence( + Sequence<OwningNonNull<mozilla::dom::MessagePort>>& aPorts) { + if (mCloneData) { + return mCloneData->TakeTransferredPortsAsSequence(aPorts); + } + + MOZ_ASSERT(mRefData); + return mRefData->TakeTransferredPortsAsSequence(aPorts); +} + +/* static */ +void SharedMessageBody::FromSharedToMessageChild( + mozilla::ipc::PBackgroundChild* aManager, SharedMessageBody* aData, + MessageData& aMessage) { + MOZ_ASSERT(aManager); + MOZ_ASSERT(aData); + + aMessage.agentClusterId() = aData->mAgentClusterId; + + if (aData->mCloneData) { + ClonedMessageData clonedData; + aData->mCloneData->BuildClonedMessageData(clonedData); + aMessage.data() = std::move(clonedData); + return; + } + + MOZ_ASSERT(aData->mRefDataId.isSome()); + aMessage.data() = RefMessageData(aData->mRefDataId.value()); +} + +/* static */ +void SharedMessageBody::FromSharedToMessagesChild( + PBackgroundChild* aManager, + const nsTArray<RefPtr<SharedMessageBody>>& aData, + nsTArray<MessageData>& aArray) { + MOZ_ASSERT(aManager); + MOZ_ASSERT(aArray.IsEmpty()); + aArray.SetCapacity(aData.Length()); + + for (auto& data : aData) { + MessageData* message = aArray.AppendElement(); + FromSharedToMessageChild(aManager, data, *message); + } +} + +/* static */ +already_AddRefed<SharedMessageBody> SharedMessageBody::FromMessageToSharedChild( + MessageData& aMessage, + StructuredCloneHolder::TransferringSupport aSupportsTransferring) { + RefPtr<SharedMessageBody> data = + new SharedMessageBody(aSupportsTransferring, aMessage.agentClusterId()); + + if (aMessage.data().type() == MessageDataType::TClonedMessageData) { + data->mCloneData = MakeUnique<ipc::StructuredCloneData>( + JS::StructuredCloneScope::UnknownDestination, aSupportsTransferring); + data->mCloneData->StealFromClonedMessageData( + aMessage.data().get_ClonedMessageData()); + } else { + MOZ_ASSERT(aMessage.data().type() == MessageDataType::TRefMessageData); + data->mRefDataId.emplace(aMessage.data().get_RefMessageData().uuid()); + } + + return data.forget(); +} + +/* static */ +already_AddRefed<SharedMessageBody> SharedMessageBody::FromMessageToSharedChild( + const MessageData& aMessage, + StructuredCloneHolder::TransferringSupport aSupportsTransferring) { + RefPtr<SharedMessageBody> data = + new SharedMessageBody(aSupportsTransferring, aMessage.agentClusterId()); + + if (aMessage.data().type() == MessageDataType::TClonedMessageData) { + data->mCloneData = MakeUnique<ipc::StructuredCloneData>( + JS::StructuredCloneScope::UnknownDestination, aSupportsTransferring); + data->mCloneData->BorrowFromClonedMessageData( + aMessage.data().get_ClonedMessageData()); + } else { + MOZ_ASSERT(aMessage.data().type() == MessageDataType::TRefMessageData); + data->mRefDataId.emplace(aMessage.data().get_RefMessageData().uuid()); + } + + return data.forget(); +} + +/* static */ +bool SharedMessageBody::FromMessagesToSharedChild( + nsTArray<MessageData>& aArray, + FallibleTArray<RefPtr<SharedMessageBody>>& aData, + StructuredCloneHolder::TransferringSupport aSupportsTransferring) { + MOZ_ASSERT(aData.IsEmpty()); + + if (NS_WARN_IF(!aData.SetCapacity(aArray.Length(), mozilla::fallible))) { + return false; + } + + for (auto& message : aArray) { + RefPtr<SharedMessageBody> data = + FromMessageToSharedChild(message, aSupportsTransferring); + if (!data || !aData.AppendElement(data, mozilla::fallible)) { + return false; + } + } + + return true; +} + +/* static */ +bool SharedMessageBody::FromSharedToMessagesParent( + PBackgroundParent* aManager, + const nsTArray<RefPtr<SharedMessageBody>>& aData, + nsTArray<MessageData>& aArray) { + MOZ_ASSERT(aManager); + MOZ_ASSERT(aArray.IsEmpty()); + + if (NS_WARN_IF(!aArray.SetCapacity(aData.Length(), mozilla::fallible))) { + return false; + } + + for (auto& data : aData) { + MessageData* message = aArray.AppendElement(); + message->agentClusterId() = data->mAgentClusterId; + + if (data->mCloneData) { + ClonedMessageData clonedData; + data->mCloneData->BuildClonedMessageData(clonedData); + message->data() = std::move(clonedData); + continue; + } + + MOZ_ASSERT(data->mRefDataId.isSome()); + message->data() = RefMessageData(data->mRefDataId.value()); + } + + return true; +} + +/* static */ +already_AddRefed<SharedMessageBody> +SharedMessageBody::FromMessageToSharedParent( + MessageData& aMessage, + StructuredCloneHolder::TransferringSupport aSupportsTransferring) { + // TODO: This alloc is not fallible and there is no codepath that returns + // nullptr. But the caller checks for nullptr and handles array allocations + // for these items as fallible. See bug 1750497. + RefPtr<SharedMessageBody> data = + new SharedMessageBody(aSupportsTransferring, aMessage.agentClusterId()); + + if (aMessage.data().type() == MessageDataType::TClonedMessageData) { + data->mCloneData = MakeUnique<ipc::StructuredCloneData>( + JS::StructuredCloneScope::UnknownDestination, aSupportsTransferring); + data->mCloneData->StealFromClonedMessageData( + aMessage.data().get_ClonedMessageData()); + } else { + MOZ_ASSERT(aMessage.data().type() == MessageDataType::TRefMessageData); + data->mRefDataId.emplace(aMessage.data().get_RefMessageData().uuid()); + } + + return data.forget(); +} + +bool SharedMessageBody::FromMessagesToSharedParent( + nsTArray<MessageData>& aArray, + FallibleTArray<RefPtr<SharedMessageBody>>& aData, + StructuredCloneHolder::TransferringSupport aSupportsTransferring) { + MOZ_ASSERT(aData.IsEmpty()); + + if (NS_WARN_IF(!aData.SetCapacity(aArray.Length(), mozilla::fallible))) { + return false; + } + + for (auto& message : aArray) { + RefPtr<SharedMessageBody> data = FromMessageToSharedParent(message); + if (!data || !aData.AppendElement(data, mozilla::fallible)) { + return false; + } + } + + return true; +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/ipc/SharedMessageBody.h b/dom/ipc/SharedMessageBody.h new file mode 100644 index 0000000000..2c82473678 --- /dev/null +++ b/dom/ipc/SharedMessageBody.h @@ -0,0 +1,112 @@ +/* -*- 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_dom_SharedMessageBody_h +#define mozilla_dom_SharedMessageBody_h + +#include "mozilla/dom/ipc/StructuredCloneData.h" +#include "mozilla/Maybe.h" + +namespace mozilla { + +namespace ipc { +class PBackgroundChild; +} + +namespace dom { + +class MessagePort; +class RefMessageBody; +class RefMessageBodyService; + +class SharedMessageBody final { + public: + NS_INLINE_DECL_REFCOUNTING(SharedMessageBody) + + SharedMessageBody( + StructuredCloneHolder::TransferringSupport aSupportsTransferring, + const Maybe<nsID>& aAgentClusterId); + + // Note that the populated MessageData borrows the underlying + // JSStructuredCloneData from the SharedMessageBody, so the caller is + // required to ensure that the MessageData instances are destroyed prior to + // the SharedMessageBody instances. + static void FromSharedToMessageChild( + mozilla::ipc::PBackgroundChild* aBackgroundManager, + SharedMessageBody* aData, MessageData& aMessage); + static void FromSharedToMessagesChild( + mozilla::ipc::PBackgroundChild* aBackgroundManager, + const nsTArray<RefPtr<SharedMessageBody>>& aData, + nsTArray<MessageData>& aArray); + + // Const MessageData. + static already_AddRefed<SharedMessageBody> FromMessageToSharedChild( + MessageData& aMessage, + StructuredCloneHolder::TransferringSupport aSupportsTransferring = + StructuredCloneHolder::TransferringSupported); + // Non-const MessageData. + static already_AddRefed<SharedMessageBody> FromMessageToSharedChild( + const MessageData& aMessage, + StructuredCloneHolder::TransferringSupport aSupportsTransferring = + StructuredCloneHolder::TransferringSupported); + // Array of MessageData objects + static bool FromMessagesToSharedChild( + nsTArray<MessageData>& aArray, + FallibleTArray<RefPtr<SharedMessageBody>>& aData, + StructuredCloneHolder::TransferringSupport aSupportsTransferring = + StructuredCloneHolder::TransferringSupported); + + // Note that the populated MessageData borrows the underlying + // JSStructuredCloneData from the SharedMessageBody, so the caller is + // required to ensure that the MessageData instances are destroyed prior to + // the SharedMessageBody instances. + static bool FromSharedToMessagesParent( + mozilla::ipc::PBackgroundParent* aManager, + const nsTArray<RefPtr<SharedMessageBody>>& aData, + nsTArray<MessageData>& aArray); + + static already_AddRefed<SharedMessageBody> FromMessageToSharedParent( + MessageData& aMessage, + StructuredCloneHolder::TransferringSupport aSupportsTransferring = + StructuredCloneHolder::TransferringSupported); + static bool FromMessagesToSharedParent( + nsTArray<MessageData>& aArray, + FallibleTArray<RefPtr<SharedMessageBody>>& aData, + StructuredCloneHolder::TransferringSupport aSupportsTransferring = + StructuredCloneHolder::TransferringSupported); + + enum ReadMethod { + StealRefMessageBody, + KeepRefMessageBody, + }; + + void Read(JSContext* aCx, JS::MutableHandle<JS::Value> aValue, + RefMessageBodyService* aRefMessageBodyService, + ReadMethod aReadMethod, ErrorResult& aRv); + + void Write(JSContext* aCx, JS::Handle<JS::Value> aValue, + JS::Handle<JS::Value> aTransfers, nsID& aPortID, + RefMessageBodyService* aRefMessageBodyService, ErrorResult& aRv); + + bool TakeTransferredPortsAsSequence( + Sequence<OwningNonNull<mozilla::dom::MessagePort>>& aPorts); + + private: + ~SharedMessageBody() = default; + + UniquePtr<ipc::StructuredCloneData> mCloneData; + + RefPtr<RefMessageBody> mRefData; + Maybe<nsID> mRefDataId; + + const StructuredCloneHolder::TransferringSupport mSupportsTransferring; + const Maybe<nsID> mAgentClusterId; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_SharedMessageBody_h diff --git a/dom/ipc/SharedStringMap.cpp b/dom/ipc/SharedStringMap.cpp new file mode 100644 index 0000000000..8d50de6863 --- /dev/null +++ b/dom/ipc/SharedStringMap.cpp @@ -0,0 +1,140 @@ +/* -*- 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 "SharedStringMap.h" + +#include "MemMapSnapshot.h" +#include "ScriptPreloader-inl.h" + +#include "mozilla/BinarySearch.h" +#include "mozilla/Try.h" +#include "mozilla/ipc/FileDescriptor.h" + +using namespace mozilla::loader; + +namespace mozilla { + +using namespace ipc; + +namespace dom::ipc { + +static constexpr uint32_t kSharedStringMapMagic = 0x9e3779b9; + +static inline size_t GetAlignmentOffset(size_t aOffset, size_t aAlign) { + auto mod = aOffset % aAlign; + return mod ? aAlign - mod : 0; +} + +SharedStringMap::SharedStringMap(const FileDescriptor& aMapFile, + size_t aMapSize) { + auto result = mMap.initWithHandle(aMapFile, aMapSize); + MOZ_RELEASE_ASSERT(result.isOk()); + MOZ_RELEASE_ASSERT(GetHeader().mMagic == kSharedStringMapMagic); + // We return literal nsStrings and nsCStrings pointing to the mapped data, + // which means that we may still have references to the mapped data even + // after this instance is destroyed. That means that we need to keep the + // mapping alive until process shutdown, in order to be safe. + mMap.setPersistent(); +} + +SharedStringMap::SharedStringMap(SharedStringMapBuilder&& aBuilder) { + auto result = aBuilder.Finalize(mMap); + MOZ_RELEASE_ASSERT(result.isOk()); + MOZ_RELEASE_ASSERT(GetHeader().mMagic == kSharedStringMapMagic); + mMap.setPersistent(); +} + +mozilla::ipc::FileDescriptor SharedStringMap::CloneFileDescriptor() const { + return mMap.cloneHandle(); +} + +bool SharedStringMap::Has(const nsCString& aKey) { + size_t index; + return Find(aKey, &index); +} + +bool SharedStringMap::Get(const nsCString& aKey, nsAString& aValue) { + const auto& entries = Entries(); + + size_t index; + if (!Find(aKey, &index)) { + return false; + } + + aValue.Assign(ValueTable().Get(entries[index].mValue)); + return true; +} + +bool SharedStringMap::Find(const nsCString& aKey, size_t* aIndex) { + const auto& keys = KeyTable(); + + return BinarySearchIf( + Entries(), 0, EntryCount(), + [&](const Entry& aEntry) { return Compare(aKey, keys.Get(aEntry.mKey)); }, + aIndex); +} + +void SharedStringMapBuilder::Add(const nsCString& aKey, + const nsString& aValue) { + mEntries.InsertOrUpdate(aKey, + Entry{mKeyTable.Add(aKey), mValueTable.Add(aValue)}); +} + +Result<Ok, nsresult> SharedStringMapBuilder::Finalize( + loader::AutoMemMap& aMap) { + using Header = SharedStringMap::Header; + + MOZ_ASSERT(mEntries.Count() == mKeyTable.Count()); + + auto keys = ToTArray<nsTArray<nsCString>>(mEntries.Keys()); + keys.Sort(); + + Header header = {kSharedStringMapMagic, uint32_t(keys.Length())}; + + size_t offset = sizeof(header); + offset += GetAlignmentOffset(offset, alignof(Header)); + + offset += keys.Length() * sizeof(SharedStringMap::Entry); + + header.mKeyStringsOffset = offset; + header.mKeyStringsSize = mKeyTable.Size(); + + offset += header.mKeyStringsSize; + offset += + GetAlignmentOffset(offset, alignof(decltype(mValueTable)::ElemType)); + + header.mValueStringsOffset = offset; + header.mValueStringsSize = mValueTable.Size(); + + offset += header.mValueStringsSize; + + MemMapSnapshot mem; + MOZ_TRY(mem.Init(offset)); + + auto headerPtr = mem.Get<Header>(); + headerPtr[0] = header; + + auto* entry = reinterpret_cast<Entry*>(&headerPtr[1]); + for (auto& key : keys) { + *entry++ = mEntries.Get(key); + } + + auto ptr = mem.Get<uint8_t>(); + + mKeyTable.Write({&ptr[header.mKeyStringsOffset], header.mKeyStringsSize}); + + mValueTable.Write( + {&ptr[header.mValueStringsOffset], header.mValueStringsSize}); + + mKeyTable.Clear(); + mValueTable.Clear(); + mEntries.Clear(); + + return mem.Finalize(aMap); +} + +} // namespace dom::ipc +} // namespace mozilla diff --git a/dom/ipc/SharedStringMap.h b/dom/ipc/SharedStringMap.h new file mode 100644 index 0000000000..cb14e09dbc --- /dev/null +++ b/dom/ipc/SharedStringMap.h @@ -0,0 +1,220 @@ +/* -*- 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 dom_ipc_SharedStringMap_h +#define dom_ipc_SharedStringMap_h + +#include "mozilla/AutoMemMap.h" +#include "mozilla/Result.h" +#include "mozilla/dom/ipc/StringTable.h" +#include "nsTHashMap.h" + +namespace mozilla::dom::ipc { + +class SharedStringMapBuilder; + +/** + * This class provides a simple, read-only key-value string store, with all + * data packed into a single segment of memory, which can be shared between + * processes. + * + * Look-ups are performed by binary search of a static table in the mapped + * memory region, and all returned strings are literals which reference the + * mapped data. No copies are performed on instantiation or look-up. + * + * Important: The mapped memory created by this class is persistent. Once an + * instance has been initialized, the memory that it allocates can never be + * freed before process shutdown. Do not use it for short-lived mappings. + */ +class SharedStringMap { + using FileDescriptor = mozilla::ipc::FileDescriptor; + + public: + /** + * The header at the beginning of the shared memory region describing its + * layout. The layout of the shared memory is as follows: + * + * - Header: + * A Header struct describing the contents of the rest of the memory region. + * + * - Optional alignment padding for Header[]. + * + * - Entry[header.mEntryCount]: + * An array of Entry structs, one for each entry in the map. Entries are + * lexocographically sorted by key. + * + * - StringTable<nsCString>: + * A region of flat, null-terminated C strings. Entry key strings are + * encoded as character offsets into this region. + * + * - Optional alignment padding for char16_t[] + * + * - StringTable<nsString>: + * A region of flat, null-terminated UTF-16 strings. Entry value strings are + * encoded as character (*not* byte) offsets into this region. + */ + struct Header { + uint32_t mMagic; + // The number of entries in this map. + uint32_t mEntryCount; + + // The raw byte offset of the beginning of the key string table, from the + // start of the shared memory region, and its size in bytes. + size_t mKeyStringsOffset; + size_t mKeyStringsSize; + + // The raw byte offset of the beginning of the value string table, from the + // start of the shared memory region, and its size in bytes (*not* + // characters). + size_t mValueStringsOffset; + size_t mValueStringsSize; + }; + + /** + * Describes a value in the string map, as offsets into the key and value + * string tables. + */ + struct Entry { + // The offset and size of the entry's UTF-8 key in the key string table. + StringTableEntry mKey; + // The offset and size of the entry's UTF-16 value in the value string + // table. + StringTableEntry mValue; + }; + + NS_INLINE_DECL_REFCOUNTING(SharedStringMap) + + // Note: These constructors are infallible on the premise that this class + // is used primarily in cases where it is critical to platform + // functionality. + explicit SharedStringMap(const FileDescriptor&, size_t); + explicit SharedStringMap(SharedStringMapBuilder&&); + + /** + * Searches for the given value in the map, and returns true if it exists. + */ + bool Has(const nsCString& aKey); + + /** + * Searches for the given value in the map, and, if it exists, returns true + * and places its value in aValue. + * + * The returned value is a literal string which references the mapped memory + * region. + */ + bool Get(const nsCString& aKey, nsAString& aValue); + + private: + /** + * Searches for an entry for the given key. If found, returns true, and + * places its index in the entry array in aIndex. + */ + bool Find(const nsCString& aKey, size_t* aIndex); + + public: + /** + * Returns the number of entries in the map. + */ + uint32_t Count() const { return EntryCount(); } + + /** + * Returns the string entry at the given index. Keys are guaranteed to be + * sorted lexographically. + * + * The given index *must* be less than the value returned by Count(). + * + * The returned value is a literal string which references the mapped memory + * region. + */ + nsCString GetKeyAt(uint32_t aIndex) const { + MOZ_ASSERT(aIndex < Count()); + return KeyTable().Get(Entries()[aIndex].mKey); + } + + /** + * Returns the string value for the entry at the given index. + * + * The given index *must* be less than the value returned by Count(). + * + * The returned value is a literal string which references the mapped memory + * region. + */ + nsString GetValueAt(uint32_t aIndex) const { + MOZ_ASSERT(aIndex < Count()); + return ValueTable().Get(Entries()[aIndex].mValue); + } + + /** + * Returns a copy of the read-only file descriptor which backs the shared + * memory region for this map. The file descriptor may be passed between + * processes, and used to construct new instances of SharedStringMap with + * the same data as this instance. + */ + FileDescriptor CloneFileDescriptor() const; + + size_t MapSize() const { return mMap.size(); } + + protected: + ~SharedStringMap() = default; + + private: + // Type-safe getters for values in the shared memory region: + const Header& GetHeader() const { return mMap.get<Header>()[0]; } + + RangedPtr<const Entry> Entries() const { + return {reinterpret_cast<const Entry*>(&GetHeader() + 1), EntryCount()}; + } + + uint32_t EntryCount() const { return GetHeader().mEntryCount; } + + StringTable<nsCString> KeyTable() const { + auto& header = GetHeader(); + return {{&mMap.get<uint8_t>()[header.mKeyStringsOffset], + header.mKeyStringsSize}}; + } + + StringTable<nsString> ValueTable() const { + auto& header = GetHeader(); + return {{&mMap.get<uint8_t>()[header.mValueStringsOffset], + header.mValueStringsSize}}; + } + + loader::AutoMemMap mMap; +}; + +/** + * A helper class which builds the contiguous look-up table used by + * SharedStringMap. Each key-value pair in the final map is added to the + * builder, before it is finalized and transformed into a snapshot. + */ +class MOZ_RAII SharedStringMapBuilder { + public: + SharedStringMapBuilder() = default; + + /** + * Adds a key-value pair to the map. + */ + void Add(const nsCString& aKey, const nsString& aValue); + + /** + * Finalizes the binary representation of the map, writes it to a shared + * memory region, and then initializes the given AutoMemMap with a reference + * to the read-only copy of it. + */ + Result<Ok, nsresult> Finalize(loader::AutoMemMap& aMap); + + private: + using Entry = SharedStringMap::Entry; + + StringTableBuilder<nsCStringHashKey, nsCString> mKeyTable; + StringTableBuilder<nsStringHashKey, nsString> mValueTable; + + nsTHashMap<nsCStringHashKey, Entry> mEntries; +}; + +} // namespace mozilla::dom::ipc + +#endif // dom_ipc_SharedStringMap_h diff --git a/dom/ipc/StringTable.h b/dom/ipc/StringTable.h new file mode 100644 index 0000000000..5de3cb9226 --- /dev/null +++ b/dom/ipc/StringTable.h @@ -0,0 +1,116 @@ +/* -*- 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 dom_ipc_StringTable_h +#define dom_ipc_StringTable_h + +#include "mozilla/RangedPtr.h" +#include "nsTHashMap.h" + +/** + * This file contains helper classes for creating and accessing compact string + * tables, which can be used as the building blocks of shared memory databases. + * Each string table a de-duplicated set of strings which can be referenced + * using their character offsets within a data block and their lengths. The + * string tables, once created, cannot be modified, and are primarily useful in + * read-only shared memory or memory mapped files. + */ + +namespace mozilla::dom::ipc { + +/** + * Contains the character offset and character length of an entry in a string + * table. This may be used for either 8-bit or 16-bit strings, and is required + * to retrieve an entry from a string table. + */ +struct StringTableEntry { + uint32_t mOffset; + uint32_t mLength; + + // Ignore mLength. It must be the same for any two strings with the same + // offset. + uint32_t Hash() const { return mOffset; } + + bool operator==(const StringTableEntry& aOther) const { + return mOffset == aOther.mOffset; + } +}; + +template <typename StringType> +class StringTable { + using ElemType = typename StringType::char_type; + + public: + MOZ_IMPLICIT StringTable(const RangedPtr<uint8_t>& aBuffer) + : mBuffer(aBuffer.ReinterpretCast<ElemType>()) { + MOZ_ASSERT(uintptr_t(aBuffer.get()) % alignof(ElemType) == 0, + "Got misalinged buffer"); + } + + StringType Get(const StringTableEntry& aEntry) const { + StringType res; + res.AssignLiteral(GetBare(aEntry), aEntry.mLength); + return res; + } + + const ElemType* GetBare(const StringTableEntry& aEntry) const { + return &mBuffer[aEntry.mOffset]; + } + + private: + RangedPtr<ElemType> mBuffer; +}; + +template <typename KeyType, typename StringType> +class StringTableBuilder { + public: + using ElemType = typename StringType::char_type; + + StringTableEntry Add(const StringType& aKey) { + return mEntries.WithEntryHandle(aKey, + [&](auto&& entry) -> StringTableEntry { + auto length = uint32_t(aKey.Length()); + entry.OrInsertWith([&]() { + Entry newEntry{mSize, aKey}; + mSize += length + 1; + + return newEntry; + }); + + return {entry->mOffset, length}; + }); + } + + void Write(const RangedPtr<uint8_t>& aBuffer) { + auto buffer = aBuffer.ReinterpretCast<ElemType>(); + + for (const auto& entry : mEntries.Values()) { + memcpy(&buffer[entry.mOffset], entry.mValue.BeginReading(), + sizeof(ElemType) * (entry.mValue.Length() + 1)); + } + } + + uint32_t Count() const { return mEntries.Count(); } + + uint32_t Size() const { return mSize * sizeof(ElemType); } + + void Clear() { mEntries.Clear(); } + + static constexpr size_t Alignment() { return alignof(ElemType); } + + private: + struct Entry { + uint32_t mOffset; + StringType mValue; + }; + + nsTHashMap<KeyType, Entry> mEntries; + uint32_t mSize = 0; +}; + +} // namespace mozilla::dom::ipc + +#endif diff --git a/dom/ipc/StructuredCloneData.cpp b/dom/ipc/StructuredCloneData.cpp new file mode 100644 index 0000000000..7406083f60 --- /dev/null +++ b/dom/ipc/StructuredCloneData.cpp @@ -0,0 +1,329 @@ +/* -*- 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 "StructuredCloneData.h" + +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/BlobBinding.h" +#include "mozilla/dom/BlobImpl.h" +#include "mozilla/dom/DOMTypes.h" +#include "mozilla/dom/File.h" +#include "mozilla/dom/IPCBlobUtils.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/ipc/IPCStreamUtils.h" +#include "mozilla/ipc/SerializedStructuredCloneBuffer.h" +#include "nsContentUtils.h" +#include "nsJSEnvironment.h" +#include "MainThreadUtils.h" +#include "StructuredCloneTags.h" +#include "jsapi.h" + +using namespace mozilla::ipc; + +namespace mozilla::dom::ipc { + +using mozilla::ipc::IPCStream; +using mozilla::ipc::PBackgroundChild; +using mozilla::ipc::PBackgroundParent; + +StructuredCloneData::StructuredCloneData() + : StructuredCloneData( + StructuredCloneHolder::StructuredCloneScope::DifferentProcess, + StructuredCloneHolder::TransferringSupported) {} + +StructuredCloneData::StructuredCloneData(StructuredCloneData&& aOther) + : StructuredCloneData( + StructuredCloneHolder::StructuredCloneScope::DifferentProcess, + StructuredCloneHolder::TransferringSupported) { + *this = std::move(aOther); +} + +StructuredCloneData::StructuredCloneData( + StructuredCloneHolder::StructuredCloneScope aScope, + TransferringSupport aSupportsTransferring) + : StructuredCloneHolder(StructuredCloneHolder::CloningSupported, + aSupportsTransferring, aScope), + mExternalData(JS::StructuredCloneScope::DifferentProcess), + mInitialized(false) { + MOZ_ASSERT( + aScope == StructuredCloneHolder::StructuredCloneScope::DifferentProcess || + aScope == + StructuredCloneHolder::StructuredCloneScope::UnknownDestination); +} + +StructuredCloneData::~StructuredCloneData() = default; + +StructuredCloneData& StructuredCloneData::operator=( + StructuredCloneData&& aOther) { + mBlobImplArray = std::move(aOther.mBlobImplArray); + mExternalData = std::move(aOther.mExternalData); + mSharedData = std::move(aOther.mSharedData); + mPortIdentifiers = std::move(aOther.mPortIdentifiers); + mInitialized = aOther.mInitialized; + + return *this; +} + +bool StructuredCloneData::Copy(const StructuredCloneData& aData) { + if (!aData.mInitialized) { + return true; + } + + if (aData.SharedData()) { + mSharedData = aData.SharedData(); + } else { + mSharedData = SharedJSAllocatedData::CreateFromExternalData(aData.Data()); + NS_ENSURE_TRUE(mSharedData, false); + } + + if (mSupportsTransferring) { + PortIdentifiers().AppendElements(aData.PortIdentifiers()); + } + + MOZ_ASSERT(BlobImpls().IsEmpty()); + BlobImpls().AppendElements(aData.BlobImpls()); + + MOZ_ASSERT(GetSurfaces().IsEmpty()); + MOZ_ASSERT(WasmModules().IsEmpty()); + + MOZ_ASSERT(InputStreams().IsEmpty()); + InputStreams().AppendElements(aData.InputStreams()); + + mInitialized = true; + + return true; +} + +void StructuredCloneData::Read(JSContext* aCx, + JS::MutableHandle<JS::Value> aValue, + ErrorResult& aRv) { + Read(aCx, aValue, JS::CloneDataPolicy(), aRv); +} + +void StructuredCloneData::Read(JSContext* aCx, + JS::MutableHandle<JS::Value> aValue, + const JS::CloneDataPolicy& aCloneDataPolicy, + ErrorResult& aRv) { + MOZ_ASSERT(mInitialized); + + nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx); + MOZ_ASSERT(global); + + ReadFromBuffer(global, aCx, Data(), aValue, aCloneDataPolicy, aRv); +} + +void StructuredCloneData::Write(JSContext* aCx, JS::Handle<JS::Value> aValue, + ErrorResult& aRv) { + Write(aCx, aValue, JS::UndefinedHandleValue, JS::CloneDataPolicy(), aRv); +} + +void StructuredCloneData::Write(JSContext* aCx, JS::Handle<JS::Value> aValue, + JS::Handle<JS::Value> aTransfer, + const JS::CloneDataPolicy& aCloneDataPolicy, + ErrorResult& aRv) { + MOZ_ASSERT(!mInitialized); + + StructuredCloneHolder::Write(aCx, aValue, aTransfer, aCloneDataPolicy, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + JSStructuredCloneData data(mBuffer->scope()); + mBuffer->giveTo(&data); + mBuffer = nullptr; + mSharedData = new SharedJSAllocatedData(std::move(data)); + mInitialized = true; +} + +bool StructuredCloneData::BuildClonedMessageData( + ClonedMessageData& aClonedData) { + SerializedStructuredCloneBuffer& buffer = aClonedData.data(); + auto iter = Data().Start(); + size_t size = Data().Size(); + bool success; + buffer.data = Data().Borrow(iter, size, &success); + if (NS_WARN_IF(!success)) { + return false; + } + if (SupportsTransferring()) { + aClonedData.identifiers().AppendElements(PortIdentifiers()); + } + + const nsTArray<RefPtr<BlobImpl>>& blobImpls = BlobImpls(); + + if (!blobImpls.IsEmpty()) { + if (NS_WARN_IF( + !aClonedData.blobs().SetLength(blobImpls.Length(), fallible))) { + return false; + } + + for (uint32_t i = 0; i < blobImpls.Length(); ++i) { + nsresult rv = + IPCBlobUtils::Serialize(blobImpls[i], aClonedData.blobs()[i]); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + } + } + + const nsTArray<nsCOMPtr<nsIInputStream>>& inputStreams = InputStreams(); + if (!inputStreams.IsEmpty()) { + nsTArray<IPCStream>& streams = aClonedData.inputStreams(); + uint32_t length = inputStreams.Length(); + streams.SetCapacity(length); + for (uint32_t i = 0; i < length; ++i) { + IPCStream value; + if (!mozilla::ipc::SerializeIPCStream(do_AddRef(inputStreams[i]), value, + /* aAllowLazy */ false)) { + return false; + } + streams.AppendElement(value); + } + } + + return true; +} + +// See the StructuredCloneData class block comment for the meanings of each val. +enum MemoryFlavorEnum { BorrowMemory = 0, CopyMemory, StealMemory }; + +template <MemoryFlavorEnum> +struct MemoryTraits {}; + +template <> +struct MemoryTraits<BorrowMemory> { + using ClonedMessageType = const mozilla::dom::ClonedMessageData; + + static void ProvideBuffer(const ClonedMessageData& aClonedData, + StructuredCloneData& aData) { + const SerializedStructuredCloneBuffer& buffer = aClonedData.data(); + aData.UseExternalData(buffer.data); + } +}; + +template <> +struct MemoryTraits<CopyMemory> { + using ClonedMessageType = const mozilla::dom::ClonedMessageData; + + static void ProvideBuffer(const ClonedMessageData& aClonedData, + StructuredCloneData& aData) { + const SerializedStructuredCloneBuffer& buffer = aClonedData.data(); + aData.CopyExternalData(buffer.data); + } +}; + +template <> +struct MemoryTraits<StealMemory> { + // note: not const! + using ClonedMessageType = mozilla::dom::ClonedMessageData; + + static void ProvideBuffer(ClonedMessageData& aClonedData, + StructuredCloneData& aData) { + SerializedStructuredCloneBuffer& buffer = aClonedData.data(); + aData.StealExternalData(buffer.data); + } +}; + +// Note that there isn't actually a difference between Parent/BackgroundParent +// and Child/BackgroundChild in this implementation. The calling methods, +// however, do maintain the distinction for code-reading purposes and are backed +// by assertions to enforce there is no misuse. +template <MemoryFlavorEnum MemoryFlavor> +static void UnpackClonedMessageData( + typename MemoryTraits<MemoryFlavor>::ClonedMessageType& aClonedData, + StructuredCloneData& aData) { + const nsTArray<MessagePortIdentifier>& identifiers = + aClonedData.identifiers(); + + MemoryTraits<MemoryFlavor>::ProvideBuffer(aClonedData, aData); + + if (aData.SupportsTransferring()) { + aData.PortIdentifiers().AppendElements(identifiers); + } + + const nsTArray<IPCBlob>& blobs = aClonedData.blobs(); + if (!blobs.IsEmpty()) { + uint32_t length = blobs.Length(); + aData.BlobImpls().SetCapacity(length); + for (uint32_t i = 0; i < length; ++i) { + RefPtr<BlobImpl> blobImpl = IPCBlobUtils::Deserialize(blobs[i]); + MOZ_ASSERT(blobImpl); + + aData.BlobImpls().AppendElement(blobImpl); + } + } + + const nsTArray<IPCStream>& streams = aClonedData.inputStreams(); + if (!streams.IsEmpty()) { + uint32_t length = streams.Length(); + aData.InputStreams().SetCapacity(length); + for (uint32_t i = 0; i < length; ++i) { + nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(streams[i]); + aData.InputStreams().AppendElement(stream); + } + } +} + +void StructuredCloneData::BorrowFromClonedMessageData( + const ClonedMessageData& aClonedData) { + UnpackClonedMessageData<BorrowMemory>(aClonedData, *this); +} + +void StructuredCloneData::CopyFromClonedMessageData( + const ClonedMessageData& aClonedData) { + UnpackClonedMessageData<CopyMemory>(aClonedData, *this); +} + +void StructuredCloneData::StealFromClonedMessageData( + ClonedMessageData& aClonedData) { + UnpackClonedMessageData<StealMemory>(aClonedData, *this); +} + +void StructuredCloneData::WriteIPCParams(IPC::MessageWriter* aWriter) const { + WriteParam(aWriter, Data()); +} + +bool StructuredCloneData::ReadIPCParams(IPC::MessageReader* aReader) { + MOZ_ASSERT(!mInitialized); + JSStructuredCloneData data(JS::StructuredCloneScope::DifferentProcess); + if (!ReadParam(aReader, &data)) { + return false; + } + mSharedData = new SharedJSAllocatedData(std::move(data)); + mInitialized = true; + return true; +} + +bool StructuredCloneData::CopyExternalData(const char* aData, + size_t aDataLength) { + MOZ_ASSERT(!mInitialized); + mSharedData = + SharedJSAllocatedData::CreateFromExternalData(aData, aDataLength); + NS_ENSURE_TRUE(mSharedData, false); + mInitialized = true; + return true; +} + +bool StructuredCloneData::CopyExternalData(const JSStructuredCloneData& aData) { + MOZ_ASSERT(!mInitialized); + mSharedData = SharedJSAllocatedData::CreateFromExternalData(aData); + NS_ENSURE_TRUE(mSharedData, false); + mInitialized = true; + return true; +} + +bool StructuredCloneData::StealExternalData(JSStructuredCloneData& aData) { + MOZ_ASSERT(!mInitialized); + mSharedData = new SharedJSAllocatedData(std::move(aData)); + mInitialized = true; + return true; +} + +already_AddRefed<SharedJSAllocatedData> StructuredCloneData::TakeSharedData() { + return mSharedData.forget(); +} + +} // namespace mozilla::dom::ipc diff --git a/dom/ipc/StructuredCloneData.h b/dom/ipc/StructuredCloneData.h new file mode 100644 index 0000000000..0566a3b07f --- /dev/null +++ b/dom/ipc/StructuredCloneData.h @@ -0,0 +1,268 @@ +/* -*- 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_dom_ipc_StructuredCloneData_h +#define mozilla_dom_ipc_StructuredCloneData_h + +#include <algorithm> +#include "mozilla/RefPtr.h" +#include "mozilla/dom/StructuredCloneHolder.h" +#include "nsISupportsImpl.h" +#include "nsIInputStream.h" + +namespace IPC { +class Message; +class MessageReader; +class MessageWriter; +} // namespace IPC +class PickleIterator; + +namespace mozilla { +namespace ipc { + +class PBackgroundChild; +class PBackgroundParent; + +} // namespace ipc + +namespace dom { + +class ContentChild; +class ContentParent; + +namespace ipc { + +/** + * Wraps the non-reference-counted JSStructuredCloneData class to have a + * reference count so that multiple StructuredCloneData instances can reference + * a single underlying serialized representation. + * + * As used by StructuredCloneData, it is an invariant that our + * JSStructuredCloneData owns its buffers. (For the non-owning case, + * StructuredCloneData uses mExternalData which holds a BufferList::Borrow()ed + * read-only view of the data.) + */ +class SharedJSAllocatedData final { + public: + explicit SharedJSAllocatedData(JSStructuredCloneData&& aData) + : mData(std::move(aData)) {} + + static already_AddRefed<SharedJSAllocatedData> CreateFromExternalData( + const char* aData, size_t aDataLength) { + JSStructuredCloneData buf(JS::StructuredCloneScope::DifferentProcess); + NS_ENSURE_TRUE(buf.AppendBytes(aData, aDataLength), nullptr); + RefPtr<SharedJSAllocatedData> sharedData = + new SharedJSAllocatedData(std::move(buf)); + return sharedData.forget(); + } + + static already_AddRefed<SharedJSAllocatedData> CreateFromExternalData( + const JSStructuredCloneData& aData) { + JSStructuredCloneData buf(aData.scope()); + NS_ENSURE_TRUE(buf.Append(aData), nullptr); + RefPtr<SharedJSAllocatedData> sharedData = + new SharedJSAllocatedData(std::move(buf)); + return sharedData.forget(); + } + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedJSAllocatedData) + + JSStructuredCloneData& Data() { return mData; } + size_t DataLength() const { return mData.Size(); } + + private: + ~SharedJSAllocatedData() = default; + + JSStructuredCloneData mData; +}; + +/** + * IPC-aware StructuredCloneHolder subclass that serves as both a helper class + * for dealing with message data (blobs, transferables) and also an IPDL + * data-type in cases where message data is not needed. If your use-case does + * not (potentially) involve IPC, then you should use StructuredCloneHolder or + * one of its other subclasses instead. + * + * ## Usage ## + * + * The general recipe for using this class is: + * - In your IPDL definition, use the ClonedMessageData type whenever you want + * to send a structured clone that may include blobs or transferables such as + * message ports. + * - To send the data, instantiate a StructuredCloneData instance and Write() + * into it like a normal structure clone. When you are ready to send the + * ClonedMessageData-bearing IPC message, use the appropriate + * BuildClonedMessageDataFor{Parent,Child,BackgroundParent,BackgroundChild} + * method to populate the ClonedMessageData and then send it before your + * StructuredCloneData instance is destroyed. (Buffer borrowing is used + * under-the-hood to avoid duplicating the serialized data, requiring this.) + * - To receive the data, instantiate a StructuredCloneData and use the + * appropriate {Borrow,Copy,Steal}FromClonedMessageDataFor{Parent,Child, + * BackgroundParent,BackgroundChild} method. See the memory management + * section for more info. + * + * Variations: + * - If transferables are not allowed (ex: BroadcastChannel), then use the + * StructuredCloneDataNoTransfers subclass instead of StructuredCloneData. + * + * ## Memory Management ## + * + * Serialized structured clone representations can be quite large. So it's best + * to avoid wasteful duplication. When Write()ing into the StructuredCloneData, + * you don't need to worry about this[1], but when you already have serialized + * structured clone data you plan to Read(), you do need to. Similarly, if + * you're using StructuredCloneData as an IPDL type, it efficiently unmarshals. + * + * The from-ClonedMessageData memory management strategies available are: + * - Borrow: Create a JSStructuredCloneData that holds a non-owning, read-only + * BufferList::Borrow()ed copy of the source. Your StructuredCloneData needs + * to be destroyed before the source is. Commonly used when the + * StructuredCloneData instance is stack-allocated (and Read() is used before + * the function returns). + * - Copy: Makes a reference-counted copy of the source JSStructuredCloneData, + * making it safe for the StructuredCloneData to outlive the source data. + * - Steal: Steal the buffers from the underlying JSStructuredCloneData so that + * it's safe for the StructuredCloneData to outlive the source data. This is + * safe to use with IPC-provided ClonedMessageData instances because + * JSStructuredCloneData's IPC ParamTraits::Read method copies the relevant + * data into owned buffers. But if it's possible the ClonedMessageData came + * from a different source that might have borrowed the buffers itself, then + * things will crash. That would be a pretty strange implementation; if you + * see one, change it to use SharedJSAllocatedData. + * + * 1: Specifically, in the Write() case an owning SharedJSAllocatedData is + * created efficiently (by stealing from StructuredCloneHolder). The + * BuildClonedMessageDataFor* method can be called at any time and it will + * borrow the underlying memory. While it would be even better if + * SerializedStructuredCloneBuffer could hold a SharedJSAllocatedData ref, + * there's no reason you can't wait to BuildClonedMessageDataFor* until you + * need to make the IPC Send* call. + */ +class StructuredCloneData : public StructuredCloneHolder { + public: + StructuredCloneData(); + + StructuredCloneData(const StructuredCloneData&) = delete; + + StructuredCloneData(StructuredCloneData&& aOther); + + // Only DifferentProcess and UnknownDestination scopes are supported. + StructuredCloneData(StructuredCloneScope aScope, + TransferringSupport aSupportsTransferring); + + ~StructuredCloneData(); + + StructuredCloneData& operator=(const StructuredCloneData& aOther) = delete; + + StructuredCloneData& operator=(StructuredCloneData&& aOther); + + const nsTArray<RefPtr<BlobImpl>>& BlobImpls() const { return mBlobImplArray; } + + nsTArray<RefPtr<BlobImpl>>& BlobImpls() { return mBlobImplArray; } + + const nsTArray<nsCOMPtr<nsIInputStream>>& InputStreams() const { + return mInputStreamArray; + } + + nsTArray<nsCOMPtr<nsIInputStream>>& InputStreams() { + return mInputStreamArray; + } + + bool Copy(const StructuredCloneData& aData); + + void Read(JSContext* aCx, JS::MutableHandle<JS::Value> aValue, + ErrorResult& aRv); + + void Read(JSContext* aCx, JS::MutableHandle<JS::Value> aValue, + const JS::CloneDataPolicy& aCloneDataPolicy, ErrorResult& aRv); + + // Write with no transfer objects and with the default CloneDataPolicy. With + // a default CloneDataPolicy, read and write will not be considered as part of + // the same agent cluster and shared memory objects will not be supported. + void Write(JSContext* aCx, JS::Handle<JS::Value> aValue, + ErrorResult& aRv) override; + + // The most generic Write method, with tansfers and CloneDataPolicy. + void Write(JSContext* aCx, JS::Handle<JS::Value> aValue, + JS::Handle<JS::Value> aTransfers, + const JS::CloneDataPolicy& aCloneDataPolicy, + ErrorResult& aRv) override; + + // Method to convert the structured clone stored in this holder by a previous + // call to Write() into ClonedMessageData IPC representation. + bool BuildClonedMessageData(ClonedMessageData& aClonedData); + + // Memory-management-strategy-varying methods to initialize this holder from a + // ClonedMessageData representation. + void BorrowFromClonedMessageData(const ClonedMessageData& aClonedData); + + void CopyFromClonedMessageData(const ClonedMessageData& aClonedData); + + // The steal variant of course takes a non-const ClonedMessageData. + void StealFromClonedMessageData(ClonedMessageData& aClonedData); + + // Initialize this instance, borrowing the contents of the given + // JSStructuredCloneData. You are responsible for ensuring that this + // StructuredCloneData instance is destroyed before aData is destroyed. + bool UseExternalData(const JSStructuredCloneData& aData) { + auto iter = aData.Start(); + bool success = false; + mExternalData = aData.Borrow(iter, aData.Size(), &success); + mInitialized = true; + return success; + } + + // Initialize this instance by copying the given data that probably came from + // nsStructuredClone doing a base64 decode. Don't use this. + bool CopyExternalData(const char* aData, size_t aDataLength); + // Initialize this instance by copying the contents of an existing + // JSStructuredCloneData. Use when this StructuredCloneData instance may + // outlive aData. + bool CopyExternalData(const JSStructuredCloneData& aData); + + // Initialize this instance by stealing the contents of aData via Move + // constructor, clearing the original aData as a side-effect. This is only + // safe if aData owns the underlying buffers. This is the case for instances + // provided by IPC to Recv calls. + bool StealExternalData(JSStructuredCloneData& aData); + + JSStructuredCloneData& Data() { + return mSharedData ? mSharedData->Data() : mExternalData; + } + + const JSStructuredCloneData& Data() const { + return mSharedData ? mSharedData->Data() : mExternalData; + } + + void InitScope(JS::StructuredCloneScope aScope) { Data().initScope(aScope); } + + size_t DataLength() const { + return mSharedData ? mSharedData->DataLength() : mExternalData.Size(); + } + + SharedJSAllocatedData* SharedData() const { return mSharedData; } + + bool SupportsTransferring() { return mSupportsTransferring; } + + // For IPC serialization + void WriteIPCParams(IPC::MessageWriter* aWriter) const; + bool ReadIPCParams(IPC::MessageReader* aReader); + + protected: + already_AddRefed<SharedJSAllocatedData> TakeSharedData(); + + private: + JSStructuredCloneData mExternalData; + RefPtr<SharedJSAllocatedData> mSharedData; + + bool mInitialized; +}; + +} // namespace ipc +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_ipc_StructuredCloneData_h diff --git a/dom/ipc/TabContext.cpp b/dom/ipc/TabContext.cpp new file mode 100644 index 0000000000..4344ec221a --- /dev/null +++ b/dom/ipc/TabContext.cpp @@ -0,0 +1,106 @@ +/* -*- 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 "mozilla/dom/TabContext.h" +#include "mozilla/dom/PTabContext.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/StaticPrefs_dom.h" +#include "nsServiceManagerUtils.h" + +using namespace mozilla::dom::ipc; +using namespace mozilla::layout; + +namespace mozilla::dom { + +TabContext::TabContext() + : mInitialized(false), + mChromeOuterWindowID(0), + mMaxTouchPoints(0) {} + +uint64_t TabContext::ChromeOuterWindowID() const { + return mChromeOuterWindowID; +} + +bool TabContext::SetTabContext(const TabContext& aContext) { + NS_ENSURE_FALSE(mInitialized, false); + + *this = aContext; + mInitialized = true; + + return true; +} + +bool TabContext::UpdateTabContextAfterSwap(const TabContext& aContext) { + // This is only used after already initialized. + MOZ_ASSERT(mInitialized); + + // The only permissable changes are to mChromeOuterWindowID. All other fields + // must match for the change to be accepted. + + mChromeOuterWindowID = aContext.mChromeOuterWindowID; + return true; +} + +bool TabContext::SetTabContext(uint64_t aChromeOuterWindowID, + uint32_t aMaxTouchPoints) { + NS_ENSURE_FALSE(mInitialized, false); + + mInitialized = true; + mChromeOuterWindowID = aChromeOuterWindowID; + mMaxTouchPoints = aMaxTouchPoints; + return true; +} + +IPCTabContext TabContext::AsIPCTabContext() const { + return IPCTabContext( + FrameIPCTabContext(mChromeOuterWindowID, mMaxTouchPoints)); +} + +MaybeInvalidTabContext::MaybeInvalidTabContext(const IPCTabContext& aParams) + : mInvalidReason(nullptr) { + uint64_t chromeOuterWindowID = 0; + uint32_t maxTouchPoints = 0; + + switch (aParams.type()) { + case IPCTabContext::TPopupIPCTabContext: { + const PopupIPCTabContext& ipcContext = aParams.get_PopupIPCTabContext(); + + chromeOuterWindowID = ipcContext.chromeOuterWindowID(); + break; + } + case IPCTabContext::TFrameIPCTabContext: { + const FrameIPCTabContext& ipcContext = aParams.get_FrameIPCTabContext(); + + chromeOuterWindowID = ipcContext.chromeOuterWindowID(); + maxTouchPoints = ipcContext.maxTouchPoints(); + break; + } + default: { + MOZ_CRASH(); + } + } + + if (!mTabContext.SetTabContext(chromeOuterWindowID, maxTouchPoints)) { + mInvalidReason = "Couldn't initialize TabContext."; + } +} + +bool MaybeInvalidTabContext::IsValid() { return mInvalidReason == nullptr; } + +const char* MaybeInvalidTabContext::GetInvalidReason() { + return mInvalidReason; +} + +const TabContext& MaybeInvalidTabContext::GetTabContext() { + if (!IsValid()) { + MOZ_CRASH("Can't GetTabContext() if !IsValid()."); + } + + return mTabContext; +} + +} // namespace mozilla::dom diff --git a/dom/ipc/TabContext.h b/dom/ipc/TabContext.h new file mode 100644 index 0000000000..00bf69c2a2 --- /dev/null +++ b/dom/ipc/TabContext.h @@ -0,0 +1,171 @@ +/* -*- 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_dom_TabContext_h +#define mozilla_dom_TabContext_h + +#include "nsCOMPtr.h" +#include "mozilla/BasePrincipal.h" +#include "nsPIDOMWindow.h" +#include "nsPIWindowRoot.h" + +namespace mozilla::dom { + +class IPCTabContext; + +/** + * TabContext encapsulates information about an iframe. + * + * BrowserParent and BrowserChild both inherit from TabContext, and you can also + * have standalone TabContext objects. + * + * This class is immutable except by calling one of the protected + * SetTabContext*() methods (and those methods can only be called once). See + * also MutableTabContext. + */ +class TabContext { + public: + TabContext(); + + /* (The implicit copy-constructor and operator= are fine.) */ + + /** + * Generates IPCTabContext of type BrowserFrameIPCTabContext from this + * TabContext's information. + */ + IPCTabContext AsIPCTabContext() const; + + uint64_t ChromeOuterWindowID() const; + + uint32_t MaxTouchPoints() const { return mMaxTouchPoints; } + + protected: + friend class MaybeInvalidTabContext; + + /** + * These protected mutator methods let you modify a TabContext once. Further + * attempts to modify a given TabContext will fail (the method will return + * false). + * + * These mutators will also fail if the TabContext was created with anything + * other than the no-args constructor. + */ + + /** + * Set this TabContext to match the given TabContext. + */ + bool SetTabContext(const TabContext& aContext); + + bool SetTabContext(uint64_t aChromeOuterWindowID, uint32_t aMaxTouchPoints); + + /** + * Modify this TabContext to match the given TabContext. This is a special + * case triggered by nsFrameLoader::SwapWithOtherRemoteLoader which may have + * caused the owner content to change. + * + * This special case only allows the field `mChromeOuterWindowID` to be + * changed. If any other fields have changed, the update is ignored and + * returns false. + */ + bool UpdateTabContextAfterSwap(const TabContext& aContext); + + void SetMaxTouchPoints(uint32_t aMaxTouchPoints) { + mMaxTouchPoints = aMaxTouchPoints; + } + + private: + /** + * Has this TabContext been initialized? If so, mutator methods will fail. + */ + bool mInitialized; + + /** + * The outerWindowID of the window hosting the remote frameloader. + */ + uint64_t mChromeOuterWindowID; + + /** + * Maximum number of touch points. + */ + uint32_t mMaxTouchPoints; +}; + +/** + * MutableTabContext is the same as MaybeInvalidTabContext, except the mutation + * methods are public instead of protected. You can still only call these + * mutation methods once on a given object. + */ +class MutableTabContext : public TabContext { + public: + bool SetTabContext(const TabContext& aContext) { + return TabContext::SetTabContext(aContext); + } + + bool SetTabContext(uint64_t aChromeOuterWindowID, uint32_t aMaxTouchPoints) { + return TabContext::SetTabContext(aChromeOuterWindowID, aMaxTouchPoints); + } +}; + +/** + * MaybeInvalidTabContext is a simple class that lets you transform an + * IPCTabContext into a TabContext. + * + * The issue is that an IPCTabContext is not necessarily valid. So to convert + * an IPCTabContext into a TabContext, you construct a MaybeInvalidTabContext, + * check whether it's valid, and, if so, read out your TabContext. + * + * Example usage: + * + * void UseTabContext(const TabContext& aTabContext); + * + * void CreateTab(const IPCTabContext& aContext) { + * MaybeInvalidTabContext tc(aContext); + * if (!tc.IsValid()) { + * NS_ERROR(nsPrintfCString("Got an invalid IPCTabContext: %s", + * tc.GetInvalidReason())); + * return; + * } + * UseTabContext(tc.GetTabContext()); + * } + */ +class MaybeInvalidTabContext { + public: + /** + * This constructor copies the information in aContext and sets IsValid() as + * appropriate. + */ + explicit MaybeInvalidTabContext(const IPCTabContext& aContext); + + /** + * Was the IPCTabContext we received in our constructor valid? + */ + bool IsValid(); + + /** + * If IsValid(), this function returns null. Otherwise, it returns a + * human-readable string indicating why the IPCTabContext passed to our + * constructor was not valid. + */ + const char* GetInvalidReason(); + + /** + * If IsValid(), this function returns a reference to a TabContext + * corresponding to the IPCTabContext passed to our constructor. If + * !IsValid(), this function crashes. + */ + const TabContext& GetTabContext(); + + private: + MaybeInvalidTabContext(const MaybeInvalidTabContext&) = delete; + MaybeInvalidTabContext& operator=(const MaybeInvalidTabContext&) = delete; + + const char* mInvalidReason; + MutableTabContext mTabContext; +}; + +} // namespace mozilla::dom + +#endif diff --git a/dom/ipc/TabMessageTypes.h b/dom/ipc/TabMessageTypes.h new file mode 100644 index 0000000000..7768c9879c --- /dev/null +++ b/dom/ipc/TabMessageTypes.h @@ -0,0 +1,23 @@ +/* -*- 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_dom_TabMessageTypes_h +#define mozilla_dom_TabMessageTypes_h + +#include "mozilla/RefPtr.h" + +namespace mozilla::dom { + +enum class EmbedderElementEventType { + NoEvent, + LoadEvent, + ErrorEvent, + EndGuard_, +}; + +} // namespace mozilla::dom + +#endif // TABMESSAGE_TYPES_H diff --git a/dom/ipc/TabMessageUtils.h b/dom/ipc/TabMessageUtils.h new file mode 100644 index 0000000000..794d3dffa2 --- /dev/null +++ b/dom/ipc/TabMessageUtils.h @@ -0,0 +1,115 @@ +/* -*- 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 TABMESSAGE_UTILS_H +#define TABMESSAGE_UTILS_H + +#include "ipc/EnumSerializer.h" +#include "mozilla/RefPtr.h" +#include "mozilla/dom/Event.h" +#include "nsIRemoteTab.h" +#include "nsPIDOMWindow.h" +#include "nsCOMPtr.h" +#include "mozilla/dom/EffectsInfo.h" +#include "mozilla/layers/LayersMessageUtils.h" +#include "TabMessageTypes.h" +#include "X11UndefineNone.h" + +namespace IPC { + +template <> +struct ParamTraits<nsSizeMode> + : public ContiguousEnumSerializer<nsSizeMode, nsSizeMode_Normal, + nsSizeMode_Invalid> {}; + +template <> +struct ParamTraits<nsIRemoteTab::NavigationType> + : public ContiguousEnumSerializerInclusive< + nsIRemoteTab::NavigationType, + nsIRemoteTab::NavigationType::NAVIGATE_BACK, + nsIRemoteTab::NavigationType::NAVIGATE_URL> {}; + +template <> +struct ParamTraits<mozilla::dom::EffectsInfo> { + typedef mozilla::dom::EffectsInfo paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.mVisibleRect); + WriteParam(aWriter, aParam.mRasterScale); + WriteParam(aWriter, aParam.mTransformToAncestorScale); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &aResult->mVisibleRect) && + ReadParam(aReader, &aResult->mRasterScale) && + ReadParam(aReader, &aResult->mTransformToAncestorScale); + } +}; + +template <> +struct ParamTraits<mozilla::WhenToScroll> + : public ContiguousEnumSerializerInclusive< + mozilla::WhenToScroll, mozilla::WhenToScroll::Always, + mozilla::WhenToScroll::IfNotFullyVisible> {}; + +template <> +struct ParamTraits<mozilla::ScrollFlags> + : public BitFlagsEnumSerializer<mozilla::ScrollFlags, + mozilla::ScrollFlags::ALL_BITS> {}; + +template <> +struct ParamTraits<mozilla::WhereToScroll> { + using paramType = mozilla::WhereToScroll; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.mPercentage); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + return ReadParam(aReader, &aResult->mPercentage); + } +}; + +template <> +struct ParamTraits<mozilla::ScrollAxis> { + typedef mozilla::ScrollAxis paramType; + + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.mWhereToScroll); + WriteParam(aWriter, aParam.mWhenToScroll); + WriteParam(aWriter, aParam.mOnlyIfPerceivedScrollableDirection); + } + + static bool Read(MessageReader* aReader, paramType* aResult) { + if (!ReadParam(aReader, &aResult->mWhereToScroll)) { + return false; + } + if (!ReadParam(aReader, &aResult->mWhenToScroll)) { + return false; + } + + // We can't set mOnlyIfPerceivedScrollableDirection directly since it's + // a bitfield. + bool value; + if (!ReadParam(aReader, &value)) { + return false; + } + aResult->mOnlyIfPerceivedScrollableDirection = value; + + return true; + } +}; + +template <> +struct ParamTraits<mozilla::dom::EmbedderElementEventType> + : public ContiguousEnumSerializer< + mozilla::dom::EmbedderElementEventType, + mozilla::dom::EmbedderElementEventType::NoEvent, + mozilla::dom::EmbedderElementEventType::EndGuard_> {}; + +} // namespace IPC + +#endif // TABMESSAGE_UTILS_H diff --git a/dom/ipc/URLClassifierChild.h b/dom/ipc/URLClassifierChild.h new file mode 100644 index 0000000000..b5f0537ddc --- /dev/null +++ b/dom/ipc/URLClassifierChild.h @@ -0,0 +1,90 @@ +/* -*- 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_dom_URLClassifierChild_h +#define mozilla_dom_URLClassifierChild_h + +#include "mozilla/dom/PURLClassifierChild.h" +#include "mozilla/dom/PURLClassifierLocalChild.h" +#include "mozilla/ipc/URIUtils.h" +#include "mozilla/net/UrlClassifierFeatureResult.h" +#include "nsIURIClassifier.h" +#include "nsIUrlClassifierFeature.h" + +namespace mozilla::dom { + +class URLClassifierChild : public PURLClassifierChild { + public: + void SetCallback(nsIURIClassifierCallback* aCallback) { + mCallback = aCallback; + } + + mozilla::ipc::IPCResult Recv__delete__(const Maybe<ClassifierInfo>& aInfo, + const nsresult& aResult) { + MOZ_ASSERT(mCallback); + if (aInfo.isSome()) { + mCallback->OnClassifyComplete(aResult, aInfo.ref().list(), + aInfo.ref().provider(), + aInfo.ref().fullhash()); + } + return IPC_OK(); + } + + private: + nsCOMPtr<nsIURIClassifierCallback> mCallback; +}; + +class URLClassifierLocalChild : public PURLClassifierLocalChild { + public: + void SetFeaturesAndCallback( + const nsTArray<RefPtr<nsIUrlClassifierFeature>>& aFeatures, + nsIUrlClassifierFeatureCallback* aCallback) { + mCallback = aCallback; + mFeatures = aFeatures.Clone(); + } + + mozilla::ipc::IPCResult Recv__delete__( + nsTArray<URLClassifierLocalResult>&& aResults) { + nsTArray<RefPtr<nsIUrlClassifierFeatureResult>> finalResults; + + nsTArray<URLClassifierLocalResult> results = std::move(aResults); + for (URLClassifierLocalResult& result : results) { + for (nsIUrlClassifierFeature* feature : mFeatures) { + nsAutoCString name; + nsresult rv = feature->GetName(name); + if (NS_WARN_IF(NS_FAILED(rv))) { + continue; + } + + if (result.featureName() != name) { + continue; + } + + RefPtr<nsIURI> uri = result.uri(); + if (NS_WARN_IF(!uri)) { + continue; + } + + RefPtr<net::UrlClassifierFeatureResult> r = + new net::UrlClassifierFeatureResult(uri, feature, + result.matchingList()); + finalResults.AppendElement(r); + break; + } + } + + mCallback->OnClassifyComplete(finalResults); + return IPC_OK(); + } + + private: + nsCOMPtr<nsIUrlClassifierFeatureCallback> mCallback; + nsTArray<RefPtr<nsIUrlClassifierFeature>> mFeatures; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_URLClassifierChild_h diff --git a/dom/ipc/URLClassifierParent.cpp b/dom/ipc/URLClassifierParent.cpp new file mode 100644 index 0000000000..d0d2f8f26e --- /dev/null +++ b/dom/ipc/URLClassifierParent.cpp @@ -0,0 +1,186 @@ +/* -*- 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 "URLClassifierParent.h" +#include "nsComponentManagerUtils.h" +#include "nsIUrlClassifierFeature.h" +#include "nsNetCID.h" +#include "mozilla/net/UrlClassifierFeatureResult.h" +#include "mozilla/Unused.h" + +using namespace mozilla; +using namespace mozilla::dom; + +///////////////////////////////////////////////////////////////////// +// URLClassifierParent. + +NS_IMPL_ISUPPORTS(URLClassifierParent, nsIURIClassifierCallback) + +mozilla::ipc::IPCResult URLClassifierParent::StartClassify( + nsIPrincipal* aPrincipal, bool* aSuccess) { + *aSuccess = false; + nsresult rv = NS_OK; + // Note that in safe mode, the URL classifier service isn't available, so we + // should handle the service not being present gracefully. + nsCOMPtr<nsIURIClassifier> uriClassifier = + do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + rv = uriClassifier->Classify(aPrincipal, this, aSuccess); + } + if (NS_FAILED(rv) || !*aSuccess) { + // We treat the case where we fail to classify and the case where the + // classifier returns successfully but doesn't perform a lookup as the + // classification not yielding any results, so we just kill the child actor + // without ever calling out callback in both cases. + // This means that code using this in the child process will only get a hit + // on its callback if some classification actually happens. + *aSuccess = false; + ClassificationFailed(); + } + return IPC_OK(); +} + +///////////////////////////////////////////////////////////////////// +// URLClassifierLocalParent. + +namespace { + +// This class implements a nsIUrlClassifierFeature on the parent side, starting +// from an IPC data struct. +class IPCFeature final : public nsIUrlClassifierFeature { + public: + NS_DECL_ISUPPORTS + + IPCFeature(nsIURI* aURI, const IPCURLClassifierFeature& aFeature) + : mURI(aURI), mIPCFeature(aFeature) {} + + NS_IMETHOD + GetName(nsACString& aName) override { + aName = mIPCFeature.featureName(); + return NS_OK; + } + + NS_IMETHOD + GetTables(nsIUrlClassifierFeature::listType, + nsTArray<nsCString>& aTables) override { + aTables.AppendElements(mIPCFeature.tables()); + return NS_OK; + } + + NS_IMETHOD + HasTable(const nsACString& aTable, nsIUrlClassifierFeature::listType, + bool* aResult) override { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mIPCFeature.tables().Contains(aTable); + return NS_OK; + } + + NS_IMETHOD + HasHostInPreferences(const nsACString& aHost, + nsIUrlClassifierFeature::listType, + nsACString& aTableName, bool* aResult) override { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = false; + return NS_OK; + } + + NS_IMETHOD + GetExceptionHostList(nsACString& aList) override { + aList = mIPCFeature.exceptionHostList(); + return NS_OK; + } + + NS_IMETHOD + ProcessChannel(nsIChannel* aChannel, const nsTArray<nsCString>& aList, + const nsTArray<nsCString>& aHashes, + bool* aShouldContinue) override { + NS_ENSURE_ARG_POINTER(aShouldContinue); + *aShouldContinue = true; + + // Nothing to do here. + return NS_OK; + } + + NS_IMETHOD + GetURIByListType(nsIChannel* aChannel, + nsIUrlClassifierFeature::listType aListType, + nsIUrlClassifierFeature::URIType* aURIType, + nsIURI** aURI) override { + NS_ENSURE_ARG_POINTER(aURI); + + // This method should not be called, but we have a URI, let's return it. + nsCOMPtr<nsIURI> uri = mURI; + uri.forget(aURI); + *aURIType = aListType == nsIUrlClassifierFeature::blocklist + ? nsIUrlClassifierFeature::URIType::blocklistURI + : nsIUrlClassifierFeature::URIType::entitylistURI; + return NS_OK; + } + + private: + ~IPCFeature() = default; + + nsCOMPtr<nsIURI> mURI; + IPCURLClassifierFeature mIPCFeature; +}; + +NS_IMPL_ISUPPORTS(IPCFeature, nsIUrlClassifierFeature) + +} // namespace + +NS_IMPL_ISUPPORTS(URLClassifierLocalParent, nsIUrlClassifierFeatureCallback) + +mozilla::ipc::IPCResult URLClassifierLocalParent::StartClassify( + nsIURI* aURI, const nsTArray<IPCURLClassifierFeature>& aFeatures) { + MOZ_ASSERT(aURI); + + nsresult rv = NS_OK; + // Note that in safe mode, the URL classifier service isn't available, so we + // should handle the service not being present gracefully. + nsCOMPtr<nsIURIClassifier> uriClassifier = + do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + OnClassifyComplete(nsTArray<RefPtr<nsIUrlClassifierFeatureResult>>()); + return IPC_OK(); + } + + nsTArray<RefPtr<nsIUrlClassifierFeature>> features; + for (const IPCURLClassifierFeature& feature : aFeatures) { + features.AppendElement(new IPCFeature(aURI, feature)); + } + + // Doesn't matter if we pass blocklist, entitylist or any other list. + // IPCFeature returns always the same values. + rv = uriClassifier->AsyncClassifyLocalWithFeatures( + aURI, features, nsIUrlClassifierFeature::blocklist, this); + if (NS_WARN_IF(NS_FAILED(rv))) { + OnClassifyComplete(nsTArray<RefPtr<nsIUrlClassifierFeatureResult>>()); + return IPC_OK(); + } + + return IPC_OK(); +} + +NS_IMETHODIMP +URLClassifierLocalParent::OnClassifyComplete( + const nsTArray<RefPtr<nsIUrlClassifierFeatureResult>>& aResults) { + if (mIPCOpen) { + nsTArray<URLClassifierLocalResult> ipcResults; + for (nsIUrlClassifierFeatureResult* result : aResults) { + URLClassifierLocalResult* ipcResult = ipcResults.AppendElement(); + + net::UrlClassifierFeatureResult* r = + static_cast<net::UrlClassifierFeatureResult*>(result); + + ipcResult->uri() = r->URI(); + r->Feature()->GetName(ipcResult->featureName()); + ipcResult->matchingList() = r->List(); + } + + Unused << Send__delete__(this, ipcResults); + } + return NS_OK; +} diff --git a/dom/ipc/URLClassifierParent.h b/dom/ipc/URLClassifierParent.h new file mode 100644 index 0000000000..1eb6b2fcce --- /dev/null +++ b/dom/ipc/URLClassifierParent.h @@ -0,0 +1,87 @@ +/* -*- 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_dom_URLClassifierParent_h +#define mozilla_dom_URLClassifierParent_h + +#include "mozilla/dom/PContent.h" +#include "mozilla/dom/PURLClassifierParent.h" +#include "mozilla/dom/PURLClassifierLocalParent.h" +#include "nsIURIClassifier.h" +#include "nsIUrlClassifierFeature.h" + +namespace mozilla::dom { + +class IPCURLClassifierFeature; + +////////////////////////////////////////////////////////////// +// URLClassifierParent + +class URLClassifierParent : public nsIURIClassifierCallback, + public PURLClassifierParent { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + mozilla::ipc::IPCResult StartClassify(nsIPrincipal* aPrincipal, + bool* aSuccess); + + // nsIURIClassifierCallback. + NS_IMETHOD OnClassifyComplete(nsresult aErrorCode, const nsACString& aList, + const nsACString& aProvider, + const nsACString& aFullHash) override { + if (mIPCOpen) { + ClassifierInfo info = ClassifierInfo( + nsCString(aList), nsCString(aProvider), nsCString(aFullHash)); + Unused << Send__delete__(this, Some(info), aErrorCode); + } + return NS_OK; + } + + // Custom. + void ClassificationFailed() { + if (mIPCOpen) { + Unused << Send__delete__(this, Nothing(), NS_ERROR_FAILURE); + } + } + + private: + ~URLClassifierParent() = default; + + // Override PURLClassifierParent::ActorDestroy. We seem to unable to + // override from the base template class. + void ActorDestroy(ActorDestroyReason aWhy) override { mIPCOpen = false; } + + bool mIPCOpen = true; +}; + +////////////////////////////////////////////////////////////// +// URLClassifierLocalParent + +class URLClassifierLocalParent : public nsIUrlClassifierFeatureCallback, + public PURLClassifierLocalParent { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + mozilla::ipc::IPCResult StartClassify( + nsIURI* aURI, const nsTArray<IPCURLClassifierFeature>& aFeatureNames); + + // nsIUrlClassifierFeatureCallback. + NS_IMETHOD + OnClassifyComplete( + const nsTArray<RefPtr<nsIUrlClassifierFeatureResult>>& aResults) override; + + private: + ~URLClassifierLocalParent() = default; + + // Override PURLClassifierLocalParent::ActorDestroy. + void ActorDestroy(ActorDestroyReason aWhy) override { mIPCOpen = false; } + + bool mIPCOpen = true; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_URLClassifierParent_h diff --git a/dom/ipc/UserActivationIPCUtils.h b/dom/ipc/UserActivationIPCUtils.h new file mode 100644 index 0000000000..ebf0aa20c9 --- /dev/null +++ b/dom/ipc/UserActivationIPCUtils.h @@ -0,0 +1,28 @@ +/* -*- 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_dom_useractivation_ipc_utils_h__ +#define mozilla_dom_useractivation_ipc_utils_h__ + +#include "ipc/EnumSerializer.h" + +// Undo X11/X.h's definition of None +#undef None + +#include "mozilla/dom/UserActivation.h" + +namespace IPC { + +template <> +struct ParamTraits<mozilla::dom::UserActivation::State> + : public ContiguousEnumSerializer< + mozilla::dom::UserActivation::State, + mozilla::dom::UserActivation::State::None, + mozilla::dom::UserActivation::State::EndGuard_> {}; + +} // namespace IPC + +#endif // mozilla_dom_useractivation_ipc_utils_h__ diff --git a/dom/ipc/VsyncChild.h b/dom/ipc/VsyncChild.h new file mode 100644 index 0000000000..98250e950d --- /dev/null +++ b/dom/ipc/VsyncChild.h @@ -0,0 +1,24 @@ +/* -*- 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_dom_ipc_VsyncChild_h +#define mozilla_dom_ipc_VsyncChild_h + +#include "mozilla/dom/PVsyncChild.h" + +namespace mozilla::dom { + +class VsyncChild : public PVsyncChild { + friend class PVsyncChild; + + protected: + virtual mozilla::ipc::IPCResult RecvNotify(const VsyncEvent& aVsync, + const float& aVsyncRate) = 0; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_ipc_VsyncChild_h diff --git a/dom/ipc/VsyncMainChild.cpp b/dom/ipc/VsyncMainChild.cpp new file mode 100644 index 0000000000..a1ea332e75 --- /dev/null +++ b/dom/ipc/VsyncMainChild.cpp @@ -0,0 +1,73 @@ +/* -*- 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 "VsyncMainChild.h" + +#include "mozilla/SchedulerGroup.h" +#include "mozilla/VsyncDispatcher.h" +#include "nsThreadUtils.h" + +namespace mozilla::dom { + +VsyncMainChild::VsyncMainChild() + : mIsShutdown(false), mVsyncRate(TimeDuration::Forever()) { + MOZ_ASSERT(NS_IsMainThread()); +} + +VsyncMainChild::~VsyncMainChild() { MOZ_ASSERT(NS_IsMainThread()); } + +void VsyncMainChild::AddChildRefreshTimer(VsyncObserver* aVsyncObserver) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mObservers.Contains(aVsyncObserver)); + + if (mIsShutdown) { + return; + } + + if (mObservers.IsEmpty()) { + Unused << PVsyncChild::SendObserve(); + } + mObservers.AppendElement(std::move(aVsyncObserver)); +} + +void VsyncMainChild::RemoveChildRefreshTimer(VsyncObserver* aVsyncObserver) { + MOZ_ASSERT(NS_IsMainThread()); + if (mIsShutdown) { + return; + } + + if (mObservers.RemoveElement(aVsyncObserver) && mObservers.IsEmpty()) { + Unused << PVsyncChild::SendUnobserve(); + } +} + +void VsyncMainChild::ActorDestroy(ActorDestroyReason aActorDestroyReason) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mIsShutdown); + mIsShutdown = true; + + if (!mObservers.IsEmpty()) { + Unused << PVsyncChild::SendUnobserve(); + } + mObservers.Clear(); +} + +mozilla::ipc::IPCResult VsyncMainChild::RecvNotify(const VsyncEvent& aVsync, + const float& aVsyncRate) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mIsShutdown); + + mVsyncRate = TimeDuration::FromMilliseconds(aVsyncRate); + + for (RefPtr<VsyncObserver> observer : mObservers.ForwardRange()) { + observer->NotifyVsync(aVsync); + } + return IPC_OK(); +} + +TimeDuration VsyncMainChild::GetVsyncRate() { return mVsyncRate; } + +} // namespace mozilla::dom diff --git a/dom/ipc/VsyncMainChild.h b/dom/ipc/VsyncMainChild.h new file mode 100644 index 0000000000..f5678ab0e9 --- /dev/null +++ b/dom/ipc/VsyncMainChild.h @@ -0,0 +1,51 @@ +/* -*- 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_dom_ipc_VsyncMainChild_h +#define mozilla_dom_ipc_VsyncMainChild_h + +#include "mozilla/dom/VsyncChild.h" +#include "nsTObserverArray.h" + +namespace mozilla { + +class VsyncObserver; + +namespace dom { + +// The PVsyncChild actor receives a vsync event from the main process and +// delivers it to the child process. Currently this is restricted to the main +// thread only. The actor will stay alive until the process dies or its +// PVsyncParent actor dies. +class VsyncMainChild final : public VsyncChild { + NS_INLINE_DECL_REFCOUNTING(VsyncMainChild, override) + + friend class PVsyncChild; + + public: + VsyncMainChild(); + + void AddChildRefreshTimer(VsyncObserver* aVsyncObserver); + void RemoveChildRefreshTimer(VsyncObserver* aVsyncObserver); + + TimeDuration GetVsyncRate(); + + private: + ~VsyncMainChild() override; + + mozilla::ipc::IPCResult RecvNotify(const VsyncEvent& aVsync, + const float& aVsyncRate) override; + void ActorDestroy(ActorDestroyReason aActorDestroyReason) override; + + bool mIsShutdown; + TimeDuration mVsyncRate; + nsTObserverArray<VsyncObserver*> mObservers; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_ipc_VsyncChild_h diff --git a/dom/ipc/VsyncParent.cpp b/dom/ipc/VsyncParent.cpp new file mode 100644 index 0000000000..617e054cd3 --- /dev/null +++ b/dom/ipc/VsyncParent.cpp @@ -0,0 +1,103 @@ +/* -*- 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 "VsyncParent.h" + +#include "mozilla/Unused.h" +#include "nsThreadUtils.h" +#include "nsIThread.h" + +namespace mozilla::dom { + +VsyncParent::VsyncParent() + : mObservingVsync(false), + mDestroyed(false), + mInitialThread(NS_GetCurrentThread()) {} + +void VsyncParent::UpdateVsyncDispatcher( + const RefPtr<VsyncDispatcher>& aVsyncDispatcher) { + if (aVsyncDispatcher == mVsyncDispatcher) { + return; + } + + if (mObservingVsync && mVsyncDispatcher) { + mVsyncDispatcher->RemoveVsyncObserver(this); + } + mVsyncDispatcher = aVsyncDispatcher; + if (mObservingVsync) { + mVsyncDispatcher->AddVsyncObserver(this); + } +} + +void VsyncParent::NotifyVsync(const VsyncEvent& aVsync) { + if (IsOnInitialThread()) { + DispatchVsyncEvent(aVsync); + return; + } + + // Called on hardware vsync thread. We should post to current ipc thread. + nsCOMPtr<nsIRunnable> vsyncEvent = NewRunnableMethod<VsyncEvent>( + "dom::VsyncParent::DispatchVsyncEvent", this, + &VsyncParent::DispatchVsyncEvent, aVsync); + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToThreadQueue( + vsyncEvent.forget(), mInitialThread, EventQueuePriority::Vsync)); +} + +void VsyncParent::DispatchVsyncEvent(const VsyncEvent& aVsync) { + AssertIsOnInitialThread(); + + // If we call NotifyVsync() when we handle ActorDestroy() message, we might + // still call DispatchVsyncEvent(). + // Similarly, we might also receive RecvUnobserveVsync() when call + // NotifyVsync(). We use mObservingVsync and mDestroyed flags to skip this + // notification. + if (mObservingVsync && !mDestroyed) { + TimeDuration vsyncRate = mVsyncDispatcher->GetVsyncRate(); + Unused << SendNotify(aVsync, vsyncRate.ToMilliseconds()); + } +} + +mozilla::ipc::IPCResult VsyncParent::RecvObserve() { + AssertIsOnInitialThread(); + if (!mObservingVsync) { + if (mVsyncDispatcher) { + mVsyncDispatcher->AddVsyncObserver(this); + } + mObservingVsync = true; + return IPC_OK(); + } + return IPC_FAIL_NO_REASON(this); +} + +mozilla::ipc::IPCResult VsyncParent::RecvUnobserve() { + AssertIsOnInitialThread(); + if (mObservingVsync) { + if (mVsyncDispatcher) { + mVsyncDispatcher->RemoveVsyncObserver(this); + } + mObservingVsync = false; + return IPC_OK(); + } + return IPC_FAIL_NO_REASON(this); +} + +void VsyncParent::ActorDestroy(ActorDestroyReason aActorDestroyReason) { + MOZ_ASSERT(!mDestroyed); + AssertIsOnInitialThread(); + if (mObservingVsync && mVsyncDispatcher) { + mVsyncDispatcher->RemoveVsyncObserver(this); + } + mVsyncDispatcher = nullptr; + mDestroyed = true; +} + +bool VsyncParent::IsOnInitialThread() { + return NS_GetCurrentThread() == mInitialThread; +} + +void VsyncParent::AssertIsOnInitialThread() { MOZ_ASSERT(IsOnInitialThread()); } + +} // namespace mozilla::dom diff --git a/dom/ipc/VsyncParent.h b/dom/ipc/VsyncParent.h new file mode 100644 index 0000000000..3fe8a8e5f2 --- /dev/null +++ b/dom/ipc/VsyncParent.h @@ -0,0 +1,54 @@ +/* -*- 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_dom_ipc_VsyncParent_h +#define mozilla_dom_ipc_VsyncParent_h + +#include "mozilla/dom/PVsyncParent.h" +#include "mozilla/VsyncDispatcher.h" +#include "nsCOMPtr.h" +#include "mozilla/RefPtr.h" + +class nsIThread; + +namespace mozilla::dom { + +// Use PBackground thread in the main process to send vsync notifications to +// content process. This actor will be released when its parent protocol calls +// DeallocPVsyncParent(). +class VsyncParent final : public PVsyncParent, public VsyncObserver { + friend class PVsyncParent; + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VsyncParent, override) + + public: + VsyncParent(); + void UpdateVsyncDispatcher(const RefPtr<VsyncDispatcher>& aVsyncDispatcher); + + private: + virtual ~VsyncParent() = default; + + void NotifyVsync(const VsyncEvent& aVsync) override; + virtual void ActorDestroy(ActorDestroyReason aActorDestroyReason) override; + + mozilla::ipc::IPCResult RecvObserve(); + mozilla::ipc::IPCResult RecvUnobserve(); + + void DispatchVsyncEvent(const VsyncEvent& aVsync); + void UpdateVsyncRate(); + + bool IsOnInitialThread(); + void AssertIsOnInitialThread(); + + bool mObservingVsync; + bool mDestroyed; + nsCOMPtr<nsIThread> mInitialThread; + RefPtr<VsyncDispatcher> mVsyncDispatcher; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_ipc_VsyncParent_h diff --git a/dom/ipc/VsyncWorkerChild.cpp b/dom/ipc/VsyncWorkerChild.cpp new file mode 100644 index 0000000000..470ff73a27 --- /dev/null +++ b/dom/ipc/VsyncWorkerChild.cpp @@ -0,0 +1,67 @@ +/* -*- 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 "VsyncWorkerChild.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/dom/WorkerRef.h" +#include "mozilla/dom/WorkerScope.h" + +namespace mozilla::dom { + +VsyncWorkerChild::VsyncWorkerChild() = default; + +VsyncWorkerChild::~VsyncWorkerChild() = default; + +bool VsyncWorkerChild::Initialize(WorkerPrivate* aWorkerPrivate) { + MOZ_ASSERT(aWorkerPrivate); + MOZ_ASSERT(!mWorkerRef); + + mWorkerRef = + IPCWorkerRef::Create(aWorkerPrivate, "VsyncWorkerChild", + [self = RefPtr{this}]() { self->Destroy(); }); + return !!mWorkerRef; +} + +void VsyncWorkerChild::Destroy() { + MOZ_ASSERT_IF(!CanSend(), !mWorkerRef); + MOZ_ASSERT_IF(!CanSend(), !mObserving); + Send__delete__(this); + MOZ_ASSERT(!mWorkerRef); + MOZ_ASSERT(!mObserving); +} + +void VsyncWorkerChild::TryObserve() { + if (CanSend() && !mObserving) { + mObserving = SendObserve(); + } +} + +void VsyncWorkerChild::TryUnobserve() { + if (CanSend() && mObserving) { + mObserving = !SendUnobserve(); + } +} + +mozilla::ipc::IPCResult VsyncWorkerChild::RecvNotify(const VsyncEvent& aVsync, + const float& aVsyncRate) { + // For MOZ_CAN_RUN_SCRIPT_BOUNDARY purposes: We know IPDL is explicitly + // keeping our actor alive until it is done processing the messages. We know + // that the WorkerGlobalScope callee is responsible keeping itself alive + // during the OnVsync callback. + WorkerPrivate* workerPrivate = mWorkerRef->Private(); + WorkerGlobalScope* scope = workerPrivate->GlobalScope(); + if (scope) { + scope->OnVsync(aVsync); + } + return IPC_OK(); +} + +void VsyncWorkerChild::ActorDestroy(ActorDestroyReason aActorDestroyReason) { + mWorkerRef = nullptr; + mObserving = false; +} + +} // namespace mozilla::dom diff --git a/dom/ipc/VsyncWorkerChild.h b/dom/ipc/VsyncWorkerChild.h new file mode 100644 index 0000000000..e34555936d --- /dev/null +++ b/dom/ipc/VsyncWorkerChild.h @@ -0,0 +1,52 @@ +/* -*- 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_dom_ipc_VsyncWorkerChild_h +#define mozilla_dom_ipc_VsyncWorkerChild_h + +#include "mozilla/dom/VsyncChild.h" +#include "mozilla/RefPtr.h" +#include "nsISupportsImpl.h" + +namespace mozilla::dom { +class DedicatedWorkerGlobalScope; +class IPCWorkerRef; +class WorkerPrivate; + +// The PVsyncWorkerChild actor receives a vsync event from the main process and +// delivers it to the child process. This is specific for workers which wish to +// subscribe to vsync events to drive requestAnimationFrame callbacks. +class VsyncWorkerChild final : public VsyncChild { + NS_INLINE_DECL_REFCOUNTING(VsyncWorkerChild, override) + + friend class PVsyncChild; + + public: + VsyncWorkerChild(); + + bool Initialize(WorkerPrivate* aWorkerPrivate); + + void Destroy(); + + void TryObserve(); + + void TryUnobserve(); + + private: + ~VsyncWorkerChild() override; + + mozilla::ipc::IPCResult RecvNotify(const VsyncEvent& aVsync, + const float& aVsyncRate) override; + + void ActorDestroy(ActorDestroyReason aActorDestroyReason) override; + + RefPtr<IPCWorkerRef> mWorkerRef; + bool mObserving = false; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_ipc_VsyncWorkerChild_h diff --git a/dom/ipc/WindowGlobalActor.cpp b/dom/ipc/WindowGlobalActor.cpp new file mode 100644 index 0000000000..20dd775bad --- /dev/null +++ b/dom/ipc/WindowGlobalActor.cpp @@ -0,0 +1,191 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/dom/WindowGlobalActor.h" + +#include "AutoplayPolicy.h" +#include "nsContentUtils.h" +#include "mozilla/Components.h" +#include "mozilla/ContentBlockingAllowList.h" +#include "mozilla/Logging.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/JSActorService.h" +#include "mozilla/dom/JSWindowActorParent.h" +#include "mozilla/dom/JSWindowActorChild.h" +#include "mozilla/dom/JSWindowActorProtocol.h" +#include "mozilla/dom/PopupBlocker.h" +#include "mozilla/net/CookieJarSettings.h" +#include "mozilla/dom/WindowContext.h" +#include "mozilla/dom/WindowGlobalChild.h" +#include "mozilla/dom/WindowGlobalParent.h" + +#include "nsGlobalWindowInner.h" +#include "nsNetUtil.h" + +namespace mozilla::dom { + +// CORPP 3.1.3 https://mikewest.github.io/corpp/#integration-html +static nsILoadInfo::CrossOriginEmbedderPolicy InheritedPolicy( + dom::BrowsingContext* aBrowsingContext) { + WindowContext* inherit = aBrowsingContext->GetParentWindowContext(); + if (inherit) { + return inherit->GetEmbedderPolicy(); + } + + return nsILoadInfo::EMBEDDER_POLICY_NULL; +} + +// Common WindowGlobalInit creation code used by both `AboutBlankInitializer` +// and `WindowInitializer`. +WindowGlobalInit WindowGlobalActor::BaseInitializer( + dom::BrowsingContext* aBrowsingContext, uint64_t aInnerWindowId, + uint64_t aOuterWindowId) { + MOZ_DIAGNOSTIC_ASSERT(aBrowsingContext); + + using Indexes = WindowContext::FieldIndexes; + + WindowGlobalInit init; + auto& ctx = init.context(); + ctx.mInnerWindowId = aInnerWindowId; + ctx.mOuterWindowId = aOuterWindowId; + ctx.mBrowsingContextId = aBrowsingContext->Id(); + + // If any synced fields need to be initialized from our BrowsingContext, we + // can initialize them here. + auto& fields = ctx.mFields; + fields.Get<Indexes::IDX_EmbedderPolicy>() = InheritedPolicy(aBrowsingContext); + fields.Get<Indexes::IDX_AutoplayPermission>() = + nsIPermissionManager::UNKNOWN_ACTION; + fields.Get<Indexes::IDX_AllowJavascript>() = true; + return init; +} + +WindowGlobalInit WindowGlobalActor::AboutBlankInitializer( + dom::BrowsingContext* aBrowsingContext, nsIPrincipal* aPrincipal) { + WindowGlobalInit init = + BaseInitializer(aBrowsingContext, nsContentUtils::GenerateWindowId(), + nsContentUtils::GenerateWindowId()); + + init.principal() = aPrincipal; + init.storagePrincipal() = aPrincipal; + Unused << NS_NewURI(getter_AddRefs(init.documentURI()), "about:blank"); + init.isInitialDocument() = true; + + return init; +} + +WindowGlobalInit WindowGlobalActor::WindowInitializer( + nsGlobalWindowInner* aWindow) { + WindowGlobalInit init = + BaseInitializer(aWindow->GetBrowsingContext(), aWindow->WindowID(), + aWindow->GetOuterWindow()->WindowID()); + + init.principal() = aWindow->GetPrincipal(); + init.storagePrincipal() = aWindow->GetEffectiveStoragePrincipal(); + init.documentURI() = aWindow->GetDocumentURI(); + + Document* doc = aWindow->GetDocument(); + + init.isInitialDocument() = doc->IsInitialDocument(); + init.blockAllMixedContent() = doc->GetBlockAllMixedContent(false); + init.upgradeInsecureRequests() = doc->GetUpgradeInsecureRequests(false); + init.sandboxFlags() = doc->GetSandboxFlags(); + net::CookieJarSettings::Cast(doc->CookieJarSettings()) + ->Serialize(init.cookieJarSettings()); + init.httpsOnlyStatus() = doc->HttpsOnlyStatus(); + + using Indexes = WindowContext::FieldIndexes; + + auto& fields = init.context().mFields; + fields.Get<Indexes::IDX_CookieBehavior>() = + Some(doc->CookieJarSettings()->GetCookieBehavior()); + fields.Get<Indexes::IDX_IsOnContentBlockingAllowList>() = + doc->CookieJarSettings()->GetIsOnContentBlockingAllowList(); + fields.Get<Indexes::IDX_IsThirdPartyWindow>() = doc->HasThirdPartyChannel(); + fields.Get<Indexes::IDX_IsThirdPartyTrackingResourceWindow>() = + nsContentUtils::IsThirdPartyTrackingResourceWindow(aWindow); + fields.Get<Indexes::IDX_ShouldResistFingerprinting>() = + doc->ShouldResistFingerprinting(RFPTarget::IsAlwaysEnabledForPrecompute); + fields.Get<Indexes::IDX_OverriddenFingerprintingSettings>() = + doc->GetOverriddenFingerprintingSettings(); + fields.Get<Indexes::IDX_IsSecureContext>() = aWindow->IsSecureContext(); + + // Initialze permission fields + fields.Get<Indexes::IDX_AutoplayPermission>() = + media::AutoplayPolicy::GetSiteAutoplayPermission(init.principal()); + fields.Get<Indexes::IDX_PopupPermission>() = + PopupBlocker::GetPopupPermission(init.principal()); + + // Initialize top level permission fields + if (aWindow->GetBrowsingContext()->IsTop()) { + fields.Get<Indexes::IDX_AllowMixedContent>() = [&] { + uint32_t permit = nsIPermissionManager::UNKNOWN_ACTION; + nsCOMPtr<nsIPermissionManager> permissionManager = + components::PermissionManager::Service(); + + if (permissionManager) { + permissionManager->TestPermissionFromPrincipal( + init.principal(), "mixed-content"_ns, &permit); + } + + return permit == nsIPermissionManager::ALLOW_ACTION; + }(); + + fields.Get<Indexes::IDX_ShortcutsPermission>() = + nsGlobalWindowInner::GetShortcutsPermission(init.principal()); + } + + if (auto policy = doc->GetEmbedderPolicy()) { + fields.Get<Indexes::IDX_EmbedderPolicy>() = *policy; + } + + // Init Mixed Content Fields + nsCOMPtr<nsIURI> innerDocURI = NS_GetInnermostURI(doc->GetDocumentURI()); + fields.Get<Indexes::IDX_IsSecure>() = + innerDocURI && innerDocURI->SchemeIs("https"); + + nsCOMPtr<nsITransportSecurityInfo> securityInfo; + if (nsCOMPtr<nsIChannel> channel = doc->GetChannel()) { + nsCOMPtr<nsILoadInfo> loadInfo(channel->LoadInfo()); + fields.Get<Indexes::IDX_IsOriginalFrameSource>() = + loadInfo->GetOriginalFrameSrcLoad(); + fields.Get<Indexes::IDX_UsingStorageAccess>() = + loadInfo->GetStoragePermission() != nsILoadInfo::NoStoragePermission; + + channel->GetSecurityInfo(getter_AddRefs(securityInfo)); + } + init.securityInfo() = securityInfo; + + fields.Get<Indexes::IDX_IsLocalIP>() = + init.principal()->GetIsLocalIpAddress(); + + // Most data here is specific to the Document, which can change without + // creating a new WindowGlobal. Anything new added here which fits that + // description should also be synchronized in + // WindowGlobalChild::OnNewDocument. + return init; +} + +already_AddRefed<JSActorProtocol> WindowGlobalActor::MatchingJSActorProtocol( + JSActorService* aActorSvc, const nsACString& aName, ErrorResult& aRv) { + RefPtr<JSWindowActorProtocol> proto = + aActorSvc->GetJSWindowActorProtocol(aName); + if (!proto) { + aRv.ThrowNotFoundError(nsPrintfCString("No such JSWindowActor '%s'", + PromiseFlatCString(aName).get())); + return nullptr; + } + + if (!proto->Matches(BrowsingContext(), GetDocumentURI(), GetRemoteType(), + aRv)) { + MOZ_ASSERT(aRv.Failed()); + return nullptr; + } + MOZ_ASSERT(!aRv.Failed()); + return proto.forget(); +} + +} // namespace mozilla::dom diff --git a/dom/ipc/WindowGlobalActor.h b/dom/ipc/WindowGlobalActor.h new file mode 100644 index 0000000000..7ab19c292e --- /dev/null +++ b/dom/ipc/WindowGlobalActor.h @@ -0,0 +1,55 @@ +/* -*- 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_dom_WindowGlobalActor_h +#define mozilla_dom_WindowGlobalActor_h + +#include "nsWrapperCache.h" +#include "nsISupports.h" +#include "mozilla/dom/BrowsingContext.h" +#include "nsIURI.h" +#include "nsString.h" +#include "mozilla/dom/JSActor.h" +#include "mozilla/dom/JSActorManager.h" +#include "mozilla/dom/WindowGlobalTypes.h" + +namespace mozilla { +class ErrorResult; + +namespace dom { + +// Common base class for WindowGlobal{Parent, Child}. +class WindowGlobalActor : public JSActorManager { + public: + // Called to determine initial state for a window global actor created for an + // initial about:blank document. + static WindowGlobalInit AboutBlankInitializer( + dom::BrowsingContext* aBrowsingContext, nsIPrincipal* aPrincipal); + + // Called to determine initial state for a window global actor created for a + // specific existing nsGlobalWindowInner. + static WindowGlobalInit WindowInitializer(nsGlobalWindowInner* aWindow); + + protected: + virtual ~WindowGlobalActor() = default; + + already_AddRefed<JSActorProtocol> MatchingJSActorProtocol( + JSActorService* aActorSvc, const nsACString& aName, + ErrorResult& aRv) final; + + virtual nsIURI* GetDocumentURI() = 0; + virtual const nsACString& GetRemoteType() = 0; + virtual dom::BrowsingContext* BrowsingContext() = 0; + + static WindowGlobalInit BaseInitializer( + dom::BrowsingContext* aBrowsingContext, uint64_t aInnerWindowId, + uint64_t aOuterWindowId); +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_WindowGlobalActor_h diff --git a/dom/ipc/WindowGlobalChild.cpp b/dom/ipc/WindowGlobalChild.cpp new file mode 100644 index 0000000000..ec2811f972 --- /dev/null +++ b/dom/ipc/WindowGlobalChild.cpp @@ -0,0 +1,893 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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 "mozilla/dom/WindowGlobalChild.h" + +#include "mozilla/AntiTrackingUtils.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/BrowsingContextGroup.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/MozFrameLoaderOwnerBinding.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/dom/BrowserBridgeChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/SecurityPolicyViolationEvent.h" +#include "mozilla/dom/SessionStoreRestoreData.h" +#include "mozilla/dom/WindowGlobalActorsBinding.h" +#include "mozilla/dom/WindowContext.h" +#include "mozilla/dom/InProcessChild.h" +#include "mozilla/dom/InProcessParent.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/PresShell.h" +#include "mozilla/ScopeExit.h" +#include "GeckoProfiler.h" +#include "nsContentUtils.h" +#include "nsDocShell.h" +#include "nsFocusManager.h" +#include "nsFrameLoaderOwner.h" +#include "nsGlobalWindowInner.h" +#include "nsNetUtil.h" +#include "nsQueryObject.h" +#include "nsSerializationHelper.h" +#include "nsFrameLoader.h" +#include "nsIScriptSecurityManager.h" + +#include "mozilla/dom/JSWindowActorBinding.h" +#include "mozilla/dom/JSWindowActorChild.h" +#include "mozilla/dom/JSActorService.h" +#include "nsIHttpChannelInternal.h" +#include "nsIURIMutator.h" +#include "nsURLHelper.h" + +using namespace mozilla::ipc; +using namespace mozilla::dom::ipc; + +namespace mozilla::dom { + +WindowGlobalChild::WindowGlobalChild(dom::WindowContext* aWindowContext, + nsIPrincipal* aPrincipal, + nsIURI* aDocumentURI) + : mWindowContext(aWindowContext), + mDocumentPrincipal(aPrincipal), + mDocumentURI(aDocumentURI) { + MOZ_DIAGNOSTIC_ASSERT(mWindowContext); + MOZ_DIAGNOSTIC_ASSERT(mDocumentPrincipal); + + if (!mDocumentURI) { + NS_NewURI(getter_AddRefs(mDocumentURI), "about:blank"); + } + + // Registers a DOM Window with the profiler. It re-registers the same Inner + // Window ID with different URIs because when a Browsing context is first + // loaded, the first url loaded in it will be about:blank. This call keeps the + // first non-about:blank registration of window and discards the previous one. + uint64_t embedderInnerWindowID = 0; + if (BrowsingContext()->GetParent()) { + embedderInnerWindowID = BrowsingContext()->GetEmbedderInnerWindowId(); + } + profiler_register_page( + BrowsingContext()->BrowserId(), InnerWindowId(), + nsContentUtils::TruncatedURLForDisplay(aDocumentURI, 1024), + embedderInnerWindowID, BrowsingContext()->UsePrivateBrowsing()); +} + +already_AddRefed<WindowGlobalChild> WindowGlobalChild::Create( + nsGlobalWindowInner* aWindow) { +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + // Opener policy is set when we start to load a document. Here, we ensure we + // have set the correct Opener policy so that it will be available in the + // parent process through window global child. + nsCOMPtr<nsIChannel> chan = aWindow->GetDocument()->GetChannel(); + nsCOMPtr<nsILoadInfo> loadInfo = chan ? chan->LoadInfo() : nullptr; + nsCOMPtr<nsIHttpChannelInternal> httpChan = do_QueryInterface(chan); + nsILoadInfo::CrossOriginOpenerPolicy policy; + if (httpChan && + loadInfo->GetExternalContentPolicyType() == + ExtContentPolicy::TYPE_DOCUMENT && + NS_SUCCEEDED(httpChan->GetCrossOriginOpenerPolicy(&policy))) { + MOZ_DIAGNOSTIC_ASSERT(policy == + aWindow->GetBrowsingContext()->GetOpenerPolicy()); + } +#endif + + WindowGlobalInit init = WindowGlobalActor::WindowInitializer(aWindow); + RefPtr<WindowGlobalChild> wgc = CreateDisconnected(init); + + // Send the link constructor over PBrowser, or link over PInProcess. + if (XRE_IsParentProcess()) { + InProcessChild* ipChild = InProcessChild::Singleton(); + InProcessParent* ipParent = InProcessParent::Singleton(); + if (!ipChild || !ipParent) { + return nullptr; + } + + ManagedEndpoint<PWindowGlobalParent> endpoint = + ipChild->OpenPWindowGlobalEndpoint(wgc); + ipParent->BindPWindowGlobalEndpoint(std::move(endpoint), + wgc->WindowContext()->Canonical()); + } else { + RefPtr<BrowserChild> browserChild = + BrowserChild::GetFrom(static_cast<mozIDOMWindow*>(aWindow)); + MOZ_ASSERT(browserChild); + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + dom::BrowsingContext* bc = aWindow->GetBrowsingContext(); +#endif + + MOZ_DIAGNOSTIC_ASSERT(bc->AncestorsAreCurrent()); + MOZ_DIAGNOSTIC_ASSERT(bc->IsInProcess()); + + ManagedEndpoint<PWindowGlobalParent> endpoint = + browserChild->OpenPWindowGlobalEndpoint(wgc); + browserChild->SendNewWindowGlobal(std::move(endpoint), init); + } + + wgc->Init(); + wgc->InitWindowGlobal(aWindow); + return wgc.forget(); +} + +already_AddRefed<WindowGlobalChild> WindowGlobalChild::CreateDisconnected( + const WindowGlobalInit& aInit) { + RefPtr<dom::BrowsingContext> browsingContext = + dom::BrowsingContext::Get(aInit.context().mBrowsingContextId); + + RefPtr<dom::WindowContext> windowContext = + dom::WindowContext::GetById(aInit.context().mInnerWindowId); + MOZ_RELEASE_ASSERT(!windowContext, "Creating duplicate WindowContext"); + + // Create our new WindowContext + if (XRE_IsParentProcess()) { + windowContext = WindowGlobalParent::CreateDisconnected(aInit); + } else { + dom::WindowContext::FieldValues fields = aInit.context().mFields; + windowContext = new dom::WindowContext( + browsingContext, aInit.context().mInnerWindowId, + aInit.context().mOuterWindowId, std::move(fields)); + } + + RefPtr<WindowGlobalChild> windowChild = new WindowGlobalChild( + windowContext, aInit.principal(), aInit.documentURI()); + windowContext->mIsInProcess = true; + windowContext->mWindowGlobalChild = windowChild; + return windowChild.forget(); +} + +void WindowGlobalChild::Init() { + MOZ_ASSERT(mWindowContext->mWindowGlobalChild == this); + mWindowContext->Init(); +} + +void WindowGlobalChild::InitWindowGlobal(nsGlobalWindowInner* aWindow) { + mWindowGlobal = aWindow; +} + +void WindowGlobalChild::OnNewDocument(Document* aDocument) { + MOZ_RELEASE_ASSERT(mWindowGlobal); + MOZ_RELEASE_ASSERT(aDocument); + + // Send a series of messages to update document-specific state on + // WindowGlobalParent, when we change documents on an existing WindowGlobal. + // This data is also all sent when we construct a WindowGlobal, so anything + // added here should also be added to WindowGlobalActor::WindowInitializer. + + // FIXME: Perhaps these should be combined into a smaller number of messages? + SendSetIsInitialDocument(aDocument->IsInitialDocument()); + SetDocumentURI(aDocument->GetDocumentURI()); + SetDocumentPrincipal(aDocument->NodePrincipal(), + aDocument->EffectiveStoragePrincipal()); + + nsCOMPtr<nsITransportSecurityInfo> securityInfo; + if (nsCOMPtr<nsIChannel> channel = aDocument->GetChannel()) { + channel->GetSecurityInfo(getter_AddRefs(securityInfo)); + } + SendUpdateDocumentSecurityInfo(securityInfo); + + SendUpdateDocumentCspSettings(aDocument->GetBlockAllMixedContent(false), + aDocument->GetUpgradeInsecureRequests(false)); + SendUpdateSandboxFlags(aDocument->GetSandboxFlags()); + + net::CookieJarSettingsArgs csArgs; + net::CookieJarSettings::Cast(aDocument->CookieJarSettings()) + ->Serialize(csArgs); + if (!SendUpdateCookieJarSettings(csArgs)) { + NS_WARNING( + "Failed to update document's cookie jar settings on the " + "WindowGlobalParent"); + } + + SendUpdateHttpsOnlyStatus(aDocument->HttpsOnlyStatus()); + + // Update window context fields for the newly loaded Document. + WindowContext::Transaction txn; + txn.SetCookieBehavior( + Some(aDocument->CookieJarSettings()->GetCookieBehavior())); + txn.SetIsOnContentBlockingAllowList( + aDocument->CookieJarSettings()->GetIsOnContentBlockingAllowList()); + txn.SetIsThirdPartyWindow(aDocument->HasThirdPartyChannel()); + txn.SetIsThirdPartyTrackingResourceWindow( + nsContentUtils::IsThirdPartyTrackingResourceWindow(mWindowGlobal)); + txn.SetIsSecureContext(mWindowGlobal->IsSecureContext()); + if (auto policy = aDocument->GetEmbedderPolicy()) { + txn.SetEmbedderPolicy(*policy); + } + txn.SetShouldResistFingerprinting(aDocument->ShouldResistFingerprinting( + RFPTarget::IsAlwaysEnabledForPrecompute)); + txn.SetOverriddenFingerprintingSettings( + aDocument->GetOverriddenFingerprintingSettings()); + + if (nsCOMPtr<nsIChannel> channel = aDocument->GetChannel()) { + nsCOMPtr<nsILoadInfo> loadInfo(channel->LoadInfo()); + txn.SetIsOriginalFrameSource(loadInfo->GetOriginalFrameSrcLoad()); + txn.SetUsingStorageAccess(loadInfo->GetStoragePermission() != + nsILoadInfo::NoStoragePermission); + } else { + txn.SetIsOriginalFrameSource(false); + } + + // Init Mixed Content Fields + nsCOMPtr<nsIURI> innerDocURI = + NS_GetInnermostURI(aDocument->GetDocumentURI()); + if (innerDocURI) { + txn.SetIsSecure(innerDocURI->SchemeIs("https")); + } + + MOZ_DIAGNOSTIC_ASSERT(mDocumentPrincipal->GetIsLocalIpAddress() == + mWindowContext->IsLocalIP()); + + MOZ_ALWAYS_SUCCEEDS(txn.Commit(mWindowContext)); +} + +/* static */ +already_AddRefed<WindowGlobalChild> WindowGlobalChild::GetByInnerWindowId( + uint64_t aInnerWindowId) { + if (RefPtr<dom::WindowContext> context = + dom::WindowContext::GetById(aInnerWindowId)) { + return do_AddRef(context->GetWindowGlobalChild()); + } + return nullptr; +} + +dom::BrowsingContext* WindowGlobalChild::BrowsingContext() { + return mWindowContext->GetBrowsingContext(); +} + +uint64_t WindowGlobalChild::InnerWindowId() { + return mWindowContext->InnerWindowId(); +} + +uint64_t WindowGlobalChild::OuterWindowId() { + return mWindowContext->OuterWindowId(); +} + +bool WindowGlobalChild::IsCurrentGlobal() { + return CanSend() && mWindowGlobal->IsCurrentInnerWindow(); +} + +already_AddRefed<WindowGlobalParent> WindowGlobalChild::GetParentActor() { + if (!CanSend()) { + return nullptr; + } + IProtocol* otherSide = InProcessChild::ParentActorFor(this); + return do_AddRef(static_cast<WindowGlobalParent*>(otherSide)); +} + +already_AddRefed<BrowserChild> WindowGlobalChild::GetBrowserChild() { + if (IsInProcess() || !CanSend()) { + return nullptr; + } + return do_AddRef(static_cast<BrowserChild*>(Manager())); +} + +uint64_t WindowGlobalChild::ContentParentId() { + if (XRE_IsParentProcess()) { + return 0; + } + return ContentChild::GetSingleton()->GetID(); +} + +// A WindowGlobalChild is the root in its process if it has no parent, or its +// embedder is in a different process. +bool WindowGlobalChild::IsProcessRoot() { + if (!BrowsingContext()->GetParent()) { + return true; + } + + return !BrowsingContext()->GetEmbedderElement(); +} + +void WindowGlobalChild::BeforeUnloadAdded() { + // Don't bother notifying the parent if we don't have an IPC link open. + if (mBeforeUnloadListeners == 0 && CanSend()) { + Unused << mWindowContext->SetHasBeforeUnload(true); + } + + mBeforeUnloadListeners++; + MOZ_ASSERT(mBeforeUnloadListeners > 0); +} + +void WindowGlobalChild::BeforeUnloadRemoved() { + mBeforeUnloadListeners--; + MOZ_ASSERT(mBeforeUnloadListeners >= 0); + + if (mBeforeUnloadListeners == 0) { + Unused << mWindowContext->SetHasBeforeUnload(false); + } +} + +void WindowGlobalChild::Destroy() { + JSActorWillDestroy(); + + mWindowContext->Discard(); + + // Perform async IPC shutdown unless we're not in-process, and our + // BrowserChild is in the process of being destroyed, which will destroy + // us as well. + RefPtr<BrowserChild> browserChild = GetBrowserChild(); + if (!browserChild || !browserChild->IsDestroyed()) { + SendDestroy(); + } +} + +mozilla::ipc::IPCResult WindowGlobalChild::RecvMakeFrameLocal( + const MaybeDiscarded<dom::BrowsingContext>& aFrameContext, + uint64_t aPendingSwitchId) { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess()); + + MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug, + ("RecvMakeFrameLocal ID=%" PRIx64, aFrameContext.ContextId())); + + if (NS_WARN_IF(aFrameContext.IsNullOrDiscarded())) { + return IPC_OK(); + } + dom::BrowsingContext* frameContext = aFrameContext.get(); + + RefPtr<Element> embedderElt = frameContext->GetEmbedderElement(); + if (NS_WARN_IF(!embedderElt)) { + return IPC_OK(); + } + + if (NS_WARN_IF(embedderElt->GetOwnerGlobal() != GetWindowGlobal())) { + return IPC_OK(); + } + + RefPtr<nsFrameLoaderOwner> flo = do_QueryObject(embedderElt); + MOZ_DIAGNOSTIC_ASSERT(flo, "Embedder must be a nsFrameLoaderOwner"); + + // Trigger a process switch into the current process. + RemotenessOptions options; + options.mRemoteType = NOT_REMOTE_TYPE; + options.mPendingSwitchID.Construct(aPendingSwitchId); + options.mSwitchingInProgressLoad = true; + flo->ChangeRemoteness(options, IgnoreErrors()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalChild::RecvMakeFrameRemote( + const MaybeDiscarded<dom::BrowsingContext>& aFrameContext, + ManagedEndpoint<PBrowserBridgeChild>&& aEndpoint, const TabId& aTabId, + const LayersId& aLayersId, MakeFrameRemoteResolver&& aResolve) { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess()); + + MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug, + ("RecvMakeFrameRemote ID=%" PRIx64, aFrameContext.ContextId())); + + if (!aLayersId.IsValid()) { + return IPC_FAIL(this, "Received an invalid LayersId"); + } + + // Resolve the promise when this function exits, as we'll have fully unloaded + // at that point. + auto scopeExit = MakeScopeExit([&] { aResolve(true); }); + + // Get a BrowsingContext if we're not null or discarded. We don't want to + // early-return before we connect the BrowserBridgeChild, as otherwise we'll + // never break the channel in the parent. + RefPtr<dom::BrowsingContext> frameContext; + if (!aFrameContext.IsDiscarded()) { + frameContext = aFrameContext.get(); + } + + // Immediately construct the BrowserBridgeChild so we can destroy it cleanly + // if the process switch fails. + RefPtr<BrowserBridgeChild> bridge = + new BrowserBridgeChild(frameContext, aTabId, aLayersId); + RefPtr<BrowserChild> manager = GetBrowserChild(); + if (NS_WARN_IF( + !manager->BindPBrowserBridgeEndpoint(std::move(aEndpoint), bridge))) { + return IPC_OK(); + } + + // Synchronously delete de actor here rather than using SendBeginDestroy(), as + // we haven't initialized it yet. + auto deleteBridge = + MakeScopeExit([&] { BrowserBridgeChild::Send__delete__(bridge); }); + + // Immediately tear down the actor if we don't have a valid FrameContext. + if (NS_WARN_IF(aFrameContext.IsNullOrDiscarded())) { + return IPC_OK(); + } + + RefPtr<Element> embedderElt = frameContext->GetEmbedderElement(); + if (NS_WARN_IF(!embedderElt)) { + return IPC_OK(); + } + + if (NS_WARN_IF(embedderElt->GetOwnerGlobal() != GetWindowGlobal())) { + return IPC_OK(); + } + + RefPtr<nsFrameLoaderOwner> flo = do_QueryObject(embedderElt); + MOZ_DIAGNOSTIC_ASSERT(flo, "Embedder must be a nsFrameLoaderOwner"); + + // Trgger a process switch into the specified process. + IgnoredErrorResult rv; + flo->ChangeRemotenessWithBridge(bridge, rv); + if (NS_WARN_IF(rv.Failed())) { + return IPC_OK(); + } + + // Everything succeeded, so don't delete the bridge. + deleteBridge.release(); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalChild::RecvDrawSnapshot( + const Maybe<IntRect>& aRect, const float& aScale, + const nscolor& aBackgroundColor, const uint32_t& aFlags, + DrawSnapshotResolver&& aResolve) { + aResolve(gfx::PaintFragment::Record(BrowsingContext(), aRect, aScale, + aBackgroundColor, + (gfx::CrossProcessPaintFlags)aFlags)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +WindowGlobalChild::RecvSaveStorageAccessPermissionGranted() { + nsCOMPtr<nsPIDOMWindowInner> inner = GetWindowGlobal(); + if (inner) { + inner->SaveStorageAccessPermissionGranted(); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalChild::RecvDispatchSecurityPolicyViolation( + const nsString& aViolationEventJSON) { + nsGlobalWindowInner* window = GetWindowGlobal(); + if (!window) { + return IPC_OK(); + } + + Document* doc = window->GetDocument(); + if (!doc) { + return IPC_OK(); + } + + SecurityPolicyViolationEventInit violationEvent; + if (!violationEvent.Init(aViolationEventJSON)) { + return IPC_OK(); + } + + RefPtr<Event> event = SecurityPolicyViolationEvent::Constructor( + doc, u"securitypolicyviolation"_ns, violationEvent); + event->SetTrusted(true); + doc->DispatchEvent(*event, IgnoreErrors()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalChild::RecvAddBlockedFrameNodeByClassifier( + const MaybeDiscardedBrowsingContext& aNode) { + if (aNode.IsNullOrDiscarded()) { + return IPC_OK(); + } + + nsGlobalWindowInner* window = GetWindowGlobal(); + if (!window) { + return IPC_OK(); + } + + Document* doc = window->GetDocument(); + if (!doc) { + return IPC_OK(); + } + + MOZ_ASSERT(aNode.get()->GetEmbedderElement()->OwnerDoc() == doc); + doc->AddBlockedNodeByClassifier(aNode.get()->GetEmbedderElement()); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalChild::RecvResetScalingZoom() { + if (Document* doc = mWindowGlobal->GetExtantDoc()) { + if (PresShell* ps = doc->GetPresShell()) { + ps->SetResolutionAndScaleTo(1.0, + ResolutionChangeOrigin::MainThreadAdjustment); + } + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalChild::RecvSetContainerFeaturePolicy( + dom::FeaturePolicy* aContainerFeaturePolicy) { + mContainerFeaturePolicy = aContainerFeaturePolicy; + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalChild::RecvRestoreDocShellState( + const dom::sessionstore::DocShellRestoreState& aState, + RestoreDocShellStateResolver&& aResolve) { + if (mWindowGlobal) { + SessionStoreUtils::RestoreDocShellState(mWindowGlobal->GetDocShell(), + aState); + } + aResolve(true); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalChild::RecvRestoreTabContent( + dom::SessionStoreRestoreData* aData, RestoreTabContentResolver&& aResolve) { + aData->RestoreInto(BrowsingContext()); + aResolve(true); + return IPC_OK(); +} + +IPCResult WindowGlobalChild::RecvRawMessage( + const JSActorMessageMeta& aMeta, const Maybe<ClonedMessageData>& aData, + const Maybe<ClonedMessageData>& aStack) { + Maybe<StructuredCloneData> data; + if (aData) { + data.emplace(); + data->BorrowFromClonedMessageData(*aData); + } + Maybe<StructuredCloneData> stack; + if (aStack) { + stack.emplace(); + stack->BorrowFromClonedMessageData(*aStack); + } + ReceiveRawMessage(aMeta, std::move(data), std::move(stack)); + return IPC_OK(); +} + +IPCResult WindowGlobalChild::RecvNotifyPermissionChange(const nsCString& aType, + uint32_t aPermission) { + nsCOMPtr<nsIObserverService> observerService = services::GetObserverService(); + NS_ENSURE_TRUE(observerService, + IPC_FAIL(this, "Failed to get observer service")); + nsPIDOMWindowInner* notifyTarget = + static_cast<nsPIDOMWindowInner*>(this->GetWindowGlobal()); + observerService->NotifyObservers(notifyTarget, "perm-changed-notify-only", + NS_ConvertUTF8toUTF16(aType).get()); + // We only need to handle the revoked permission case here. The permission + // grant case is handled via the Storage Access API code. + if (this->GetWindowGlobal() && + this->GetWindowGlobal()->UsingStorageAccess() && + aPermission != nsIPermissionManager::ALLOW_ACTION) { + this->GetWindowGlobal()->SaveStorageAccessPermissionRevoked(); + } + return IPC_OK(); +} + +void WindowGlobalChild::SetDocumentURI(nsIURI* aDocumentURI) { + // Registers a DOM Window with the profiler. It re-registers the same Inner + // Window ID with different URIs because when a Browsing context is first + // loaded, the first url loaded in it will be about:blank. This call keeps the + // first non-about:blank registration of window and discards the previous one. + uint64_t embedderInnerWindowID = 0; + if (BrowsingContext()->GetParent()) { + embedderInnerWindowID = BrowsingContext()->GetEmbedderInnerWindowId(); + } + profiler_register_page( + BrowsingContext()->BrowserId(), InnerWindowId(), + nsContentUtils::TruncatedURLForDisplay(aDocumentURI, 1024), + embedderInnerWindowID, BrowsingContext()->UsePrivateBrowsing()); + + if (StaticPrefs::dom_security_setdocumenturi()) { + auto isLoadableViaInternet = [](nsIURI* uri) { + return (uri && (net::SchemeIsHTTP(uri) || net::SchemeIsHTTPS(uri))); + }; + if (isLoadableViaInternet(aDocumentURI)) { + nsCOMPtr<nsIURI> principalURI = mDocumentPrincipal->GetURI(); + if (mDocumentPrincipal->GetIsNullPrincipal()) { + nsCOMPtr<nsIPrincipal> precursor = + mDocumentPrincipal->GetPrecursorPrincipal(); + if (precursor) { + principalURI = precursor->GetURI(); + } + } + + if (isLoadableViaInternet(principalURI)) { + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + + if (!NS_SUCCEEDED(ssm->CheckSameOriginURI(principalURI, aDocumentURI, + false, false))) { + MOZ_DIAGNOSTIC_ASSERT(false, + "Setting DocumentURI with a different origin " + "than principal URI"); + } + } + } + } + + mDocumentURI = aDocumentURI; + SendUpdateDocumentURI(WrapNotNull(aDocumentURI)); +} + +void WindowGlobalChild::SetDocumentPrincipal( + nsIPrincipal* aNewDocumentPrincipal, + nsIPrincipal* aNewDocumentStoragePrincipal) { + MOZ_ASSERT(mDocumentPrincipal->Equals(aNewDocumentPrincipal)); + mDocumentPrincipal = aNewDocumentPrincipal; + SendUpdateDocumentPrincipal(aNewDocumentPrincipal, + aNewDocumentStoragePrincipal); +} + +const nsACString& WindowGlobalChild::GetRemoteType() { + if (XRE_IsContentProcess()) { + return ContentChild::GetSingleton()->GetRemoteType(); + } + + return NOT_REMOTE_TYPE; +} + +already_AddRefed<JSWindowActorChild> WindowGlobalChild::GetActor( + JSContext* aCx, const nsACString& aName, ErrorResult& aRv) { + return JSActorManager::GetActor(aCx, aName, aRv) + .downcast<JSWindowActorChild>(); +} + +already_AddRefed<JSWindowActorChild> WindowGlobalChild::GetExistingActor( + const nsACString& aName) { + return JSActorManager::GetExistingActor(aName).downcast<JSWindowActorChild>(); +} + +already_AddRefed<JSActor> WindowGlobalChild::InitJSActor( + JS::Handle<JSObject*> aMaybeActor, const nsACString& aName, + ErrorResult& aRv) { + RefPtr<JSWindowActorChild> actor; + if (aMaybeActor.get()) { + aRv = UNWRAP_OBJECT(JSWindowActorChild, aMaybeActor.get(), actor); + if (aRv.Failed()) { + return nullptr; + } + } else { + actor = new JSWindowActorChild(); + } + + MOZ_RELEASE_ASSERT(!actor->GetManager(), + "mManager was already initialized once!"); + actor->Init(aName, this); + return actor.forget(); +} + +void WindowGlobalChild::ActorDestroy(ActorDestroyReason aWhy) { + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript(), + "Destroying WindowGlobalChild can run script"); + + // If our WindowContext hasn't been marked as discarded yet, ensure it's + // marked as discarded at this point. + mWindowContext->Discard(); + + profiler_unregister_page(InnerWindowId()); + + // Destroy our JSActors, and reject any pending queries. + JSActorDidDestroy(); +} + +bool WindowGlobalChild::IsSameOriginWith( + const dom::WindowContext* aOther) const { + if (aOther == WindowContext()) { + return true; + } + + MOZ_DIAGNOSTIC_ASSERT(WindowContext()->Group() == aOther->Group()); + if (nsGlobalWindowInner* otherWin = aOther->GetInnerWindow()) { + return mDocumentPrincipal->Equals(otherWin->GetPrincipal()); + } + return false; +} + +bool WindowGlobalChild::SameOriginWithTop() { + return IsSameOriginWith(WindowContext()->TopWindowContext()); +} + +// For historical context, see: +// +// Bug 13871: Prevent frameset spoofing +// Bug 103638: Targets with same name in different windows open in wrong +// window with javascript +// Bug 408052: Adopt "ancestor" frame navigation policy +// Bug 1570207: Refactor logic to rely on BrowsingContextGroups to enforce +// origin attribute isolation +// Bug 1810619: Crash at null in nsDocShell::ValidateOrigin +bool WindowGlobalChild::CanNavigate(dom::BrowsingContext* aTarget, + bool aConsiderOpener) { + MOZ_DIAGNOSTIC_ASSERT(WindowContext()->Group() == aTarget->Group(), + "A WindowGlobalChild should never try to navigate a " + "BrowsingContext from another group"); + + auto isFileScheme = [](nsIPrincipal* aPrincipal) -> bool { + // NOTE: This code previously checked for a file scheme using + // `nsIPrincipal::GetURI()` combined with `NS_GetInnermostURI`. We no longer + // use GetURI, as it has been deprecated, and it makes more sense to take + // advantage of the pre-computed origin, which will already use the + // innermost URI (bug 1810619) + nsAutoCString origin, scheme; + return NS_SUCCEEDED(aPrincipal->GetOriginNoSuffix(origin)) && + NS_SUCCEEDED(net_ExtractURLScheme(origin, scheme)) && + scheme == "file"_ns; + }; + + // A frame can navigate itself and its own root. + if (aTarget == BrowsingContext() || aTarget == BrowsingContext()->Top()) { + return true; + } + + // If the target frame doesn't yet have a WindowContext, start checking + // principals from its direct ancestor instead. It would inherit its principal + // from this document upon creation. + dom::WindowContext* initialWc = aTarget->GetCurrentWindowContext(); + if (!initialWc) { + initialWc = aTarget->GetParentWindowContext(); + } + + // A frame can navigate any frame with a same-origin ancestor. + bool isFileDocument = isFileScheme(DocumentPrincipal()); + for (dom::WindowContext* wc = initialWc; wc; + wc = wc->GetParentWindowContext()) { + dom::WindowGlobalChild* wgc = wc->GetWindowGlobalChild(); + if (!wgc) { + continue; // out-of process, so not same-origin. + } + + if (DocumentPrincipal()->Equals(wgc->DocumentPrincipal())) { + return true; + } + + // Not strictly equal, special case if both are file: URIs. + // + // file: URIs are considered the same domain for the purpose of frame + // navigation, regardless of script accessibility (bug 420425). + if (isFileDocument && isFileScheme(wgc->DocumentPrincipal())) { + return true; + } + } + + // If the target is a top-level document, a frame can navigate it if it can + // navigate its opener. + if (aConsiderOpener && !aTarget->GetParent()) { + if (RefPtr<dom::BrowsingContext> opener = aTarget->GetOpener()) { + return CanNavigate(opener, false); + } + } + + return false; +} + +// FindWithName follows the rules for choosing a browsing context, +// with the exception of sandboxing for iframes. The implementation +// for arbitrarily choosing between two browsing contexts with the +// same name is as follows: +// +// 1) The start browsing context, i.e. 'this' +// 2) Descendants in insertion order +// 3) The parent +// 4) Siblings and their children, both in insertion order +// 5) After this we iteratively follow the parent chain, repeating 3 +// and 4 until +// 6) If there is no parent, consider all other top level browsing +// contexts and their children, both in insertion order +// +// See +// https://html.spec.whatwg.org/multipage/browsers.html#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name +dom::BrowsingContext* WindowGlobalChild::FindBrowsingContextWithName( + const nsAString& aName, bool aUseEntryGlobalForAccessCheck) { + RefPtr<WindowGlobalChild> requestingContext = this; + if (aUseEntryGlobalForAccessCheck) { + if (nsGlobalWindowInner* caller = nsContentUtils::EntryInnerWindow()) { + if (caller->GetBrowsingContextGroup() == WindowContext()->Group()) { + requestingContext = caller->GetWindowGlobalChild(); + } else { + MOZ_RELEASE_ASSERT(caller->GetPrincipal()->IsSystemPrincipal(), + "caller must be either same-group or system"); + } + } + } + MOZ_ASSERT(requestingContext, "must have a requestingContext"); + + dom::BrowsingContext* found = nullptr; + if (aName.IsEmpty()) { + // You can't find a browsing context with an empty name. + found = nullptr; + } else if (aName.LowerCaseEqualsLiteral("_blank")) { + // Just return null. Caller must handle creating a new window with + // a blank name. + found = nullptr; + } else if (nsContentUtils::IsSpecialName(aName)) { + found = BrowsingContext()->FindWithSpecialName(aName, *requestingContext); + } else if (dom::BrowsingContext* child = + BrowsingContext()->FindWithNameInSubtree(aName, + requestingContext)) { + found = child; + } else { + dom::WindowContext* current = WindowContext(); + + do { + Span<RefPtr<dom::BrowsingContext>> siblings; + dom::WindowContext* parent = current->GetParentWindowContext(); + + if (!parent) { + // We've reached the root of the tree, consider browsing + // contexts in the same browsing context group. + siblings = WindowContext()->Group()->Toplevels(); + } else if (dom::BrowsingContext* bc = parent->GetBrowsingContext(); + bc && bc->NameEquals(aName) && + requestingContext->CanNavigate(bc) && bc->IsTargetable()) { + found = bc; + break; + } else { + siblings = parent->NonSyntheticChildren(); + } + + for (dom::BrowsingContext* sibling : siblings) { + if (sibling == current->GetBrowsingContext()) { + continue; + } + + if (dom::BrowsingContext* relative = + sibling->FindWithNameInSubtree(aName, requestingContext)) { + found = relative; + // Breaks the outer loop + parent = nullptr; + break; + } + } + + current = parent; + } while (current); + } + + // Helpers should perform access control checks, which means that we + // only need to assert that we can access found. + MOZ_DIAGNOSTIC_ASSERT(!found || requestingContext->CanNavigate(found)); + + return found; +} + +void WindowGlobalChild::UnblockBFCacheFor(BFCacheStatus aStatus) { + SendUpdateBFCacheStatus(0, aStatus); +} + +void WindowGlobalChild::BlockBFCacheFor(BFCacheStatus aStatus) { + SendUpdateBFCacheStatus(aStatus, 0); +} + +WindowGlobalChild::~WindowGlobalChild() = default; + +JSObject* WindowGlobalChild::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return WindowGlobalChild_Binding::Wrap(aCx, this, aGivenProto); +} + +nsISupports* WindowGlobalChild::GetParentObject() { + return xpc::NativeGlobal(xpc::PrivilegedJunkScope()); +} + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK_PTR(WindowGlobalChild, mWindowGlobal, + mContainerFeaturePolicy, + mWindowContext) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WindowGlobalChild) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(WindowGlobalChild) +NS_IMPL_CYCLE_COLLECTING_RELEASE(WindowGlobalChild) + +} // namespace mozilla::dom diff --git a/dom/ipc/WindowGlobalChild.h b/dom/ipc/WindowGlobalChild.h new file mode 100644 index 0000000000..107b3f471d --- /dev/null +++ b/dom/ipc/WindowGlobalChild.h @@ -0,0 +1,225 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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/. */ + +#ifndef mozilla_dom_WindowGlobalChild_h +#define mozilla_dom_WindowGlobalChild_h + +#include "mozilla/RefPtr.h" +#include "mozilla/WeakPtr.h" +#include "mozilla/dom/PWindowGlobalChild.h" +#include "nsRefPtrHashtable.h" +#include "nsWrapperCache.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/WindowGlobalActor.h" + +class nsGlobalWindowInner; +class nsDocShell; + +namespace mozilla::dom { + +class BrowsingContext; +class FeaturePolicy; +class WindowContext; +class WindowGlobalParent; +class JSWindowActorChild; +class JSActorMessageMeta; +class BrowserChild; + +/** + * Actor for a single nsGlobalWindowInner. This actor is used to communicate + * information to the parent process asynchronously. + */ +class WindowGlobalChild final : public WindowGlobalActor, + public nsWrapperCache, + public PWindowGlobalChild, + public SupportsWeakPtr { + friend class PWindowGlobalChild; + + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(WindowGlobalChild) + + static already_AddRefed<WindowGlobalChild> GetByInnerWindowId( + uint64_t aInnerWindowId); + + static already_AddRefed<WindowGlobalChild> GetByInnerWindowId( + const GlobalObject& aGlobal, uint64_t aInnerWindowId) { + return GetByInnerWindowId(aInnerWindowId); + } + + dom::BrowsingContext* BrowsingContext() override; + dom::WindowContext* WindowContext() const { return mWindowContext; } + nsGlobalWindowInner* GetWindowGlobal() const { return mWindowGlobal; } + + // Has this actor been shut down + bool IsClosed() { return !CanSend(); } + void Destroy(); + + // Check if this actor is managed by PInProcess, as-in the document is loaded + // in the chrome process. + bool IsInProcess() { return XRE_IsParentProcess(); } + + nsIURI* GetDocumentURI() override { return mDocumentURI; } + void SetDocumentURI(nsIURI* aDocumentURI); + // See the corresponding comment for `UpdateDocumentPrincipal` in + // PWindowGlobal on why and when this is allowed + void SetDocumentPrincipal(nsIPrincipal* aNewDocumentPrincipal, + nsIPrincipal* aNewDocumentStoragePrincipal); + + nsIPrincipal* DocumentPrincipal() { return mDocumentPrincipal; } + + // The Window ID for this WindowGlobal + uint64_t InnerWindowId(); + uint64_t OuterWindowId(); + + uint64_t ContentParentId(); + + int64_t BeforeUnloadListeners() { return mBeforeUnloadListeners; } + void BeforeUnloadAdded(); + void BeforeUnloadRemoved(); + + bool IsCurrentGlobal(); + + bool IsProcessRoot(); + + // Get the other side of this actor if it is an in-process actor. Returns + // |nullptr| if the actor has been torn down, or is not in-process. + already_AddRefed<WindowGlobalParent> GetParentActor(); + + // Get this actor's manager if it is not an in-process actor. Returns + // |nullptr| if the actor has been torn down, or is in-process. + already_AddRefed<BrowserChild> GetBrowserChild(); + + // Get a JS actor object by name. + already_AddRefed<JSWindowActorChild> GetActor(JSContext* aCx, + const nsACString& aName, + ErrorResult& aRv); + already_AddRefed<JSWindowActorChild> GetExistingActor( + const nsACString& aName); + + // Create and initialize the WindowGlobalChild object. + static already_AddRefed<WindowGlobalChild> Create( + nsGlobalWindowInner* aWindow); + static already_AddRefed<WindowGlobalChild> CreateDisconnected( + const WindowGlobalInit& aInit); + + void Init(); + + void InitWindowGlobal(nsGlobalWindowInner* aWindow); + + // Called when a new document is loaded in this WindowGlobalChild. + void OnNewDocument(Document* aNewDocument); + + // Returns true if this WindowGlobal is same-origin with the given + // WindowContext. Out-of-process WindowContexts are supported, and are assumed + // to be cross-origin. + // + // The given WindowContext must be in the same BrowsingContextGroup as this + // window global. + bool IsSameOriginWith(const dom::WindowContext* aOther) const; + + bool SameOriginWithTop(); + + // Returns `true` if this WindowGlobal is allowed to navigate the given + // BrowsingContext. BrowsingContexts which are currently out-of-process are + // supported, and assumed to be cross-origin. + // + // The given BrowsingContext must be in the same BrowsingContextGroup as this + // WindowGlobal. + bool CanNavigate(dom::BrowsingContext* aTarget, bool aConsiderOpener = true); + + // Using the rules for choosing a browsing context we try to find + // the browsing context with the given name in the set of + // transitively reachable browsing contexts. Performs access control + // checks with regard to this. + // See + // https://html.spec.whatwg.org/multipage/browsers.html#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name. + dom::BrowsingContext* FindBrowsingContextWithName( + const nsAString& aName, bool aUseEntryGlobalForAccessCheck = true); + + nsISupports* GetParentObject(); + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + dom::FeaturePolicy* GetContainerFeaturePolicy() const { + return mContainerFeaturePolicy; + } + + void UnblockBFCacheFor(BFCacheStatus aStatus); + void BlockBFCacheFor(BFCacheStatus aStatus); + + protected: + const nsACString& GetRemoteType() override; + + already_AddRefed<JSActor> InitJSActor(JS::Handle<JSObject*> aMaybeActor, + const nsACString& aName, + ErrorResult& aRv) override; + mozilla::ipc::IProtocol* AsNativeActor() override { return this; } + + // IPC messages + mozilla::ipc::IPCResult RecvRawMessage( + const JSActorMessageMeta& aMeta, const Maybe<ClonedMessageData>& aData, + const Maybe<ClonedMessageData>& aStack); + + mozilla::ipc::IPCResult RecvMakeFrameLocal( + const MaybeDiscarded<dom::BrowsingContext>& aFrameContext, + uint64_t aPendingSwitchId); + + mozilla::ipc::IPCResult RecvMakeFrameRemote( + const MaybeDiscarded<dom::BrowsingContext>& aFrameContext, + ManagedEndpoint<PBrowserBridgeChild>&& aEndpoint, const TabId& aTabId, + const LayersId& aLayersId, MakeFrameRemoteResolver&& aResolve); + + mozilla::ipc::IPCResult RecvDrawSnapshot(const Maybe<IntRect>& aRect, + const float& aScale, + const nscolor& aBackgroundColor, + const uint32_t& aFlags, + DrawSnapshotResolver&& aResolve); + + mozilla::ipc::IPCResult RecvDispatchSecurityPolicyViolation( + const nsString& aViolationEventJSON); + + mozilla::ipc::IPCResult RecvSaveStorageAccessPermissionGranted(); + + mozilla::ipc::IPCResult RecvAddBlockedFrameNodeByClassifier( + const MaybeDiscardedBrowsingContext& aNode); + + mozilla::ipc::IPCResult RecvResetScalingZoom(); + + mozilla::ipc::IPCResult RecvSetContainerFeaturePolicy( + dom::FeaturePolicy* aContainerFeaturePolicy); + + mozilla::ipc::IPCResult RecvRestoreDocShellState( + const dom::sessionstore::DocShellRestoreState& aState, + RestoreDocShellStateResolver&& aResolve); + + // TODO: Use MOZ_CAN_RUN_SCRIPT when it gains IPDL support (bug 1539864) + MOZ_CAN_RUN_SCRIPT_BOUNDARY mozilla::ipc::IPCResult RecvRestoreTabContent( + dom::SessionStoreRestoreData* aData, + RestoreTabContentResolver&& aResolve); + + mozilla::ipc::IPCResult RecvNotifyPermissionChange(const nsCString& aType, + uint32_t aPermission); + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + private: + WindowGlobalChild(dom::WindowContext* aWindowContext, + nsIPrincipal* aPrincipal, nsIURI* aURI); + + ~WindowGlobalChild(); + + RefPtr<nsGlobalWindowInner> mWindowGlobal; + RefPtr<dom::WindowContext> mWindowContext; + nsCOMPtr<nsIPrincipal> mDocumentPrincipal; + RefPtr<dom::FeaturePolicy> mContainerFeaturePolicy; + nsCOMPtr<nsIURI> mDocumentURI; + int64_t mBeforeUnloadListeners = 0; +}; + +} // namespace mozilla::dom + +#endif // !defined(mozilla_dom_WindowGlobalChild_h) diff --git a/dom/ipc/WindowGlobalParent.cpp b/dom/ipc/WindowGlobalParent.cpp new file mode 100644 index 0000000000..13fbe2940c --- /dev/null +++ b/dom/ipc/WindowGlobalParent.cpp @@ -0,0 +1,1724 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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 "mozilla/dom/WindowGlobalParent.h" + +#include <algorithm> + +#include "mozilla/AntiTrackingUtils.h" +#include "mozilla/AsyncEventDispatcher.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/ContentBlockingAllowList.h" +#include "mozilla/dom/InProcessParent.h" +#include "mozilla/dom/BrowserBridgeParent.h" +#include "mozilla/dom/BrowsingContextGroup.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/dom/ClientInfo.h" +#include "mozilla/dom/ClientIPCTypes.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/BrowserHost.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/IdentityCredential.h" +#include "mozilla/dom/MediaController.h" +#include "mozilla/dom/WindowGlobalChild.h" +#include "mozilla/dom/ChromeUtils.h" +#include "mozilla/dom/UseCounterMetrics.h" +#include "mozilla/dom/ipc/IdType.h" +#include "mozilla/dom/ipc/StructuredCloneData.h" +#include "mozilla/glean/GleanMetrics.h" +#include "mozilla/Components.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/ServoCSSParser.h" +#include "mozilla/ServoStyleSet.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Variant.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "nsContentUtils.h" +#include "nsDocShell.h" +#include "nsDocShellLoadState.h" +#include "nsError.h" +#include "nsFrameLoader.h" +#include "nsFrameLoaderOwner.h" +#include "nsICookieManager.h" +#include "nsICookieService.h" +#include "nsQueryObject.h" +#include "nsNetUtil.h" +#include "nsSandboxFlags.h" +#include "nsSerializationHelper.h" +#include "nsIBrowser.h" +#include "nsIEffectiveTLDService.h" +#include "nsIHttpsOnlyModePermission.h" +#include "nsIPromptCollection.h" +#include "nsITimer.h" +#include "nsITransportSecurityInfo.h" +#include "nsISharePicker.h" +#include "nsIURIMutator.h" +#include "nsIWebProgressListener.h" +#include "nsScriptSecurityManager.h" +#include "nsIOService.h" + +#include "mozilla/dom/DOMException.h" +#include "mozilla/dom/DOMExceptionBinding.h" + +#include "mozilla/dom/JSActorService.h" +#include "mozilla/dom/JSWindowActorBinding.h" +#include "mozilla/dom/JSWindowActorParent.h" + +#include "mozilla/net/NeckoParent.h" +#include "mozilla/net/PCookieServiceParent.h" +#include "mozilla/net/CookieServiceParent.h" + +#include "SessionStoreFunctions.h" +#include "nsIXPConnect.h" +#include "nsImportModule.h" +#include "nsIXULRuntime.h" + +#include "mozilla/dom/PBackgroundSessionStorageCache.h" + +using namespace mozilla::ipc; +using namespace mozilla::dom::ipc; + +extern mozilla::LazyLogModule gSHIPBFCacheLog; +extern mozilla::LazyLogModule gUseCountersLog; + +namespace mozilla::dom { + +WindowGlobalParent::WindowGlobalParent( + CanonicalBrowsingContext* aBrowsingContext, uint64_t aInnerWindowId, + uint64_t aOuterWindowId, FieldValues&& aInit) + : WindowContext(aBrowsingContext, aInnerWindowId, aOuterWindowId, + std::move(aInit)), + mSandboxFlags(0), + mDocumentHasLoaded(false), + mDocumentHasUserInteracted(false), + mBlockAllMixedContent(false), + mUpgradeInsecureRequests(false) { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess(), "Parent process only"); +} + +already_AddRefed<WindowGlobalParent> WindowGlobalParent::CreateDisconnected( + const WindowGlobalInit& aInit) { + RefPtr<CanonicalBrowsingContext> browsingContext = + CanonicalBrowsingContext::Get(aInit.context().mBrowsingContextId); + if (NS_WARN_IF(!browsingContext)) { + return nullptr; + } + + RefPtr<WindowGlobalParent> wgp = + GetByInnerWindowId(aInit.context().mInnerWindowId); + MOZ_RELEASE_ASSERT(!wgp, "Creating duplicate WindowGlobalParent"); + + FieldValues fields(aInit.context().mFields); + wgp = + new WindowGlobalParent(browsingContext, aInit.context().mInnerWindowId, + aInit.context().mOuterWindowId, std::move(fields)); + wgp->mDocumentPrincipal = aInit.principal(); + wgp->mDocumentURI = aInit.documentURI(); + wgp->mIsInitialDocument = Some(aInit.isInitialDocument()); + wgp->mBlockAllMixedContent = aInit.blockAllMixedContent(); + wgp->mUpgradeInsecureRequests = aInit.upgradeInsecureRequests(); + wgp->mSandboxFlags = aInit.sandboxFlags(); + wgp->mHttpsOnlyStatus = aInit.httpsOnlyStatus(); + wgp->mSecurityInfo = aInit.securityInfo(); + net::CookieJarSettings::Deserialize(aInit.cookieJarSettings(), + getter_AddRefs(wgp->mCookieJarSettings)); + MOZ_RELEASE_ASSERT(wgp->mDocumentPrincipal, "Must have a valid principal"); + + nsresult rv = wgp->SetDocumentStoragePrincipal(aInit.storagePrincipal()); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), + "Must succeed in setting storage principal"); + + return wgp.forget(); +} + +void WindowGlobalParent::Init() { + MOZ_ASSERT(Manager(), "Should have a manager!"); + + // Invoke our base class' `Init` method. This will register us in + // `gWindowContexts`. + WindowContext::Init(); + + // Determine which content process the window global is coming from. + dom::ContentParentId processId(0); + ContentParent* cp = nullptr; + if (!IsInProcess()) { + cp = static_cast<ContentParent*>(Manager()->Manager()); + processId = cp->ChildID(); + + // Ensure the content process has permissions for this principal. + cp->TransmitPermissionsForPrincipal(mDocumentPrincipal); + } + + MOZ_DIAGNOSTIC_ASSERT( + !BrowsingContext()->GetParent() || + BrowsingContext()->GetEmbedderInnerWindowId(), + "When creating a non-root WindowGlobalParent, the WindowGlobalParent " + "for our embedder should've already been created."); + + // Ensure we have a document URI + if (!mDocumentURI) { + NS_NewURI(getter_AddRefs(mDocumentURI), "about:blank"); + } + + // NOTE: `cp` may be nullptr, but that's OK, we need to tell every other + // process in our group in that case. + IPCInitializer ipcinit = GetIPCInitializer(); + Group()->EachOtherParent(cp, [&](ContentParent* otherContent) { + Unused << otherContent->SendCreateWindowContext(ipcinit); + }); + + if (!BrowsingContext()->IsDiscarded()) { + MOZ_ALWAYS_SUCCEEDS( + BrowsingContext()->SetCurrentInnerWindowId(InnerWindowId())); + + Unused << SendSetContainerFeaturePolicy( + BrowsingContext()->GetContainerFeaturePolicy()); + } + + if (BrowsingContext()->IsTopContent()) { + // For top level sandboxed documents we need to create a new principal + // from URI + OriginAttributes, since the document principal will be a + // NullPrincipal. See Bug 1654546. + if (mSandboxFlags & SANDBOXED_ORIGIN) { + ContentBlockingAllowList::RecomputePrincipal( + mDocumentURI, mDocumentPrincipal->OriginAttributesRef(), + getter_AddRefs(mDocContentBlockingAllowListPrincipal)); + } else { + ContentBlockingAllowList::ComputePrincipal( + mDocumentPrincipal, + getter_AddRefs(mDocContentBlockingAllowListPrincipal)); + } + } + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->NotifyObservers(ToSupports(this), "window-global-created", nullptr); + } + + if (!BrowsingContext()->IsDiscarded() && ShouldTrackSiteOriginTelemetry()) { + mOriginCounter.emplace(); + mOriginCounter->UpdateSiteOriginsFrom(this, /* aIncrease = */ true); + } +} + +void WindowGlobalParent::OriginCounter::UpdateSiteOriginsFrom( + WindowGlobalParent* aParent, bool aIncrease) { + MOZ_RELEASE_ASSERT(aParent); + + if (aParent->DocumentPrincipal()->GetIsContentPrincipal()) { + nsAutoCString origin; + aParent->DocumentPrincipal()->GetSiteOrigin(origin); + + if (aIncrease) { + int32_t& count = mOriginMap.LookupOrInsert(origin); + count += 1; + mMaxOrigins = std::max(mMaxOrigins, mOriginMap.Count()); + } else if (auto entry = mOriginMap.Lookup(origin)) { + entry.Data() -= 1; + + if (entry.Data() == 0) { + entry.Remove(); + } + } + } +} + +void WindowGlobalParent::OriginCounter::Accumulate() { + mozilla::glean::geckoview::per_document_site_origins.AccumulateSamples( + {mMaxOrigins}); + + mMaxOrigins = 0; + mOriginMap.Clear(); +} + +/* static */ +already_AddRefed<WindowGlobalParent> WindowGlobalParent::GetByInnerWindowId( + uint64_t aInnerWindowId) { + if (!XRE_IsParentProcess()) { + return nullptr; + } + + return WindowContext::GetById(aInnerWindowId).downcast<WindowGlobalParent>(); +} + +already_AddRefed<WindowGlobalChild> WindowGlobalParent::GetChildActor() { + if (!CanSend()) { + return nullptr; + } + IProtocol* otherSide = InProcessParent::ChildActorFor(this); + return do_AddRef(static_cast<WindowGlobalChild*>(otherSide)); +} + +BrowserParent* WindowGlobalParent::GetBrowserParent() { + if (IsInProcess() || !CanSend()) { + return nullptr; + } + return static_cast<BrowserParent*>(Manager()); +} + +ContentParent* WindowGlobalParent::GetContentParent() { + if (IsInProcess() || !CanSend()) { + return nullptr; + } + return static_cast<ContentParent*>(Manager()->Manager()); +} + +already_AddRefed<nsFrameLoader> WindowGlobalParent::GetRootFrameLoader() { + dom::BrowsingContext* top = BrowsingContext()->Top(); + + RefPtr<nsFrameLoaderOwner> frameLoaderOwner = + do_QueryObject(top->GetEmbedderElement()); + if (frameLoaderOwner) { + return frameLoaderOwner->GetFrameLoader(); + } + return nullptr; +} + +uint64_t WindowGlobalParent::ContentParentId() { + RefPtr<BrowserParent> browserParent = GetBrowserParent(); + return browserParent ? browserParent->Manager()->ChildID() : 0; +} + +int32_t WindowGlobalParent::OsPid() { + RefPtr<BrowserParent> browserParent = GetBrowserParent(); + return browserParent ? browserParent->Manager()->Pid() : -1; +} + +// A WindowGlobalPaernt is the root in its process if it has no parent, or its +// embedder is in a different process. +bool WindowGlobalParent::IsProcessRoot() { + if (!BrowsingContext()->GetParent()) { + return true; + } + + RefPtr<WindowGlobalParent> embedder = + BrowsingContext()->GetEmbedderWindowGlobal(); + if (NS_WARN_IF(!embedder)) { + return false; + } + + return ContentParentId() != embedder->ContentParentId(); +} + +uint32_t WindowGlobalParent::ContentBlockingEvents() { + return GetContentBlockingLog()->GetContentBlockingEventsInLog(); +} + +void WindowGlobalParent::GetContentBlockingLog(nsAString& aLog) { + NS_ConvertUTF8toUTF16 log(GetContentBlockingLog()->Stringify()); + aLog.Assign(std::move(log)); +} + +mozilla::ipc::IPCResult WindowGlobalParent::RecvLoadURI( + const MaybeDiscarded<dom::BrowsingContext>& aTargetBC, + nsDocShellLoadState* aLoadState, bool aSetNavigating) { + if (aTargetBC.IsNullOrDiscarded()) { + MOZ_LOG( + BrowsingContext::GetLog(), LogLevel::Debug, + ("ParentIPC: Trying to send a message with dead or detached context")); + return IPC_OK(); + } + + if (net::SchemeIsJavascript(aLoadState->URI())) { + return IPC_FAIL(this, "Illegal cross-process javascript: load attempt"); + } + + RefPtr<CanonicalBrowsingContext> targetBC = aTargetBC.get_canonical(); + + // FIXME: For cross-process loads, we should double check CanAccess() for the + // source browsing context in the parent process. + + if (targetBC->Group() != BrowsingContext()->Group()) { + return IPC_FAIL(this, "Illegal cross-group BrowsingContext load"); + } + + // FIXME: We should really initiate the load in the parent before bouncing + // back down to the child. + + targetBC->LoadURI(aLoadState, aSetNavigating); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalParent::RecvInternalLoad( + nsDocShellLoadState* aLoadState) { + if (!aLoadState->Target().IsEmpty() || + aLoadState->TargetBrowsingContext().IsNull()) { + return IPC_FAIL(this, "must already be retargeted"); + } + if (aLoadState->TargetBrowsingContext().IsDiscarded()) { + MOZ_LOG( + BrowsingContext::GetLog(), LogLevel::Debug, + ("ParentIPC: Trying to send a message with dead or detached context")); + return IPC_OK(); + } + + if (net::SchemeIsJavascript(aLoadState->URI())) { + return IPC_FAIL(this, "Illegal cross-process javascript: load attempt"); + } + + RefPtr<CanonicalBrowsingContext> targetBC = + aLoadState->TargetBrowsingContext().get_canonical(); + + // FIXME: For cross-process loads, we should double check CanAccess() for the + // source browsing context in the parent process. + + if (targetBC->Group() != BrowsingContext()->Group()) { + return IPC_FAIL(this, "Illegal cross-group BrowsingContext load"); + } + + // FIXME: We should really initiate the load in the parent before bouncing + // back down to the child. + + targetBC->InternalLoad(aLoadState); + return IPC_OK(); +} + +IPCResult WindowGlobalParent::RecvUpdateDocumentURI(NotNull<nsIURI*> aURI) { + // XXX(nika): Assert that the URI change was one which makes sense (either + // about:blank -> a real URI, or a legal push/popstate URI change): + if (StaticPrefs::dom_security_setdocumenturi()) { + nsAutoCString scheme; + if (NS_FAILED(aURI->GetScheme(scheme))) { + return IPC_FAIL(this, "Setting DocumentURI without scheme."); + } + + nsCOMPtr<nsIIOService> ios = do_GetIOService(); + if (!ios) { + return IPC_FAIL(this, "Cannot get IOService"); + } + nsCOMPtr<nsIProtocolHandler> handler; + ios->GetProtocolHandler(scheme.get(), getter_AddRefs(handler)); + if (!handler) { + return IPC_FAIL(this, "Setting DocumentURI with unknown protocol."); + } + + auto isLoadableViaInternet = [](nsIURI* uri) { + return (uri && (net::SchemeIsHTTP(uri) || net::SchemeIsHTTPS(uri))); + }; + + if (isLoadableViaInternet(aURI)) { + nsCOMPtr<nsIURI> principalURI = mDocumentPrincipal->GetURI(); + if (mDocumentPrincipal->GetIsNullPrincipal()) { + nsCOMPtr<nsIPrincipal> precursor = + mDocumentPrincipal->GetPrecursorPrincipal(); + if (precursor) { + principalURI = precursor->GetURI(); + } + } + + if (isLoadableViaInternet(principalURI) && + !nsScriptSecurityManager::SecurityCompareURIs(principalURI, aURI)) { + return IPC_FAIL(this, + "Setting DocumentURI with a different Origin than " + "principal URI"); + } + } + } + + mDocumentURI = aURI; + return IPC_OK(); +} + +nsresult WindowGlobalParent::SetDocumentStoragePrincipal( + nsIPrincipal* aNewDocumentStoragePrincipal) { + if (mDocumentPrincipal->Equals(aNewDocumentStoragePrincipal)) { + mDocumentStoragePrincipal = mDocumentPrincipal; + return NS_OK; + } + + // Compare originNoSuffix to ensure it's equal. + nsCString noSuffix; + nsresult rv = mDocumentPrincipal->GetOriginNoSuffix(noSuffix); + if (NS_FAILED(rv)) { + return rv; + } + + nsCString storageNoSuffix; + rv = aNewDocumentStoragePrincipal->GetOriginNoSuffix(storageNoSuffix); + if (NS_FAILED(rv)) { + return rv; + } + + if (noSuffix != storageNoSuffix) { + return NS_ERROR_FAILURE; + } + + if (!mDocumentPrincipal->OriginAttributesRef().EqualsIgnoringPartitionKey( + aNewDocumentStoragePrincipal->OriginAttributesRef())) { + return NS_ERROR_FAILURE; + } + + mDocumentStoragePrincipal = aNewDocumentStoragePrincipal; + return NS_OK; +} + +IPCResult WindowGlobalParent::RecvUpdateDocumentPrincipal( + nsIPrincipal* aNewDocumentPrincipal, + nsIPrincipal* aNewDocumentStoragePrincipal) { + if (!mDocumentPrincipal->Equals(aNewDocumentPrincipal)) { + return IPC_FAIL(this, + "Trying to reuse WindowGlobalParent but the principal of " + "the new document does not match the old one"); + } + mDocumentPrincipal = aNewDocumentPrincipal; + + if (NS_FAILED(SetDocumentStoragePrincipal(aNewDocumentStoragePrincipal))) { + return IPC_FAIL(this, + "Trying to reuse WindowGlobalParent but the principal of " + "the new document does not match the storage principal"); + } + + return IPC_OK(); +} +mozilla::ipc::IPCResult WindowGlobalParent::RecvUpdateDocumentTitle( + const nsString& aTitle) { + if (mDocumentTitle.isSome() && mDocumentTitle.value() == aTitle) { + return IPC_OK(); + } + + mDocumentTitle = Some(aTitle); + + // Send a pagetitlechanged event only for changes to the title + // for top-level frames. + if (!BrowsingContext()->IsTop()) { + return IPC_OK(); + } + + // Notify media controller in order to update its default metadata. + if (BrowsingContext()->HasCreatedMediaController()) { + BrowsingContext()->GetMediaController()->NotifyPageTitleChanged(); + } + + Element* frameElement = BrowsingContext()->GetEmbedderElement(); + if (!frameElement) { + return IPC_OK(); + } + + AsyncEventDispatcher::RunDOMEventWhenSafe( + *frameElement, u"pagetitlechanged"_ns, CanBubble::eYes, + ChromeOnlyDispatch::eYes); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalParent::RecvUpdateHttpsOnlyStatus( + uint32_t aHttpsOnlyStatus) { + mHttpsOnlyStatus = aHttpsOnlyStatus; + return IPC_OK(); +} + +IPCResult WindowGlobalParent::RecvUpdateDocumentHasLoaded( + bool aDocumentHasLoaded) { + mDocumentHasLoaded = aDocumentHasLoaded; + return IPC_OK(); +} + +IPCResult WindowGlobalParent::RecvUpdateDocumentHasUserInteracted( + bool aDocumentHasUserInteracted) { + mDocumentHasUserInteracted = aDocumentHasUserInteracted; + return IPC_OK(); +} + +IPCResult WindowGlobalParent::RecvUpdateSandboxFlags(uint32_t aSandboxFlags) { + mSandboxFlags = aSandboxFlags; + return IPC_OK(); +} + +IPCResult WindowGlobalParent::RecvUpdateDocumentCspSettings( + bool aBlockAllMixedContent, bool aUpgradeInsecureRequests) { + mBlockAllMixedContent = aBlockAllMixedContent; + mUpgradeInsecureRequests = aUpgradeInsecureRequests; + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalParent::RecvSetClientInfo( + const IPCClientInfo& aIPCClientInfo) { + mClientInfo = Some(ClientInfo(aIPCClientInfo)); + return IPC_OK(); +} + +IPCResult WindowGlobalParent::RecvDestroy() { + // Make a copy so that we can avoid potential iterator invalidation when + // calling the user-provided Destroy() methods. + JSActorWillDestroy(); + + if (CanSend()) { + RefPtr<BrowserParent> browserParent = GetBrowserParent(); + if (!browserParent || !browserParent->IsDestroyed()) { + Unused << Send__delete__(this); + } + } + return IPC_OK(); +} + +IPCResult WindowGlobalParent::RecvRawMessage( + const JSActorMessageMeta& aMeta, const Maybe<ClonedMessageData>& aData, + const Maybe<ClonedMessageData>& aStack) { + Maybe<StructuredCloneData> data; + if (aData) { + data.emplace(); + data->BorrowFromClonedMessageData(*aData); + } + Maybe<StructuredCloneData> stack; + if (aStack) { + stack.emplace(); + stack->BorrowFromClonedMessageData(*aStack); + } + ReceiveRawMessage(aMeta, std::move(data), std::move(stack)); + return IPC_OK(); +} + +const nsACString& WindowGlobalParent::GetRemoteType() { + if (RefPtr<BrowserParent> browserParent = GetBrowserParent()) { + return browserParent->Manager()->GetRemoteType(); + } + + return NOT_REMOTE_TYPE; +} + +void WindowGlobalParent::NotifyContentBlockingEvent( + uint32_t aEvent, nsIRequest* aRequest, bool aBlocked, + const nsACString& aTrackingOrigin, + const nsTArray<nsCString>& aTrackingFullHashes, + const Maybe<ContentBlockingNotifier::StorageAccessPermissionGrantedReason>& + aReason, + const Maybe<ContentBlockingNotifier::CanvasFingerprinter>& + aCanvasFingerprinter, + const Maybe<bool> aCanvasFingerprinterKnownText) { + MOZ_ASSERT(NS_IsMainThread()); + DebugOnly<bool> isCookiesBlocked = + aEvent == nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER || + aEvent == nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER; + MOZ_ASSERT_IF(aBlocked, aReason.isNothing()); + MOZ_ASSERT_IF(!isCookiesBlocked, aReason.isNothing()); + MOZ_ASSERT_IF(isCookiesBlocked && !aBlocked, aReason.isSome()); + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + // TODO: temporarily remove this until we find the root case of Bug 1609144 + // MOZ_DIAGNOSTIC_ASSERT_IF(XRE_IsE10sParentProcess(), !IsInProcess()); + + // Return early if this WindowGlobalParent is in process. + if (IsInProcess()) { + return; + } + + Maybe<uint32_t> event = GetContentBlockingLog()->RecordLogParent( + aTrackingOrigin, aEvent, aBlocked, aReason, aTrackingFullHashes, + aCanvasFingerprinter, aCanvasFingerprinterKnownText); + + // Notify the OnContentBlockingEvent if necessary. + if (event) { + if (auto* webProgress = GetBrowsingContext()->GetWebProgress()) { + webProgress->OnContentBlockingEvent(webProgress, aRequest, event.value()); + } + } +} + +already_AddRefed<JSWindowActorParent> WindowGlobalParent::GetActor( + JSContext* aCx, const nsACString& aName, ErrorResult& aRv) { + return JSActorManager::GetActor(aCx, aName, aRv) + .downcast<JSWindowActorParent>(); +} + +already_AddRefed<JSWindowActorParent> WindowGlobalParent::GetExistingActor( + const nsACString& aName) { + return JSActorManager::GetExistingActor(aName) + .downcast<JSWindowActorParent>(); +} + +already_AddRefed<JSActor> WindowGlobalParent::InitJSActor( + JS::Handle<JSObject*> aMaybeActor, const nsACString& aName, + ErrorResult& aRv) { + RefPtr<JSWindowActorParent> actor; + if (aMaybeActor.get()) { + aRv = UNWRAP_OBJECT(JSWindowActorParent, aMaybeActor.get(), actor); + if (aRv.Failed()) { + return nullptr; + } + } else { + actor = new JSWindowActorParent(); + } + + MOZ_RELEASE_ASSERT(!actor->GetManager(), + "mManager was already initialized once!"); + actor->Init(aName, this); + return actor.forget(); +} + +bool WindowGlobalParent::IsCurrentGlobal() { + if (mozilla::SessionHistoryInParent() && BrowsingContext() && + BrowsingContext()->IsInBFCache()) { + return false; + } + + return CanSend() && BrowsingContext()->GetCurrentWindowGlobal() == this; +} + +bool WindowGlobalParent::IsActiveInTab() { + if (!CanSend()) { + return false; + } + + CanonicalBrowsingContext* bc = BrowsingContext(); + if (!bc || bc->GetCurrentWindowGlobal() != this) { + return false; + } + + // We check the top BC so we don't need to worry about getting a stale value. + // That may not be necessary. + MOZ_ASSERT(bc->Top()->IsInBFCache() == bc->IsInBFCache(), + "BFCache bit out of sync?"); + return bc->AncestorsAreCurrent() && !bc->Top()->IsInBFCache(); +} + +namespace { + +class ShareHandler final : public PromiseNativeHandler { + public: + explicit ShareHandler( + mozilla::dom::WindowGlobalParent::ShareResolver&& aResolver) + : mResolver(std::move(aResolver)) {} + + NS_DECL_ISUPPORTS + + public: + virtual void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, + ErrorResult& aRv) override { + mResolver(NS_OK); + } + + virtual void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, + ErrorResult& aRv) override { + if (NS_WARN_IF(!aValue.isObject())) { + mResolver(NS_ERROR_FAILURE); + return; + } + + // nsresult is stored as Exception internally in Promise + JS::Rooted<JSObject*> obj(aCx, &aValue.toObject()); + RefPtr<DOMException> unwrapped; + nsresult rv = UNWRAP_OBJECT(DOMException, &obj, unwrapped); + if (NS_WARN_IF(NS_FAILED(rv))) { + mResolver(NS_ERROR_FAILURE); + return; + } + + mResolver(unwrapped->GetResult()); + } + + private: + ~ShareHandler() = default; + + mozilla::dom::WindowGlobalParent::ShareResolver mResolver; +}; + +NS_IMPL_ISUPPORTS0(ShareHandler) + +} // namespace + +mozilla::ipc::IPCResult WindowGlobalParent::RecvGetContentBlockingEvents( + WindowGlobalParent::GetContentBlockingEventsResolver&& aResolver) { + uint32_t events = GetContentBlockingLog()->GetContentBlockingEventsInLog(); + aResolver(events); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalParent::RecvUpdateCookieJarSettings( + const CookieJarSettingsArgs& aCookieJarSettingsArgs) { + net::CookieJarSettings::Deserialize(aCookieJarSettingsArgs, + getter_AddRefs(mCookieJarSettings)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalParent::RecvUpdateDocumentSecurityInfo( + nsITransportSecurityInfo* aSecurityInfo) { + mSecurityInfo = aSecurityInfo; + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalParent::RecvShare( + IPCWebShareData&& aData, WindowGlobalParent::ShareResolver&& aResolver) { + // Widget Layer handoff... + nsCOMPtr<nsISharePicker> sharePicker = + do_GetService("@mozilla.org/sharepicker;1"); + if (!sharePicker) { + aResolver(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return IPC_OK(); + } + + // Initialize the ShareWidget + RefPtr<BrowserParent> parent = GetBrowserParent(); + nsCOMPtr<mozIDOMWindowProxy> openerWindow; + if (parent) { + openerWindow = parent->GetParentWindowOuter(); + if (!openerWindow) { + aResolver(NS_ERROR_FAILURE); + return IPC_OK(); + } + } + sharePicker->Init(openerWindow); + + // And finally share the data... + RefPtr<Promise> promise; + nsresult rv = sharePicker->Share(aData.title(), aData.text(), aData.url(), + getter_AddRefs(promise)); + if (NS_FAILED(rv)) { + aResolver(rv); + return IPC_OK(); + } + + // Handler finally awaits response... + RefPtr<ShareHandler> handler = new ShareHandler(std::move(aResolver)); + promise->AppendNativeHandler(handler); + + return IPC_OK(); +} + +namespace { + +class CheckPermitUnloadRequest final : public PromiseNativeHandler, + public nsITimerCallback { + public: + CheckPermitUnloadRequest(WindowGlobalParent* aWGP, bool aHasInProcessBlocker, + nsIDocumentViewer::PermitUnloadAction aAction, + std::function<void(bool)>&& aResolver) + : mResolver(std::move(aResolver)), + mWGP(aWGP), + mAction(aAction), + mFoundBlocker(aHasInProcessBlocker) {} + + void Run(ContentParent* aIgnoreProcess = nullptr, uint32_t aTimeout = 0) { + MOZ_ASSERT(mState == State::UNINITIALIZED); + mState = State::WAITING; + + RefPtr<CheckPermitUnloadRequest> self(this); + + AutoTArray<ContentParent*, 8> seen; + if (aIgnoreProcess) { + seen.AppendElement(aIgnoreProcess); + } + + BrowsingContext* bc = mWGP->GetBrowsingContext(); + bc->PreOrderWalk([&](dom::BrowsingContext* aBC) { + if (WindowGlobalParent* wgp = + aBC->Canonical()->GetCurrentWindowGlobal()) { + ContentParent* cp = wgp->GetContentParent(); + if (wgp->HasBeforeUnload() && !seen.ContainsSorted(cp)) { + seen.InsertElementSorted(cp); + mPendingRequests++; + auto resolve = [self](bool blockNavigation) { + if (blockNavigation) { + self->mFoundBlocker = true; + } + self->ResolveRequest(); + }; + if (cp) { + cp->SendDispatchBeforeUnloadToSubtree( + bc, resolve, [self](auto) { self->ResolveRequest(); }); + } else { + ContentChild::DispatchBeforeUnloadToSubtree(bc, resolve); + } + } + } + }); + + if (mPendingRequests && aTimeout) { + Unused << NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, aTimeout, + nsITimer::TYPE_ONE_SHOT); + } + + CheckDoneWaiting(); + } + + void ResolveRequest() { + mPendingRequests--; + CheckDoneWaiting(); + } + + NS_IMETHODIMP Notify(nsITimer* aTimer) override { + MOZ_ASSERT(aTimer == mTimer); + if (mState == State::WAITING) { + mState = State::TIMED_OUT; + CheckDoneWaiting(); + } + return NS_OK; + } + + void CheckDoneWaiting() { + // If we've found a blocker, we prompt immediately without waiting for + // further responses. The user's response applies to the entire navigation + // attempt, regardless of how many "beforeunload" listeners we call. + if (mState != State::TIMED_OUT && + (mState != State::WAITING || (mPendingRequests && !mFoundBlocker))) { + return; + } + + mState = State::PROMPTING; + + // Clearing our reference to the timer will automatically cancel it if it's + // still running. + mTimer = nullptr; + + if (!mFoundBlocker) { + SendReply(true); + return; + } + + auto action = mAction; + if (StaticPrefs::dom_disable_beforeunload()) { + action = nsIDocumentViewer::eDontPromptAndUnload; + } + if (action != nsIDocumentViewer::ePrompt) { + SendReply(action == nsIDocumentViewer::eDontPromptAndUnload); + return; + } + + // Handle any failure in prompting by aborting the navigation. See comment + // in nsContentViewer::PermitUnload for reasoning. + auto cleanup = MakeScopeExit([&]() { SendReply(false); }); + + if (nsCOMPtr<nsIPromptCollection> prompt = + do_GetService("@mozilla.org/embedcomp/prompt-collection;1")) { + RefPtr<Promise> promise; + prompt->AsyncBeforeUnloadCheck(mWGP->GetBrowsingContext(), + getter_AddRefs(promise)); + + if (!promise) { + return; + } + + promise->AppendNativeHandler(this); + cleanup.release(); + } + } + + void SendReply(bool aAllow) { + MOZ_ASSERT(mState != State::REPLIED); + mResolver(aAllow); + mState = State::REPLIED; + } + + void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, + ErrorResult& aRv) override { + MOZ_ASSERT(mState == State::PROMPTING); + + SendReply(JS::ToBoolean(aValue)); + } + + void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, + ErrorResult& aRv) override { + MOZ_ASSERT(mState == State::PROMPTING); + + SendReply(false); + } + + NS_DECL_ISUPPORTS + + private: + ~CheckPermitUnloadRequest() { + // We may get here without having sent a reply if the promise we're waiting + // on is destroyed without being resolved or rejected. + if (mState != State::REPLIED) { + SendReply(false); + } + } + + enum class State : uint8_t { + UNINITIALIZED, + WAITING, + TIMED_OUT, + PROMPTING, + REPLIED, + }; + + std::function<void(bool)> mResolver; + + RefPtr<WindowGlobalParent> mWGP; + nsCOMPtr<nsITimer> mTimer; + + uint32_t mPendingRequests = 0; + + nsIDocumentViewer::PermitUnloadAction mAction; + + State mState = State::UNINITIALIZED; + + bool mFoundBlocker = false; +}; + +NS_IMPL_ISUPPORTS(CheckPermitUnloadRequest, nsITimerCallback) + +} // namespace + +mozilla::ipc::IPCResult WindowGlobalParent::RecvCheckPermitUnload( + bool aHasInProcessBlocker, XPCOMPermitUnloadAction aAction, + CheckPermitUnloadResolver&& aResolver) { + if (!IsCurrentGlobal()) { + aResolver(false); + return IPC_OK(); + } + + auto request = MakeRefPtr<CheckPermitUnloadRequest>( + this, aHasInProcessBlocker, aAction, std::move(aResolver)); + request->Run(/* aIgnoreProcess */ GetContentParent()); + + return IPC_OK(); +} + +already_AddRefed<Promise> WindowGlobalParent::PermitUnload( + PermitUnloadAction aAction, uint32_t aTimeout, mozilla::ErrorResult& aRv) { + nsIGlobalObject* global = GetParentObject(); + RefPtr<Promise> promise = Promise::Create(global, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + auto request = MakeRefPtr<CheckPermitUnloadRequest>( + this, /* aHasInProcessBlocker */ false, + nsIDocumentViewer::PermitUnloadAction(aAction), + [promise](bool aAllow) { promise->MaybeResolve(aAllow); }); + request->Run(/* aIgnoreProcess */ nullptr, aTimeout); + + return promise.forget(); +} + +void WindowGlobalParent::PermitUnload(std::function<void(bool)>&& aResolver) { + RefPtr<CheckPermitUnloadRequest> request = new CheckPermitUnloadRequest( + this, /* aHasInProcessBlocker */ false, + nsIDocumentViewer::PermitUnloadAction::ePrompt, std::move(aResolver)); + request->Run(); +} + +already_AddRefed<mozilla::dom::Promise> WindowGlobalParent::DrawSnapshot( + const DOMRect* aRect, double aScale, const nsACString& aBackgroundColor, + bool aResetScrollPosition, mozilla::ErrorResult& aRv) { + nsIGlobalObject* global = GetParentObject(); + RefPtr<Promise> promise = Promise::Create(global, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + nscolor color; + if (NS_WARN_IF(!ServoCSSParser::ComputeColor(nullptr, NS_RGB(0, 0, 0), + aBackgroundColor, &color, + nullptr, nullptr))) { + aRv = NS_ERROR_FAILURE; + return nullptr; + } + + gfx::CrossProcessPaintFlags flags = + gfx::CrossProcessPaintFlags::UseHighQualityScaling; + if (!aRect) { + // If no explicit Rect was passed, we want the currently visible viewport. + flags |= gfx::CrossProcessPaintFlags::DrawView; + } else if (aResetScrollPosition) { + flags |= gfx::CrossProcessPaintFlags::ResetScrollPosition; + } + + if (!gfx::CrossProcessPaint::Start(this, aRect, (float)aScale, color, flags, + promise)) { + aRv = NS_ERROR_FAILURE; + return nullptr; + } + return promise.forget(); +} + +void WindowGlobalParent::DrawSnapshotInternal(gfx::CrossProcessPaint* aPaint, + const Maybe<IntRect>& aRect, + float aScale, + nscolor aBackgroundColor, + uint32_t aFlags) { + auto promise = SendDrawSnapshot(aRect, aScale, aBackgroundColor, aFlags); + + RefPtr<gfx::CrossProcessPaint> paint(aPaint); + RefPtr<WindowGlobalParent> wgp(this); + promise->Then( + GetMainThreadSerialEventTarget(), __func__, + [paint, wgp](PaintFragment&& aFragment) { + paint->ReceiveFragment(wgp, std::move(aFragment)); + }, + [paint, wgp](ResponseRejectReason&& aReason) { + paint->LostFragment(wgp); + }); +} + +/** + * Accumulated page use counter data for a given top-level content document. + */ +struct PageUseCounters { + // The number of page use counter data messages we are still waiting for. + uint32_t mWaiting = 0; + + // Whether we have received any page use counter data. + bool mReceivedAny = false; + + // The accumulated page use counters. + UseCounters mUseCounters; +}; + +mozilla::ipc::IPCResult WindowGlobalParent::RecvExpectPageUseCounters( + const MaybeDiscarded<WindowContext>& aTop) { + if (aTop.IsNull()) { + return IPC_FAIL(this, "aTop must not be null"); + } + + MOZ_LOG(gUseCountersLog, LogLevel::Debug, + ("Expect page use counters: WindowContext %" PRIu64 " -> %" PRIu64, + InnerWindowId(), aTop.ContextId())); + + // We've been called to indicate that the document in our window intends + // to send use counter data to accumulate towards the top-level document's + // page use counters. This causes us to wait for this window to go away + // (in WindowGlobalParent::ActorDestroy) before reporting the page use + // counters via Telemetry. + RefPtr<WindowGlobalParent> page = + static_cast<WindowGlobalParent*>(aTop.GetMaybeDiscarded()); + if (!page || page->mSentPageUseCounters) { + MOZ_LOG(gUseCountersLog, LogLevel::Debug, + (" > too late, won't report page use counters for this straggler")); + return IPC_OK(); + } + + if (mPageUseCountersWindow) { + if (mPageUseCountersWindow != page) { + return IPC_FAIL(this, + "ExpectPageUseCounters called on the same " + "WindowContext with a different aTop value"); + } + + // We can get called with the same aTop value more than once, e.g. for + // initial about:blank documents and then subsequent "real" documents loaded + // into the same window. We must note each source window only once. + return IPC_OK(); + } + + // Note that the top-level document must wait for one more window's use + // counters before reporting via Telemetry. + mPageUseCountersWindow = page; + if (!page->mPageUseCounters) { + page->mPageUseCounters = MakeUnique<PageUseCounters>(); + } + ++page->mPageUseCounters->mWaiting; + + MOZ_LOG( + gUseCountersLog, LogLevel::Debug, + (" > top-level now waiting on %d\n", page->mPageUseCounters->mWaiting)); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalParent::RecvAccumulatePageUseCounters( + const UseCounters& aUseCounters) { + // We've been called to accumulate use counter data into the page use counters + // for the document in mPageUseCountersWindow. + + MOZ_LOG( + gUseCountersLog, LogLevel::Debug, + ("Accumulate page use counters: WindowContext %" PRIu64 " -> %" PRIu64, + InnerWindowId(), + mPageUseCountersWindow ? mPageUseCountersWindow->InnerWindowId() : 0)); + + if (!mPageUseCountersWindow || mPageUseCountersWindow->mSentPageUseCounters) { + MOZ_LOG(gUseCountersLog, LogLevel::Debug, + (" > too late, won't report page use counters for this straggler")); + return IPC_OK(); + } + + MOZ_ASSERT(mPageUseCountersWindow->mPageUseCounters); + MOZ_ASSERT(mPageUseCountersWindow->mPageUseCounters->mWaiting > 0); + + mPageUseCountersWindow->mPageUseCounters->mUseCounters |= aUseCounters; + mPageUseCountersWindow->mPageUseCounters->mReceivedAny = true; + return IPC_OK(); +} + +// This is called on the top-level WindowGlobal, i.e. the one that is +// accumulating the page use counters, not the (potentially descendant) window +// that has finished providing use counter data. +void WindowGlobalParent::FinishAccumulatingPageUseCounters() { + MOZ_LOG(gUseCountersLog, LogLevel::Debug, + ("Stop expecting page use counters: -> WindowContext %" PRIu64, + InnerWindowId())); + + if (!mPageUseCounters) { + MOZ_ASSERT_UNREACHABLE("Not expecting page use counter data"); + MOZ_LOG(gUseCountersLog, LogLevel::Debug, + (" > not expecting page use counter data")); + return; + } + + MOZ_ASSERT(mPageUseCounters->mWaiting > 0); + --mPageUseCounters->mWaiting; + + if (mPageUseCounters->mWaiting > 0) { + MOZ_LOG(gUseCountersLog, LogLevel::Debug, + (" > now waiting on %d", mPageUseCounters->mWaiting)); + return; + } + + if (mPageUseCounters->mReceivedAny) { + MOZ_LOG(gUseCountersLog, LogLevel::Debug, + (" > reporting [%s]", + nsContentUtils::TruncatedURLForDisplay(mDocumentURI).get())); + + Maybe<nsCString> urlForLogging; + const bool dumpCounters = StaticPrefs::dom_use_counters_dump_page(); + if (dumpCounters) { + urlForLogging.emplace( + nsContentUtils::TruncatedURLForDisplay(mDocumentURI)); + } + + glean::use_counter::top_level_content_documents_destroyed.Add(); + + bool any = false; + for (int32_t c = 0; c < eUseCounter_Count; ++c) { + auto uc = static_cast<UseCounter>(c); + if (!mPageUseCounters->mUseCounters[uc]) { + continue; + } + any = true; + const char* metricName = IncrementUseCounter(uc, /* aIsPage = */ true); + if (dumpCounters) { + printf_stderr("USE_COUNTER_PAGE: %s - %s\n", metricName, + urlForLogging->get()); + } + } + + if (!any) { + MOZ_LOG(gUseCountersLog, LogLevel::Debug, + (" > page use counter data was received, but was empty")); + } + } else { + MOZ_LOG(gUseCountersLog, LogLevel::Debug, + (" > no page use counter data was received")); + } + + mSentPageUseCounters = true; + mPageUseCounters = nullptr; +} + +Element* WindowGlobalParent::GetRootOwnerElement() { + WindowGlobalParent* top = TopWindowContext(); + if (!top) { + return nullptr; + } + + if (IsInProcess()) { + return top->BrowsingContext()->GetEmbedderElement(); + } + + if (BrowserParent* parent = top->GetBrowserParent()) { + return parent->GetOwnerElement(); + } + + return nullptr; +} + +void WindowGlobalParent::NotifySessionStoreUpdatesComplete(Element* aEmbedder) { + if (!aEmbedder) { + aEmbedder = GetRootOwnerElement(); + } + if (aEmbedder) { + if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) { + obs->NotifyWhenScriptSafe(ToSupports(aEmbedder), + "browser-shutdown-tabstate-updated", nullptr); + } + } +} + +mozilla::ipc::IPCResult WindowGlobalParent::RecvRequestRestoreTabContent() { + CanonicalBrowsingContext* bc = BrowsingContext(); + if (bc && bc->AncestorsAreCurrent()) { + bc->Top()->RequestRestoreTabContent(this); + } + return IPC_OK(); +} + +nsCString BFCacheStatusToString(uint32_t aFlags) { + if (aFlags == 0) { + return "0"_ns; + } + + nsCString flags; +#define ADD_BFCACHESTATUS_TO_STRING(_flag) \ + if (aFlags & BFCacheStatus::_flag) { \ + if (!flags.IsEmpty()) { \ + flags.Append('|'); \ + } \ + flags.AppendLiteral(#_flag); \ + aFlags &= ~BFCacheStatus::_flag; \ + } + + ADD_BFCACHESTATUS_TO_STRING(NOT_ALLOWED); + ADD_BFCACHESTATUS_TO_STRING(EVENT_HANDLING_SUPPRESSED); + ADD_BFCACHESTATUS_TO_STRING(SUSPENDED); + ADD_BFCACHESTATUS_TO_STRING(UNLOAD_LISTENER); + ADD_BFCACHESTATUS_TO_STRING(REQUEST); + ADD_BFCACHESTATUS_TO_STRING(ACTIVE_GET_USER_MEDIA); + ADD_BFCACHESTATUS_TO_STRING(ACTIVE_PEER_CONNECTION); + ADD_BFCACHESTATUS_TO_STRING(CONTAINS_EME_CONTENT); + ADD_BFCACHESTATUS_TO_STRING(CONTAINS_MSE_CONTENT); + ADD_BFCACHESTATUS_TO_STRING(HAS_ACTIVE_SPEECH_SYNTHESIS); + ADD_BFCACHESTATUS_TO_STRING(HAS_USED_VR); + ADD_BFCACHESTATUS_TO_STRING(CONTAINS_REMOTE_SUBFRAMES); + ADD_BFCACHESTATUS_TO_STRING(NOT_ONLY_TOPLEVEL_IN_BCG); + ADD_BFCACHESTATUS_TO_STRING(BEFOREUNLOAD_LISTENER); + ADD_BFCACHESTATUS_TO_STRING(ACTIVE_LOCK); + +#undef ADD_BFCACHESTATUS_TO_STRING + + MOZ_ASSERT(aFlags == 0, + "Missing stringification for enum value in BFCacheStatus."); + return flags; +} + +mozilla::ipc::IPCResult WindowGlobalParent::RecvUpdateBFCacheStatus( + const uint32_t& aOnFlags, const uint32_t& aOffFlags) { + if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug))) { + nsAutoCString uri("[no uri]"); + if (mDocumentURI) { + uri = mDocumentURI->GetSpecOrDefault(); + } + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, + ("Setting BFCache flags for %s +(%s) -(%s)", uri.get(), + BFCacheStatusToString(aOnFlags).get(), + BFCacheStatusToString(aOffFlags).get())); + } + mBFCacheStatus |= aOnFlags; + mBFCacheStatus &= ~aOffFlags; + return IPC_OK(); +} + +mozilla::ipc::IPCResult +WindowGlobalParent::RecvUpdateActivePeerConnectionStatus(bool aIsAdded) { + if (aIsAdded) { + RecvUpdateBFCacheStatus(BFCacheStatus::ACTIVE_PEER_CONNECTION, 0); + } else { + RecvUpdateBFCacheStatus(0, BFCacheStatus::ACTIVE_PEER_CONNECTION); + } + + if (WindowGlobalParent* top = TopWindowContext()) { + CheckedUint32 newValue(top->mNumOfProcessesWithActivePeerConnections); + if (aIsAdded) { + ++newValue; + } else { + --newValue; + } + if (!newValue.isValid()) { + return IPC_FAIL(this, + "mNumOfProcessesWithActivePeerConnections overflowed"); + } + + top->mNumOfProcessesWithActivePeerConnections = newValue.value(); + Unused << top->SetHasActivePeerConnections(newValue.value() > 0); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalParent::RecvSetSingleChannelId( + const Maybe<uint64_t>& aSingleChannelId) { + mSingleChannelId = aSingleChannelId; + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalParent::RecvSetDocumentDomain( + NotNull<nsIURI*> aDomain) { + if (mSandboxFlags & SANDBOXED_DOMAIN) { + // We're sandboxed; disallow setting domain + return IPC_FAIL(this, "Sandbox disallows domain setting."); + } + + // Might need to do a featurepolicy check here, like we currently do in the + // child process? + + nsCOMPtr<nsIURI> uri; + mDocumentPrincipal->GetDomain(getter_AddRefs(uri)); + if (!uri) { + uri = mDocumentPrincipal->GetURI(); + if (!uri) { + return IPC_OK(); + } + } + + if (!Document::IsValidDomain(uri, aDomain)) { + // Error: illegal domain + return IPC_FAIL( + this, "Setting domain that's not a suffix of existing domain value."); + } + + if (Group()->IsPotentiallyCrossOriginIsolated()) { + return IPC_FAIL(this, "Setting domain in a cross-origin isolated BC."); + } + + mDocumentPrincipal->SetDomain(aDomain); + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalParent::RecvReloadWithHttpsOnlyException() { + nsresult rv; + nsCOMPtr<nsIURI> currentUri = BrowsingContext()->Top()->GetCurrentURI(); + + if (!currentUri) { + return IPC_FAIL(this, "HTTPS-only mode: Failed to get current URI"); + } + + bool isViewSource = currentUri->SchemeIs("view-source"); + + nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(currentUri); + nsCOMPtr<nsIURI> innerURI; + if (isViewSource) { + nestedURI->GetInnerURI(getter_AddRefs(innerURI)); + } else { + innerURI = currentUri; + } + + if (!innerURI->SchemeIs("https") && !innerURI->SchemeIs("http")) { + return IPC_FAIL(this, "HTTPS-only mode: Illegal state"); + } + + // If the error page is within an iFrame, we create an exception for whatever + // scheme the top-level site is currently on, because the user wants to + // unbreak the iFrame and not the top-level page. When the error page shows up + // on a top-level request, then we replace the scheme with http, because the + // user wants to unbreak the whole page. + nsCOMPtr<nsIURI> newURI; + if (!BrowsingContext()->IsTop()) { + newURI = innerURI; + } else { + Unused << NS_MutateURI(innerURI).SetScheme("http"_ns).Finalize( + getter_AddRefs(newURI)); + } + + OriginAttributes originAttributes = + TopWindowContext()->DocumentPrincipal()->OriginAttributesRef(); + + originAttributes.SetFirstPartyDomain(true, newURI); + + nsCOMPtr<nsIPermissionManager> permMgr = + components::PermissionManager::Service(); + if (!permMgr) { + return IPC_FAIL( + this, "HTTPS-only mode: Failed to get Permission Manager service"); + } + + nsCOMPtr<nsIPrincipal> principal = + BasePrincipal::CreateContentPrincipal(newURI, originAttributes); + + rv = permMgr->AddFromPrincipal( + principal, "https-only-load-insecure"_ns, + nsIHttpsOnlyModePermission::LOAD_INSECURE_ALLOW_SESSION, + nsIPermissionManager::EXPIRE_SESSION, 0); + + if (NS_FAILED(rv)) { + return IPC_FAIL( + this, "HTTPS-only mode: Failed to add permission to the principal"); + } + + nsCOMPtr<nsIURI> insecureURI = newURI; + if (isViewSource) { + nsAutoCString spec; + MOZ_ALWAYS_SUCCEEDS(newURI->GetSpec(spec)); + if (NS_FAILED( + NS_NewURI(getter_AddRefs(insecureURI), "view-source:"_ns + spec))) { + return IPC_FAIL( + this, "HTTPS-only mode: Failed to re-construct view-source URI"); + } + } + + RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(insecureURI); + loadState->SetTriggeringPrincipal(nsContentUtils::GetSystemPrincipal()); + loadState->SetLoadType(LOAD_NORMAL_REPLACE); + + RefPtr<CanonicalBrowsingContext> topBC = BrowsingContext()->Top(); + topBC->LoadURI(loadState, /* setNavigating */ true); + + return IPC_OK(); +} + +IPCResult WindowGlobalParent::RecvDiscoverIdentityCredentialFromExternalSource( + const IdentityCredentialRequestOptions& aOptions, + const DiscoverIdentityCredentialFromExternalSourceResolver& aResolver) { + IdentityCredential::DiscoverFromExternalSourceInMainProcess( + DocumentPrincipal(), this->BrowsingContext(), aOptions) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [aResolver](const IPCIdentityCredential& aResult) { + return aResolver(Some(aResult)); + }, + [aResolver](nsresult aErr) { aResolver(Nothing()); }); + return IPC_OK(); +} + +IPCResult WindowGlobalParent::RecvGetStorageAccessPermission( + GetStorageAccessPermissionResolver&& aResolve) { + WindowGlobalParent* top = TopWindowContext(); + if (!top) { + return IPC_FAIL_NO_REASON(this); + } + nsIPrincipal* topPrincipal = top->DocumentPrincipal(); + nsIPrincipal* principal = DocumentPrincipal(); + uint32_t result; + nsresult rv = AntiTrackingUtils::TestStoragePermissionInParent( + topPrincipal, principal, &result); + if (NS_WARN_IF(NS_FAILED(rv))) { + aResolve(nsIPermissionManager::UNKNOWN_ACTION); + return IPC_OK(); + } + + aResolve(result); + return IPC_OK(); +} + +void WindowGlobalParent::ActorDestroy(ActorDestroyReason aWhy) { + if (GetBrowsingContext()->IsTopContent()) { + Telemetry::Accumulate(Telemetry::ORB_DID_EVER_BLOCK_RESPONSE, + mShouldReportHasBlockedOpaqueResponse); + } + + if (mPageUseCountersWindow) { + mPageUseCountersWindow->FinishAccumulatingPageUseCounters(); + mPageUseCountersWindow = nullptr; + } + + if (GetBrowsingContext()->IsTopContent() && + !mDocumentPrincipal->SchemeIs("about")) { + // Record the page load + uint32_t pageLoaded = 1; + Accumulate(Telemetry::MIXED_CONTENT_UNBLOCK_COUNTER, pageLoaded); + + // Record the mixed content status of the docshell in Telemetry + enum { + NO_MIXED_CONTENT = 0, // There is no Mixed Content on the page + MIXED_DISPLAY_CONTENT = + 1, // The page attempted to load Mixed Display Content + MIXED_ACTIVE_CONTENT = + 2, // The page attempted to load Mixed Active Content + MIXED_DISPLAY_AND_ACTIVE_CONTENT = 3 // The page attempted to load Mixed + // Display & Mixed Active Content + }; + + bool hasMixedDisplay = + mSecurityState & + (nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT | + nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT); + bool hasMixedActive = + mSecurityState & + (nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT | + nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT); + + uint32_t mixedContentLevel = NO_MIXED_CONTENT; + if (hasMixedDisplay && hasMixedActive) { + mixedContentLevel = MIXED_DISPLAY_AND_ACTIVE_CONTENT; + } else if (hasMixedActive) { + mixedContentLevel = MIXED_ACTIVE_CONTENT; + } else if (hasMixedDisplay) { + mixedContentLevel = MIXED_DISPLAY_CONTENT; + } + Accumulate(Telemetry::MIXED_CONTENT_PAGE_LOAD, mixedContentLevel); + + if (GetDocTreeHadMedia()) { + ScalarAdd(Telemetry::ScalarID::MEDIA_ELEMENT_IN_PAGE_COUNT, 1); + } + } + + ContentParent* cp = nullptr; + if (!IsInProcess()) { + cp = static_cast<ContentParent*>(Manager()->Manager()); + } + + Group()->EachOtherParent(cp, [&](ContentParent* otherContent) { + // Keep the WindowContext and our BrowsingContextGroup alive until other + // processes have acknowledged it has been discarded. + Group()->AddKeepAlive(); + auto callback = [self = RefPtr{this}](auto) { + self->Group()->RemoveKeepAlive(); + }; + otherContent->SendDiscardWindowContext(InnerWindowId(), callback, callback); + }); + + // Note that our WindowContext has become discarded. + WindowContext::Discard(); + + // Report content blocking log when destroyed. + // There shouldn't have any content blocking log when a document is loaded in + // the parent process(See NotifyContentBlockingEvent), so we could skip + // reporting log when it is in-process. + if (!IsInProcess()) { + RefPtr<BrowserParent> browserParent = + static_cast<BrowserParent*>(Manager()); + if (browserParent) { + nsCOMPtr<nsILoadContext> loadContext = browserParent->GetLoadContext(); + if (loadContext && !loadContext->UsePrivateBrowsing() && + BrowsingContext()->IsTopContent()) { + GetContentBlockingLog()->ReportLog(DocumentPrincipal()); + + if (mDocumentURI && (net::SchemeIsHTTP(mDocumentURI) || + net::SchemeIsHTTPS(mDocumentURI))) { + GetContentBlockingLog()->ReportCanvasFingerprintingLog( + DocumentPrincipal()); + GetContentBlockingLog()->ReportFontFingerprintingLog( + DocumentPrincipal()); + GetContentBlockingLog()->ReportEmailTrackingLog(DocumentPrincipal()); + } + } + } + } + + // Destroy our JSWindowActors, and reject any pending queries. + JSActorDidDestroy(); + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + obs->NotifyObservers(ToSupports(this), "window-global-destroyed", nullptr); + } + + if (mOriginCounter) { + mOriginCounter->Accumulate(); + } +} + +WindowGlobalParent::~WindowGlobalParent() = default; + +JSObject* WindowGlobalParent::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return WindowGlobalParent_Binding::Wrap(aCx, this, aGivenProto); +} + +nsIGlobalObject* WindowGlobalParent::GetParentObject() { + return xpc::NativeGlobal(xpc::PrivilegedJunkScope()); +} + +nsIDOMProcessParent* WindowGlobalParent::GetDomProcess() { + if (RefPtr<BrowserParent> browserParent = GetBrowserParent()) { + return browserParent->Manager(); + } + return InProcessParent::Singleton(); +} + +void WindowGlobalParent::DidBecomeCurrentWindowGlobal(bool aCurrent) { + WindowGlobalParent* top = BrowsingContext()->GetTopWindowContext(); + if (top && top->mOriginCounter) { + top->mOriginCounter->UpdateSiteOriginsFrom(this, + /* aIncrease = */ aCurrent); + } + + if (!aCurrent && Fullscreen()) { + ExitTopChromeDocumentFullscreen(); + } +} + +bool WindowGlobalParent::ShouldTrackSiteOriginTelemetry() { + CanonicalBrowsingContext* bc = BrowsingContext(); + + if (!bc->IsTopContent()) { + return false; + } + + RefPtr<BrowserParent> browserParent = GetBrowserParent(); + if (!browserParent || + !IsWebRemoteType(browserParent->Manager()->GetRemoteType())) { + return false; + } + + return DocumentPrincipal()->GetIsContentPrincipal(); +} + +void WindowGlobalParent::AddSecurityState(uint32_t aStateFlags) { + MOZ_ASSERT(TopWindowContext() == this); + MOZ_ASSERT((aStateFlags & + (nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT | + nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT | + nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT | + nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT | + nsIWebProgressListener::STATE_HTTPS_ONLY_MODE_UPGRADED | + nsIWebProgressListener::STATE_HTTPS_ONLY_MODE_UPGRADE_FAILED | + nsIWebProgressListener::STATE_HTTPS_ONLY_MODE_UPGRADED_FIRST)) == + aStateFlags, + "Invalid flags specified!"); + + if ((mSecurityState & aStateFlags) == aStateFlags) { + return; + } + + mSecurityState |= aStateFlags; + + if (GetBrowsingContext()->GetCurrentWindowGlobal() == this) { + GetBrowsingContext()->UpdateSecurityState(); + } +} + +bool WindowGlobalParent::HasActivePeerConnections() { + MOZ_ASSERT(TopWindowContext() == this, + "mNumOfProcessesWithActivePeerConnections is set only " + "in the top window context"); + return mNumOfProcessesWithActivePeerConnections > 0; +} + +void WindowGlobalParent::ExitTopChromeDocumentFullscreen() { + RefPtr<CanonicalBrowsingContext> chromeTop = + BrowsingContext()->TopCrossChromeBoundary(); + if (Document* chromeDoc = chromeTop->GetDocument()) { + Document::ClearPendingFullscreenRequests(chromeDoc); + if (chromeDoc->Fullscreen()) { + // This only clears the DOM fullscreen, will not exit from browser UI + // fullscreen mode. + Document::AsyncExitFullscreen(chromeDoc); + } + } +} + +void WindowGlobalParent::SetShouldReportHasBlockedOpaqueResponse( + nsContentPolicyType aContentPolicy) { + // It's always okay to block TYPE_BEACON, TYPE_PING and TYPE_CSP_REPORT in + // the parent process because content processes can do nothing to their + // responses. Hence excluding them from the telemetry as blocking + // them have no webcompat concerns. + if (aContentPolicy != nsIContentPolicy::TYPE_BEACON && + aContentPolicy != nsIContentPolicy::TYPE_PING && + aContentPolicy != nsIContentPolicy::TYPE_CSP_REPORT) { + if (IsTop()) { + mShouldReportHasBlockedOpaqueResponse = true; + } + } +} + +IPCResult WindowGlobalParent::RecvSetCookies( + const nsCString& aBaseDomain, const OriginAttributes& aOriginAttributes, + nsIURI* aHost, bool aFromHttp, const nsTArray<CookieStruct>& aCookies) { + // Get CookieServiceParent via + // ContentParent->NeckoParent->CookieServiceParent. + ContentParent* contentParent = GetContentParent(); + NS_ENSURE_TRUE(contentParent, IPC_OK()); + + net::PNeckoParent* neckoParent = + LoneManagedOrNullAsserts(contentParent->ManagedPNeckoParent()); + NS_ENSURE_TRUE(neckoParent, IPC_OK()); + net::PCookieServiceParent* csParent = + LoneManagedOrNullAsserts(neckoParent->ManagedPCookieServiceParent()); + NS_ENSURE_TRUE(csParent, IPC_OK()); + auto* cs = static_cast<net::CookieServiceParent*>(csParent); + + return cs->SetCookies(aBaseDomain, aOriginAttributes, aHost, aFromHttp, + aCookies, GetBrowsingContext()); +} + +NS_IMPL_CYCLE_COLLECTION_INHERITED(WindowGlobalParent, WindowContext, + mPageUseCountersWindow) + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(WindowGlobalParent, + WindowContext) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WindowGlobalParent) +NS_INTERFACE_MAP_END_INHERITING(WindowContext) + +NS_IMPL_ADDREF_INHERITED(WindowGlobalParent, WindowContext) +NS_IMPL_RELEASE_INHERITED(WindowGlobalParent, WindowContext) + +} // namespace mozilla::dom diff --git a/dom/ipc/WindowGlobalParent.h b/dom/ipc/WindowGlobalParent.h new file mode 100644 index 0000000000..be801de0d3 --- /dev/null +++ b/dom/ipc/WindowGlobalParent.h @@ -0,0 +1,444 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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/. */ + +#ifndef mozilla_dom_WindowGlobalParent_h +#define mozilla_dom_WindowGlobalParent_h + +#include "mozilla/ContentBlockingLog.h" +#include "mozilla/ContentBlockingNotifier.h" +#include "mozilla/Maybe.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/ClientInfo.h" +#include "mozilla/dom/ClientIPCTypes.h" +#include "mozilla/dom/DOMRect.h" +#include "mozilla/dom/PWindowGlobalParent.h" +#include "mozilla/dom/WindowContext.h" +#include "mozilla/dom/WindowGlobalActorsBinding.h" +#include "nsTHashMap.h" +#include "nsRefPtrHashtable.h" +#include "nsWrapperCache.h" +#include "nsISupports.h" +#include "nsIDOMProcessParent.h" +#include "mozilla/dom/WindowGlobalActor.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "mozilla/net/CookieJarSettings.h" + +class nsIPrincipal; +class nsIURI; +class nsFrameLoader; + +namespace mozilla { + +namespace gfx { +class CrossProcessPaint; +} // namespace gfx + +namespace dom { + +class BrowserParent; +class WindowGlobalChild; +class JSWindowActorParent; +class JSActorMessageMeta; +struct PageUseCounters; +class WindowSessionStoreState; +struct WindowSessionStoreUpdate; +class SSCacheQueryResult; + +/** + * A handle in the parent process to a specific nsGlobalWindowInner object. + */ +class WindowGlobalParent final : public WindowContext, + public WindowGlobalActor, + public PWindowGlobalParent { + friend class gfx::CrossProcessPaint; + friend class PWindowGlobalParent; + + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(WindowGlobalParent, + WindowContext) + + static already_AddRefed<WindowGlobalParent> GetByInnerWindowId( + uint64_t aInnerWindowId); + + static already_AddRefed<WindowGlobalParent> GetByInnerWindowId( + const GlobalObject& aGlobal, uint64_t aInnerWindowId) { + return GetByInnerWindowId(aInnerWindowId); + } + + // The same as the corresponding methods on `WindowContext`, except that the + // return types are already cast to their parent-process type variants, such + // as `WindowGlobalParent` or `CanonicalBrowsingContext`. + WindowGlobalParent* GetParentWindowContext() { + return static_cast<WindowGlobalParent*>( + WindowContext::GetParentWindowContext()); + } + WindowGlobalParent* TopWindowContext() { + return static_cast<WindowGlobalParent*>(WindowContext::TopWindowContext()); + } + CanonicalBrowsingContext* GetBrowsingContext() const { + return CanonicalBrowsingContext::Cast(WindowContext::GetBrowsingContext()); + } + + Element* GetRootOwnerElement(); + + // Has this actor been shut down + bool IsClosed() { return !CanSend(); } + + // Get the other side of this actor if it is an in-process actor. Returns + // |nullptr| if the actor has been torn down, or is not in-process. + already_AddRefed<WindowGlobalChild> GetChildActor(); + + // Get a JS actor object by name. + already_AddRefed<JSWindowActorParent> GetActor(JSContext* aCx, + const nsACString& aName, + ErrorResult& aRv); + already_AddRefed<JSWindowActorParent> GetExistingActor( + const nsACString& aName); + + // Get this actor's manager if it is not an in-process actor. Returns + // |nullptr| if the actor has been torn down, or is in-process. + BrowserParent* GetBrowserParent(); + + ContentParent* GetContentParent(); + + // The principal of this WindowGlobal. This value will not change over the + // lifetime of the WindowGlobal object, even to reflect changes in + // |document.domain|. + nsIPrincipal* DocumentPrincipal() { return mDocumentPrincipal; } + + nsIPrincipal* DocumentStoragePrincipal() { return mDocumentStoragePrincipal; } + + // The BrowsingContext which this WindowGlobal has been loaded into. + // FIXME: It's quite awkward that this method has a slightly different name + // than the one on WindowContext. + CanonicalBrowsingContext* BrowsingContext() override { + return GetBrowsingContext(); + } + + // Get the root nsFrameLoader object for the tree of BrowsingContext nodes + // which this WindowGlobal is a part of. This will be the nsFrameLoader + // holding the BrowserParent for remote tabs, and the root content frameloader + // for non-remote tabs. + already_AddRefed<nsFrameLoader> GetRootFrameLoader(); + + // The current URI which loaded in the document. + nsIURI* GetDocumentURI() override { return mDocumentURI; } + + void GetDocumentTitle(nsAString& aTitle) const { + aTitle = mDocumentTitle.valueOr(nsString()); + } + + nsIPrincipal* GetContentBlockingAllowListPrincipal() const { + return mDocContentBlockingAllowListPrincipal; + } + + Maybe<ClientInfo> GetClientInfo() { return mClientInfo; } + + uint64_t ContentParentId(); + + int32_t OsPid(); + + bool IsCurrentGlobal(); + + bool IsActiveInTab(); + + bool IsProcessRoot(); + + uint32_t ContentBlockingEvents(); + + void GetContentBlockingLog(nsAString& aLog); + + bool IsInitialDocument() { + return mIsInitialDocument.isSome() && mIsInitialDocument.value(); + } + + already_AddRefed<mozilla::dom::Promise> PermitUnload( + PermitUnloadAction aAction, uint32_t aTimeout, mozilla::ErrorResult& aRv); + + void PermitUnload(std::function<void(bool)>&& aResolver); + + already_AddRefed<mozilla::dom::Promise> DrawSnapshot( + const DOMRect* aRect, double aScale, const nsACString& aBackgroundColor, + bool aResetScrollPosition, mozilla::ErrorResult& aRv); + + static already_AddRefed<WindowGlobalParent> CreateDisconnected( + const WindowGlobalInit& aInit); + + // Initialize the mFrameLoader fields for a created WindowGlobalParent. Must + // be called after setting the Manager actor. + void Init() final; + + nsIGlobalObject* GetParentObject(); + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + void NotifyContentBlockingEvent( + uint32_t aEvent, nsIRequest* aRequest, bool aBlocked, + const nsACString& aTrackingOrigin, + const nsTArray<nsCString>& aTrackingFullHashes, + const Maybe< + ContentBlockingNotifier::StorageAccessPermissionGrantedReason>& + aReason, + const Maybe<ContentBlockingNotifier::CanvasFingerprinter>& + aCanvasFingerprinter, + const Maybe<bool> aCanvasFingerprinterKnownText); + + ContentBlockingLog* GetContentBlockingLog() { return &mContentBlockingLog; } + + nsIDOMProcessParent* GetDomProcess(); + + nsICookieJarSettings* CookieJarSettings() { return mCookieJarSettings; } + + nsICookieJarSettings* GetCookieJarSettings() const { + return mCookieJarSettings; + } + + bool DocumentHasLoaded() { return mDocumentHasLoaded; } + + bool DocumentHasUserInteracted() { return mDocumentHasUserInteracted; } + + uint32_t SandboxFlags() { return mSandboxFlags; } + + bool GetDocumentBlockAllMixedContent() { return mBlockAllMixedContent; } + + bool GetDocumentUpgradeInsecureRequests() { return mUpgradeInsecureRequests; } + + void DidBecomeCurrentWindowGlobal(bool aCurrent); + + uint32_t HttpsOnlyStatus() { return mHttpsOnlyStatus; } + + void AddSecurityState(uint32_t aStateFlags); + uint32_t GetSecurityFlags() { return mSecurityState; } + + nsITransportSecurityInfo* GetSecurityInfo() { return mSecurityInfo; } + + const nsACString& GetRemoteType() override; + + void NotifySessionStoreUpdatesComplete(Element* aEmbedder); + + Maybe<uint64_t> GetSingleChannelId() { return mSingleChannelId; } + + uint32_t GetBFCacheStatus() { return mBFCacheStatus; } + + bool HasActivePeerConnections(); + + bool Fullscreen() { return mFullscreen; } + void SetFullscreen(bool aFullscreen) { mFullscreen = aFullscreen; } + + void ExitTopChromeDocumentFullscreen(); + + void SetShouldReportHasBlockedOpaqueResponse( + nsContentPolicyType aContentPolicy); + + protected: + already_AddRefed<JSActor> InitJSActor(JS::Handle<JSObject*> aMaybeActor, + const nsACString& aName, + ErrorResult& aRv) override; + mozilla::ipc::IProtocol* AsNativeActor() override { return this; } + + // IPC messages + mozilla::ipc::IPCResult RecvLoadURI( + const MaybeDiscarded<dom::BrowsingContext>& aTargetBC, + nsDocShellLoadState* aLoadState, bool aSetNavigating); + mozilla::ipc::IPCResult RecvInternalLoad(nsDocShellLoadState* aLoadState); + mozilla::ipc::IPCResult RecvUpdateDocumentURI(NotNull<nsIURI*> aURI); + mozilla::ipc::IPCResult RecvUpdateDocumentPrincipal( + nsIPrincipal* aNewDocumentPrincipal, + nsIPrincipal* aNewDocumentStoragePrincipal); + mozilla::ipc::IPCResult RecvUpdateDocumentHasLoaded(bool aDocumentHasLoaded); + mozilla::ipc::IPCResult RecvUpdateDocumentHasUserInteracted( + bool aDocumentHasUserInteracted); + mozilla::ipc::IPCResult RecvUpdateSandboxFlags(uint32_t aSandboxFlags); + mozilla::ipc::IPCResult RecvUpdateDocumentCspSettings( + bool aBlockAllMixedContent, bool aUpgradeInsecureRequests); + mozilla::ipc::IPCResult RecvUpdateDocumentTitle(const nsString& aTitle); + mozilla::ipc::IPCResult RecvUpdateHttpsOnlyStatus(uint32_t aHttpsOnlyStatus); + mozilla::ipc::IPCResult RecvSetIsInitialDocument(bool aIsInitialDocument) { + if (aIsInitialDocument && mIsInitialDocument.isSome() && + (mIsInitialDocument.value() != aIsInitialDocument)) { + return IPC_FAIL_NO_REASON(this); + } + + mIsInitialDocument = Some(aIsInitialDocument); + return IPC_OK(); + } + mozilla::ipc::IPCResult RecvUpdateDocumentSecurityInfo( + nsITransportSecurityInfo* aSecurityInfo); + mozilla::ipc::IPCResult RecvSetClientInfo( + const IPCClientInfo& aIPCClientInfo); + mozilla::ipc::IPCResult RecvDestroy(); + mozilla::ipc::IPCResult RecvRawMessage( + const JSActorMessageMeta& aMeta, const Maybe<ClonedMessageData>& aData, + const Maybe<ClonedMessageData>& aStack); + + mozilla::ipc::IPCResult RecvGetContentBlockingEvents( + GetContentBlockingEventsResolver&& aResolver); + mozilla::ipc::IPCResult RecvUpdateCookieJarSettings( + const CookieJarSettingsArgs& aCookieJarSettingsArgs); + + void ActorDestroy(ActorDestroyReason aWhy) override; + + void DrawSnapshotInternal(gfx::CrossProcessPaint* aPaint, + const Maybe<IntRect>& aRect, float aScale, + nscolor aBackgroundColor, uint32_t aFlags); + + // WebShare API - try to share + mozilla::ipc::IPCResult RecvShare(IPCWebShareData&& aData, + ShareResolver&& aResolver); + + mozilla::ipc::IPCResult RecvCheckPermitUnload( + bool aHasInProcessBlocker, XPCOMPermitUnloadAction aAction, + CheckPermitUnloadResolver&& aResolver); + + mozilla::ipc::IPCResult RecvExpectPageUseCounters( + const MaybeDiscarded<dom::WindowContext>& aTop); + mozilla::ipc::IPCResult RecvAccumulatePageUseCounters( + const UseCounters& aUseCounters); + + mozilla::ipc::IPCResult RecvRequestRestoreTabContent(); + + mozilla::ipc::IPCResult RecvUpdateBFCacheStatus(const uint32_t& aOnFlags, + const uint32_t& aOffFlags); + + // This IPC method is to notify the parent process that the caller process + // creates the first active peer connection (aIsAdded = true) or closes the + // last active peer connection (aIsAdded = false). + mozilla::ipc::IPCResult RecvUpdateActivePeerConnectionStatus(bool aIsAdded); + + public: + mozilla::ipc::IPCResult RecvSetSingleChannelId( + const Maybe<uint64_t>& aSingleChannelId); + + mozilla::ipc::IPCResult RecvSetDocumentDomain(NotNull<nsIURI*> aDomain); + + mozilla::ipc::IPCResult RecvReloadWithHttpsOnlyException(); + + mozilla::ipc::IPCResult RecvDiscoverIdentityCredentialFromExternalSource( + const IdentityCredentialRequestOptions& aOptions, + const DiscoverIdentityCredentialFromExternalSourceResolver& aResolver); + + mozilla::ipc::IPCResult RecvGetStorageAccessPermission( + GetStorageAccessPermissionResolver&& aResolve); + + mozilla::ipc::IPCResult RecvSetCookies( + const nsCString& aBaseDomain, const OriginAttributes& aOriginAttributes, + nsIURI* aHost, bool aFromHttp, const nsTArray<CookieStruct>& aCookies); + + private: + WindowGlobalParent(CanonicalBrowsingContext* aBrowsingContext, + uint64_t aInnerWindowId, uint64_t aOuterWindowId, + FieldValues&& aInit); + + ~WindowGlobalParent(); + + bool ShouldTrackSiteOriginTelemetry(); + void FinishAccumulatingPageUseCounters(); + + // Returns failure if the new storage principal cannot be validated + // against the current document principle. + nsresult SetDocumentStoragePrincipal( + nsIPrincipal* aNewDocumentStoragePrincipal); + + // NOTE: Neither this document principal nor the document storage + // principal doesn't reflect possible |document.domain| mutations + // which may have been made in the actual document. + nsCOMPtr<nsIPrincipal> mDocumentPrincipal; + nsCOMPtr<nsIPrincipal> mDocumentStoragePrincipal; + + // The principal to use for the content blocking allow list. + nsCOMPtr<nsIPrincipal> mDocContentBlockingAllowListPrincipal; + + nsCOMPtr<nsIURI> mDocumentURI; + Maybe<nsString> mDocumentTitle; + + Maybe<bool> mIsInitialDocument; + + // True if this window has a "beforeunload" event listener. + bool mHasBeforeUnload; + + // The log of all content blocking actions taken on the document related to + // this WindowGlobalParent. This is only stored on top-level documents and + // includes the activity log for all of the nested subdocuments as well. + ContentBlockingLog mContentBlockingLog; + + uint32_t mSecurityState = 0; + + Maybe<ClientInfo> mClientInfo; + // Fields being mirrored from the corresponding document + nsCOMPtr<nsICookieJarSettings> mCookieJarSettings; + nsCOMPtr<nsITransportSecurityInfo> mSecurityInfo; + + uint32_t mSandboxFlags; + + struct OriginCounter { + void UpdateSiteOriginsFrom(WindowGlobalParent* aParent, bool aIncrease); + void Accumulate(); + + nsTHashMap<nsCStringHashKey, int32_t> mOriginMap; + uint32_t mMaxOrigins = 0; + }; + + // Used to collect unique site origin telemetry. + // + // Is only Some() on top-level content windows. + Maybe<OriginCounter> mOriginCounter; + + bool mDocumentHasLoaded; + bool mDocumentHasUserInteracted; + bool mDocumentTreeWouldPreloadResources = false; + bool mBlockAllMixedContent; + bool mUpgradeInsecureRequests; + + // HTTPS-Only Mode flags + uint32_t mHttpsOnlyStatus; + + // The window of the document whose page use counters our document's use + // counters will contribute to. (If we are a top-level document, this + // will point to ourselves.) + RefPtr<WindowGlobalParent> mPageUseCountersWindow; + + // Our page use counters, if we are a top-level document. + UniquePtr<PageUseCounters> mPageUseCounters; + + // Whether we have sent our page use counters, and so should ignore any + // subsequent ExpectPageUseCounters calls. + bool mSentPageUseCounters = false; + + uint32_t mBFCacheStatus = 0; + + // If this WindowGlobalParent is a top window, this field indicates how many + // child processes have active peer connections for this window and its + // descendants. + uint32_t mNumOfProcessesWithActivePeerConnections = 0; + + // mSingleChannelId records whether the loadgroup contains a single request + // with an id. If there is one channel in the loadgroup and it has an id then + // mSingleChannelId is set to Some(id) (ids are non-zero). If there is one + // request in the loadgroup and it's not a channel or it doesn't have an id, + // or there are multiple requests in the loadgroup, then mSingleChannelId is + // set to Some(0). If there are no requests in the loadgroup then + // mSingleChannelId is set to Nothing(). + // Note: We ignore favicon loads when considering the requests in the + // loadgroup. + Maybe<uint64_t> mSingleChannelId; + + // True if the current loaded document is in fullscreen. + bool mFullscreen = false; + + bool mShouldReportHasBlockedOpaqueResponse = false; +}; + +} // namespace dom +} // namespace mozilla + +inline nsISupports* ToSupports( + mozilla::dom::WindowGlobalParent* aWindowGlobal) { + return static_cast<mozilla::dom::WindowContext*>(aWindowGlobal); +} + +#endif // !defined(mozilla_dom_WindowGlobalParent_h) diff --git a/dom/ipc/WindowGlobalTypes.ipdlh b/dom/ipc/WindowGlobalTypes.ipdlh new file mode 100644 index 0000000000..d669aa44f6 --- /dev/null +++ b/dom/ipc/WindowGlobalTypes.ipdlh @@ -0,0 +1,40 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* vim: set sw=4 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 "mozilla/dom/PermissionMessageUtils.h"; +include "mozilla/ipc/TransportSecurityInfoUtils.h"; +include "mozilla/ipc/URIUtils.h"; + +include NeckoChannelParams; +include DOMTypes; + +using mozilla::dom::WindowContextInitializer from "mozilla/dom/WindowContext.h"; +[RefCounted] using class nsITransportSecurityInfo from "nsITransportSecurityInfo.h"; + +namespace mozilla { +namespace dom { + +struct WindowGlobalInit +{ + // Fields which are synchronized to other processes are found here. + WindowContextInitializer context; + + // Private fields only shared with the parent process. + nullable nsIPrincipal principal; + nullable nsIPrincipal storagePrincipal; + nullable nsIURI documentURI; + + bool isInitialDocument; + bool blockAllMixedContent; + bool upgradeInsecureRequests; + uint32_t sandboxFlags; + CookieJarSettingsArgs cookieJarSettings; + uint32_t httpsOnlyStatus; + nullable nsITransportSecurityInfo securityInfo; +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/ipc/components.conf b/dom/ipc/components.conf new file mode 100644 index 0000000000..c027392f4b --- /dev/null +++ b/dom/ipc/components.conf @@ -0,0 +1,17 @@ +# -*- 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/. + +Classes = [ + { + 'cid': '{91fdaa4e-eba4-4ed3-831c-ce05c142822d}', + 'contract_ids': ['@mozilla.org/login-detection-service;1'], + 'type': 'mozilla::dom::LoginDetectionService', + 'singleton': True, + 'headers': ['/dom/ipc/LoginDetectionService.h'], + 'constructor': 'mozilla::dom::LoginDetectionService::GetSingleton', + 'processes': ProcessSelector.MAIN_PROCESS_ONLY, + }, +] diff --git a/dom/ipc/gtest/ProcessIsolationTest.cpp b/dom/ipc/gtest/ProcessIsolationTest.cpp new file mode 100644 index 0000000000..197feabf8c --- /dev/null +++ b/dom/ipc/gtest/ProcessIsolationTest.cpp @@ -0,0 +1,234 @@ +/* -*- 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 "gtest/gtest.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/dom/ProcessIsolation.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/ExpandedPrincipal.h" +#include "mozilla/ExtensionPolicyService.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/gtest/MozHelpers.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/SystemPrincipal.h" +#include "mozilla/StaticPrefs_browser.h" + +using namespace mozilla; +using namespace mozilla::dom; + +static nsCOMPtr<nsIPrincipal> MakeTestPrincipal(const char* aURI) { + nsCOMPtr<nsIURI> uri; + MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), aURI)); + return BasePrincipal::CreateContentPrincipal(uri, {}); +} + +namespace { +struct RemoteTypes { + nsCString mIsolated; + nsCString mUnisolated; +}; + +struct WorkerExpectation { + nsCOMPtr<nsIPrincipal> mPrincipal; + WorkerKind mWorkerKind = WorkerKindShared; + Result<RemoteTypes, nsresult> mExpected = Err(NS_ERROR_FAILURE); + nsCString mCurrentRemoteType = "fakeRemoteType"_ns; + + void Check(bool aUseRemoteSubframes) { + nsAutoCString origin; + ASSERT_NS_SUCCEEDED(mPrincipal->GetOrigin(origin)); + + nsPrintfCString describe( + "origin: %s, workerKind: %s, currentRemoteType: %s, " + "useRemoteSubframes: %d", + origin.get(), mWorkerKind == WorkerKindShared ? "shared" : "service", + mCurrentRemoteType.get(), aUseRemoteSubframes); + + auto result = IsolationOptionsForWorker( + mPrincipal, mWorkerKind, mCurrentRemoteType, aUseRemoteSubframes); + ASSERT_EQ(result.isOk(), mExpected.isOk()) + << "Unexpected status (expected " << (mExpected.isOk() ? "ok" : "err") + << ") for " << describe; + if (mExpected.isOk()) { + const nsCString& expected = aUseRemoteSubframes + ? mExpected.inspect().mIsolated + : mExpected.inspect().mUnisolated; + ASSERT_EQ(result.inspect().mRemoteType, expected) + << "Unexpected remote type (expected " << expected << ") for " + << describe; + } + } +}; +} // namespace + +static nsCString WebIsolatedRemoteType(nsIPrincipal* aPrincipal) { + nsAutoCString origin; + MOZ_ALWAYS_SUCCEEDS(aPrincipal->GetSiteOrigin(origin)); + return FISSION_WEB_REMOTE_TYPE + "="_ns + origin; +} + +static nsCString CoopCoepRemoteType(nsIPrincipal* aPrincipal) { + nsAutoCString origin; + MOZ_ALWAYS_SUCCEEDS(aPrincipal->GetSiteOrigin(origin)); + return WITH_COOP_COEP_REMOTE_TYPE + "="_ns + origin; +} + +static nsCString ServiceWorkerIsolatedRemoteType(nsIPrincipal* aPrincipal) { + nsAutoCString origin; + MOZ_ALWAYS_SUCCEEDS(aPrincipal->GetSiteOrigin(origin)); + return SERVICEWORKER_REMOTE_TYPE + "="_ns + origin; +} + +TEST(ProcessIsolationTest, WorkerOptions) +{ + // Forcibly enable the privileged mozilla content process for the duration of + // the test. + MOZ_ALWAYS_SUCCEEDS(Preferences::SetCString( + "browser.tabs.remote.separatedMozillaDomains", "addons.mozilla.org")); + MOZ_ALWAYS_SUCCEEDS(Preferences::SetBool( + "browser.tabs.remote.separatePrivilegedMozillaWebContentProcess", true)); + auto cleanup = MakeScopeExit([&] { + MOZ_ALWAYS_SUCCEEDS( + Preferences::ClearUser("browser.tabs.remote.separatedMozillaDomains")); + MOZ_ALWAYS_SUCCEEDS(Preferences::ClearUser( + "browser.tabs.remote.separatePrivilegedMozillaWebContentProcess")); + }); + + nsCOMPtr<nsIPrincipal> systemPrincipal = SystemPrincipal::Get(); + nsCOMPtr<nsIPrincipal> nullPrincipal = + NullPrincipal::CreateWithoutOriginAttributes(); + nsCOMPtr<nsIPrincipal> secureComPrincipal = + MakeTestPrincipal("https://example.com"); + nsCOMPtr<nsIPrincipal> secureOrgPrincipal = + MakeTestPrincipal("https://example.org"); + nsCOMPtr<nsIPrincipal> insecureOrgPrincipal = + MakeTestPrincipal("http://example.org"); + nsCOMPtr<nsIPrincipal> filePrincipal = + MakeTestPrincipal("file:///path/to/dir"); + nsCOMPtr<nsIPrincipal> extensionPrincipal = + MakeTestPrincipal("moz-extension://fake-uuid"); + nsCOMPtr<nsIPrincipal> privilegedMozillaPrincipal = + MakeTestPrincipal("https://addons.mozilla.org"); + nsCOMPtr<nsIPrincipal> expandedPrincipal = ExpandedPrincipal::Create( + nsTArray{secureComPrincipal, extensionPrincipal}, {}); + nsCOMPtr<nsIPrincipal> nullSecureComPrecursorPrincipal = + NullPrincipal::CreateWithInheritedAttributes(secureComPrincipal); + + nsCString extensionRemoteType = + ExtensionPolicyService::GetSingleton().UseRemoteExtensions() + ? EXTENSION_REMOTE_TYPE + : NOT_REMOTE_TYPE; + nsCString fileRemoteType = + StaticPrefs::browser_tabs_remote_separateFileUriProcess() + ? FILE_REMOTE_TYPE + : WEB_REMOTE_TYPE; + + WorkerExpectation expectations[] = { + // Neither service not shared workers can have expanded principals + {.mPrincipal = expandedPrincipal, + .mWorkerKind = WorkerKindService, + .mExpected = Err(NS_ERROR_UNEXPECTED)}, + {.mPrincipal = expandedPrincipal, + .mWorkerKind = WorkerKindShared, + .mExpected = Err(NS_ERROR_UNEXPECTED)}, + + // Service workers cannot have system or null principals + {.mPrincipal = systemPrincipal, + .mWorkerKind = WorkerKindService, + .mExpected = Err(NS_ERROR_UNEXPECTED)}, + {.mPrincipal = nullPrincipal, + .mWorkerKind = WorkerKindService, + .mExpected = Err(NS_ERROR_UNEXPECTED)}, + {.mPrincipal = nullSecureComPrecursorPrincipal, + .mWorkerKind = WorkerKindService, + .mExpected = Err(NS_ERROR_UNEXPECTED)}, + + // Service workers with various content principals + {.mPrincipal = secureComPrincipal, + .mWorkerKind = WorkerKindService, + .mExpected = + RemoteTypes{ServiceWorkerIsolatedRemoteType(secureComPrincipal), + WEB_REMOTE_TYPE}}, + {.mPrincipal = secureOrgPrincipal, + .mWorkerKind = WorkerKindService, + .mExpected = + RemoteTypes{ServiceWorkerIsolatedRemoteType(secureOrgPrincipal), + WEB_REMOTE_TYPE}}, + {.mPrincipal = extensionPrincipal, + .mWorkerKind = WorkerKindService, + .mExpected = RemoteTypes{extensionRemoteType, extensionRemoteType}}, + {.mPrincipal = privilegedMozillaPrincipal, + .mWorkerKind = WorkerKindService, + .mExpected = RemoteTypes{PRIVILEGEDMOZILLA_REMOTE_TYPE, + PRIVILEGEDMOZILLA_REMOTE_TYPE}}, + + // Shared Worker loaded from within a webCOOP+COEP remote type process, + // should load elsewhere. + {.mPrincipal = secureComPrincipal, + .mWorkerKind = WorkerKindShared, + .mExpected = RemoteTypes{WebIsolatedRemoteType(secureComPrincipal), + WEB_REMOTE_TYPE}, + .mCurrentRemoteType = CoopCoepRemoteType(secureComPrincipal)}, + + // Even precursorless null principal should load elsewhere. + {.mPrincipal = nullPrincipal, + .mWorkerKind = WorkerKindShared, + .mExpected = RemoteTypes{WEB_REMOTE_TYPE, WEB_REMOTE_TYPE}, + .mCurrentRemoteType = CoopCoepRemoteType(secureComPrincipal)}, + + // System principal shared workers can only load in the parent process or + // the privilegedabout remote type. + {.mPrincipal = systemPrincipal, + .mWorkerKind = WorkerKindShared, + .mExpected = RemoteTypes{NOT_REMOTE_TYPE, NOT_REMOTE_TYPE}, + .mCurrentRemoteType = NOT_REMOTE_TYPE}, + {.mPrincipal = systemPrincipal, + .mWorkerKind = WorkerKindShared, + .mExpected = RemoteTypes{PRIVILEGEDABOUT_REMOTE_TYPE, + PRIVILEGEDABOUT_REMOTE_TYPE}, + .mCurrentRemoteType = PRIVILEGEDABOUT_REMOTE_TYPE}, + {.mPrincipal = systemPrincipal, + .mWorkerKind = WorkerKindShared, + .mExpected = Err(NS_ERROR_UNEXPECTED)}, + {.mPrincipal = systemPrincipal, + .mWorkerKind = WorkerKindShared, + .mExpected = Err(NS_ERROR_UNEXPECTED)}, + + // Content principals should load in the appropriate remote types, + // ignoring the current remote type. + {.mPrincipal = secureComPrincipal, + .mWorkerKind = WorkerKindShared, + .mExpected = RemoteTypes{WebIsolatedRemoteType(secureComPrincipal), + WEB_REMOTE_TYPE}}, + {.mPrincipal = secureOrgPrincipal, + .mWorkerKind = WorkerKindShared, + .mExpected = RemoteTypes{WebIsolatedRemoteType(secureOrgPrincipal), + WEB_REMOTE_TYPE}}, + {.mPrincipal = insecureOrgPrincipal, + .mWorkerKind = WorkerKindShared, + .mExpected = RemoteTypes{WebIsolatedRemoteType(insecureOrgPrincipal), + WEB_REMOTE_TYPE}}, + {.mPrincipal = filePrincipal, + .mWorkerKind = WorkerKindShared, + .mExpected = RemoteTypes{fileRemoteType, fileRemoteType}}, + {.mPrincipal = extensionPrincipal, + .mWorkerKind = WorkerKindShared, + .mExpected = RemoteTypes{extensionRemoteType, extensionRemoteType}}, + {.mPrincipal = privilegedMozillaPrincipal, + .mWorkerKind = WorkerKindShared, + .mExpected = RemoteTypes{PRIVILEGEDMOZILLA_REMOTE_TYPE, + PRIVILEGEDMOZILLA_REMOTE_TYPE}}, + {.mPrincipal = nullSecureComPrecursorPrincipal, + .mWorkerKind = WorkerKindShared, + .mExpected = RemoteTypes{WebIsolatedRemoteType(secureComPrincipal), + WEB_REMOTE_TYPE}}, + }; + + for (auto& expectation : expectations) { + expectation.Check(true); + expectation.Check(false); + } +} diff --git a/dom/ipc/gtest/moz.build b/dom/ipc/gtest/moz.build new file mode 100644 index 0000000000..c064a751ef --- /dev/null +++ b/dom/ipc/gtest/moz.build @@ -0,0 +1,13 @@ +# -*- 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/. + +UNIFIED_SOURCES += [ + "ProcessIsolationTest.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul-gtest" diff --git a/dom/ipc/jar.mn b/dom/ipc/jar.mn new file mode 100644 index 0000000000..6c43857aea --- /dev/null +++ b/dom/ipc/jar.mn @@ -0,0 +1,7 @@ +# 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/. + +toolkit.jar: + content/global/BrowserElementChild.js (../browser-element/BrowserElementChild.js) + content/global/BrowserElementChildPreload.js (../browser-element/BrowserElementChildPreload.js) diff --git a/dom/ipc/jsactor/JSActor.cpp b/dom/ipc/jsactor/JSActor.cpp new file mode 100644 index 0000000000..03926fee04 --- /dev/null +++ b/dom/ipc/jsactor/JSActor.cpp @@ -0,0 +1,503 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/dom/JSActor.h" +#include "mozilla/dom/JSActorBinding.h" + +#include "chrome/common/ipc_channel.h" +#include "mozilla/Attributes.h" +#include "mozilla/FunctionRef.h" +#include "mozilla/dom/AutoEntryScript.h" +#include "mozilla/dom/ClonedErrorHolder.h" +#include "mozilla/dom/DOMException.h" +#include "mozilla/dom/DOMExceptionBinding.h" +#include "mozilla/dom/JSActorManager.h" +#include "mozilla/dom/MessageManagerBinding.h" +#include "mozilla/dom/PWindowGlobal.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/RootedDictionary.h" +#include "mozilla/dom/ipc/StructuredCloneData.h" +#include "mozilla/ProfilerMarkers.h" +#include "js/Promise.h" +#include "xpcprivate.h" +#include "nsFrameMessageManager.h" +#include "nsICrashReporter.h" + +namespace mozilla::dom { + +struct JSActorMessageMarker { + static constexpr Span<const char> MarkerTypeName() { + return MakeStringSpan("JSActorMessage"); + } + static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter, + const ProfilerString8View& aActorName, + const ProfilerString16View& aMessageName) { + aWriter.StringProperty("actor", aActorName); + aWriter.StringProperty("name", NS_ConvertUTF16toUTF8(aMessageName)); + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; + schema.AddKeyLabelFormatSearchable( + "actor", "Actor Name", MS::Format::String, MS::Searchable::Searchable); + schema.AddKeyLabelFormatSearchable( + "name", "Message Name", MS::Format::String, MS::Searchable::Searchable); + schema.SetTooltipLabel("JSActor - {marker.name}"); + schema.SetTableLabel( + "{marker.name} - [{marker.data.actor}] {marker.data.name}"); + return schema; + } +}; + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSActor) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(JSActor) +NS_IMPL_CYCLE_COLLECTING_RELEASE(JSActor) + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(JSActor) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(JSActor) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mWrappedJS) + tmp->mPendingQueries.Clear(); + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(JSActor) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWrappedJS) + for (const auto& query : tmp->mPendingQueries.Values()) { + CycleCollectionNoteChild(cb, query.mPromise.get(), "Pending Query Promise"); + } +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +JSActor::JSActor(nsISupports* aGlobal) { + mGlobal = do_QueryInterface(aGlobal); + if (!mGlobal) { + mGlobal = xpc::NativeGlobal(xpc::PrivilegedJunkScope()); + } +} + +void JSActor::StartDestroy() { mCanSend = false; } + +void JSActor::AfterDestroy() { + mCanSend = false; + + // Take our queries out, in case somehow rejecting promises can trigger + // additions or removals. + const nsTHashMap<nsUint64HashKey, PendingQuery> pendingQueries = + std::move(mPendingQueries); + for (const auto& entry : pendingQueries.Values()) { + nsPrintfCString message( + "Actor '%s' destroyed before query '%s' was resolved", mName.get(), + NS_LossyConvertUTF16toASCII(entry.mMessageName).get()); + entry.mPromise->MaybeRejectWithAbortError(message); + } + + InvokeCallback(CallbackFunction::DidDestroy); + ClearManager(); +} + +void JSActor::InvokeCallback(CallbackFunction callback) { + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); + + AutoEntryScript aes(GetParentObject(), "JSActor destroy callback"); + JSContext* cx = aes.cx(); + MozJSActorCallbacks callbacksHolder; + JS::Rooted<JS::Value> val(cx, JS::ObjectOrNullValue(GetWrapper())); + if (NS_WARN_IF(!callbacksHolder.Init(cx, val))) { + return; + } + + // Destroy callback is optional. + if (callback == CallbackFunction::DidDestroy) { + if (callbacksHolder.mDidDestroy.WasPassed()) { + callbacksHolder.mDidDestroy.Value()->Call(this); + } + } else { + if (callbacksHolder.mActorCreated.WasPassed()) { + callbacksHolder.mActorCreated.Value()->Call(this); + } + } +} + +nsresult JSActor::QueryInterfaceActor(const nsIID& aIID, void** aPtr) { + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); + if (!GetWrapperPreserveColor()) { + // If we have no preserved wrapper, we won't implement any interfaces. + return NS_NOINTERFACE; + } + + if (!mWrappedJS) { + AutoEntryScript aes(GetParentObject(), "JSActor query interface"); + JSContext* cx = aes.cx(); + + JS::Rooted<JSObject*> self(cx, GetWrapper()); + JSAutoRealm ar(cx, self); + + RefPtr<nsXPCWrappedJS> wrappedJS; + nsresult rv = nsXPCWrappedJS::GetNewOrUsed( + cx, self, NS_GET_IID(nsISupports), getter_AddRefs(wrappedJS)); + NS_ENSURE_SUCCESS(rv, rv); + + mWrappedJS = do_QueryInterface(wrappedJS); + MOZ_ASSERT(mWrappedJS); + } + + return mWrappedJS->QueryInterface(aIID, aPtr); +} + +void JSActor::SetName(const nsACString& aName) { + MOZ_ASSERT(mName.IsEmpty(), "Cannot set name twice!"); + mName = aName; +} + +void JSActor::ThrowStateErrorForGetter(const char* aName, + ErrorResult& aRv) const { + if (mName.IsEmpty()) { + aRv.ThrowInvalidStateError(nsPrintfCString( + "Cannot access property '%s' before actor is initialized", aName)); + } else { + aRv.ThrowInvalidStateError(nsPrintfCString( + "Cannot access property '%s' after actor '%s' has been destroyed", + aName, mName.get())); + } +} + +static Maybe<ipc::StructuredCloneData> TryClone(JSContext* aCx, + JS::Handle<JS::Value> aValue) { + Maybe<ipc::StructuredCloneData> data{std::in_place}; + + // Try to directly serialize the passed-in data, and return it to our caller. + IgnoredErrorResult rv; + data->Write(aCx, aValue, rv); + if (rv.Failed()) { + // Serialization failed, return `Nothing()` instead. + JS_ClearPendingException(aCx); + data.reset(); + } + return data; +} + +static Maybe<ipc::StructuredCloneData> CloneJSStack( + JSContext* aCx, JS::Handle<JSObject*> aStack) { + JS::Rooted<JS::Value> stackVal(aCx, JS::ObjectOrNullValue(aStack)); + return TryClone(aCx, stackVal); +} + +static Maybe<ipc::StructuredCloneData> CaptureJSStack(JSContext* aCx) { + JS::Rooted<JSObject*> stack(aCx, nullptr); + if (JS::IsAsyncStackCaptureEnabledForRealm(aCx) && + !JS::CaptureCurrentStack(aCx, &stack)) { + JS_ClearPendingException(aCx); + } + + return CloneJSStack(aCx, stack); +} + +void JSActor::SendAsyncMessage(JSContext* aCx, const nsAString& aMessageName, + JS::Handle<JS::Value> aObj, + JS::Handle<JS::Value> aTransfers, + ErrorResult& aRv) { + profiler_add_marker("SendAsyncMessage", geckoprofiler::category::IPC, {}, + JSActorMessageMarker{}, mName, aMessageName); + Maybe<ipc::StructuredCloneData> data{std::in_place}; + if (!nsFrameMessageManager::GetParamsForMessage(aCx, aObj, aTransfers, + *data)) { + aRv.ThrowDataCloneError(nsPrintfCString( + "Failed to serialize message '%s::%s'", + NS_LossyConvertUTF16toASCII(aMessageName).get(), mName.get())); + return; + } + + JSActorMessageMeta meta; + meta.actorName() = mName; + meta.messageName() = aMessageName; + meta.kind() = JSActorMessageKind::Message; + + SendRawMessage(meta, std::move(data), CaptureJSStack(aCx), aRv); +} + +already_AddRefed<Promise> JSActor::SendQuery(JSContext* aCx, + const nsAString& aMessageName, + JS::Handle<JS::Value> aObj, + ErrorResult& aRv) { + profiler_add_marker("SendQuery", geckoprofiler::category::IPC, {}, + JSActorMessageMarker{}, mName, aMessageName); + Maybe<ipc::StructuredCloneData> data{std::in_place}; + if (!nsFrameMessageManager::GetParamsForMessage( + aCx, aObj, JS::UndefinedHandleValue, *data)) { + aRv.ThrowDataCloneError(nsPrintfCString( + "Failed to serialize message '%s::%s'", + NS_LossyConvertUTF16toASCII(aMessageName).get(), mName.get())); + return nullptr; + } + + nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx); + if (NS_WARN_IF(!global)) { + aRv.ThrowUnknownError("Unable to get current native global"); + return nullptr; + } + + RefPtr<Promise> promise = Promise::Create(global, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + JSActorMessageMeta meta; + meta.actorName() = mName; + meta.messageName() = aMessageName; + meta.queryId() = mNextQueryId++; + meta.kind() = JSActorMessageKind::Query; + + SendRawMessage(meta, std::move(data), CaptureJSStack(aCx), aRv); + if (aRv.Failed()) { + return nullptr; + } + + mPendingQueries.InsertOrUpdate(meta.queryId(), + PendingQuery{promise, meta.messageName()}); + + return promise.forget(); +} + +void JSActor::CallReceiveMessage(JSContext* aCx, + const JSActorMessageMeta& aMetadata, + JS::Handle<JS::Value> aData, + JS::MutableHandle<JS::Value> aRetVal, + ErrorResult& aRv) { + // The argument which we want to pass to IPC. + RootedDictionary<ReceiveMessageArgument> argument(aCx); + argument.mTarget = this; + argument.mName = aMetadata.messageName(); + argument.mData = aData; + argument.mJson = aData; + argument.mSync = false; + + if (GetWrapperPreserveColor()) { + // Invoke the actual callback. + JS::Rooted<JSObject*> global(aCx, JS::GetNonCCWObjectGlobal(GetWrapper())); + RefPtr<MessageListener> messageListener = + new MessageListener(GetWrapper(), global, nullptr, nullptr); + messageListener->ReceiveMessage(argument, aRetVal, aRv, + "JSActor receive message", + MessageListener::eRethrowExceptions); + } else { + aRv.ThrowTypeError<MSG_NOT_CALLABLE>("Property 'receiveMessage'"); + } +} + +void JSActor::ReceiveMessage(JSContext* aCx, + const JSActorMessageMeta& aMetadata, + JS::Handle<JS::Value> aData, ErrorResult& aRv) { + MOZ_ASSERT(aMetadata.kind() == JSActorMessageKind::Message); + profiler_add_marker("ReceiveMessage", geckoprofiler::category::IPC, {}, + JSActorMessageMarker{}, mName, aMetadata.messageName()); + + JS::Rooted<JS::Value> retval(aCx); + CallReceiveMessage(aCx, aMetadata, aData, &retval, aRv); +} + +void JSActor::ReceiveQuery(JSContext* aCx, const JSActorMessageMeta& aMetadata, + JS::Handle<JS::Value> aData, ErrorResult& aRv) { + MOZ_ASSERT(aMetadata.kind() == JSActorMessageKind::Query); + profiler_add_marker("ReceiveQuery", geckoprofiler::category::IPC, {}, + JSActorMessageMarker{}, mName, aMetadata.messageName()); + + // This promise will be resolved or rejected once the listener has been + // called. Our listener on this promise will then send the reply. + RefPtr<Promise> promise = Promise::Create(GetParentObject(), aRv); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + RefPtr<QueryHandler> handler = new QueryHandler(this, aMetadata, promise); + promise->AppendNativeHandler(handler); + + ErrorResult error; + JS::Rooted<JS::Value> retval(aCx); + CallReceiveMessage(aCx, aMetadata, aData, &retval, error); + + // If we have a promise, resolve or reject it respectively. + if (error.Failed()) { + if (error.IsUncatchableException()) { + promise->MaybeRejectWithTimeoutError( + "Message handler threw uncatchable exception"); + } else { + promise->MaybeReject(std::move(error)); + } + } else { + promise->MaybeResolve(retval); + } + error.SuppressException(); +} + +void JSActor::ReceiveQueryReply(JSContext* aCx, + const JSActorMessageMeta& aMetadata, + JS::Handle<JS::Value> aData, ErrorResult& aRv) { + if (NS_WARN_IF(aMetadata.actorName() != mName)) { + aRv.ThrowUnknownError("Mismatched actor name for query reply"); + return; + } + + Maybe<PendingQuery> query = mPendingQueries.Extract(aMetadata.queryId()); + if (NS_WARN_IF(!query)) { + aRv.ThrowUnknownError("Received reply for non-pending query"); + return; + } + + profiler_add_marker("ReceiveQueryReply", geckoprofiler::category::IPC, {}, + JSActorMessageMarker{}, mName, aMetadata.messageName()); + + Promise* promise = query->mPromise; + JSAutoRealm ar(aCx, promise->PromiseObj()); + JS::RootedValue data(aCx, aData); + if (NS_WARN_IF(!JS_WrapValue(aCx, &data))) { + aRv.NoteJSContextException(aCx); + return; + } + + if (aMetadata.kind() == JSActorMessageKind::QueryResolve) { + promise->MaybeResolve(data); + } else { + promise->MaybeReject(data); + } +} + +void JSActor::SendRawMessageInProcess(const JSActorMessageMeta& aMeta, + Maybe<ipc::StructuredCloneData>&& aData, + Maybe<ipc::StructuredCloneData>&& aStack, + OtherSideCallback&& aGetOtherSide) { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + NS_DispatchToMainThread(NS_NewRunnableFunction( + "JSActor Async Message", + [aMeta, data{std::move(aData)}, stack{std::move(aStack)}, + getOtherSide{std::move(aGetOtherSide)}]() mutable { + if (RefPtr<JSActorManager> otherSide = getOtherSide()) { + otherSide->ReceiveRawMessage(aMeta, std::move(data), + std::move(stack)); + } + })); +} + +// Native handler for our generated promise which is used to handle Queries and +// send the reply when their promises have been resolved. +JSActor::QueryHandler::QueryHandler(JSActor* aActor, + const JSActorMessageMeta& aMetadata, + Promise* aPromise) + : mActor(aActor), + mPromise(aPromise), + mMessageName(aMetadata.messageName()), + mQueryId(aMetadata.queryId()) {} + +void JSActor::QueryHandler::RejectedCallback(JSContext* aCx, + JS::Handle<JS::Value> aValue, + ErrorResult& aRv) { + if (!mActor) { + // Make sure that this rejection is reported. See comment below. + if (!JS::CallOriginalPromiseReject(aCx, aValue)) { + JS_ClearPendingException(aCx); + } + return; + } + + JS::Rooted<JS::Value> value(aCx, aValue); + if (value.isObject()) { + JS::Rooted<JSObject*> error(aCx, &value.toObject()); + if (UniquePtr<ClonedErrorHolder> ceh = + ClonedErrorHolder::Create(aCx, error, IgnoreErrors())) { + if (!ToJSValue(aCx, std::move(ceh), &value)) { + JS_ClearPendingException(aCx); + } + } else { + JS_ClearPendingException(aCx); + } + } + + Maybe<ipc::StructuredCloneData> data = TryClone(aCx, value); + if (!data) { + // Failed to clone the rejection value. Make sure that this + // rejection is reported, despite being "handled". This is done by + // creating a new promise in the rejected state, and throwing it + // away. This will be reported as an unhandled rejected promise. + if (!JS::CallOriginalPromiseReject(aCx, aValue)) { + JS_ClearPendingException(aCx); + } + } + + SendReply(aCx, JSActorMessageKind::QueryReject, std::move(data)); +} + +void JSActor::QueryHandler::ResolvedCallback(JSContext* aCx, + JS::Handle<JS::Value> aValue, + ErrorResult& aRv) { + if (!mActor) { + return; + } + + Maybe<ipc::StructuredCloneData> data{std::in_place}; + data->InitScope(JS::StructuredCloneScope::DifferentProcess); + + IgnoredErrorResult error; + data->Write(aCx, aValue, error); + if (NS_WARN_IF(error.Failed())) { + JS_ClearPendingException(aCx); + + nsAutoCString msg; + msg.Append(mActor->Name()); + msg.Append(':'); + msg.Append(NS_LossyConvertUTF16toASCII(mMessageName)); + msg.AppendLiteral(": message reply cannot be cloned."); + + auto exc = MakeRefPtr<Exception>(msg, NS_ERROR_FAILURE, "DataCloneError"_ns, + nullptr, nullptr); + + JS::Rooted<JS::Value> val(aCx); + if (ToJSValue(aCx, exc, &val)) { + RejectedCallback(aCx, val, aRv); + } else { + JS_ClearPendingException(aCx); + } + return; + } + + SendReply(aCx, JSActorMessageKind::QueryResolve, std::move(data)); +} + +void JSActor::QueryHandler::SendReply(JSContext* aCx, JSActorMessageKind aKind, + Maybe<ipc::StructuredCloneData>&& aData) { + MOZ_ASSERT(mActor); + profiler_add_marker("SendQueryReply", geckoprofiler::category::IPC, {}, + JSActorMessageMarker{}, mActor->Name(), mMessageName); + + JSActorMessageMeta meta; + meta.actorName() = mActor->Name(); + meta.messageName() = mMessageName; + meta.queryId() = mQueryId; + meta.kind() = aKind; + + JS::Rooted<JSObject*> promise(aCx, mPromise->PromiseObj()); + JS::Rooted<JSObject*> stack(aCx, JS::GetPromiseResolutionSite(promise)); + + mActor->SendRawMessage(meta, std::move(aData), CloneJSStack(aCx, stack), + IgnoreErrors()); + mActor = nullptr; + mPromise = nullptr; +} + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSActor::QueryHandler) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(JSActor::QueryHandler) +NS_IMPL_CYCLE_COLLECTING_RELEASE(JSActor::QueryHandler) + +NS_IMPL_CYCLE_COLLECTION(JSActor::QueryHandler, mActor, mPromise) + +} // namespace mozilla::dom diff --git a/dom/ipc/jsactor/JSActor.h b/dom/ipc/jsactor/JSActor.h new file mode 100644 index 0000000000..6c6678a7ff --- /dev/null +++ b/dom/ipc/jsactor/JSActor.h @@ -0,0 +1,174 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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_dom_JSActor_h +#define mozilla_dom_JSActor_h + +#include "js/TypeDecls.h" +#include "ipc/EnumSerializer.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/PromiseNativeHandler.h" +#include "nsCycleCollectionParticipant.h" +#include "nsTHashMap.h" +#include "nsWrapperCache.h" + +class nsIGlobalObject; +class nsQueryJSActor; + +namespace mozilla { +class ErrorResult; + +namespace dom { + +namespace ipc { +class StructuredCloneData; +} + +class JSActorManager; +class JSActorMessageMeta; +class QueryPromiseHandler; + +enum class JSActorMessageKind { + Message, + Query, + QueryResolve, + QueryReject, + EndGuard_, +}; + +// Common base class for JSWindowActor{Parent,Child}. +class JSActor : public nsISupports, public nsWrapperCache { + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(JSActor) + + explicit JSActor(nsISupports* aGlobal = nullptr); + + const nsCString& Name() const { return mName; } + void GetName(nsCString& aName) { aName = Name(); } + + void SendAsyncMessage(JSContext* aCx, const nsAString& aMessageName, + JS::Handle<JS::Value> aObj, + JS::Handle<JS::Value> aTransfers, ErrorResult& aRv); + + already_AddRefed<Promise> SendQuery(JSContext* aCx, + const nsAString& aMessageName, + JS::Handle<JS::Value> aObj, + ErrorResult& aRv); + + nsIGlobalObject* GetParentObject() const { return mGlobal; }; + + protected: + // Send the message described by the structured clone data |aData|, and the + // message metadata |aMetadata|. The underlying transport should call the + // |ReceiveMessage| method on the other side asynchronously. + virtual void SendRawMessage(const JSActorMessageMeta& aMetadata, + Maybe<ipc::StructuredCloneData>&& aData, + Maybe<ipc::StructuredCloneData>&& aStack, + ErrorResult& aRv) = 0; + + // Helper method to send an in-process raw message. + using OtherSideCallback = std::function<already_AddRefed<JSActorManager>()>; + static void SendRawMessageInProcess(const JSActorMessageMeta& aMeta, + Maybe<ipc::StructuredCloneData>&& aData, + Maybe<ipc::StructuredCloneData>&& aStack, + OtherSideCallback&& aGetOtherSide); + + virtual ~JSActor() = default; + + void SetName(const nsACString& aName); + + bool CanSend() const { return mCanSend; } + + void ThrowStateErrorForGetter(const char* aName, ErrorResult& aRv) const; + + void StartDestroy(); + void AfterDestroy(); + + enum class CallbackFunction { DidDestroy, ActorCreated }; + void InvokeCallback(CallbackFunction callback); + + virtual void ClearManager() = 0; + + private: + friend class JSActorManager; + friend class ::nsQueryJSActor; // for QueryInterfaceActor + + nsresult QueryInterfaceActor(const nsIID& aIID, void** aPtr); + + // Called by JSActorManager when they receive raw message data destined for + // this actor. + void ReceiveMessage(JSContext* aCx, const JSActorMessageMeta& aMetadata, + JS::Handle<JS::Value> aData, ErrorResult& aRv); + void ReceiveQuery(JSContext* aCx, const JSActorMessageMeta& aMetadata, + JS::Handle<JS::Value> aData, ErrorResult& aRv); + void ReceiveQueryReply(JSContext* aCx, const JSActorMessageMeta& aMetadata, + JS::Handle<JS::Value> aData, ErrorResult& aRv); + + // Call the actual `ReceiveMessage` method, and get the return value. + void CallReceiveMessage(JSContext* aCx, const JSActorMessageMeta& aMetadata, + JS::Handle<JS::Value> aData, + JS::MutableHandle<JS::Value> aRetVal, + ErrorResult& aRv); + + // Helper object used while processing query messages to send the final reply + // message. + class QueryHandler final : public PromiseNativeHandler { + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(QueryHandler) + + QueryHandler(JSActor* aActor, const JSActorMessageMeta& aMetadata, + Promise* aPromise); + + void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, + ErrorResult& aRv) override; + + void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, + ErrorResult& aRv) override; + + private: + ~QueryHandler() = default; + + void SendReply(JSContext* aCx, JSActorMessageKind aKind, + Maybe<ipc::StructuredCloneData>&& aData); + + RefPtr<JSActor> mActor; + RefPtr<Promise> mPromise; + nsString mMessageName; + uint64_t mQueryId; + }; + + // A query which hasn't been resolved yet, along with metadata about what + // query the promise is for. + struct PendingQuery { + RefPtr<Promise> mPromise; + nsString mMessageName; + }; + + nsCOMPtr<nsIGlobalObject> mGlobal; + nsCOMPtr<nsISupports> mWrappedJS; + nsCString mName; + nsTHashMap<nsUint64HashKey, PendingQuery> mPendingQueries; + uint64_t mNextQueryId = 0; + bool mCanSend = true; +}; + +} // namespace dom +} // namespace mozilla + +namespace IPC { + +template <> +struct ParamTraits<mozilla::dom::JSActorMessageKind> + : public ContiguousEnumSerializer< + mozilla::dom::JSActorMessageKind, + mozilla::dom::JSActorMessageKind::Message, + mozilla::dom::JSActorMessageKind::EndGuard_> {}; + +} // namespace IPC + +#endif // !defined(mozilla_dom_JSActor_h) diff --git a/dom/ipc/jsactor/JSActorManager.cpp b/dom/ipc/jsactor/JSActorManager.cpp new file mode 100644 index 0000000000..b8791570d6 --- /dev/null +++ b/dom/ipc/jsactor/JSActorManager.cpp @@ -0,0 +1,266 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/dom/JSActorManager.h" + +#include "mozilla/dom/AutoEntryScript.h" +#include "mozilla/dom/JSActorService.h" +#include "mozilla/dom/MessagePort.h" +#include "mozilla/dom/PWindowGlobal.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/AppShutdown.h" +#include "mozilla/CycleCollectedJSRuntime.h" +#include "mozilla/ScopeExit.h" +#include "mozJSModuleLoader.h" +#include "jsapi.h" +#include "js/CallAndConstruct.h" // JS::Construct +#include "js/PropertyAndElement.h" // JS_GetProperty +#include "nsContentUtils.h" + +namespace mozilla::dom { + +already_AddRefed<JSActor> JSActorManager::GetActor(JSContext* aCx, + const nsACString& aName, + ErrorResult& aRv) { + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); + + // If our connection has been closed, return an error. + mozilla::ipc::IProtocol* nativeActor = AsNativeActor(); + if (!nativeActor->CanSend()) { + aRv.ThrowInvalidStateError(nsPrintfCString( + "Cannot get actor '%s'. Native '%s' actor is destroyed.", + PromiseFlatCString(aName).get(), nativeActor->GetProtocolName())); + return nullptr; + } + + // Check if this actor has already been created, and return it if it has. + if (RefPtr<JSActor> actor = mJSActors.Get(aName)) { + return actor.forget(); + } + + RefPtr<JSActorService> actorSvc = JSActorService::GetSingleton(); + if (!actorSvc) { + aRv.ThrowInvalidStateError("JSActorService hasn't been initialized"); + return nullptr; + } + + // Check if this actor satisfies the requirements of the protocol + // corresponding to `aName`, and get the module which implements it. + RefPtr<JSActorProtocol> protocol = + MatchingJSActorProtocol(actorSvc, aName, aRv); + if (!protocol) { + return nullptr; + } + + auto& side = nativeActor->GetSide() == mozilla::ipc::ParentSide + ? protocol->Parent() + : protocol->Child(); + + // Load the module using mozJSModuleLoader. + // If the JSActor uses `loadInDevToolsLoader`, force loading in the DevTools + // specific's loader. + RefPtr loader = protocol->mLoadInDevToolsLoader + ? mozJSModuleLoader::GetOrCreateDevToolsLoader() + : mozJSModuleLoader::Get(); + MOZ_ASSERT(loader); + + // We're about to construct the actor, so make sure we're in the loader realm + // while importing etc. + JSAutoRealm ar(aCx, loader->GetSharedGlobal(aCx)); + + // If a module URI was provided, use it to construct an instance of the actor. + JS::Rooted<JSObject*> actorObj(aCx); + if (side.mModuleURI || side.mESModuleURI) { + JS::Rooted<JSObject*> exports(aCx); + if (side.mModuleURI) { + JS::Rooted<JSObject*> global(aCx); + aRv = loader->Import(aCx, side.mModuleURI.ref(), &global, &exports); + if (aRv.Failed()) { + return nullptr; + } + } else { + aRv = loader->ImportESModule(aCx, side.mESModuleURI.ref(), &exports); + if (aRv.Failed()) { + return nullptr; + } + } + MOZ_ASSERT(exports, "null exports!"); + + // Load the specific property from our module. + JS::Rooted<JS::Value> ctor(aCx); + nsAutoCString ctorName(aName); + ctorName.Append(StringFromIPCSide(nativeActor->GetSide())); + if (!JS_GetProperty(aCx, exports, ctorName.get(), &ctor)) { + aRv.NoteJSContextException(aCx); + return nullptr; + } + + if (NS_WARN_IF(!ctor.isObject())) { + aRv.ThrowNotFoundError(nsPrintfCString( + "Could not find actor constructor '%s'", ctorName.get())); + return nullptr; + } + + // Invoke the constructor loaded from the module. + if (!JS::Construct(aCx, ctor, JS::HandleValueArray::empty(), &actorObj)) { + aRv.NoteJSContextException(aCx); + return nullptr; + } + } + + // Initialize our newly-constructed actor, and return it. + RefPtr<JSActor> actor = InitJSActor(actorObj, aName, aRv); + if (aRv.Failed()) { + return nullptr; + } + mJSActors.InsertOrUpdate(aName, RefPtr{actor}); + return actor.forget(); +} + +already_AddRefed<JSActor> JSActorManager::GetExistingActor( + const nsACString& aName) { + if (!AsNativeActor()->CanSend()) { + return nullptr; + } + return mJSActors.Get(aName); +} + +#define CHILD_DIAGNOSTIC_ASSERT(test, msg) \ + do { \ + if (XRE_IsParentProcess()) { \ + MOZ_ASSERT(test, msg); \ + } else { \ + MOZ_DIAGNOSTIC_ASSERT(test, msg); \ + } \ + } while (0) + +void JSActorManager::ReceiveRawMessage( + const JSActorMessageMeta& aMetadata, + Maybe<ipc::StructuredCloneData>&& aData, + Maybe<ipc::StructuredCloneData>&& aStack) { + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); + + CrashReporter::AutoAnnotateCrashReport autoActorName( + CrashReporter::Annotation::JSActorName, aMetadata.actorName()); + CrashReporter::AutoAnnotateCrashReport autoMessageName( + CrashReporter::Annotation::JSActorMessage, + NS_LossyConvertUTF16toASCII(aMetadata.messageName())); + + // We're going to be running JS. Enter the privileged junk realm so we can set + // up our JS state correctly. + AutoEntryScript aes(xpc::PrivilegedJunkScope(), "JSActor message handler"); + JSContext* cx = aes.cx(); + + // Ensure any errors reported to `error` are set on the scope, so they're + // reported. + ErrorResult error; + auto autoSetException = + MakeScopeExit([&] { Unused << error.MaybeSetPendingException(cx); }); + + // If an async stack was provided, set up our async stack state. + JS::Rooted<JSObject*> stack(cx); + Maybe<JS::AutoSetAsyncStackForNewCalls> stackSetter; + { + JS::Rooted<JS::Value> stackVal(cx); + if (aStack) { + aStack->Read(cx, &stackVal, error); + if (error.Failed()) { + error.SuppressException(); + JS_ClearPendingException(cx); + stackVal.setUndefined(); + } + } + + if (stackVal.isObject()) { + stack = &stackVal.toObject(); + if (!js::IsSavedFrame(stack)) { + CHILD_DIAGNOSTIC_ASSERT(false, "Stack must be a SavedFrame object"); + error.ThrowDataError("Actor async stack must be a SavedFrame object"); + return; + } + stackSetter.emplace(cx, stack, "JSActor query"); + } + } + + RefPtr<JSActor> actor = GetActor(cx, aMetadata.actorName(), error); + if (error.Failed()) { + return; + } + + JS::Rooted<JS::Value> data(cx); + if (aData) { + aData->Read(cx, &data, error); + // StructuredCloneHolder populates an array of ports for MessageEvent.ports + // which we don't need, but which StructuredCloneHolder's destructor will + // assert on for thread safety reasons (that do not apply in this case) if + // we do not consume the array. It's possible for the Read call above to + // populate this array even in event of an error, so we must consume the + // array before processing the error. + nsTArray<RefPtr<MessagePort>> ports = aData->TakeTransferredPorts(); + // Cast to void so that the ports will actually be moved, and then + // discarded. + (void)ports; + if (error.Failed()) { + CHILD_DIAGNOSTIC_ASSERT(CycleCollectedJSRuntime::Get()->OOMReported(), + "Should not receive non-decodable data"); + return; + } + } + + switch (aMetadata.kind()) { + case JSActorMessageKind::QueryResolve: + case JSActorMessageKind::QueryReject: + actor->ReceiveQueryReply(cx, aMetadata, data, error); + break; + + case JSActorMessageKind::Message: + actor->ReceiveMessage(cx, aMetadata, data, error); + break; + + case JSActorMessageKind::Query: + actor->ReceiveQuery(cx, aMetadata, data, error); + break; + + default: + MOZ_ASSERT_UNREACHABLE(); + } +} + +void JSActorManager::JSActorWillDestroy() { + for (const auto& entry : mJSActors.Values()) { + entry->StartDestroy(); + } +} + +void JSActorManager::JSActorDidDestroy() { + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); + CrashReporter::AutoAnnotateCrashReport autoMessageName( + CrashReporter::Annotation::JSActorMessage, "<DidDestroy>"_ns); + + // Swap the table with `mJSActors` so that we don't invalidate it while + // iterating. + const nsRefPtrHashtable<nsCStringHashKey, JSActor> actors = + std::move(mJSActors); + for (const auto& entry : actors.Values()) { + CrashReporter::AutoAnnotateCrashReport autoActorName( + CrashReporter::Annotation::JSActorName, entry->Name()); + // Do not risk to run script very late in shutdown + if (!AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownFinal)) { + entry->AfterDestroy(); + } + } +} + +void JSActorManager::JSActorUnregister(const nsACString& aName) { + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); + + RefPtr<JSActor> actor; + if (mJSActors.Remove(aName, getter_AddRefs(actor))) { + actor->AfterDestroy(); + } +} + +} // namespace mozilla::dom diff --git a/dom/ipc/jsactor/JSActorManager.h b/dom/ipc/jsactor/JSActorManager.h new file mode 100644 index 0000000000..86efa9d7af --- /dev/null +++ b/dom/ipc/jsactor/JSActorManager.h @@ -0,0 +1,100 @@ +/* -*- 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_dom_JSActorManager_h +#define mozilla_dom_JSActorManager_h + +#include "js/TypeDecls.h" +#include "mozilla/dom/JSActor.h" +#include "nsRefPtrHashtable.h" +#include "nsString.h" + +namespace mozilla { +class ErrorResult; + +namespace ipc { +class IProtocol; +} + +namespace dom { + +class JSActorProtocol; +class JSActorService; + +class JSActorManager : public nsISupports { + public: + /** + * Get or create an actor by its name. + * + * Will set an error on |aRv| if the actor fails to be constructed. + */ + already_AddRefed<JSActor> GetActor(JSContext* aCx, const nsACString& aName, + ErrorResult& aRv); + + /** + * Look up an existing actor by its name, returning nullptr if it doesn't + * already exist. Will not attempt to create the actor. + */ + already_AddRefed<JSActor> GetExistingActor(const nsACString& aName); + + /** + * Handle receiving a raw message from the other side. + */ + void ReceiveRawMessage(const JSActorMessageMeta& aMetadata, + Maybe<ipc::StructuredCloneData>&& aData, + Maybe<ipc::StructuredCloneData>&& aStack); + + protected: + /** + * The actor is about to be destroyed so prevent it from sending any + * more messages. + */ + void JSActorWillDestroy(); + + /** + * Lifecycle method which will fire the `didDestroy` methods on relevant + * actors. + */ + void JSActorDidDestroy(); + + /** + * Return the protocol with the given name, if it is supported by the current + * actor. + */ + virtual already_AddRefed<JSActorProtocol> MatchingJSActorProtocol( + JSActorService* aActorSvc, const nsACString& aName, ErrorResult& aRv) = 0; + + /** + * Initialize a JSActor instance given the constructed JS object. + * `aMaybeActor` may be `nullptr`, which should construct the default empty + * actor. + */ + virtual already_AddRefed<JSActor> InitJSActor( + JS::Handle<JSObject*> aMaybeActor, const nsACString& aName, + ErrorResult& aRv) = 0; + + /** + * Return this native actor. This should be the same object which is + * implementing `JSActorManager`. + */ + virtual mozilla::ipc::IProtocol* AsNativeActor() = 0; + + private: + friend class JSActorService; + + /** + * Note that a particular actor name has been unregistered, and fire the + * `didDestroy` method on the actor, if it's been initialized. + */ + void JSActorUnregister(const nsACString& aName); + + nsRefPtrHashtable<nsCStringHashKey, JSActor> mJSActors; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_JSActorManager_h diff --git a/dom/ipc/jsactor/JSActorProtocolUtils.h b/dom/ipc/jsactor/JSActorProtocolUtils.h new file mode 100644 index 0000000000..20c0440d63 --- /dev/null +++ b/dom/ipc/jsactor/JSActorProtocolUtils.h @@ -0,0 +1,121 @@ +/* -*- 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_dom_JSActorProtocolUtils_h +#define mozilla_dom_JSActorProtocolUtils_h + +#include "mozilla/Assertions.h" // MOZ_ASSERT +#include "mozilla/ErrorResult.h" // ErrorResult + +namespace mozilla { + +namespace dom { + +class JSActorProtocolUtils { + public: + template <typename ProtoT, typename ActorInfoT> + static void FromIPCShared(ProtoT& aProto, const ActorInfoT& aInfo) { + aProto->mRemoteTypes = aInfo.remoteTypes().Clone(); + + if (aInfo.isESModule()) { + aProto->mChild.mESModuleURI = aInfo.url(); + } else { + aProto->mChild.mModuleURI = aInfo.url(); + } + + aProto->mLoadInDevToolsLoader = aInfo.loadInDevToolsLoader(); + + aProto->mChild.mObservers = aInfo.observers().Clone(); + } + + template <typename ProtoT, typename ActorInfoT> + static void ToIPCShared(ActorInfoT& aInfo, const ProtoT& aProto) { + aInfo.name() = aProto->mName; + + aInfo.remoteTypes() = aProto->mRemoteTypes.Clone(); + + if (aProto->mChild.mModuleURI) { + aInfo.url() = aProto->mChild.mModuleURI; + aInfo.isESModule() = false; + } else { + aInfo.url() = aProto->mChild.mESModuleURI; + aInfo.isESModule() = true; + } + + aInfo.loadInDevToolsLoader() = aProto->mLoadInDevToolsLoader; + + aInfo.observers() = aProto->mChild.mObservers.Clone(); + } + + template <typename ProtoT, typename ActorOptionsT> + static bool FromWebIDLOptionsShared(ProtoT& aProto, + const ActorOptionsT& aOptions, + ErrorResult& aRv) { + if (aOptions.mRemoteTypes.WasPassed()) { + MOZ_ASSERT(aOptions.mRemoteTypes.Value().Length()); + aProto->mRemoteTypes = aOptions.mRemoteTypes.Value(); + } + + if (aOptions.mParent.WasPassed()) { + const auto& parentOptions = aOptions.mParent.Value(); + + if (parentOptions.mModuleURI.WasPassed()) { + if (parentOptions.mEsModuleURI.WasPassed()) { + aRv.ThrowNotSupportedError( + "moduleURI and esModuleURI are mutually exclusive."); + return false; + } + + aProto->mParent.mModuleURI.emplace(parentOptions.mModuleURI.Value()); + } else if (parentOptions.mEsModuleURI.WasPassed()) { + aProto->mParent.mESModuleURI.emplace( + parentOptions.mEsModuleURI.Value()); + } else { + aRv.ThrowNotSupportedError( + "Either moduleURI or esModuleURI is required."); + return false; + } + } + if (aOptions.mChild.WasPassed()) { + const auto& childOptions = aOptions.mChild.Value(); + + if (childOptions.mModuleURI.WasPassed()) { + if (childOptions.mEsModuleURI.WasPassed()) { + aRv.ThrowNotSupportedError( + "moduleURI and esModuleURI are exclusive."); + return false; + } + + aProto->mChild.mModuleURI.emplace(childOptions.mModuleURI.Value()); + } else if (childOptions.mEsModuleURI.WasPassed()) { + aProto->mChild.mESModuleURI.emplace(childOptions.mEsModuleURI.Value()); + } else { + aRv.ThrowNotSupportedError( + "Either moduleURI or esModuleURI is required."); + return false; + } + } + + if (!aOptions.mChild.WasPassed() && !aOptions.mParent.WasPassed()) { + aRv.ThrowNotSupportedError( + "No point registering an actor with neither child nor parent " + "specifications."); + return false; + } + + if (aOptions.mChild.WasPassed() && + aOptions.mChild.Value().mObservers.WasPassed()) { + aProto->mChild.mObservers = aOptions.mChild.Value().mObservers.Value(); + } + + return true; + } +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_JSActorProtocolUtils_h diff --git a/dom/ipc/jsactor/JSActorService.cpp b/dom/ipc/jsactor/JSActorService.cpp new file mode 100644 index 0000000000..3fde76a206 --- /dev/null +++ b/dom/ipc/jsactor/JSActorService.cpp @@ -0,0 +1,322 @@ +/* -*- 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 "mozilla/dom/JSActorService.h" +#include "mozilla/dom/ChromeUtilsBinding.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/EventListenerBinding.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/EventTargetBinding.h" +#include "mozilla/dom/EventTarget.h" +#include "mozilla/dom/InProcessChild.h" +#include "mozilla/dom/InProcessParent.h" +#include "mozilla/dom/JSActorManager.h" +#include "mozilla/dom/JSProcessActorBinding.h" +#include "mozilla/dom/JSProcessActorChild.h" +#include "mozilla/dom/JSProcessActorProtocol.h" +#include "mozilla/dom/JSWindowActorBinding.h" +#include "mozilla/dom/JSWindowActorChild.h" +#include "mozilla/dom/JSWindowActorProtocol.h" +#include "mozilla/dom/MessageManagerBinding.h" +#include "mozilla/dom/PContent.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/WindowGlobalChild.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/ArrayAlgorithm.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Logging.h" +#include "nsIObserverService.h" + +namespace mozilla::dom { +namespace { +StaticRefPtr<JSActorService> gJSActorService; +} + +JSActorService::JSActorService() { MOZ_ASSERT(NS_IsMainThread()); } + +JSActorService::~JSActorService() { MOZ_ASSERT(NS_IsMainThread()); } + +/* static */ +already_AddRefed<JSActorService> JSActorService::GetSingleton() { + MOZ_ASSERT(NS_IsMainThread()); + if (!gJSActorService) { + gJSActorService = new JSActorService(); + ClearOnShutdown(&gJSActorService); + } + + RefPtr<JSActorService> service = gJSActorService.get(); + return service.forget(); +} + +void JSActorService::RegisterWindowActor(const nsACString& aName, + const WindowActorOptions& aOptions, + ErrorResult& aRv) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(XRE_IsParentProcess()); + + const auto proto = mWindowActorDescriptors.WithEntryHandle( + aName, [&](auto&& entry) -> RefPtr<JSWindowActorProtocol> { + if (entry) { + aRv.ThrowNotSupportedError( + nsPrintfCString("'%s' actor is already registered.", + PromiseFlatCString(aName).get())); + return nullptr; + } + + // Insert a new entry for the protocol. + RefPtr<JSWindowActorProtocol> protocol = + JSWindowActorProtocol::FromWebIDLOptions(aName, aOptions, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + entry.Insert(protocol); + + return protocol; + }); + + if (!proto) { + MOZ_ASSERT(aRv.Failed()); + return; + } + + // Send information about the newly added entry to every existing content + // process. + AutoTArray<JSWindowActorInfo, 1> windowInfos{proto->ToIPC()}; + nsTArray<JSProcessActorInfo> contentInfos{}; + for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) { + Unused << cp->SendInitJSActorInfos(contentInfos, windowInfos); + } + + // Register event listeners for any existing chrome targets. + for (EventTarget* target : mChromeEventTargets) { + proto->RegisterListenersFor(target); + } + + // Add observers to the protocol. + proto->AddObservers(); +} + +void JSActorService::UnregisterWindowActor(const nsACString& aName) { + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); + CrashReporter::AutoAnnotateCrashReport autoActorName( + CrashReporter::Annotation::JSActorName, aName); + CrashReporter::AutoAnnotateCrashReport autoMessageName( + CrashReporter::Annotation::JSActorMessage, "<Unregister>"_ns); + + nsAutoCString name(aName); + RefPtr<JSWindowActorProtocol> proto; + if (mWindowActorDescriptors.Remove(name, getter_AddRefs(proto))) { + // Remove listeners for this actor from each of our chrome targets. + for (EventTarget* target : mChromeEventTargets) { + proto->UnregisterListenersFor(target); + } + + // Remove observers for this actor from observer serivce. + proto->RemoveObservers(); + + // Tell every content process to also unregister, and accumulate the set of + // potential managers, to have the actor disabled. + nsTArray<RefPtr<JSActorManager>> managers; + if (XRE_IsParentProcess()) { + for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) { + Unused << cp->SendUnregisterJSWindowActor(name); + for (const auto& bp : cp->ManagedPBrowserParent()) { + for (const auto& wgp : bp->ManagedPWindowGlobalParent()) { + managers.AppendElement(static_cast<WindowGlobalParent*>(wgp)); + } + } + } + + for (const auto& wgp : + InProcessParent::Singleton()->ManagedPWindowGlobalParent()) { + managers.AppendElement(static_cast<WindowGlobalParent*>(wgp)); + } + for (const auto& wgc : + InProcessChild::Singleton()->ManagedPWindowGlobalChild()) { + managers.AppendElement(static_cast<WindowGlobalChild*>(wgc)); + } + } else { + for (const auto& bc : + ContentChild::GetSingleton()->ManagedPBrowserChild()) { + for (const auto& wgc : bc->ManagedPWindowGlobalChild()) { + managers.AppendElement(static_cast<WindowGlobalChild*>(wgc)); + } + } + } + + for (auto& mgr : managers) { + mgr->JSActorUnregister(name); + } + } +} + +void JSActorService::LoadJSActorInfos(nsTArray<JSProcessActorInfo>& aProcess, + nsTArray<JSWindowActorInfo>& aWindow) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(XRE_IsContentProcess()); + + for (auto& info : aProcess) { + // Create our JSProcessActorProtocol, register it in + // mProcessActorDescriptors. + auto name = info.name(); + RefPtr<JSProcessActorProtocol> proto = + JSProcessActorProtocol::FromIPC(std::move(info)); + mProcessActorDescriptors.InsertOrUpdate(std::move(name), RefPtr{proto}); + + // Add observers for each actor. + proto->AddObservers(); + } + + for (auto& info : aWindow) { + auto name = info.name(); + RefPtr<JSWindowActorProtocol> proto = + JSWindowActorProtocol::FromIPC(std::move(info)); + mWindowActorDescriptors.InsertOrUpdate(std::move(name), RefPtr{proto}); + + // Register listeners for each chrome target. + for (EventTarget* target : mChromeEventTargets) { + proto->RegisterListenersFor(target); + } + + // Add observers for each actor. + proto->AddObservers(); + } +} + +void JSActorService::GetJSWindowActorInfos( + nsTArray<JSWindowActorInfo>& aInfos) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(XRE_IsParentProcess()); + + for (const auto& data : mWindowActorDescriptors.Values()) { + aInfos.AppendElement(data->ToIPC()); + } +} + +void JSActorService::RegisterChromeEventTarget(EventTarget* aTarget) { + MOZ_ASSERT(!mChromeEventTargets.Contains(aTarget)); + mChromeEventTargets.AppendElement(aTarget); + + // Register event listeners on the newly added Window Root. + for (const auto& data : mWindowActorDescriptors.Values()) { + data->RegisterListenersFor(aTarget); + } + + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + obs->NotifyObservers(aTarget, "chrome-event-target-created", nullptr); +} + +/* static */ +void JSActorService::UnregisterChromeEventTarget(EventTarget* aTarget) { + if (gJSActorService) { + // NOTE: No need to unregister listeners here, as the target is going away. + gJSActorService->mChromeEventTargets.RemoveElement(aTarget); + } +} + +void JSActorService::RegisterProcessActor(const nsACString& aName, + const ProcessActorOptions& aOptions, + ErrorResult& aRv) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(XRE_IsParentProcess()); + + const auto proto = mProcessActorDescriptors.WithEntryHandle( + aName, [&](auto&& entry) -> RefPtr<JSProcessActorProtocol> { + if (entry) { + aRv.ThrowNotSupportedError( + nsPrintfCString("'%s' actor is already registered.", + PromiseFlatCString(aName).get())); + return nullptr; + } + + // Insert a new entry for the protocol. + RefPtr<JSProcessActorProtocol> protocol = + JSProcessActorProtocol::FromWebIDLOptions(aName, aOptions, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + entry.Insert(protocol); + + return protocol; + }); + + if (!proto) { + MOZ_ASSERT(aRv.Failed()); + return; + } + + // Send information about the newly added entry to every existing content + // process. + AutoTArray<JSProcessActorInfo, 1> contentInfos{proto->ToIPC()}; + nsTArray<JSWindowActorInfo> windowInfos{}; + for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) { + Unused << cp->SendInitJSActorInfos(contentInfos, windowInfos); + } + + // Add observers to the protocol. + proto->AddObservers(); +} + +void JSActorService::UnregisterProcessActor(const nsACString& aName) { + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); + CrashReporter::AutoAnnotateCrashReport autoActorName( + CrashReporter::Annotation::JSActorName, aName); + CrashReporter::AutoAnnotateCrashReport autoMessageName( + CrashReporter::Annotation::JSActorMessage, "<Unregister>"_ns); + + nsAutoCString name(aName); + RefPtr<JSProcessActorProtocol> proto; + if (mProcessActorDescriptors.Remove(name, getter_AddRefs(proto))) { + // Remove observers for this actor from observer serivce. + proto->RemoveObservers(); + + // Tell every content process to also unregister, and accumulate the set of + // potential managers, to have the actor disabled. + nsTArray<RefPtr<JSActorManager>> managers; + if (XRE_IsParentProcess()) { + for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) { + Unused << cp->SendUnregisterJSProcessActor(name); + managers.AppendElement(cp); + } + managers.AppendElement(InProcessChild::Singleton()); + managers.AppendElement(InProcessParent::Singleton()); + } else { + managers.AppendElement(ContentChild::GetSingleton()); + } + + for (auto& mgr : managers) { + mgr->JSActorUnregister(name); + } + } +} + +void JSActorService::GetJSProcessActorInfos( + nsTArray<JSProcessActorInfo>& aInfos) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(XRE_IsParentProcess()); + + for (const auto& data : mProcessActorDescriptors.Values()) { + aInfos.AppendElement(data->ToIPC()); + } +} + +already_AddRefed<JSProcessActorProtocol> +JSActorService::GetJSProcessActorProtocol(const nsACString& aName) { + return mProcessActorDescriptors.Get(aName); +} + +already_AddRefed<JSWindowActorProtocol> +JSActorService::GetJSWindowActorProtocol(const nsACString& aName) { + return mWindowActorDescriptors.Get(aName); +} + +} // namespace mozilla::dom diff --git a/dom/ipc/jsactor/JSActorService.h b/dom/ipc/jsactor/JSActorService.h new file mode 100644 index 0000000000..1e7a5fcdc7 --- /dev/null +++ b/dom/ipc/jsactor/JSActorService.h @@ -0,0 +1,113 @@ +/* -*- 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_dom_JSActorService_h +#define mozilla_dom_JSActorService_h + +#include "mozilla/dom/BrowsingContext.h" +#include "nsIURI.h" +#include "nsRefPtrHashtable.h" +#include "nsString.h" +#include "nsTArray.h" +#include "mozilla/dom/JSActor.h" +#include "nsIObserver.h" +#include "nsIDOMEventListener.h" +#include "mozilla/EventListenerManager.h" + +namespace mozilla { +class ErrorResult; + +namespace dom { + +struct ProcessActorOptions; +struct WindowActorOptions; +class JSProcessActorInfo; +class JSWindowActorInfo; +class EventTarget; +class JSWindowActorProtocol; +class JSProcessActorProtocol; + +class JSActorService final { + public: + NS_INLINE_DECL_REFCOUNTING(JSActorService) + + static already_AddRefed<JSActorService> GetSingleton(); + + // Register or unregister a chrome event target. + void RegisterChromeEventTarget(EventTarget* aTarget); + + // NOTE: This method is static, as it may be called during shutdown. + static void UnregisterChromeEventTarget(EventTarget* aTarget); + + // Register child's Actor for content process. + void LoadJSActorInfos(nsTArray<JSProcessActorInfo>& aProcess, + nsTArray<JSWindowActorInfo>& aWindow); + + // --- Window Actor + + void RegisterWindowActor(const nsACString& aName, + const WindowActorOptions& aOptions, + ErrorResult& aRv); + + void UnregisterWindowActor(const nsACString& aName); + + // Get the named of Window Actor and the child's WindowActorOptions + // from mDescriptors to JSWindowActorInfos. + void GetJSWindowActorInfos(nsTArray<JSWindowActorInfo>& aInfos); + + already_AddRefed<JSWindowActorProtocol> GetJSWindowActorProtocol( + const nsACString& aName); + + // -- Content Actor + + void RegisterProcessActor(const nsACString& aName, + const ProcessActorOptions& aOptions, + ErrorResult& aRv); + + void UnregisterProcessActor(const nsACString& aName); + + // Get the named of Content Actor and the child's ProcessActorOptions + // from mDescriptors to JSProcessActorInfos. + void GetJSProcessActorInfos(nsTArray<JSProcessActorInfo>& aInfos); + + already_AddRefed<JSProcessActorProtocol> GetJSProcessActorProtocol( + const nsACString& aName); + + private: + JSActorService(); + ~JSActorService(); + + nsTArray<EventTarget*> mChromeEventTargets; + + // -- Window Actor + nsRefPtrHashtable<nsCStringHashKey, JSWindowActorProtocol> + mWindowActorDescriptors; + + // -- Process Actor + nsRefPtrHashtable<nsCStringHashKey, JSProcessActorProtocol> + mProcessActorDescriptors; +}; + +/** + * Base class for both `JSWindowActorProtocol` and `JSProcessActorProtocol` + * which can be used by generic code. + */ +class JSActorProtocol : public nsISupports { + public: + struct Sided { + Maybe<nsCString> mModuleURI; + Maybe<nsCString> mESModuleURI; + }; + + virtual const Sided& Parent() const = 0; + virtual const Sided& Child() const = 0; + bool mLoadInDevToolsLoader = false; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_JSActorService_h diff --git a/dom/ipc/jsactor/JSProcessActorChild.cpp b/dom/ipc/jsactor/JSProcessActorChild.cpp new file mode 100644 index 0000000000..a9379838cf --- /dev/null +++ b/dom/ipc/jsactor/JSProcessActorChild.cpp @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/dom/ContentChild.h" +#include "mozilla/dom/JSProcessActorBinding.h" +#include "mozilla/dom/JSProcessActorChild.h" +#include "mozilla/dom/InProcessChild.h" +#include "mozilla/dom/InProcessParent.h" + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_INHERITED(JSProcessActorChild, JSActor, mManager) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSProcessActorChild) +NS_INTERFACE_MAP_END_INHERITING(JSActor) + +NS_IMPL_ADDREF_INHERITED(JSProcessActorChild, JSActor) +NS_IMPL_RELEASE_INHERITED(JSProcessActorChild, JSActor) + +JSObject* JSProcessActorChild::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return JSProcessActorChild_Binding::Wrap(aCx, this, aGivenProto); +} + +void JSProcessActorChild::SendRawMessage( + const JSActorMessageMeta& aMeta, Maybe<ipc::StructuredCloneData>&& aData, + Maybe<ipc::StructuredCloneData>&& aStack, ErrorResult& aRv) { + if (NS_WARN_IF(!CanSend() || !mManager || !mManager->GetCanSend())) { + aRv.ThrowInvalidStateError("JSProcessActorChild cannot send at the moment"); + return; + } + + // If the parent side is in the same process, we have a PInProcess manager, + // and can dispatch the message directly to the event loop. + ContentChild* contentChild = mManager->AsContentChild(); + if (!contentChild) { + SendRawMessageInProcess(aMeta, std::move(aData), std::move(aStack), []() { + return do_AddRef(InProcessParent::Singleton()); + }); + return; + } + + // Cross-process case - send data over ContentChild to other side. + Maybe<ClonedMessageData> msgData; + if (aData) { + msgData.emplace(); + if (NS_WARN_IF(!aData->BuildClonedMessageData(*msgData))) { + aRv.ThrowDataCloneError( + nsPrintfCString("JSProcessActorChild serialization error: cannot " + "clone, in actor '%s'", + PromiseFlatCString(aMeta.actorName()).get())); + return; + } + } + + Maybe<ClonedMessageData> stackData; + if (aStack) { + stackData.emplace(); + if (!aStack->BuildClonedMessageData(*stackData)) { + stackData.reset(); + } + } + + if (NS_WARN_IF(!contentChild->SendRawMessage(aMeta, msgData, stackData))) { + aRv.ThrowOperationError( + nsPrintfCString("JSProcessActorChild send error in actor '%s'", + PromiseFlatCString(aMeta.actorName()).get())); + return; + } +} + +void JSProcessActorChild::Init(const nsACString& aName, + nsIDOMProcessChild* aManager) { + MOZ_ASSERT(!mManager, "Cannot Init() a JSProcessActorChild twice!"); + SetName(aName); + mManager = aManager; + + InvokeCallback(CallbackFunction::ActorCreated); +} + +void JSProcessActorChild::ClearManager() { mManager = nullptr; } + +} // namespace mozilla::dom diff --git a/dom/ipc/jsactor/JSProcessActorChild.h b/dom/ipc/jsactor/JSProcessActorChild.h new file mode 100644 index 0000000000..a8be65211c --- /dev/null +++ b/dom/ipc/jsactor/JSProcessActorChild.h @@ -0,0 +1,54 @@ +/* -*- 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_dom_JSProcessActorChild_h +#define mozilla_dom_JSProcessActorChild_h + +#include "mozilla/dom/JSActor.h" +#include "nsIDOMProcessChild.h" + +namespace mozilla::dom { + +// Placeholder implementation. +class JSProcessActorChild final : public JSActor { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(JSProcessActorChild, JSActor) + + explicit JSProcessActorChild(nsISupports* aGlobal = nullptr) + : JSActor(aGlobal) {} + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + static already_AddRefed<JSProcessActorChild> Constructor( + GlobalObject& aGlobal) { + return MakeAndAddRef<JSProcessActorChild>(aGlobal.GetAsSupports()); + } + + nsIDOMProcessChild* Manager() const { return mManager; } + + void Init(const nsACString& aName, nsIDOMProcessChild* aManager); + void ClearManager() override; + + protected: + // Send the message described by the structured clone data |aData|, and the + // message metadata |aMetadata|. The underlying transport should call the + // |ReceiveMessage| method on the other side asynchronously. + virtual void SendRawMessage(const JSActorMessageMeta& aMetadata, + Maybe<ipc::StructuredCloneData>&& aData, + Maybe<ipc::StructuredCloneData>&& aStack, + ErrorResult& aRv) override; + + private: + ~JSProcessActorChild() { MOZ_ASSERT(!mManager); } + + nsCOMPtr<nsIDOMProcessChild> mManager; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_JSProcessActorChild_h diff --git a/dom/ipc/jsactor/JSProcessActorParent.cpp b/dom/ipc/jsactor/JSProcessActorParent.cpp new file mode 100644 index 0000000000..eec8ad17c7 --- /dev/null +++ b/dom/ipc/jsactor/JSProcessActorParent.cpp @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/dom/JSProcessActorBinding.h" +#include "mozilla/dom/JSProcessActorParent.h" +#include "mozilla/dom/InProcessChild.h" +#include "mozilla/dom/InProcessParent.h" + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_INHERITED(JSProcessActorParent, JSActor, mManager) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSProcessActorParent) +NS_INTERFACE_MAP_END_INHERITING(JSActor) + +NS_IMPL_ADDREF_INHERITED(JSProcessActorParent, JSActor) +NS_IMPL_RELEASE_INHERITED(JSProcessActorParent, JSActor) + +JSObject* JSProcessActorParent::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return JSProcessActorParent_Binding::Wrap(aCx, this, aGivenProto); +} + +void JSProcessActorParent::Init(const nsACString& aName, + nsIDOMProcessParent* aManager) { + MOZ_ASSERT(!mManager, "Cannot Init() a JSProcessActorParent twice!"); + SetName(aName); + mManager = aManager; + + InvokeCallback(CallbackFunction::ActorCreated); +} + +JSProcessActorParent::~JSProcessActorParent() { MOZ_ASSERT(!mManager); } + +void JSProcessActorParent::SendRawMessage( + const JSActorMessageMeta& aMeta, Maybe<ipc::StructuredCloneData>&& aData, + Maybe<ipc::StructuredCloneData>&& aStack, ErrorResult& aRv) { + if (NS_WARN_IF(!CanSend() || !mManager || !mManager->GetCanSend())) { + aRv.ThrowInvalidStateError( + nsPrintfCString("Actor '%s' cannot send message '%s' during shutdown.", + PromiseFlatCString(aMeta.actorName()).get(), + NS_ConvertUTF16toUTF8(aMeta.messageName()).get())); + return; + } + + // If the parent side is in the same process, we have a PInProcess manager, + // and can dispatch the message directly to the event loop. + ContentParent* contentParent = mManager->AsContentParent(); + if (!contentParent) { + SendRawMessageInProcess(aMeta, std::move(aData), std::move(aStack), []() { + return do_AddRef(InProcessChild::Singleton()); + }); + return; + } + + // Cross-process case - send data over ContentParent to other side. + Maybe<ClonedMessageData> msgData; + if (aData) { + msgData.emplace(); + if (NS_WARN_IF(!aData->BuildClonedMessageData(*msgData))) { + aRv.ThrowDataCloneError( + nsPrintfCString("Actor '%s' cannot send message '%s': cannot clone.", + PromiseFlatCString(aMeta.actorName()).get(), + NS_ConvertUTF16toUTF8(aMeta.messageName()).get())); + return; + } + } + + Maybe<ClonedMessageData> stackData; + if (aStack) { + stackData.emplace(); + if (!aStack->BuildClonedMessageData(*stackData)) { + stackData.reset(); + } + } + + if (NS_WARN_IF(!contentParent->SendRawMessage(aMeta, msgData, stackData))) { + aRv.ThrowOperationError( + nsPrintfCString("JSProcessActorParent send error in actor '%s'", + PromiseFlatCString(aMeta.actorName()).get())); + return; + } +} + +void JSProcessActorParent::ClearManager() { mManager = nullptr; } + +} // namespace mozilla::dom diff --git a/dom/ipc/jsactor/JSProcessActorParent.h b/dom/ipc/jsactor/JSProcessActorParent.h new file mode 100644 index 0000000000..7b2a1535f9 --- /dev/null +++ b/dom/ipc/jsactor/JSProcessActorParent.h @@ -0,0 +1,63 @@ +/* -*- 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_dom_JSProcessActorParent_h +#define mozilla_dom_JSProcessActorParent_h + +#include "js/TypeDecls.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/JSActor.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIDOMProcessParent.h" +#include "nsWrapperCache.h" + +namespace mozilla { +class ErrorResult; + +namespace dom { + +class JSProcessActorParent final : public JSActor { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(JSProcessActorParent, JSActor) + + explicit JSProcessActorParent(nsISupports* aGlobal = nullptr) + : JSActor(aGlobal) {} + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + static already_AddRefed<JSProcessActorParent> Constructor( + GlobalObject& aGlobal) { + return MakeAndAddRef<JSProcessActorParent>(aGlobal.GetAsSupports()); + } + + nsIDOMProcessParent* Manager() const { return mManager; } + + void Init(const nsACString& aName, nsIDOMProcessParent* aManager); + void ClearManager() override; + + protected: + // Send the message described by the structured clone data |aData|, and the + // message metadata |aMetadata|. The underlying transport should call the + // |ReceiveMessage| method on the other side asynchronously. + virtual void SendRawMessage(const JSActorMessageMeta& aMetadata, + Maybe<ipc::StructuredCloneData>&& aData, + Maybe<ipc::StructuredCloneData>&& aStack, + ErrorResult& aRv) override; + + private: + ~JSProcessActorParent(); + + nsCOMPtr<nsIDOMProcessParent> mManager; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_JSProcessActorParent_h diff --git a/dom/ipc/jsactor/JSProcessActorProtocol.cpp b/dom/ipc/jsactor/JSProcessActorProtocol.cpp new file mode 100644 index 0000000000..e973cdec44 --- /dev/null +++ b/dom/ipc/jsactor/JSProcessActorProtocol.cpp @@ -0,0 +1,149 @@ +/* -*- 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 "mozilla/dom/JSProcessActorProtocol.h" +#include "mozilla/dom/InProcessChild.h" +#include "mozilla/dom/JSProcessActorBinding.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/JSActorBinding.h" +#include "mozilla/dom/PContent.h" + +#include "nsContentUtils.h" +#include "JSActorProtocolUtils.h" + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTING_ADDREF(JSProcessActorProtocol) +NS_IMPL_CYCLE_COLLECTING_RELEASE(JSProcessActorProtocol) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSProcessActorProtocol) + NS_INTERFACE_MAP_ENTRY(nsIObserver) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION(JSProcessActorProtocol) + +/* static */ already_AddRefed<JSProcessActorProtocol> +JSProcessActorProtocol::FromIPC(const JSProcessActorInfo& aInfo) { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess()); + + RefPtr<JSProcessActorProtocol> proto = + new JSProcessActorProtocol(aInfo.name()); + JSActorProtocolUtils::FromIPCShared(proto, aInfo); + + // Content processes aren't the parent process, so this flag is irrelevant and + // not propagated. + proto->mIncludeParent = false; + + return proto.forget(); +} + +JSProcessActorInfo JSProcessActorProtocol::ToIPC() { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + + JSProcessActorInfo info; + JSActorProtocolUtils::ToIPCShared(info, this); + + return info; +} + +already_AddRefed<JSProcessActorProtocol> +JSProcessActorProtocol::FromWebIDLOptions(const nsACString& aName, + const ProcessActorOptions& aOptions, + ErrorResult& aRv) { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + + RefPtr<JSProcessActorProtocol> proto = new JSProcessActorProtocol(aName); + if (!JSActorProtocolUtils::FromWebIDLOptionsShared(proto, aOptions, aRv)) { + return nullptr; + } + + proto->mIncludeParent = aOptions.mIncludeParent; + + proto->mLoadInDevToolsLoader = aOptions.mLoadInDevToolsLoader; + + return proto.forget(); +} + +NS_IMETHODIMP JSProcessActorProtocol::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) { + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); + + RefPtr<JSActorManager> manager; + if (XRE_IsParentProcess()) { + manager = InProcessChild::Singleton(); + } else { + manager = ContentChild::GetSingleton(); + } + + // Ensure our actor is present. + AutoJSAPI jsapi; + jsapi.Init(); + RefPtr<JSActor> actor = manager->GetActor(jsapi.cx(), mName, IgnoreErrors()); + if (!actor || NS_WARN_IF(!actor->GetWrapperPreserveColor())) { + return NS_OK; + } + + // Build a observer callback. + JS::Rooted<JSObject*> global(jsapi.cx(), + JS::GetNonCCWObjectGlobal(actor->GetWrapper())); + RefPtr<MozObserverCallback> observerCallback = + new MozObserverCallback(actor->GetWrapper(), global, nullptr, nullptr); + observerCallback->Observe(aSubject, nsDependentCString(aTopic), + aData ? nsDependentString(aData) : VoidString()); + return NS_OK; +} + +void JSProcessActorProtocol::AddObservers() { + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + for (auto& topic : mChild.mObservers) { + // This makes the observer service hold an owning reference to the + // JSProcessActorProtocol. The JSWindowActorProtocol objects will be living + // for the full lifetime of the content process, thus the extra strong + // referencec doesn't have a negative impact. + os->AddObserver(this, topic.get(), false); + } +} + +void JSProcessActorProtocol::RemoveObservers() { + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + for (auto& topic : mChild.mObservers) { + os->RemoveObserver(this, topic.get()); + } +} + +bool JSProcessActorProtocol::RemoteTypePrefixMatches( + const nsDependentCSubstring& aRemoteType) { + for (auto& remoteType : mRemoteTypes) { + if (StringBeginsWith(aRemoteType, remoteType)) { + return true; + } + } + return false; +} + +bool JSProcessActorProtocol::Matches(const nsACString& aRemoteType, + ErrorResult& aRv) { + if (!mIncludeParent && aRemoteType.IsEmpty()) { + aRv.ThrowNotSupportedError(nsPrintfCString( + "Process protocol '%s' doesn't match the parent process", mName.get())); + return false; + } + + if (!mRemoteTypes.IsEmpty() && + !RemoteTypePrefixMatches(RemoteTypePrefix(aRemoteType))) { + aRv.ThrowNotSupportedError(nsPrintfCString( + "Process protocol '%s' doesn't support remote type '%s'", mName.get(), + PromiseFlatCString(aRemoteType).get())); + return false; + } + + return true; +} + +} // namespace mozilla::dom diff --git a/dom/ipc/jsactor/JSProcessActorProtocol.h b/dom/ipc/jsactor/JSProcessActorProtocol.h new file mode 100644 index 0000000000..eab8876c35 --- /dev/null +++ b/dom/ipc/jsactor/JSProcessActorProtocol.h @@ -0,0 +1,79 @@ +/* -*- 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_dom_JSProcessActorProtocol_h +#define mozilla_dom_JSProcessActorProtocol_h + +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/JSActorService.h" +#include "nsIURI.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsIObserver.h" + +namespace mozilla { +class ErrorResult; + +namespace dom { + +struct ProcessActorOptions; +class JSProcessActorInfo; +class EventTarget; +class JSActorProtocolUtils; + +/** + * Object corresponding to a single process actor protocol + * + * This object also can act as a carrier for methods and other state related to + * a single protocol managed by the JSActorService. + */ +class JSProcessActorProtocol final : public JSActorProtocol, + public nsIObserver { + public: + NS_DECL_NSIOBSERVER + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(JSProcessActorProtocol, nsIObserver) + + static already_AddRefed<JSProcessActorProtocol> FromIPC( + const JSProcessActorInfo& aInfo); + JSProcessActorInfo ToIPC(); + + static already_AddRefed<JSProcessActorProtocol> FromWebIDLOptions( + const nsACString& aName, const ProcessActorOptions& aOptions, + ErrorResult& aRv); + + struct ParentSide : public Sided {}; + + struct ChildSide : public Sided { + nsTArray<nsCString> mObservers; + }; + + const ParentSide& Parent() const override { return mParent; } + const ChildSide& Child() const override { return mChild; } + + void AddObservers(); + void RemoveObservers(); + bool Matches(const nsACString& aRemoteType, ErrorResult& aRv); + + private: + explicit JSProcessActorProtocol(const nsACString& aName) : mName(aName) {} + bool RemoteTypePrefixMatches(const nsDependentCSubstring& aRemoteType); + ~JSProcessActorProtocol() = default; + + nsCString mName; + nsTArray<nsCString> mRemoteTypes; + bool mIncludeParent = false; + + friend class JSActorProtocolUtils; + + ParentSide mParent; + ChildSide mChild; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_JSProcessActorProtocol_h diff --git a/dom/ipc/jsactor/JSWindowActorChild.cpp b/dom/ipc/jsactor/JSWindowActorChild.cpp new file mode 100644 index 0000000000..4d028a0789 --- /dev/null +++ b/dom/ipc/jsactor/JSWindowActorChild.cpp @@ -0,0 +1,160 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/dom/JSWindowActorBinding.h" +#include "mozilla/dom/JSWindowActorChild.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/WindowGlobalChild.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/dom/WindowProxyHolder.h" +#include "mozilla/dom/MessageManagerBinding.h" +#include "mozilla/dom/BrowsingContext.h" +#include "nsGlobalWindowInner.h" + +namespace mozilla::dom { + +JSWindowActorChild::~JSWindowActorChild() { MOZ_ASSERT(!mManager); } + +JSObject* JSWindowActorChild::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return JSWindowActorChild_Binding::Wrap(aCx, this, aGivenProto); +} + +WindowGlobalChild* JSWindowActorChild::GetManager() const { return mManager; } + +WindowContext* JSWindowActorChild::GetWindowContext() const { + return mManager ? mManager->WindowContext() : nullptr; +} + +void JSWindowActorChild::Init(const nsACString& aName, + WindowGlobalChild* aManager) { + MOZ_ASSERT(!mManager, "Cannot Init() a JSWindowActorChild twice!"); + SetName(aName); + mManager = aManager; + + InvokeCallback(CallbackFunction::ActorCreated); +} + +#ifdef DEBUG +# define DEBUG_WARN_MESSAGE_UNSENT(aMeta, aWarning) \ + NS_DebugBreak( \ + NS_DEBUG_WARNING, \ + nsPrintfCString( \ + "JSWindowActorChild::SendRawMessage (%s, %s) not sent: %s", \ + (aMeta).actorName().get(), \ + NS_LossyConvertUTF16toASCII((aMeta).messageName()).get(), \ + (aWarning)) \ + .get(), \ + nullptr, __FILE__, __LINE__) +#else +# define DEBUG_WARN_MESSAGE_UNSENT(aMeta, aWarning) +#endif + +void JSWindowActorChild::SendRawMessage( + const JSActorMessageMeta& aMeta, Maybe<ipc::StructuredCloneData>&& aData, + Maybe<ipc::StructuredCloneData>&& aStack, ErrorResult& aRv) { + if (!CanSend() || !mManager || !mManager->CanSend()) { + DEBUG_WARN_MESSAGE_UNSENT( + aMeta, "!CanSend() || !mManager || !mManager->CanSend()"); + aRv.ThrowInvalidStateError("JSWindowActorChild cannot send at the moment"); + return; + } + + if (mManager->IsInProcess()) { + SendRawMessageInProcess( + aMeta, std::move(aData), std::move(aStack), + [manager{mManager}]() { return manager->GetParentActor(); }); + return; + } + + // Cross-process case - send data over WindowGlobalChild to other side. + Maybe<ClonedMessageData> msgData; + if (aData) { + msgData.emplace(); + if (!aData->BuildClonedMessageData(*msgData)) { + DEBUG_WARN_MESSAGE_UNSENT(aMeta, + "!aData->BuildClonedMessageData(*msgData)"); + aRv.ThrowDataCloneError( + nsPrintfCString("JSWindowActorChild serialization error: cannot " + "clone, in actor '%s'", + PromiseFlatCString(aMeta.actorName()).get())); + return; + } + } + + Maybe<ClonedMessageData> stackData; + if (aStack) { + stackData.emplace(); + if (!aStack->BuildClonedMessageData(*stackData)) { + stackData.reset(); + } + } + + if (!mManager->SendRawMessage(aMeta, msgData, stackData)) { + DEBUG_WARN_MESSAGE_UNSENT( + aMeta, "!mManager->SendRawMessage(aMeta, msgData, stackData)"); + aRv.ThrowOperationError( + nsPrintfCString("JSWindowActorChild send error in actor '%s'", + PromiseFlatCString(aMeta.actorName()).get())); + return; + } +} + +Document* JSWindowActorChild::GetDocument(ErrorResult& aRv) { + if (!mManager) { + ThrowStateErrorForGetter("document", aRv); + return nullptr; + } + + nsGlobalWindowInner* window = mManager->GetWindowGlobal(); + return window ? window->GetDocument() : nullptr; +} + +BrowsingContext* JSWindowActorChild::GetBrowsingContext(ErrorResult& aRv) { + if (!mManager) { + ThrowStateErrorForGetter("browsingContext", aRv); + return nullptr; + } + + return mManager->BrowsingContext(); +} + +nsIDocShell* JSWindowActorChild::GetDocShell(ErrorResult& aRv) { + if (!mManager) { + ThrowStateErrorForGetter("docShell", aRv); + return nullptr; + } + + return mManager->BrowsingContext()->GetDocShell(); +} + +Nullable<WindowProxyHolder> JSWindowActorChild::GetContentWindow( + ErrorResult& aRv) { + if (!mManager) { + ThrowStateErrorForGetter("contentWindow", aRv); + return nullptr; + } + + if (nsGlobalWindowInner* window = mManager->GetWindowGlobal()) { + if (window->IsCurrentInnerWindow()) { + return WindowProxyHolder(window->GetBrowsingContext()); + } + } + + return nullptr; +} + +void JSWindowActorChild::ClearManager() { mManager = nullptr; } + +NS_IMPL_CYCLE_COLLECTION_INHERITED(JSWindowActorChild, JSActor, mManager) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSWindowActorChild) +NS_INTERFACE_MAP_END_INHERITING(JSActor) + +NS_IMPL_ADDREF_INHERITED(JSWindowActorChild, JSActor) +NS_IMPL_RELEASE_INHERITED(JSWindowActorChild, JSActor) + +} // namespace mozilla::dom diff --git a/dom/ipc/jsactor/JSWindowActorChild.h b/dom/ipc/jsactor/JSWindowActorChild.h new file mode 100644 index 0000000000..8e43d6a6c7 --- /dev/null +++ b/dom/ipc/jsactor/JSWindowActorChild.h @@ -0,0 +1,82 @@ +/* -*- 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_dom_JSWindowActorChild_h +#define mozilla_dom_JSWindowActorChild_h + +#include "js/RootingAPI.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/RefPtr.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/JSActor.h" +#include "mozilla/dom/WindowGlobalChild.h" +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIGlobalObject.h" +#include "nsISupports.h" +#include "nsStringFwd.h" + +class nsIDocShell; + +namespace mozilla { +class ErrorResult; + +namespace dom { + +template <typename> +struct Nullable; + +class BrowsingContext; +class Document; +class WindowProxyHolder; + +} // namespace dom +} // namespace mozilla + +namespace mozilla::dom { + +class JSWindowActorChild final : public JSActor { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(JSWindowActorChild, JSActor) + + explicit JSWindowActorChild(nsISupports* aGlobal = nullptr) + : JSActor(aGlobal) {} + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + static already_AddRefed<JSWindowActorChild> Constructor( + GlobalObject& aGlobal) { + return MakeAndAddRef<JSWindowActorChild>(aGlobal.GetAsSupports()); + } + + WindowGlobalChild* GetManager() const; + WindowContext* GetWindowContext() const; + void Init(const nsACString& aName, WindowGlobalChild* aManager); + void ClearManager() override; + Document* GetDocument(ErrorResult& aRv); + BrowsingContext* GetBrowsingContext(ErrorResult& aRv); + nsIDocShell* GetDocShell(ErrorResult& aRv); + Nullable<WindowProxyHolder> GetContentWindow(ErrorResult& aRv); + + protected: + void SendRawMessage(const JSActorMessageMeta& aMeta, + Maybe<ipc::StructuredCloneData>&& aData, + Maybe<ipc::StructuredCloneData>&& aStack, + ErrorResult& aRv) override; + + private: + ~JSWindowActorChild(); + + RefPtr<WindowGlobalChild> mManager; + + nsCOMPtr<nsIGlobalObject> mGlobal; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_JSWindowActorChild_h diff --git a/dom/ipc/jsactor/JSWindowActorParent.cpp b/dom/ipc/jsactor/JSWindowActorParent.cpp new file mode 100644 index 0000000000..0bb82a9b8e --- /dev/null +++ b/dom/ipc/jsactor/JSWindowActorParent.cpp @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/dom/JSWindowActorBinding.h" +#include "mozilla/dom/JSWindowActorParent.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/WindowGlobalChild.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/dom/MessageManagerBinding.h" + +namespace mozilla::dom { + +JSWindowActorParent::~JSWindowActorParent() { MOZ_ASSERT(!mManager); } + +JSObject* JSWindowActorParent::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return JSWindowActorParent_Binding::Wrap(aCx, this, aGivenProto); +} + +WindowGlobalParent* JSWindowActorParent::GetManager() const { return mManager; } + +WindowContext* JSWindowActorParent::GetWindowContext() const { + return mManager; +} + +void JSWindowActorParent::Init(const nsACString& aName, + WindowGlobalParent* aManager) { + MOZ_ASSERT(!mManager, "Cannot Init() a JSWindowActorParent twice!"); + SetName(aName); + mManager = aManager; + + InvokeCallback(CallbackFunction::ActorCreated); +} + +void JSWindowActorParent::SendRawMessage( + const JSActorMessageMeta& aMeta, Maybe<ipc::StructuredCloneData>&& aData, + Maybe<ipc::StructuredCloneData>&& aStack, ErrorResult& aRv) { + if (NS_WARN_IF(!CanSend() || !mManager || !mManager->CanSend())) { + aRv.ThrowInvalidStateError("JSWindowActorParent cannot send at the moment"); + return; + } + + if (mManager->IsInProcess()) { + SendRawMessageInProcess( + aMeta, std::move(aData), std::move(aStack), + [manager{mManager}]() { return manager->GetChildActor(); }); + return; + } + + Maybe<ClonedMessageData> msgData; + if (aData) { + msgData.emplace(); + if (NS_WARN_IF(!aData->BuildClonedMessageData(*msgData))) { + aRv.ThrowDataCloneError( + nsPrintfCString("JSWindowActorParent serialization error: cannot " + "clone, in actor '%s'", + PromiseFlatCString(aMeta.actorName()).get())); + return; + } + } + + Maybe<ClonedMessageData> stackData; + if (aStack) { + stackData.emplace(); + if (!aStack->BuildClonedMessageData(*stackData)) { + stackData.reset(); + } + } + + if (NS_WARN_IF(!mManager->SendRawMessage(aMeta, msgData, stackData))) { + aRv.ThrowOperationError( + nsPrintfCString("JSWindowActorParent send error in actor '%s'", + PromiseFlatCString(aMeta.actorName()).get())); + return; + } +} + +CanonicalBrowsingContext* JSWindowActorParent::GetBrowsingContext( + ErrorResult& aRv) { + if (!mManager) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + + return mManager->BrowsingContext(); +} + +void JSWindowActorParent::ClearManager() { mManager = nullptr; } + +NS_IMPL_CYCLE_COLLECTION_INHERITED(JSWindowActorParent, JSActor, mManager) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSWindowActorParent) +NS_INTERFACE_MAP_END_INHERITING(JSActor) + +NS_IMPL_ADDREF_INHERITED(JSWindowActorParent, JSActor) +NS_IMPL_RELEASE_INHERITED(JSWindowActorParent, JSActor) + +} // namespace mozilla::dom diff --git a/dom/ipc/jsactor/JSWindowActorParent.h b/dom/ipc/jsactor/JSWindowActorParent.h new file mode 100644 index 0000000000..05fb88f8ac --- /dev/null +++ b/dom/ipc/jsactor/JSWindowActorParent.h @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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_dom_JSWindowActorParent_h +#define mozilla_dom_JSWindowActorParent_h + +#include "js/TypeDecls.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/JSActor.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" + +namespace mozilla { +class ErrorResult; + +namespace dom { + +class WindowGlobalParent; + +} // namespace dom +} // namespace mozilla + +namespace mozilla::dom { + +class JSWindowActorParent final : public JSActor { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(JSWindowActorParent, JSActor) + + explicit JSWindowActorParent(nsISupports* aGlobal = nullptr) + : JSActor(aGlobal) {} + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + static already_AddRefed<JSWindowActorParent> Constructor( + GlobalObject& aGlobal) { + return MakeAndAddRef<JSWindowActorParent>(aGlobal.GetAsSupports()); + } + + WindowGlobalParent* GetManager() const; + WindowContext* GetWindowContext() const; + void Init(const nsACString& aName, WindowGlobalParent* aManager); + void ClearManager() override; + CanonicalBrowsingContext* GetBrowsingContext(ErrorResult& aRv); + + protected: + void SendRawMessage(const JSActorMessageMeta& aMeta, + Maybe<ipc::StructuredCloneData>&& aData, + Maybe<ipc::StructuredCloneData>&& aStack, + ErrorResult& aRv) override; + + private: + ~JSWindowActorParent(); + + RefPtr<WindowGlobalParent> mManager; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_JSWindowActorParent_h diff --git a/dom/ipc/jsactor/JSWindowActorProtocol.cpp b/dom/ipc/jsactor/JSWindowActorProtocol.cpp new file mode 100644 index 0000000000..a459d94204 --- /dev/null +++ b/dom/ipc/jsactor/JSWindowActorProtocol.cpp @@ -0,0 +1,380 @@ +/* -*- 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 "mozilla/dom/ContentParent.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/JSActorBinding.h" +#include "mozilla/dom/JSActorService.h" +#include "mozilla/dom/JSWindowActorBinding.h" +#include "mozilla/dom/JSWindowActorChild.h" +#include "mozilla/dom/JSWindowActorProtocol.h" +#include "mozilla/dom/PContent.h" +#include "mozilla/dom/WindowGlobalChild.h" + +#include "mozilla/extensions/MatchPattern.h" +#include "nsContentUtils.h" +#include "JSActorProtocolUtils.h" + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTING_ADDREF(JSWindowActorProtocol) +NS_IMPL_CYCLE_COLLECTING_RELEASE(JSWindowActorProtocol) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSWindowActorProtocol) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION(JSWindowActorProtocol) + +/* static */ already_AddRefed<JSWindowActorProtocol> +JSWindowActorProtocol::FromIPC(const JSWindowActorInfo& aInfo) { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess()); + + RefPtr<JSWindowActorProtocol> proto = new JSWindowActorProtocol(aInfo.name()); + JSActorProtocolUtils::FromIPCShared(proto, aInfo); + + // Content processes cannot load chrome browsing contexts, so this flag is + // irrelevant and not propagated. + proto->mIncludeChrome = false; + proto->mAllFrames = aInfo.allFrames(); + proto->mMatches = aInfo.matches().Clone(); + proto->mMessageManagerGroups = aInfo.messageManagerGroups().Clone(); + + proto->mChild.mEvents.SetCapacity(aInfo.events().Length()); + for (auto& ipc : aInfo.events()) { + auto event = proto->mChild.mEvents.AppendElement(); + event->mName.Assign(ipc.name()); + event->mFlags.mCapture = ipc.capture(); + event->mFlags.mInSystemGroup = ipc.systemGroup(); + event->mFlags.mAllowUntrustedEvents = ipc.allowUntrusted(); + if (ipc.passive()) { + event->mPassive.Construct(ipc.passive().value()); + } + event->mCreateActor = ipc.createActor(); + } + + return proto.forget(); +} + +JSWindowActorInfo JSWindowActorProtocol::ToIPC() { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + + JSWindowActorInfo info; + JSActorProtocolUtils::ToIPCShared(info, this); + + info.allFrames() = mAllFrames; + info.matches() = mMatches.Clone(); + info.messageManagerGroups() = mMessageManagerGroups.Clone(); + + info.events().SetCapacity(mChild.mEvents.Length()); + for (auto& event : mChild.mEvents) { + auto ipc = info.events().AppendElement(); + ipc->name().Assign(event.mName); + ipc->capture() = event.mFlags.mCapture; + ipc->systemGroup() = event.mFlags.mInSystemGroup; + ipc->allowUntrusted() = event.mFlags.mAllowUntrustedEvents; + if (event.mPassive.WasPassed()) { + ipc->passive() = Some(event.mPassive.Value()); + } + ipc->createActor() = event.mCreateActor; + } + + return info; +} + +already_AddRefed<JSWindowActorProtocol> +JSWindowActorProtocol::FromWebIDLOptions(const nsACString& aName, + const WindowActorOptions& aOptions, + ErrorResult& aRv) { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + + RefPtr<JSWindowActorProtocol> proto = new JSWindowActorProtocol(aName); + if (!JSActorProtocolUtils::FromWebIDLOptionsShared(proto, aOptions, aRv)) { + return nullptr; + } + + proto->mAllFrames = aOptions.mAllFrames; + proto->mIncludeChrome = aOptions.mIncludeChrome; + + if (aOptions.mMatches.WasPassed()) { + MOZ_ASSERT(aOptions.mMatches.Value().Length()); + proto->mMatches = aOptions.mMatches.Value(); + } + + if (aOptions.mMessageManagerGroups.WasPassed()) { + proto->mMessageManagerGroups = aOptions.mMessageManagerGroups.Value(); + } + + // For each event declared in the source dictionary, initialize the + // corresponding event declaration entry in the JSWindowActorProtocol. + if (aOptions.mChild.WasPassed() && + aOptions.mChild.Value().mEvents.WasPassed()) { + auto& entries = aOptions.mChild.Value().mEvents.Value().Entries(); + proto->mChild.mEvents.SetCapacity(entries.Length()); + + for (auto& entry : entries) { + // We don't support the mOnce field, as it doesn't work well in this + // environment. For now, throw an error in that case. + if (entry.mValue.mOnce) { + aRv.ThrowNotSupportedError("mOnce is not supported"); + return nullptr; + } + + // Add the EventDecl to our list of events. + EventDecl* evt = proto->mChild.mEvents.AppendElement(); + evt->mName = entry.mKey; + evt->mFlags.mCapture = entry.mValue.mCapture; + evt->mFlags.mInSystemGroup = entry.mValue.mMozSystemGroup; + evt->mFlags.mAllowUntrustedEvents = + entry.mValue.mWantUntrusted.WasPassed() + ? entry.mValue.mWantUntrusted.Value() + : false; + if (entry.mValue.mPassive.WasPassed()) { + evt->mPassive.Construct(entry.mValue.mPassive.Value()); + } + evt->mCreateActor = entry.mValue.mCreateActor; + } + } + + return proto.forget(); +} + +/** + * This listener only listens for events for the child side of the protocol. + * This will work in both content and parent processes. + */ +NS_IMETHODIMP JSWindowActorProtocol::HandleEvent(Event* aEvent) { + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); + + // Determine which inner window we're associated with, and get its + // WindowGlobalChild actor. + EventTarget* target = aEvent->GetOriginalTarget(); + if (NS_WARN_IF(!target)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsPIDOMWindowInner> inner = + do_QueryInterface(target->GetOwnerGlobal()); + if (NS_WARN_IF(!inner)) { + return NS_ERROR_FAILURE; + } + + RefPtr<WindowGlobalChild> wgc = inner->GetWindowGlobalChild(); + if (NS_WARN_IF(!wgc)) { + return NS_ERROR_FAILURE; + } + + if (aEvent->ShouldIgnoreChromeEventTargetListener()) { + return NS_OK; + } + + // Ensure our actor is present. + RefPtr<JSActor> actor = wgc->GetExistingActor(mName); + if (!actor) { + // Check if we're supposed to create the actor when this event is fired. + bool createActor = true; + nsAutoString typeStr; + aEvent->GetType(typeStr); + for (auto& event : mChild.mEvents) { + if (event.mName == typeStr) { + createActor = event.mCreateActor; + break; + } + } + + // If we're supposed to create the actor, call GetActor to cause it to be + // created. + if (createActor) { + AutoJSAPI jsapi; + jsapi.Init(); + actor = wgc->GetActor(jsapi.cx(), mName, IgnoreErrors()); + } + } + if (!actor || NS_WARN_IF(!actor->GetWrapperPreserveColor())) { + return NS_OK; + } + + // Build our event listener & call it. + JS::Rooted<JSObject*> global(RootingCx(), + JS::GetNonCCWObjectGlobal(actor->GetWrapper())); + RefPtr<EventListener> eventListener = + new EventListener(actor->GetWrapper(), global, nullptr, nullptr); + eventListener->HandleEvent(*aEvent, "JSWindowActorProtocol::HandleEvent"); + return NS_OK; +} + +NS_IMETHODIMP JSWindowActorProtocol::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) { + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); + + nsCOMPtr<nsPIDOMWindowInner> inner = do_QueryInterface(aSubject); + RefPtr<WindowGlobalChild> wgc; + + if (!inner) { + nsCOMPtr<nsPIDOMWindowOuter> outer = do_QueryInterface(aSubject); + if (NS_WARN_IF(!outer)) { + nsContentUtils::LogSimpleConsoleError( + NS_ConvertUTF8toUTF16(nsPrintfCString( + "JSWindowActor %s: expected window subject for topic '%s'.", + mName.get(), aTopic)), + "JSActor"_ns, + /* aFromPrivateWindow */ false, + /* aFromChromeContext */ true); + return NS_ERROR_FAILURE; + } + if (NS_WARN_IF(!outer->GetCurrentInnerWindow())) { + return NS_ERROR_FAILURE; + } + wgc = outer->GetCurrentInnerWindow()->GetWindowGlobalChild(); + } else { + wgc = inner->GetWindowGlobalChild(); + } + + if (NS_WARN_IF(!wgc)) { + return NS_ERROR_FAILURE; + } + + // Ensure our actor is present. + AutoJSAPI jsapi; + jsapi.Init(); + RefPtr<JSActor> actor = wgc->GetActor(jsapi.cx(), mName, IgnoreErrors()); + if (!actor || NS_WARN_IF(!actor->GetWrapperPreserveColor())) { + return NS_OK; + } + + // Build a observer callback. + JS::Rooted<JSObject*> global(jsapi.cx(), + JS::GetNonCCWObjectGlobal(actor->GetWrapper())); + RefPtr<MozObserverCallback> observerCallback = + new MozObserverCallback(actor->GetWrapper(), global, nullptr, nullptr); + observerCallback->Observe(aSubject, nsDependentCString(aTopic), + aData ? nsDependentString(aData) : VoidString()); + return NS_OK; +} + +void JSWindowActorProtocol::RegisterListenersFor(EventTarget* aTarget) { + EventListenerManager* elm = aTarget->GetOrCreateListenerManager(); + + for (auto& event : mChild.mEvents) { + elm->AddEventListenerByType(EventListenerHolder(this), event.mName, + event.mFlags, event.mPassive); + } +} + +void JSWindowActorProtocol::UnregisterListenersFor(EventTarget* aTarget) { + EventListenerManager* elm = aTarget->GetOrCreateListenerManager(); + + for (auto& event : mChild.mEvents) { + elm->RemoveEventListenerByType(EventListenerHolder(this), event.mName, + event.mFlags); + } +} + +void JSWindowActorProtocol::AddObservers() { + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + for (auto& topic : mChild.mObservers) { + // This makes the observer service hold an owning reference to the + // JSWindowActorProtocol. The JSWindowActorProtocol objects will be living + // for the full lifetime of the content process, thus the extra strong + // referencec doesn't have a negative impact. + os->AddObserver(this, topic.get(), false); + } +} + +void JSWindowActorProtocol::RemoveObservers() { + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + for (auto& topic : mChild.mObservers) { + os->RemoveObserver(this, topic.get()); + } +} + +extensions::MatchPatternSetCore* JSWindowActorProtocol::GetURIMatcher() { + // If we've already created the pattern set, return it. + if (mURIMatcher || mMatches.IsEmpty()) { + return mURIMatcher; + } + + nsTArray<RefPtr<extensions::MatchPatternCore>> patterns(mMatches.Length()); + for (const nsString& pattern : mMatches) { + patterns.AppendElement(new extensions::MatchPatternCore( + pattern, false, false, IgnoreErrors())); + } + mURIMatcher = new extensions::MatchPatternSetCore(std::move(patterns)); + return mURIMatcher; +} + +bool JSWindowActorProtocol::RemoteTypePrefixMatches( + const nsDependentCSubstring& aRemoteType) { + for (auto& remoteType : mRemoteTypes) { + if (StringBeginsWith(aRemoteType, remoteType)) { + return true; + } + } + return false; +} + +bool JSWindowActorProtocol::MessageManagerGroupMatches( + BrowsingContext* aBrowsingContext) { + BrowsingContext* top = aBrowsingContext->Top(); + for (auto& group : mMessageManagerGroups) { + if (group == top->GetMessageManagerGroup()) { + return true; + } + } + return false; +} + +bool JSWindowActorProtocol::Matches(BrowsingContext* aBrowsingContext, + nsIURI* aURI, const nsACString& aRemoteType, + ErrorResult& aRv) { + MOZ_ASSERT(aBrowsingContext, "DocShell without a BrowsingContext!"); + MOZ_ASSERT(aURI, "Must have URI!"); + + if (!mAllFrames && aBrowsingContext->GetParent()) { + aRv.ThrowNotSupportedError(nsPrintfCString( + "Window protocol '%s' doesn't match subframes", mName.get())); + return false; + } + + if (!mIncludeChrome && !aBrowsingContext->IsContent()) { + aRv.ThrowNotSupportedError(nsPrintfCString( + "Window protocol '%s' doesn't match chrome browsing contexts", + mName.get())); + return false; + } + + if (!mRemoteTypes.IsEmpty() && + !RemoteTypePrefixMatches(RemoteTypePrefix(aRemoteType))) { + aRv.ThrowNotSupportedError( + nsPrintfCString("Window protocol '%s' doesn't match remote type '%s'", + mName.get(), PromiseFlatCString(aRemoteType).get())); + return false; + } + + if (!mMessageManagerGroups.IsEmpty() && + !MessageManagerGroupMatches(aBrowsingContext)) { + aRv.ThrowNotSupportedError(nsPrintfCString( + "Window protocol '%s' doesn't match message manager group", + mName.get())); + return false; + } + + if (extensions::MatchPatternSetCore* uriMatcher = GetURIMatcher()) { + if (!uriMatcher->Matches(aURI)) { + aRv.ThrowNotSupportedError(nsPrintfCString( + "Window protocol '%s' doesn't match uri %s", mName.get(), + nsContentUtils::TruncatedURLForDisplay(aURI).get())); + return false; + } + } + + return true; +} + +} // namespace mozilla::dom diff --git a/dom/ipc/jsactor/JSWindowActorProtocol.h b/dom/ipc/jsactor/JSWindowActorProtocol.h new file mode 100644 index 0000000000..b0128d9307 --- /dev/null +++ b/dom/ipc/jsactor/JSWindowActorProtocol.h @@ -0,0 +1,103 @@ +/* -*- 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_dom_JSWindowActorProtocol_h +#define mozilla_dom_JSWindowActorProtocol_h + +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/JSActorService.h" +#include "mozilla/extensions/MatchPattern.h" +#include "nsIURI.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsIObserver.h" +#include "nsIDOMEventListener.h" + +namespace mozilla { +class ErrorResult; + +namespace dom { + +struct WindowActorOptions; +class JSWindowActorInfo; +class EventTarget; +class JSActorProtocolUtils; + +/** + * Object corresponding to a single window actor protocol. This object acts as + * an Event listener for the actor which is called for events which would + * trigger actor creation. + * + * This object also can act as a carrier for methods and other state related to + * a single protocol managed by the JSActorService. + */ +class JSWindowActorProtocol final : public JSActorProtocol, + public nsIObserver, + public nsIDOMEventListener { + public: + NS_DECL_NSIOBSERVER + NS_DECL_NSIDOMEVENTLISTENER + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(JSWindowActorProtocol, nsIObserver) + + static already_AddRefed<JSWindowActorProtocol> FromIPC( + const JSWindowActorInfo& aInfo); + JSWindowActorInfo ToIPC(); + + static already_AddRefed<JSWindowActorProtocol> FromWebIDLOptions( + const nsACString& aName, const WindowActorOptions& aOptions, + ErrorResult& aRv); + + struct ParentSide : public Sided {}; + + struct EventDecl { + nsString mName; + EventListenerFlags mFlags; + Optional<bool> mPassive; + bool mCreateActor = true; + }; + + struct ChildSide : public Sided { + nsTArray<EventDecl> mEvents; + nsTArray<nsCString> mObservers; + }; + + const ParentSide& Parent() const override { return mParent; } + const ChildSide& Child() const override { return mChild; } + + void RegisterListenersFor(EventTarget* aTarget); + void UnregisterListenersFor(EventTarget* aTarget); + void AddObservers(); + void RemoveObservers(); + bool Matches(BrowsingContext* aBrowsingContext, nsIURI* aURI, + const nsACString& aRemoteType, ErrorResult& aRv); + + private: + explicit JSWindowActorProtocol(const nsACString& aName) : mName(aName) {} + extensions::MatchPatternSetCore* GetURIMatcher(); + bool RemoteTypePrefixMatches(const nsDependentCSubstring& aRemoteType); + bool MessageManagerGroupMatches(BrowsingContext* aBrowsingContext); + ~JSWindowActorProtocol() = default; + + nsCString mName; + bool mAllFrames = false; + bool mIncludeChrome = false; + nsTArray<nsString> mMatches; + nsTArray<nsCString> mRemoteTypes; + nsTArray<nsString> mMessageManagerGroups; + + friend class JSActorProtocolUtils; + + ParentSide mParent; + ChildSide mChild; + + RefPtr<extensions::MatchPatternSetCore> mURIMatcher; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_JSWindowActorProtocol_h diff --git a/dom/ipc/jsactor/moz.build b/dom/ipc/jsactor/moz.build new file mode 100644 index 0000000000..6bc66df923 --- /dev/null +++ b/dom/ipc/jsactor/moz.build @@ -0,0 +1,42 @@ +# -*- 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.dom += [ + "JSActor.h", + "JSActorManager.h", + "JSActorService.h", + "JSProcessActorChild.h", + "JSProcessActorParent.h", + "JSProcessActorProtocol.h", + "JSWindowActorChild.h", + "JSWindowActorParent.h", + "JSWindowActorProtocol.h", +] + +EXPORTS += [ + "nsQueryActor.h", +] + +UNIFIED_SOURCES += [ + "JSActor.cpp", + "JSActorManager.cpp", + "JSActorService.cpp", + "JSProcessActorChild.cpp", + "JSProcessActorParent.cpp", + "JSProcessActorProtocol.cpp", + "JSWindowActorChild.cpp", + "JSWindowActorParent.cpp", + "JSWindowActorProtocol.cpp", +] + +LOCAL_INCLUDES += [ + "/js/xpconnect/loader", + "/js/xpconnect/src", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" diff --git a/dom/ipc/jsactor/nsQueryActor.h b/dom/ipc/jsactor/nsQueryActor.h new file mode 100644 index 0000000000..cbbda7e473 --- /dev/null +++ b/dom/ipc/jsactor/nsQueryActor.h @@ -0,0 +1,90 @@ +/* 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 nsQueryActor_h +#define nsQueryActor_h + +#include <type_traits> + +#include "nsCOMPtr.h" +#include "nsPIDOMWindow.h" +#include "mozilla/RefPtr.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/JSActor.h" +#include "mozilla/dom/JSActorManager.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/WindowGlobalChild.h" + +class nsIDOMProcessChild; +class nsIDOMProcessParent; + +// This type is used to get an XPCOM interface implemented by a JSActor from its +// native manager. +class MOZ_STACK_CLASS nsQueryJSActor final : public nsCOMPtr_helper { + public: + nsQueryJSActor(const nsLiteralCString aActorName, + mozilla::dom::JSActorManager* aManager) + : mActorName(aActorName), mManager(aManager) {} + + nsresult NS_FASTCALL operator()(const nsIID& aIID, + void** aResult) const override { + if (!mManager) { + return NS_ERROR_NO_INTERFACE; + } + + mozilla::dom::AutoJSAPI jsapi; + jsapi.Init(); + + RefPtr<mozilla::dom::JSActor> actor = + mManager->GetActor(jsapi.cx(), mActorName, mozilla::IgnoreErrors()); + if (!actor) { + return NS_ERROR_NO_INTERFACE; + } + + return actor->QueryInterfaceActor(aIID, aResult); + } + + private: + const nsLiteralCString mActorName; + mozilla::dom::JSActorManager* mManager; +}; + +// Request an XPCOM interface from a JSActor managed by `aManager`. +// +// These APIs will work with both JSWindowActors and JSProcessActors. +template <size_t length> +inline nsQueryJSActor do_QueryActor(const char (&aActorName)[length], + mozilla::dom::JSActorManager* aManager) { + return nsQueryJSActor(nsLiteralCString(aActorName), aManager); +} + +// Overload for directly querying a JSWindowActorChild from an inner window. +template <size_t length> +inline nsQueryJSActor do_QueryActor(const char (&aActorName)[length], + nsPIDOMWindowInner* aWindow) { + return nsQueryJSActor(nsLiteralCString(aActorName), + aWindow ? aWindow->GetWindowGlobalChild() : nullptr); +} + +// Overload for directly querying a JSWindowActorChild from a document. +template <size_t length> +inline nsQueryJSActor do_QueryActor(const char (&aActorName)[length], + mozilla::dom::Document* aDoc) { + return nsQueryJSActor(nsLiteralCString(aActorName), + aDoc ? aDoc->GetWindowGlobalChild() : nullptr); +} + +// Overload for directly querying from a nsIDOMProcess{Parent,Child} without +// confusing overload selection for types inheriting from both +// nsIDOMProcess{Parent,Child} and JSActorManager. +template <size_t length, typename T, + typename = std::enable_if_t<std::is_same_v<T, nsIDOMProcessParent> || + std::is_same_v<T, nsIDOMProcessChild>>> +inline nsQueryJSActor do_QueryActor(const char (&aActorName)[length], + T* aManager) { + return nsQueryJSActor(nsLiteralCString(aActorName), + aManager ? aManager->AsJSActorManager() : nullptr); +} + +#endif // defined nsQueryActor_h diff --git a/dom/ipc/moz.build b/dom/ipc/moz.build new file mode 100644 index 0000000000..68f82f5a38 --- /dev/null +++ b/dom/ipc/moz.build @@ -0,0 +1,276 @@ +# -*- 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/. + +with Files("**"): + BUG_COMPONENT = ("Core", "DOM: Content Processes") + +DIRS += ["jsactor"] + +TEST_DIRS += ["gtest"] + +XPIDL_SOURCES += [ + "nsIDOMProcessChild.idl", + "nsIDOMProcessParent.idl", + "nsIHangReport.idl", + "nsILoginDetectionService.idl", +] + +XPIDL_MODULE = "dom" + +XPCOM_MANIFESTS += [ + "components.conf", +] + +EXTRA_JS_MODULES += [ + "ManifestMessagesChild.sys.mjs", +] + +EXPORTS.mozilla.dom.ipc += [ + "IdType.h", + "MemMapSnapshot.h", + "SharedMap.h", + "SharedMapChangeEvent.h", + "SharedStringMap.h", + "StringTable.h", + "StructuredCloneData.h", +] + +EXPORTS.mozilla.dom += [ + "BrowserBridgeChild.h", + "BrowserBridgeHost.h", + "BrowserBridgeParent.h", + "BrowserChild.h", + "BrowserHost.h", + "BrowserParent.h", + "ClonedErrorHolder.h", + "CoalescedInputData.h", + "CoalescedMouseData.h", + "CoalescedTouchData.h", + "CoalescedWheelData.h", + "ContentChild.h", + "ContentParent.h", + "ContentProcess.h", + "ContentProcessManager.h", + "CSPMessageUtils.h", + "DocShellMessageUtils.h", + "EffectsInfo.h", + "FilePickerMessageUtils.h", + "FilePickerParent.h", + "InProcessChild.h", + "InProcessParent.h", + "JSOracleChild.h", + "JSOracleParent.h", + "JSValidatorChild.h", + "JSValidatorParent.h", + "JSValidatorUtils.h", + "LoginDetectionService.h", + "MaybeDiscarded.h", + "MemoryReportRequest.h", + "NativeThreadId.h", + "PageLoadEventUtils.h", + "PermissionMessageUtils.h", + "ProcessActor.h", + "ProcessIsolation.h", + "PropertyBagUtils.h", + "ReferrerInfoUtils.h", + "RefMessageBodyService.h", + "RemoteBrowser.h", + "RemoteType.h", + "RemoteWebProgressRequest.h", + "SharedMessageBody.h", + "TabContext.h", + "TabMessageTypes.h", + "TabMessageUtils.h", + "URLClassifierChild.h", + "URLClassifierParent.h", + "UserActivationIPCUtils.h", + "VsyncChild.h", + "VsyncMainChild.h", + "VsyncParent.h", + "VsyncWorkerChild.h", + "WindowGlobalActor.h", + "WindowGlobalChild.h", + "WindowGlobalParent.h", +] + +EXPORTS.mozilla += [ + "PreallocatedProcessManager.h", + "ProcessHangMonitor.h", + "ProcessHangMonitorIPC.h", + "ProcessPriorityManager.h", +] + +UNIFIED_SOURCES += [ + "BrowserBridgeChild.cpp", + "BrowserBridgeHost.cpp", + "BrowserBridgeParent.cpp", + "BrowserChild.cpp", + "BrowserHost.cpp", + "BrowserParent.cpp", + "ClonedErrorHolder.cpp", + "CoalescedInputData.cpp", + "CoalescedMouseData.cpp", + "CoalescedTouchData.cpp", + "CoalescedWheelData.cpp", + "ColorPickerParent.cpp", + "ContentParent.cpp", + "ContentProcess.cpp", + "ContentProcessManager.cpp", + "CSPMessageUtils.cpp", + "DocShellMessageUtils.cpp", + "FilePickerParent.cpp", + "InProcessImpl.cpp", + "JSOracleChild.cpp", + "JSOracleParent.cpp", + "JSValidatorChild.cpp", + "JSValidatorParent.cpp", + "JSValidatorUtils.cpp", + "LoginDetectionService.cpp", + "MemMapSnapshot.cpp", + "MemoryReportRequest.cpp", + "MMPrinter.cpp", + "PermissionMessageUtils.cpp", + "PreallocatedProcessManager.cpp", + "ProcessActor.cpp", + "ProcessIsolation.cpp", + "ProcessPriorityManager.cpp", + "PropertyBagUtils.cpp", + "ReferrerInfoUtils.cpp", + "RefMessageBodyService.cpp", + "RemoteBrowser.cpp", + "RemoteWebProgressRequest.cpp", + "SharedMap.cpp", + "SharedMessageBody.cpp", + "SharedStringMap.cpp", + "StructuredCloneData.cpp", + "TabContext.cpp", + "URLClassifierParent.cpp", + "WindowGlobalActor.cpp", + "WindowGlobalChild.cpp", + "WindowGlobalParent.cpp", +] + +# ContentChild.cpp cannot be compiled in unified mode on linux due to Time conflict +SOURCES += [ + "ContentChild.cpp", + "ProcessHangMonitor.cpp", + "VsyncMainChild.cpp", + "VsyncParent.cpp", + "VsyncWorkerChild.cpp", +] + +PREPROCESSED_IPDL_SOURCES += [ + "PBrowser.ipdl", + "PBrowserBridge.ipdl", + "PContent.ipdl", +] + +IPDL_SOURCES += [ + "CustomElementTypes.ipdlh", + "DOMTypes.ipdlh", + "IPCTransferable.ipdlh", + "MemoryReportTypes.ipdlh", + "PColorPicker.ipdl", + "PContentPermission.ipdlh", + "PContentPermissionRequest.ipdl", + "PCycleCollectWithLogs.ipdl", + "PFilePicker.ipdl", + "PInProcess.ipdl", + "PJSOracle.ipdl", + "PJSValidator.ipdl", + "PProcessHangMonitor.ipdl", + "PrefsTypes.ipdlh", + "PTabContext.ipdlh", + "PURLClassifier.ipdl", + "PURLClassifierInfo.ipdlh", + "PURLClassifierLocal.ipdl", + "PVsync.ipdl", + "PWindowGlobal.ipdl", + "ServiceWorkerConfiguration.ipdlh", + "WindowGlobalTypes.ipdlh", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +if CONFIG["MOZ_SANDBOX"] and (CONFIG["OS_TARGET"] in ["Darwin", "Linux"]): + USE_LIBS += [ + "mozsandbox", + ] + +LOCAL_INCLUDES += [ + "/caps", + "/chrome", + "/docshell/base", + "/dom/base", + "/dom/bindings", + "/dom/events", + "/dom/filesystem", + "/dom/geolocation", + "/dom/media/webrtc", + "/dom/media/webspeech/synth/ipc", + "/dom/security", + "/dom/storage", + "/extensions/spellcheck/src", + "/gfx/2d", + "/hal/sandbox", + "/js/xpconnect/loader", + "/js/xpconnect/src", + "/layout/base", + "/media/webrtc", + "/netwerk/base", + "/netwerk/dns", + "/netwerk/protocol/http", + "/toolkit/components/printingui/ipc", + "/toolkit/crashreporter", + "/toolkit/xre", + "/uriloader/exthandler", + "/widget", + "/xpcom/base", + "/xpcom/threads", +] + +if CONFIG["MOZ_SANDBOX"] and CONFIG["OS_ARCH"] == "WINNT": + LOCAL_INCLUDES += [ + "/security/sandbox/chromium", + "/security/sandbox/chromium-shim", + ] + +if CONFIG["MOZ_SANDBOX"] and CONFIG["OS_ARCH"] == "OpenBSD": + LOCAL_INCLUDES += [ + "/xpcom/build", + ] + +if CONFIG["OS_ARCH"] != "WINNT": + LOCAL_INCLUDES += [ + "/modules/libjar", + ] + +DEFINES["BIN_SUFFIX"] = '"%s"' % CONFIG["BIN_SUFFIX"] + +DEFINES["MOZ_APP_NAME"] = '"%s"' % CONFIG["MOZ_APP_NAME"] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "android": + DEFINES["MOZ_ENABLE_FREETYPE"] = True + +JAR_MANIFESTS += ["jar.mn"] + +BROWSER_CHROME_MANIFESTS += [ + "tests/browser.toml", + "tests/JSProcessActor/browser.toml", + "tests/JSWindowActor/browser.toml", +] + +MOCHITEST_CHROME_MANIFESTS += ["tests/chrome.toml"] +MOCHITEST_MANIFESTS += ["tests/mochitest.toml"] +XPCSHELL_TESTS_MANIFESTS += ["tests/xpcshell.toml"] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"] + +# Add libFuzzer configuration directives +include("/tools/fuzzing/libfuzzer-config.mozbuild") diff --git a/dom/ipc/nsIDOMProcessChild.idl b/dom/ipc/nsIDOMProcessChild.idl new file mode 100644 index 0000000000..ff6875f395 --- /dev/null +++ b/dom/ipc/nsIDOMProcessChild.idl @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "nsISupports.idl" + +%{C++ +namespace mozilla { +namespace dom { +class ContentChild; +class JSActorManager; +} // namespace dom +} // namespace mozilla +%} +[ptr] native ContentChildPtr(mozilla::dom::ContentChild); +[ptr] native JSActorManagerPtr(mozilla::dom::JSActorManager); + +webidl JSProcessActorChild; + +/** + * Child actor interface for a process which can host DOM content. + * + * Implemented by either `InProcessChild` for the parent process, or + * `ContentChild` for a content process. + */ +[scriptable, builtinclass, uuid(b0c6e5f3-02f1-4f11-a0af-336fc231f3bf)] +interface nsIDOMProcessChild: nsISupports { + %{C++ + /** + * Get the nsIDOMProcessChild singleton for this content process. This will + * either be an InProcessChild in the parent process, or ContentChild in the + * child process. + * + * Implemented in ContentChild.cpp + */ + static nsIDOMProcessChild* GetSingleton(); + %} + + /** + * Internal child process ID. `0` is reserved for the parent process. + */ + [infallible] readonly attribute unsigned long long childID; + + /** + * Lookup a JSProcessActorChild managed by this interface by name. + */ + [implicit_jscontext] JSProcessActorChild getActor(in ACString name); + JSProcessActorChild getExistingActor(in ACString name); + + /** Can the actor still send messages? */ + [infallible] readonly attribute boolean canSend; + + [notxpcom, nostdcall] ContentChildPtr AsContentChild(); + + /** Cast this nsIDOMProcessChild to a JSActorManager */ + [notxpcom, nostdcall] JSActorManagerPtr AsJSActorManager(); +}; diff --git a/dom/ipc/nsIDOMProcessParent.idl b/dom/ipc/nsIDOMProcessParent.idl new file mode 100644 index 0000000000..daa11deddf --- /dev/null +++ b/dom/ipc/nsIDOMProcessParent.idl @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "nsISupports.idl" + +%{C++ +namespace mozilla { +namespace dom { +class ContentParent; +class JSActorManager; +} // namespace dom +} // namespace mozilla +%} +[ptr] native ContentParentPtr(mozilla::dom::ContentParent); +[ptr] native JSActorManagerPtr(mozilla::dom::JSActorManager); + +webidl JSProcessActorParent; + +/** + * Parent actor interface for a process which can host DOM content. + * + * Implemented by either `InProcessParent` for the parent process, or + * `ContentParent` for a content process. + */ +[scriptable, builtinclass, uuid(81fc08b9-c901-471f-ab0d-876362eba770)] +interface nsIDOMProcessParent: nsISupports { + /** + * Internal child process ID. `0` is reserved for the parent process. + */ + [infallible] readonly attribute unsigned long long childID; + + /** + * OS ID of the process. + */ + [infallible] readonly attribute long osPid; + + /** + * Lookup a JSProcessActorParent managed by this interface by name. + */ + [implicit_jscontext] JSProcessActorParent getActor(in ACString name); + JSProcessActorParent getExistingActor(in ACString name); + + /** Can the actor still send messages? */ + [infallible] readonly attribute boolean canSend; + + [notxpcom, nostdcall] ContentParentPtr AsContentParent(); + + /** Cast this nsIDOMProcessParent to a JSActorManager */ + [notxpcom, nostdcall] JSActorManagerPtr AsJSActorManager(); + + /** + * Remote type of the process. + */ + readonly attribute ACString remoteType; +}; diff --git a/dom/ipc/nsIHangReport.idl b/dom/ipc/nsIHangReport.idl new file mode 100644 index 0000000000..ff01d7f51a --- /dev/null +++ b/dom/ipc/nsIHangReport.idl @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "nsISupports.idl" + +webidl FrameLoader; +webidl Element; + +/** + * When a content process hangs, Gecko notifies "process-hang-report" observers + * and passes an nsIHangReport for the subject parameter. There is at most one + * nsIHangReport associated with a given content process. As long as the content + * process stays stuck, the "process-hang-report" observer will continue to be + * notified at regular intervals (approximately once per second). The content + * process will continue to run uninhibitedly during this time. + */ + +[scriptable, uuid(5fcffbb9-be62-49b1-b8a1-36e820787a74)] +interface nsIHangReport : nsISupports +{ + readonly attribute Element scriptBrowser; + readonly attribute ACString scriptFileName; + // Duration of the hang so far. + readonly attribute double hangDuration; + readonly attribute AString addonId; + + // The child id of the process in which the hang happened. + readonly attribute unsigned long long childID; + + // Called by front end code when user ignores or cancels + // the notification. + void userCanceled(); + + // Terminate the slow script if it is still running. + void terminateScript(); + + // Ask the content process to start up the slow script debugger. + void beginStartingDebugger(); + + // Inform the content process that the slow script debugger has finished + // spinning up. The content process will run a nested event loop until this + // method is called. + void endStartingDebugger(); + + // Inquire whether the report is for a content process loaded by the given + // frameloader, or any descendents in its BrowsingContext tree. + bool isReportForBrowserOrChildren(in FrameLoader aFrameLoader); +}; diff --git a/dom/ipc/nsILoginDetectionService.idl b/dom/ipc/nsILoginDetectionService.idl new file mode 100644 index 0000000000..a0b1ae90a7 --- /dev/null +++ b/dom/ipc/nsILoginDetectionService.idl @@ -0,0 +1,22 @@ +/* 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 "nsISupports.idl" + +interface nsIURI; + +[scriptable, uuid(4c3c9a82-722a-4b0b-9c7d-36ef90135537)] +interface nsILoginDetectionService : nsISupports +{ + /** + * called to initialize the login detection service. + */ + void init(); + + /** + * Returns true if we have loaded logins from the password manager. + * This is now used by testcase only. + */ + bool isLoginsLoaded(); +}; diff --git a/dom/ipc/tests/JSProcessActor/browser.toml b/dom/ipc/tests/JSProcessActor/browser.toml new file mode 100644 index 0000000000..807b54dd81 --- /dev/null +++ b/dom/ipc/tests/JSProcessActor/browser.toml @@ -0,0 +1,18 @@ +[DEFAULT] +support-files = ["head.js"] + +["browser_devtools_loader.js"] + +["browser_getActor.js"] + +["browser_getActor_filter.js"] + +["browser_observer_notification.js"] + +["browser_registerProcessActor.js"] + +["browser_sendAsyncMessage.js"] + +["browser_sendQuery.js"] + +["browser_uri_combination.js"] diff --git a/dom/ipc/tests/JSProcessActor/browser_devtools_loader.js b/dom/ipc/tests/JSProcessActor/browser_devtools_loader.js new file mode 100644 index 0000000000..0d2699c5a9 --- /dev/null +++ b/dom/ipc/tests/JSProcessActor/browser_devtools_loader.js @@ -0,0 +1,57 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +declTest("getActor in the regular shared loader", { + loadInDevToolsLoader: false, + + async test(browser) { + let parent = browser.browsingContext.currentWindowGlobal.domProcess; + let parentActor = parent.getActor("TestProcessActor"); + ok(parentActor, "JSProcessActorParent should have value."); + is( + Cu.getRealmLocation(Cu.getGlobalForObject(parentActor)), + "shared JSM global", + "The JSActor module in the parent process should be loaded in the shared global" + ); + + await SpecialPowers.spawn(browser, [], async function () { + let child = ChromeUtils.domProcessChild; + ok(child, "DOMProcessChild should have value."); + let childActor = child.getActor("TestProcessActor"); + ok(childActor, "JSProcessActorChild should have value."); + is( + Cu.getRealmLocation(Cu.getGlobalForObject(childActor)), + "shared JSM global", + "The JSActor module in the child process should be loaded in the shared global" + ); + }); + }, +}); + +declTest("getActor in the distinct DevTools loader", { + loadInDevToolsLoader: true, + + async test(browser) { + let parent = browser.browsingContext.currentWindowGlobal.domProcess; + let parentActor = parent.getActor("TestProcessActor"); + ok(parentActor, "JSProcessActorParent should have value."); + is( + Cu.getRealmLocation(Cu.getGlobalForObject(parentActor)), + "DevTools global", + "The JSActor module in the parent process should be loaded in the distinct DevTools global" + ); + + await SpecialPowers.spawn(browser, [], async function () { + let child = ChromeUtils.domProcessChild; + ok(child, "DOMProcessChild should have value."); + let childActor = child.getActor("TestProcessActor"); + ok(childActor, "JSProcessActorChild should have value."); + is( + Cu.getRealmLocation(Cu.getGlobalForObject(childActor)), + "DevTools global", + "The JSActor module in the child process should be loaded in the distinct DevTools global" + ); + }); + }, +}); diff --git a/dom/ipc/tests/JSProcessActor/browser_getActor.js b/dom/ipc/tests/JSProcessActor/browser_getActor.js new file mode 100644 index 0000000000..e111f1a274 --- /dev/null +++ b/dom/ipc/tests/JSProcessActor/browser_getActor.js @@ -0,0 +1,47 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +declTest("getActor on both sides", { + async test(browser) { + let parent = browser.browsingContext.currentWindowGlobal.domProcess; + ok(parent, "WindowGlobalParent should have value."); + let actorParent = parent.getActor("TestProcessActor"); + is( + actorParent.show(), + "TestProcessActorParent", + "actor `show` should have value." + ); + is( + actorParent.manager, + parent, + "manager should match WindowGlobalParent.domProcess" + ); + + ok( + actorParent.sawActorCreated, + "Checking that we can observe parent creation" + ); + + await SpecialPowers.spawn(browser, [], async function () { + let child = ChromeUtils.domProcessChild; + ok(child, "WindowGlobalChild should have value."); + let actorChild = child.getActor("TestProcessActor"); + is( + actorChild.show(), + "TestProcessActorChild", + "actor show should have vaule." + ); + is( + actorChild.manager, + child, + "manager should match ChromeUtils.domProcessChild." + ); + + ok( + actorChild.sawActorCreated, + "Checking that we can observe child creation" + ); + }); + }, +}); diff --git a/dom/ipc/tests/JSProcessActor/browser_getActor_filter.js b/dom/ipc/tests/JSProcessActor/browser_getActor_filter.js new file mode 100644 index 0000000000..f79340fd89 --- /dev/null +++ b/dom/ipc/tests/JSProcessActor/browser_getActor_filter.js @@ -0,0 +1,80 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +declTest("getActor with remoteType match", { + remoteTypes: ["web"], + + async test(browser) { + let parent = browser.browsingContext.currentWindowGlobal.domProcess; + ok( + parent.getActor("TestProcessActor"), + "JSProcessActorParent should have value." + ); + + await SpecialPowers.spawn(browser, [], async function () { + let child = ChromeUtils.domProcessChild; + ok(child, "DOMProcessChild should have value."); + ok( + child.getActor("TestProcessActor"), + "JSProcessActorChild should have value." + ); + }); + }, +}); + +declTest("getActor with remoteType mismatch", { + remoteTypes: ["privilegedabout"], + url: TEST_URL, + + async test(browser) { + let parent = browser.browsingContext.currentWindowGlobal.domProcess; + Assert.throws( + () => parent.getActor("TestProcessActor"), + /NotSupportedError/, + "Should throw if its remoteTypes don't match." + ); + + await SpecialPowers.spawn(browser, [], async function () { + let child = ChromeUtils.domProcessChild; + ok(child, "DOMProcessChild should have value."); + Assert.throws( + () => child.getActor("TestProcessActor"), + /NotSupportedError/, + "Should throw if its remoteTypes don't match." + ); + }); + }, +}); + +declTest("getActor without includeParent", { + includeParent: false, + + async test(_browser, win) { + let parent = win.docShell.browsingContext.currentWindowGlobal.domProcess; + SimpleTest.doesThrow( + () => parent.getActor("TestProcessActor"), + "Should throw if includeParent is false." + ); + + let child = ChromeUtils.domProcessChild; + SimpleTest.doesThrow( + () => child.getActor("TestProcessActor"), + "Should throw if includeParent is false." + ); + }, +}); + +declTest("getActor with includeParent", { + includeParent: true, + + async test(_browser, win) { + let parent = win.docShell.browsingContext.currentWindowGlobal.domProcess; + let actorParent = parent.getActor("TestProcessActor"); + ok(actorParent, "JSProcessActorParent should have value."); + + let child = ChromeUtils.domProcessChild; + let actorChild = child.getActor("TestProcessActor"); + ok(actorChild, "JSProcessActorChild should have value."); + }, +}); diff --git a/dom/ipc/tests/JSProcessActor/browser_observer_notification.js b/dom/ipc/tests/JSProcessActor/browser_observer_notification.js new file mode 100644 index 0000000000..28dfa16481 --- /dev/null +++ b/dom/ipc/tests/JSProcessActor/browser_observer_notification.js @@ -0,0 +1,41 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +/* eslint-disable no-unused-vars */ +declTest("test observer triggering actor creation", { + async test(browser) { + await SpecialPowers.spawn(browser, [], async function () { + const TOPIC = "test-js-content-actor-child-observer"; + Services.obs.notifyObservers(content.window, TOPIC, "dataString"); + + let child = ChromeUtils.domProcessChild; + let actorChild = child.getActor("TestProcessActor"); + ok(actorChild, "JSProcessActorChild should have value."); + ok( + actorChild.lastObserved, + "JSProcessActorChild lastObserved should have value." + ); + let { subject, topic, data } = actorChild.lastObserved; + is(topic, TOPIC, "Topic matches"); + is(data, "dataString", "Data matches"); + }); + }, +}); + +declTest("test observers with null data", { + async test(browser) { + await SpecialPowers.spawn(browser, [], async function () { + const TOPIC = "test-js-content-actor-child-observer"; + Services.obs.notifyObservers(content.window, TOPIC); + + let child = ChromeUtils.domProcessChild; + let actorChild = child.getActor("TestProcessActor"); + ok(actorChild, "JSProcessActorChild should have value."); + let { subject, topic, data } = actorChild.lastObserved; + + is(topic, TOPIC, "Topic matches"); + is(data, null, "Data matches"); + }); + }, +}); diff --git a/dom/ipc/tests/JSProcessActor/browser_registerProcessActor.js b/dom/ipc/tests/JSProcessActor/browser_registerProcessActor.js new file mode 100644 index 0000000000..1fa4e1c17c --- /dev/null +++ b/dom/ipc/tests/JSProcessActor/browser_registerProcessActor.js @@ -0,0 +1,16 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +declTest("double register", { + async test(_browser, _window, fileExt) { + SimpleTest.doesThrow( + () => + ChromeUtils.registerContentActor( + "TestProcessActor", + processActorOptions[fileExt] + ), + "Should throw if register has duplicate name." + ); + }, +}); diff --git a/dom/ipc/tests/JSProcessActor/browser_sendAsyncMessage.js b/dom/ipc/tests/JSProcessActor/browser_sendAsyncMessage.js new file mode 100644 index 0000000000..02ad64ee8b --- /dev/null +++ b/dom/ipc/tests/JSProcessActor/browser_sendAsyncMessage.js @@ -0,0 +1,51 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +declTest("asyncMessage testing", { + async test(browser) { + let parent = browser.browsingContext.currentWindowGlobal.domProcess; + let actorParent = parent.getActor("TestProcessActor"); + ok(actorParent, "JSProcessActorParent should have value."); + + await ContentTask.spawn(browser, {}, async function () { + let child = ChromeUtils.domProcessChild; + let actorChild = child.getActor("TestProcessActor"); + ok(actorChild, "JSProcessActorChild should have value."); + + let promise = new Promise(resolve => { + actorChild.sendAsyncMessage("init", {}); + actorChild.done = data => resolve(data); + }).then(data => { + ok(data.initial, "Initial should be true."); + ok(data.toParent, "ToParent should be true."); + ok(data.toChild, "ToChild should be true."); + }); + + await promise; + }); + }, +}); + +declTest("asyncMessage without both sides", { + async test(browser) { + // If we don't create a parent actor, make sure the parent actor + // gets created by having sent the message. + await ContentTask.spawn(browser, {}, async function () { + let child = ChromeUtils.domProcessChild; + let actorChild = child.getActor("TestProcessActor"); + ok(actorChild, "JSProcessActorChild should have value."); + + let promise = new Promise(resolve => { + actorChild.sendAsyncMessage("init", {}); + actorChild.done = data => resolve(data); + }).then(data => { + ok(data.initial, "Initial should be true."); + ok(data.toParent, "ToParent should be true."); + ok(data.toChild, "ToChild should be true."); + }); + + await promise; + }); + }, +}); diff --git a/dom/ipc/tests/JSProcessActor/browser_sendQuery.js b/dom/ipc/tests/JSProcessActor/browser_sendQuery.js new file mode 100644 index 0000000000..5a9767bf3a --- /dev/null +++ b/dom/ipc/tests/JSProcessActor/browser_sendQuery.js @@ -0,0 +1,99 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +const ERROR_LINE_NUMBERS = { + jsm: 31, + "sys.mjs": 28, +}; +const EXCEPTION_LINE_NUMBERS = { + jsm: ERROR_LINE_NUMBERS.jsm + 3, + "sys.mjs": ERROR_LINE_NUMBERS["sys.mjs"] + 3, +}; +const ERROR_COLUMN_NUMBERS = { + jsm: 31, + "sys.mjs": 31, +}; +const EXCEPTION_COLUMN_NUMBERS = { + jsm: 22, + "sys.mjs": 22, +}; + +function maybeAsyncStack(offset, column) { + if ( + Services.prefs.getBoolPref( + "javascript.options.asyncstack_capture_debuggee_only" + ) + ) { + return ""; + } + + let stack = Error().stack.replace(/^.*?\n/, ""); + return ( + "JSActor query*" + + stack.replace( + /^([^\n]+?):(\d+):\d+/, + (m0, m1, m2) => `${m1}:${+m2 + offset}:${column}` + ) + ); +} + +declTest("sendQuery Error", { + async test(browser, _window, fileExt) { + let parent = browser.browsingContext.currentWindowGlobal.domProcess; + let actorParent = parent.getActor("TestProcessActor"); + + let asyncStack = maybeAsyncStack(2, 8); + let error = await actorParent + .sendQuery("error", { message: "foo" }) + .catch(e => e); + + is(error.message, "foo", "Error should have the correct message"); + is(error.name, "SyntaxError", "Error should have the correct name"); + is( + error.stack, + `receiveMessage@resource://testing-common/TestProcessActorChild.${fileExt}:${ERROR_LINE_NUMBERS[fileExt]}:${ERROR_COLUMN_NUMBERS[fileExt]}\n` + + asyncStack, + "Error should have the correct stack" + ); + }, +}); + +declTest("sendQuery Exception", { + async test(browser, _window, fileExt) { + let parent = browser.browsingContext.currentWindowGlobal.domProcess; + let actorParent = parent.getActor("TestProcessActor"); + + let asyncStack = maybeAsyncStack(2, 8); + let error = await actorParent + .sendQuery("exception", { + message: "foo", + result: Cr.NS_ERROR_INVALID_ARG, + }) + .catch(e => e); + + is(error.message, "foo", "Error should have the correct message"); + is( + error.result, + Cr.NS_ERROR_INVALID_ARG, + "Error should have the correct result code" + ); + is( + error.stack, + `receiveMessage@resource://testing-common/TestProcessActorChild.${fileExt}:${EXCEPTION_LINE_NUMBERS[fileExt]}:${EXCEPTION_COLUMN_NUMBERS[fileExt]}\n` + + asyncStack, + "Error should have the correct stack" + ); + }, +}); + +declTest("sendQuery testing", { + async test(browser) { + let parent = browser.browsingContext.currentWindowGlobal.domProcess; + let actorParent = parent.getActor("TestProcessActor"); + ok(actorParent, "JSWindowActorParent should have value."); + + let { result } = await actorParent.sendQuery("asyncAdd", { a: 10, b: 20 }); + is(result, 30); + }, +}); diff --git a/dom/ipc/tests/JSProcessActor/browser_uri_combination.js b/dom/ipc/tests/JSProcessActor/browser_uri_combination.js new file mode 100644 index 0000000000..33394cf54a --- /dev/null +++ b/dom/ipc/tests/JSProcessActor/browser_uri_combination.js @@ -0,0 +1,81 @@ +add_task(function specify_both() { + // Specifying both should throw. + + SimpleTest.doesThrow(() => { + ChromeUtils.registerProcessActor("TestProcessActor", { + parent: { + moduleURI: "resource://testing-common/TestProcessActorParent.jsm", + }, + child: { + moduleURI: "resource://testing-common/TestProcessActorChild.jsm", + esModuleURI: "resource://testing-common/TestProcessActorChild.sys.mjs", + }, + }); + }, "Should throw if both moduleURI and esModuleURI are specified."); + + SimpleTest.doesThrow(() => { + ChromeUtils.registerProcessActor("TestProcessActor", { + parent: { + esModuleURI: "resource://testing-common/TestProcessActorParent.sys.mjs", + }, + child: { + moduleURI: "resource://testing-common/TestProcessActorChild.jsm", + esModuleURI: "resource://testing-common/TestProcessActorChild.sys.mjs", + }, + }); + }, "Should throw if both moduleURI and esModuleURI are specified."); + + SimpleTest.doesThrow(() => { + ChromeUtils.registerProcessActor("TestProcessActor", { + parent: { + moduleURI: "resource://testing-common/TestProcessActorParent.jsm", + esModuleURI: "resource://testing-common/TestProcessActorParent.sys.mjs", + }, + child: { + moduleURI: "resource://testing-common/TestProcessActorChild.jsm", + }, + }); + }, "Should throw if both moduleURI and esModuleURI are specified."); + + SimpleTest.doesThrow(() => { + ChromeUtils.registerProcessActor("TestProcessActor", { + parent: { + moduleURI: "resource://testing-common/TestProcessActorParent.jsm", + esModuleURI: "resource://testing-common/TestProcessActorParent.sys.mjs", + }, + child: { + esModuleURI: "resource://testing-common/TestProcessActorChild.sys.mjs", + }, + }); + }, "Should throw if both moduleURI and esModuleURI are specified."); +}); + +add_task(function specify_mixed() { + // Mixing JSM and ESM should work. + + try { + ChromeUtils.registerProcessActor("TestProcessActor", { + parent: { + moduleURI: "resource://testing-common/TestProcessActorParent.jsm", + }, + child: { + esModuleURI: "resource://testing-common/TestProcessActorChild.sys.mjs", + }, + }); + } finally { + ChromeUtils.unregisterProcessActor("TestProcessActor"); + } + + try { + ChromeUtils.registerProcessActor("TestProcessActor", { + parent: { + esModuleURI: "resource://testing-common/TestProcessActorParent.sys.mjs", + }, + child: { + moduleURI: "resource://testing-common/TestProcessActorChild.jsm", + }, + }); + } finally { + ChromeUtils.unregisterProcessActor("TestProcessActor"); + } +}); diff --git a/dom/ipc/tests/JSProcessActor/head.js b/dom/ipc/tests/JSProcessActor/head.js new file mode 100644 index 0000000000..cde193ca2e --- /dev/null +++ b/dom/ipc/tests/JSProcessActor/head.js @@ -0,0 +1,88 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Provide infrastructure for JSProcessActor tests. + */ + +const URL = "about:blank"; +const TEST_URL = "http://test2.example.org/"; +let processActorOptions = { + jsm: { + parent: { + moduleURI: "resource://testing-common/TestProcessActorParent.jsm", + }, + child: { + moduleURI: "resource://testing-common/TestProcessActorChild.jsm", + observers: ["test-js-content-actor-child-observer"], + }, + }, + "sys.mjs": { + parent: { + esModuleURI: "resource://testing-common/TestProcessActorParent.sys.mjs", + }, + child: { + esModuleURI: "resource://testing-common/TestProcessActorChild.sys.mjs", + observers: ["test-js-content-actor-child-observer"], + }, + }, +}; + +function promiseNotification(aNotification) { + let notificationResolve; + let notificationObserver = function observer() { + notificationResolve(); + Services.obs.removeObserver(notificationObserver, aNotification); + }; + return new Promise(resolve => { + notificationResolve = resolve; + Services.obs.addObserver(notificationObserver, aNotification); + }); +} + +function declTest(name, cfg) { + declTestWithOptions(name, cfg, "jsm"); + declTestWithOptions(name, cfg, "sys.mjs"); +} + +function declTestWithOptions(name, cfg, fileExt) { + let { + url = "about:blank", + includeParent = false, + remoteTypes, + loadInDevToolsLoader = false, + test, + } = cfg; + + // Build the actor options object which will be used to register & unregister + // our process actor. + let actorOptions = { + parent: Object.assign({}, processActorOptions[fileExt].parent), + child: Object.assign({}, processActorOptions[fileExt].child), + }; + actorOptions.includeParent = includeParent; + if (remoteTypes !== undefined) { + actorOptions.remoteTypes = remoteTypes; + } + if (loadInDevToolsLoader) { + actorOptions.loadInDevToolsLoader = true; + } + + // Add a new task for the actor test declared here. + add_task(async function () { + info("Entering test: " + name); + + // Register our actor, and load a new tab with the provided URL + ChromeUtils.registerProcessActor("TestProcessActor", actorOptions); + try { + await BrowserTestUtils.withNewTab(url, async browser => { + info("browser ready"); + await Promise.resolve(test(browser, window, fileExt)); + }); + } finally { + // Unregister the actor after the test is complete. + ChromeUtils.unregisterProcessActor("TestProcessActor"); + info("Exiting test: " + name); + } + }); +} diff --git a/dom/ipc/tests/JSWindowActor/audio.ogg b/dom/ipc/tests/JSWindowActor/audio.ogg Binary files differnew file mode 100644 index 0000000000..bed764fbf1 --- /dev/null +++ b/dom/ipc/tests/JSWindowActor/audio.ogg diff --git a/dom/ipc/tests/JSWindowActor/browser.toml b/dom/ipc/tests/JSWindowActor/browser.toml new file mode 100644 index 0000000000..a9dc7e8b8f --- /dev/null +++ b/dom/ipc/tests/JSWindowActor/browser.toml @@ -0,0 +1,32 @@ +[DEFAULT] +support-files = ["head.js"] + +["browser_contentWindow.js"] + +["browser_crash_report.js"] + +["browser_destroy_callbacks.js"] +skip-if = ["!debug && os == 'mac'"] #Bug 1604538 + +["browser_event_listener.js"] +support-files = ["file_dummyChromePage.html"] + +["browser_getActor.js"] + +["browser_getActor_filter.js"] + +["browser_observer_notification.js"] +support-files = [ + "file_mediaPlayback.html", + "audio.ogg", +] + +["browser_process_childid.js"] + +["browser_registerWindowActor.js"] + +["browser_sendAsyncMessage.js"] + +["browser_sendQuery.js"] + +["browser_uri_combination.js"] diff --git a/dom/ipc/tests/JSWindowActor/browser_contentWindow.js b/dom/ipc/tests/JSWindowActor/browser_contentWindow.js new file mode 100644 index 0000000000..e061fd895c --- /dev/null +++ b/dom/ipc/tests/JSWindowActor/browser_contentWindow.js @@ -0,0 +1,35 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +const CONTENT_WINDOW_URL = "https://example.com/"; + +declTest("contentWindow null when inner window inactive", { + matches: [CONTENT_WINDOW_URL + "*"], + url: CONTENT_WINDOW_URL + "?1", + + async test(browser) { + // If Fission is disabled, the pref is no-op. + await SpecialPowers.pushPrefEnv({ + set: [["fission.bfcacheInParent", true]], + }); + + let parent = browser.browsingContext.currentWindowGlobal; + let actorParent = parent.getActor("TestWindow"); + + await actorParent.sendQuery("storeActor"); + + let url = CONTENT_WINDOW_URL + "?2"; + let loaded = BrowserTestUtils.browserLoaded(browser, false, url); + BrowserTestUtils.startLoadingURIString(browser, url); + await loaded; + + let result = await actorParent.sendQuery("checkActor"); + + is(result.status, "success", "Should succeed when bfcache is enabled"); + ok( + result.valueIsNull, + "Should get a null contentWindow when inner window is inactive" + ); + }, +}); diff --git a/dom/ipc/tests/JSWindowActor/browser_crash_report.js b/dom/ipc/tests/JSWindowActor/browser_crash_report.js new file mode 100644 index 0000000000..f029f1a85a --- /dev/null +++ b/dom/ipc/tests/JSWindowActor/browser_crash_report.js @@ -0,0 +1,114 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +declTest("crash actor", { + allFrames: true, + + async test(browser) { + if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) { + ok(true, "Cannot test crash annotations without a crash reporter"); + return; + } + + { + info("Creating a new tab."); + let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URL); + let newTabBrowser = newTab.linkedBrowser; + + let parent = + newTabBrowser.browsingContext.currentWindowGlobal.getActor( + "TestWindow" + ); + ok(parent, "JSWindowActorParent should have value."); + + await SpecialPowers.spawn(newTabBrowser, [], async function () { + let child = content.windowGlobalChild; + ok(child, "WindowGlobalChild should have value."); + is( + child.isInProcess, + false, + "Actor should be loaded in the content process." + ); + // Make sure that the actor is loaded. + let actorChild = child.getActor("TestWindow"); + is( + actorChild.show(), + "TestWindowChild", + "actor show should have value." + ); + is( + actorChild.manager, + child, + "manager should match WindowGlobalChild." + ); + }); + + info( + "Crashing from withing an actor. We should have an actor name and a message name." + ); + let report = await BrowserTestUtils.crashFrame( + newTabBrowser, + /* shouldShowTabCrashPage = */ false, + /* shouldClearMinidumps = */ true, + /* browsingContext = */ null, + { asyncCrash: false } + ); + + is(report.JSActorName, "BrowserTestUtils"); + is(report.JSActorMessage, "BrowserTestUtils:CrashFrame"); + + BrowserTestUtils.removeTab(newTab); + } + + { + info("Creating a new tab for async crash"); + let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URL); + let newTabBrowser = newTab.linkedBrowser; + + let parent = + newTabBrowser.browsingContext.currentWindowGlobal.getActor( + "TestWindow" + ); + ok(parent, "JSWindowActorParent should have value."); + + await SpecialPowers.spawn(newTabBrowser, [], async function () { + let child = content.windowGlobalChild; + ok(child, "WindowGlobalChild should have value."); + is( + child.isInProcess, + false, + "Actor should be loaded in the content process." + ); + // Make sure that the actor is loaded. + let actorChild = child.getActor("TestWindow"); + is( + actorChild.show(), + "TestWindowChild", + "actor show should have value." + ); + is( + actorChild.manager, + child, + "manager should match WindowGlobalChild." + ); + }); + + info( + "Crashing from without an actor. We should have neither an actor name nor a message name." + ); + let report = await BrowserTestUtils.crashFrame( + newTabBrowser, + /* shouldShowTabCrashPage = */ false, + /* shouldClearMinidumps = */ true, + /* browsingContext = */ null, + { asyncCrash: true } + ); + + ok(!report.JSActorName); + ok(!report.JSActorMessage); + + BrowserTestUtils.removeTab(newTab); + } + }, +}); diff --git a/dom/ipc/tests/JSWindowActor/browser_destroy_callbacks.js b/dom/ipc/tests/JSWindowActor/browser_destroy_callbacks.js new file mode 100644 index 0000000000..74cbae9415 --- /dev/null +++ b/dom/ipc/tests/JSWindowActor/browser_destroy_callbacks.js @@ -0,0 +1,193 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +declTest("destroy actor by iframe remove", { + allFrames: true, + + async test(browser) { + await SpecialPowers.spawn(browser, [], async function () { + // Create and append an iframe into the window's document. + let frame = content.document.createElement("iframe"); + frame.id = "frame"; + content.document.body.appendChild(frame); + await ContentTaskUtils.waitForEvent(frame, "load"); + is(content.window.frames.length, 1, "There should be an iframe."); + let child = frame.contentWindow.windowGlobalChild; + let actorChild = child.getActor("TestWindow"); + ok(actorChild, "JSWindowActorChild should have value."); + + { + let error = actorChild.uninitializedGetterError; + const prop = "contentWindow"; + Assert.ok( + error, + `Should get error accessing '${prop}' before actor initialization` + ); + if (error) { + Assert.equal( + error.name, + "InvalidStateError", + "Error should be an InvalidStateError" + ); + Assert.equal( + error.message, + `JSWindowActorChild.${prop} getter: Cannot access property '${prop}' before actor is initialized`, + "Error should have informative message" + ); + } + } + + let didDestroyPromise = new Promise(resolve => { + const TOPIC = "test-js-window-actor-diddestroy"; + Services.obs.addObserver(function obs(subject, topic, data) { + ok(data, "didDestroyCallback data should be true."); + is(subject, actorChild, "Should have this value"); + + Services.obs.removeObserver(obs, TOPIC); + // Make a trip through the event loop to ensure that the + // actor's manager has been cleared before running remaining + // checks. + Services.tm.dispatchToMainThread(resolve); + }, TOPIC); + }); + + info("Remove frame"); + content.document.getElementById("frame").remove(); + await didDestroyPromise; + + Assert.throws( + () => child.getActor("TestWindow"), + /InvalidStateError/, + "Should throw if frame destroy." + ); + + for (let prop of [ + "document", + "browsingContext", + "docShell", + "contentWindow", + ]) { + let error; + try { + void actorChild[prop]; + } catch (e) { + error = e; + } + Assert.ok( + error, + `Should get error accessing '${prop}' after actor destruction` + ); + if (error) { + Assert.equal( + error.name, + "InvalidStateError", + "Error should be an InvalidStateError" + ); + Assert.equal( + error.message, + `JSWindowActorChild.${prop} getter: Cannot access property '${prop}' after actor 'TestWindow' has been destroyed`, + "Error should have informative message" + ); + } + } + }); + }, +}); + +declTest("destroy actor by page navigates", { + allFrames: true, + + async test(browser) { + info("creating an in-process frame"); + await SpecialPowers.spawn(browser, [URL], async function (url) { + let frame = content.document.createElement("iframe"); + frame.src = url; + content.document.body.appendChild(frame); + }); + + info("navigating page"); + await SpecialPowers.spawn(browser, [TEST_URL], async function (url) { + let frame = content.document.querySelector("iframe"); + frame.contentWindow.location = url; + let child = frame.contentWindow.windowGlobalChild; + let actorChild = child.getActor("TestWindow"); + ok(actorChild, "JSWindowActorChild should have value."); + + let didDestroyPromise = new Promise(resolve => { + const TOPIC = "test-js-window-actor-diddestroy"; + Services.obs.addObserver(function obs(subject, topic, data) { + ok(data, "didDestroyCallback data should be true."); + is(subject, actorChild, "Should have this value"); + + Services.obs.removeObserver(obs, TOPIC); + resolve(); + }, TOPIC); + }); + + await Promise.all([ + didDestroyPromise, + ContentTaskUtils.waitForEvent(frame, "load"), + ]); + + Assert.throws( + () => child.getActor("TestWindow"), + /InvalidStateError/, + "Should throw if frame destroy." + ); + }); + }, +}); + +declTest("destroy actor by tab being closed", { + allFrames: true, + + async test(browser) { + info("creating a new tab"); + let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URL); + let newTabBrowser = newTab.linkedBrowser; + + let parent = + newTabBrowser.browsingContext.currentWindowGlobal.getActor("TestWindow"); + ok(parent, "JSWindowActorParent should have value."); + + // We can't depend on `SpecialPowers.spawn` to resolve our promise, as the + // frame message manager will be being shut down at the same time. Instead + // send messages over the per-process message manager which should still be + // active. + let didDestroyPromise = new Promise(resolve => { + Services.ppmm.addMessageListener( + "test-jswindowactor-diddestroy", + function onmessage(msg) { + Services.ppmm.removeMessageListener( + "test-jswindowactor-diddestroy", + onmessage + ); + resolve(); + } + ); + }); + + info("setting up destroy listeners"); + await SpecialPowers.spawn(newTabBrowser, [], () => { + let child = content.windowGlobalChild; + let actorChild = child.getActor("TestWindow"); + ok(actorChild, "JSWindowActorChild should have value."); + + Services.obs.addObserver(function obs(subject, topic, data) { + if (subject != actorChild) { + return; + } + dump("DidDestroy called\n"); + Services.obs.removeObserver(obs, "test-js-window-actor-diddestroy"); + Services.cpmm.sendAsyncMessage("test-jswindowactor-diddestroy", data); + }, "test-js-window-actor-diddestroy"); + }); + + info("removing new tab"); + await BrowserTestUtils.removeTab(newTab); + info("waiting for destroy callbacks to fire"); + await didDestroyPromise; + info("got didDestroy callback"); + }, +}); diff --git a/dom/ipc/tests/JSWindowActor/browser_event_listener.js b/dom/ipc/tests/JSWindowActor/browser_event_listener.js new file mode 100644 index 0000000000..725c2c3753 --- /dev/null +++ b/dom/ipc/tests/JSWindowActor/browser_event_listener.js @@ -0,0 +1,171 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +async function createAndShowDropdown(browser) { + // Add a select element to the DOM of the loaded document. + await SpecialPowers.spawn(browser, [], async function () { + content.document.body.innerHTML += ` + <select id="testSelect"> + <option>A</option> + <option>B</option> + </select>`; + }); + + // Click on the select to show the dropdown. + await BrowserTestUtils.synthesizeMouseAtCenter("#testSelect", {}, browser); +} + +declTest("test event triggering actor creation", { + events: { mozshowdropdown: {} }, + + async test(browser) { + // Wait for the observer notification. + let observePromise = TestUtils.topicObserved( + "test-js-window-actor-parent-event" + ); + + await createAndShowDropdown(browser); + + // Wait for the observer notification to fire, and inspect the results. + let [subject, data] = await observePromise; + is(data, "mozshowdropdown"); + + let parent = browser.browsingContext.currentWindowGlobal; + let actorParent = parent.getActor("TestWindow"); + ok(actorParent, "JSWindowActorParent should have value."); + is( + subject.wrappedJSObject, + actorParent, + "Should have been recieved on the right actor" + ); + }, +}); + +declTest("test createActor:false not triggering actor creation", { + events: { mozshowdropdown: { createActor: false } }, + + async test(browser) { + info("before actor has been created"); + const TOPIC = "test-js-window-actor-parent-event"; + function obs() { + ok(false, "actor should not be created"); + } + Services.obs.addObserver(obs, TOPIC); + + await createAndShowDropdown(browser); + + // Bounce into the content process & create the actor after showing the + // dropdown, in order to be sure that if an event was going to be + // delivered, it already would've been. + await SpecialPowers.spawn(browser, [], async () => { + content.windowGlobalChild.getActor("TestWindow"); + }); + ok(true, "observer notification should not have fired"); + Services.obs.removeObserver(obs, TOPIC); + + // ---------------------------------------------------- + info("after actor has been created"); + let observePromise = TestUtils.topicObserved( + "test-js-window-actor-parent-event" + ); + await createAndShowDropdown(browser); + + // Wait for the observer notification to fire, and inspect the results. + let [subject, data] = await observePromise; + is(data, "mozshowdropdown"); + + let parent = browser.browsingContext.currentWindowGlobal; + let actorParent = parent.getActor("TestWindow"); + ok(actorParent, "JSWindowActorParent should have value."); + is( + subject.wrappedJSObject, + actorParent, + "Should have been recieved on the right actor" + ); + }, +}); + +async function testEventProcessedOnce(browser, waitForUrl) { + let notificationCount = 0; + let firstNotificationResolve; + let firstNotification = new Promise(resolve => { + firstNotificationResolve = resolve; + }); + + const TOPIC = "test-js-window-actor-parent-event"; + function obs(subject, topic, data) { + is(data, "mozwindowactortestevent"); + notificationCount++; + if (firstNotificationResolve) { + firstNotificationResolve(); + firstNotificationResolve = null; + } + } + Services.obs.addObserver(obs, TOPIC); + + if (waitForUrl) { + info("Waiting for URI to be alright"); + await TestUtils.waitForCondition(() => { + if (!browser.browsingContext?.currentWindowGlobal) { + info("No CWG yet"); + return false; + } + return SpecialPowers.spawn(browser, [waitForUrl], async function (url) { + info(content.document.documentURI); + return content.document.documentURI.includes(url); + }); + }); + } + + info("Dispatching event"); + await SpecialPowers.spawn(browser, [], async function () { + content.document.dispatchEvent( + new content.CustomEvent("mozwindowactortestevent", { bubbles: true }) + ); + }); + + info("Waiting for notification"); + await firstNotification; + + await new Promise(r => setTimeout(r, 0)); + + is(notificationCount, 1, "Should get only one notification"); + + Services.obs.removeObserver(obs, TOPIC); +} + +declTest("test in-process content events are not processed twice", { + url: "about:preferences", + events: { mozwindowactortestevent: { wantUntrusted: true } }, + async test(browser) { + is( + browser.getAttribute("type"), + "content", + "Should be a content <browser>" + ); + is(browser.getAttribute("remotetype"), "", "Should not be remote"); + await testEventProcessedOnce(browser); + }, +}); + +declTest("test in-process chrome events are processed correctly", { + url: "about:blank", + events: { mozwindowactortestevent: { wantUntrusted: true } }, + allFrames: true, + includeChrome: true, + async test(browser) { + let dialogBox = gBrowser.getTabDialogBox(browser); + let { dialogClosed, dialog } = dialogBox.open( + "chrome://mochitests/content/browser/dom/ipc/tests/JSWindowActor/file_dummyChromePage.html" + ); + let chromeBrowser = dialog._frame; + is(chromeBrowser.getAttribute("type"), "", "Should be a chrome <browser>"); + is(chromeBrowser.getAttribute("remotetype"), "", "Should not be remote"); + + await testEventProcessedOnce(chromeBrowser, "dummyChromePage.html"); + + dialog.close(); + await dialogClosed; + }, +}); diff --git a/dom/ipc/tests/JSWindowActor/browser_getActor.js b/dom/ipc/tests/JSWindowActor/browser_getActor.js new file mode 100644 index 0000000000..8d002daf99 --- /dev/null +++ b/dom/ipc/tests/JSWindowActor/browser_getActor.js @@ -0,0 +1,36 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +declTest("getActor on both sides", { + async test(browser) { + let parent = browser.browsingContext.currentWindowGlobal; + ok(parent, "WindowGlobalParent should have value."); + let actorParent = parent.getActor("TestWindow"); + is(actorParent.show(), "TestWindowParent", "actor show should have vaule."); + is(actorParent.manager, parent, "manager should match WindowGlobalParent."); + + ok( + actorParent.sawActorCreated, + "Checking that we can observe parent creation" + ); + + await SpecialPowers.spawn(browser, [], async function () { + let child = content.windowGlobalChild; + ok(child, "WindowGlobalChild should have value."); + is( + child.isInProcess, + false, + "Actor should be loaded in the content process." + ); + let actorChild = child.getActor("TestWindow"); + is(actorChild.show(), "TestWindowChild", "actor show should have vaule."); + is(actorChild.manager, child, "manager should match WindowGlobalChild."); + + ok( + actorChild.sawActorCreated, + "Checking that we can observe child creation" + ); + }); + }, +}); diff --git a/dom/ipc/tests/JSWindowActor/browser_getActor_filter.js b/dom/ipc/tests/JSWindowActor/browser_getActor_filter.js new file mode 100644 index 0000000000..a10697c989 --- /dev/null +++ b/dom/ipc/tests/JSWindowActor/browser_getActor_filter.js @@ -0,0 +1,259 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +requestLongerTimeout(2); + +declTest("getActor with mismatch", { + matches: ["*://*/*"], + + async test(browser) { + let parent = browser.browsingContext.currentWindowGlobal; + ok(parent, "WindowGlobalParent should have value."); + Assert.throws( + () => parent.getActor("TestWindow"), + /NotSupportedError/, + "Should throw if it doesn't match." + ); + + await SpecialPowers.spawn(browser, [], async function () { + let child = content.windowGlobalChild; + ok(child, "WindowGlobalChild should have value."); + + Assert.throws( + () => child.getActor("TestWindow"), + /NotSupportedError/, + "Should throw if it doesn't match." + ); + }); + }, +}); + +declTest("getActor with matches", { + matches: ["*://*/*"], + url: TEST_URL, + + async test(browser) { + let parent = browser.browsingContext.currentWindowGlobal; + ok(parent.getActor("TestWindow"), "JSWindowActorParent should have value."); + + await SpecialPowers.spawn(browser, [], async function () { + let child = content.windowGlobalChild; + ok(child, "WindowGlobalChild should have value."); + ok(child.getActor("TestWindow"), "JSWindowActorChild should have value."); + }); + }, +}); + +declTest("getActor with iframe matches", { + allFrames: true, + matches: ["*://*/*"], + + async test(browser) { + await SpecialPowers.spawn(browser, [TEST_URL], async function (url) { + // Create and append an iframe into the window's document. + let frame = content.document.createElement("iframe"); + frame.src = url; + content.document.body.appendChild(frame); + await ContentTaskUtils.waitForEvent(frame, "load"); + + is(content.frames.length, 1, "There should be an iframe."); + await content.SpecialPowers.spawn(frame, [], () => { + let child = content.windowGlobalChild; + Assert.ok( + child.getActor("TestWindow"), + "JSWindowActorChild should have value." + ); + }); + }); + }, +}); + +declTest("getActor with iframe mismatch", { + allFrames: true, + matches: ["about:home"], + + async test(browser) { + await SpecialPowers.spawn(browser, [TEST_URL], async function (url) { + // Create and append an iframe into the window's document. + let frame = content.document.createElement("iframe"); + frame.src = url; + content.document.body.appendChild(frame); + await ContentTaskUtils.waitForEvent(frame, "load"); + + is(content.frames.length, 1, "There should be an iframe."); + await content.SpecialPowers.spawn(frame, [], () => { + let child = content.windowGlobalChild; + Assert.throws( + () => child.getActor("TestWindow"), + /NotSupportedError/, + "Should throw if it doesn't match." + ); + }); + }); + }, +}); + +declTest("getActor with remoteType match", { + remoteTypes: ["web"], + + async test(browser) { + let parent = browser.browsingContext.currentWindowGlobal; + ok(parent.getActor("TestWindow"), "JSWindowActorParent should have value."); + + await SpecialPowers.spawn(browser, [], async function () { + let child = content.windowGlobalChild; + ok(child, "WindowGlobalChild should have value."); + ok(child.getActor("TestWindow"), "JSWindowActorChild should have value."); + }); + }, +}); + +declTest("getActor with iframe remoteType match", { + allFrames: true, + remoteTypes: ["web"], + + async test(browser) { + await SpecialPowers.spawn(browser, [TEST_URL], async function (url) { + let child = content.windowGlobalChild; + ok(child, "WindowGlobalChild should have value."); + ok(child.getActor("TestWindow"), "JSWindowActorChild should have value."); + + // Create and append an iframe into the window's document. + let frame = content.document.createElement("iframe"); + frame.src = url; + content.document.body.appendChild(frame); + await ContentTaskUtils.waitForEvent(frame, "load"); + + is(content.frames.length, 1, "There should be an iframe."); + await content.SpecialPowers.spawn(frame, [], () => { + child = content.windowGlobalChild; + Assert.ok( + child.getActor("TestWindow"), + "JSWindowActorChild should have value." + ); + }); + }); + }, +}); + +declTest("getActor with remoteType mismatch", { + remoteTypes: ["privilegedabout"], + url: TEST_URL, + + async test(browser) { + let parent = browser.browsingContext.currentWindowGlobal; + Assert.throws( + () => parent.getActor("TestWindow"), + /NotSupportedError/, + "Should throw if its remoteTypes don't match." + ); + + await SpecialPowers.spawn(browser, [], async function () { + let child = content.windowGlobalChild; + ok(child, "WindowGlobalChild should have value."); + Assert.throws( + () => child.getActor("TestWindow"), + /NotSupportedError/, + "Should throw if its remoteTypes don't match." + ); + }); + }, +}); + +declTest("getActor with iframe messageManagerGroups match", { + allFrames: true, + messageManagerGroups: ["browsers"], + + async test(browser) { + let parent = browser.browsingContext.currentWindowGlobal; + ok(parent.getActor("TestWindow"), "JSWindowActorParent should have value."); + + await SpecialPowers.spawn(browser, [TEST_URL], async function (url) { + let child = content.windowGlobalChild; + ok(child, "WindowGlobalChild should have value."); + ok(child.getActor("TestWindow"), "JSWindowActorChild should have value."); + }); + }, +}); + +declTest("getActor with iframe messageManagerGroups mismatch", { + allFrames: true, + messageManagerGroups: ["sidebars"], + + async test(browser) { + let parent = browser.browsingContext.currentWindowGlobal; + Assert.throws( + () => parent.getActor("TestWindow"), + /NotSupportedError/, + "Should throw if its messageManagerGroups doesn't match." + ); + + await SpecialPowers.spawn(browser, [TEST_URL], async function (url) { + let child = content.windowGlobalChild; + ok(child, "WindowGlobalChild should have value."); + Assert.throws( + () => child.getActor("TestWindow"), + /NotSupportedError/, + "Should throw if its messageManagerGroups doesn't match." + ); + }); + }, +}); + +declTest("getActor without allFrames", { + allFrames: false, + + async test(browser) { + await SpecialPowers.spawn(browser, [], async function () { + // Create and append an iframe into the window's document. + let frame = content.document.createElement("iframe"); + content.document.body.appendChild(frame); + is(content.frames.length, 1, "There should be an iframe."); + let child = frame.contentWindow.windowGlobalChild; + Assert.throws( + () => child.getActor("TestWindow"), + /NotSupportedError/, + "Should throw if allFrames is false." + ); + }); + }, +}); + +declTest("getActor with allFrames", { + allFrames: true, + + async test(browser) { + await SpecialPowers.spawn(browser, [], async function () { + // Create and append an iframe into the window's document. + let frame = content.document.createElement("iframe"); + content.document.body.appendChild(frame); + is(content.frames.length, 1, "There should be an iframe."); + let child = frame.contentWindow.windowGlobalChild; + let actorChild = child.getActor("TestWindow"); + ok(actorChild, "JSWindowActorChild should have value."); + }); + }, +}); + +declTest("getActor without includeChrome", { + includeChrome: false, + + async test(_browser, win) { + let parent = win.docShell.browsingContext.currentWindowGlobal; + SimpleTest.doesThrow( + () => parent.getActor("TestWindow"), + "Should throw if includeChrome is false." + ); + }, +}); + +declTest("getActor with includeChrome", { + includeChrome: true, + + async test(_browser, win) { + let parent = win.docShell.browsingContext.currentWindowGlobal; + let actorParent = parent.getActor("TestWindow"); + ok(actorParent, "JSWindowActorParent should have value."); + }, +}); diff --git a/dom/ipc/tests/JSWindowActor/browser_observer_notification.js b/dom/ipc/tests/JSWindowActor/browser_observer_notification.js new file mode 100644 index 0000000000..c0fe2249e8 --- /dev/null +++ b/dom/ipc/tests/JSWindowActor/browser_observer_notification.js @@ -0,0 +1,118 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +/* eslint-disable no-unused-vars */ +declTest("test observer triggering actor creation", { + observers: ["test-js-window-actor-child-observer"], + + async test(browser) { + await SpecialPowers.spawn(browser, [], async function () { + const TOPIC = "test-js-window-actor-child-observer"; + Services.obs.notifyObservers(content.window, TOPIC, "dataString"); + + let child = content.windowGlobalChild; + let actorChild = child.getActor("TestWindow"); + ok(actorChild, "JSWindowActorChild should have value."); + let { subject, topic, data } = actorChild.lastObserved; + + is( + subject.windowGlobalChild.getActor("TestWindow"), + actorChild, + "Should have been recieved on the right actor" + ); + is(topic, TOPIC, "Topic matches"); + is(data, "dataString", "Data matches"); + }); + }, +}); + +declTest("test observers with null data", { + observers: ["test-js-window-actor-child-observer"], + + async test(browser) { + await SpecialPowers.spawn(browser, [], async function () { + const TOPIC = "test-js-window-actor-child-observer"; + Services.obs.notifyObservers(content.window, TOPIC); + + let child = content.windowGlobalChild; + let actorChild = child.getActor("TestWindow"); + ok(actorChild, "JSWindowActorChild should have value."); + let { subject, topic, data } = actorChild.lastObserved; + + is( + subject.windowGlobalChild.getActor("TestWindow"), + actorChild, + "Should have been recieved on the right actor" + ); + is(topic, TOPIC, "Topic matches"); + is(data, null, "Data matches"); + }); + }, +}); + +declTest("observers don't notify with wrong window", { + observers: ["test-js-window-actor-child-observer"], + + async test(browser) { + const MSG_RE = + /JSWindowActor TestWindow: expected window subject for topic 'test-js-window-actor-child-observer'/; + let expectMessage = new Promise(resolve => { + Services.console.registerListener(function consoleListener(msg) { + // Run everything async in order to avoid logging messages from the + // console listener. + Cu.dispatch(() => { + if (!MSG_RE.test(msg.message)) { + info("ignoring non-matching console message: " + msg.message); + return; + } + info("received console message: " + msg.message); + is(msg.logLevel, Ci.nsIConsoleMessage.error, "should be an error"); + + Services.console.unregisterListener(consoleListener); + resolve(); + }); + }); + }); + + await SpecialPowers.spawn(browser, [], async function () { + const TOPIC = "test-js-window-actor-child-observer"; + Services.obs.notifyObservers(null, TOPIC); + let child = content.windowGlobalChild; + let actorChild = child.getActor("TestWindow"); + ok(actorChild, "JSWindowActorChild should have value."); + is( + actorChild.lastObserved, + undefined, + "Should not receive wrong window's observer notification!" + ); + }); + + await expectMessage; + }, +}); + +declTest("observers notify with audio-playback", { + observers: ["audio-playback"], + url: "http://example.com/browser/dom/ipc/tests/JSWindowActor/file_mediaPlayback.html", + + async test(browser) { + await SpecialPowers.spawn(browser, [], async function () { + let audio = content.document.querySelector("audio"); + audio.play(); + + let child = content.windowGlobalChild; + let actorChild = child.getActor("TestWindow"); + ok(actorChild, "JSWindowActorChild should have value."); + + let observePromise = new Promise(resolve => { + actorChild.done = ({ subject, topic, data }) => + resolve({ subject, topic, data }); + }); + + let { subject, topic, data } = await observePromise; + is(topic, "audio-playback", "Topic matches"); + is(data, "active", "Data matches"); + }); + }, +}); diff --git a/dom/ipc/tests/JSWindowActor/browser_process_childid.js b/dom/ipc/tests/JSWindowActor/browser_process_childid.js new file mode 100644 index 0000000000..ba6db1dabb --- /dev/null +++ b/dom/ipc/tests/JSWindowActor/browser_process_childid.js @@ -0,0 +1,27 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that `process.childID` is defined. + +declTest("test childid", { + async test(browser) { + let parent = browser.browsingContext.currentWindowGlobal; + ok( + parent.domProcess.childID, + "parent domProcess.childID should have a value." + ); + await SpecialPowers.spawn( + browser, + [parent.domProcess.childID], + async function (parentChildID) { + ok( + ChromeUtils.domProcessChild.childID, + "child process.childID should have a value." + ); + let childID = ChromeUtils.domProcessChild.childID; + is(parentChildID, childID); + } + ); + }, +}); diff --git a/dom/ipc/tests/JSWindowActor/browser_registerWindowActor.js b/dom/ipc/tests/JSWindowActor/browser_registerWindowActor.js new file mode 100644 index 0000000000..edaa99d2ed --- /dev/null +++ b/dom/ipc/tests/JSWindowActor/browser_registerWindowActor.js @@ -0,0 +1,16 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +declTest("double register", { + async test(_browser, _window, fileExt) { + SimpleTest.doesThrow( + () => + ChromeUtils.registerWindowActor( + "TestWindow", + windowActorOptions[fileExt] + ), + "Should throw if register has duplicate name." + ); + }, +}); diff --git a/dom/ipc/tests/JSWindowActor/browser_sendAsyncMessage.js b/dom/ipc/tests/JSWindowActor/browser_sendAsyncMessage.js new file mode 100644 index 0000000000..9ba33a3936 --- /dev/null +++ b/dom/ipc/tests/JSWindowActor/browser_sendAsyncMessage.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +declTest("asyncMessage testing", { + async test(browser) { + let parent = browser.browsingContext.currentWindowGlobal; + let actorParent = parent.getActor("TestWindow"); + ok(actorParent, "JSWindowActorParent should have value."); + + await ContentTask.spawn(browser, {}, async function () { + let child = content.windowGlobalChild; + let actorChild = child.getActor("TestWindow"); + ok(actorChild, "JSWindowActorChild should have value."); + + let promise = new Promise(resolve => { + actorChild.sendAsyncMessage("init", {}); + actorChild.done = data => resolve(data); + }).then(data => { + ok(data.initial, "Initial should be true."); + ok(data.toParent, "ToParent should be true."); + ok(data.toChild, "ToChild should be true."); + }); + + await promise; + }); + }, +}); + +declTest("asyncMessage without both sides", { + async test(browser) { + // If we don't create a parent actor, make sure the parent actor + // gets created by having sent the message. + await ContentTask.spawn(browser, {}, async function () { + let child = content.windowGlobalChild; + let actorChild = child.getActor("TestWindow"); + ok(actorChild, "JSWindowActorChild should have value."); + + let promise = new Promise(resolve => { + actorChild.sendAsyncMessage("init", {}); + actorChild.done = data => resolve(data); + }).then(data => { + ok(data.initial, "Initial should be true."); + ok(data.toParent, "ToParent should be true."); + ok(data.toChild, "ToChild should be true."); + }); + + await promise; + }); + }, +}); + +declTest("asyncMessage can transfer MessagePorts", { + async test(browser) { + await ContentTask.spawn(browser, {}, async function () { + let child = content.windowGlobalChild; + let actorChild = child.getActor("TestWindow"); + + let { port1, port2 } = new MessageChannel(); + actorChild.sendAsyncMessage("messagePort", { port: port2 }, [port2]); + let reply = await new Promise(resolve => { + port1.onmessage = message => { + resolve(message.data); + port1.close(); + }; + }); + + is(reply, "Message sent from parent over a MessagePort."); + }); + }, +}); diff --git a/dom/ipc/tests/JSWindowActor/browser_sendQuery.js b/dom/ipc/tests/JSWindowActor/browser_sendQuery.js new file mode 100644 index 0000000000..0d1a845949 --- /dev/null +++ b/dom/ipc/tests/JSWindowActor/browser_sendQuery.js @@ -0,0 +1,131 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +const ERROR_LINE_NUMBERS = { + jsm: 39, + "sys.mjs": 36, +}; +const EXCEPTION_LINE_NUMBERS = { + jsm: ERROR_LINE_NUMBERS.jsm + 3, + "sys.mjs": ERROR_LINE_NUMBERS["sys.mjs"] + 3, +}; +const ERROR_COLUMN_NUMBERS = { + jsm: 31, + "sys.mjs": 31, +}; +const EXCEPTION_COLUMN_NUMBERS = { + jsm: 22, + "sys.mjs": 22, +}; + +function maybeAsyncStack(offset, column) { + if ( + Services.prefs.getBoolPref( + "javascript.options.asyncstack_capture_debuggee_only" + ) + ) { + return ""; + } + + let stack = Error().stack.replace(/^.*?\n/, ""); + return ( + "JSActor query*" + + stack.replace( + /^([^\n]+?):(\d+):\d+/, + (m0, m1, m2) => `${m1}:${+m2 + offset}:${column}` + ) + ); +} + +declTest("sendQuery Error", { + async test(browser, _window, fileExt) { + let parent = browser.browsingContext.currentWindowGlobal; + let actorParent = parent.getActor("TestWindow"); + + let asyncStack = maybeAsyncStack(2, 8); + let error = await actorParent + .sendQuery("error", { message: "foo" }) + .catch(e => e); + + is(error.message, "foo", "Error should have the correct message"); + is(error.name, "SyntaxError", "Error should have the correct name"); + is( + error.stack, + `receiveMessage@resource://testing-common/TestWindowChild.${fileExt}:${ERROR_LINE_NUMBERS[fileExt]}:${ERROR_COLUMN_NUMBERS[fileExt]}\n` + + asyncStack, + "Error should have the correct stack" + ); + }, +}); + +declTest("sendQuery Exception", { + async test(browser, _window, fileExt) { + let parent = browser.browsingContext.currentWindowGlobal; + let actorParent = parent.getActor("TestWindow"); + + let asyncStack = maybeAsyncStack(2, 8); + let error = await actorParent + .sendQuery("exception", { + message: "foo", + result: Cr.NS_ERROR_INVALID_ARG, + }) + .catch(e => e); + + is(error.message, "foo", "Error should have the correct message"); + is( + error.result, + Cr.NS_ERROR_INVALID_ARG, + "Error should have the correct result code" + ); + is( + error.stack, + `receiveMessage@resource://testing-common/TestWindowChild.${fileExt}:${EXCEPTION_LINE_NUMBERS[fileExt]}:${EXCEPTION_COLUMN_NUMBERS[fileExt]}\n` + + asyncStack, + "Error should have the correct stack" + ); + }, +}); + +declTest("sendQuery testing", { + async test(browser) { + let parent = browser.browsingContext.currentWindowGlobal; + let actorParent = parent.getActor("TestWindow"); + ok(actorParent, "JSWindowActorParent should have value."); + + let { result } = await actorParent.sendQuery("asyncAdd", { a: 10, b: 20 }); + is(result, 30); + }, +}); + +declTest("sendQuery in-process early lifetime", { + url: "about:mozilla", + allFrames: true, + + async test(browser) { + let iframe = browser.contentDocument.createElement("iframe"); + browser.contentDocument.body.appendChild(iframe); + let wgc = iframe.contentWindow.windowGlobalChild; + let actorChild = wgc.getActor("TestWindow"); + let { result } = await actorChild.sendQuery("asyncMul", { a: 10, b: 20 }); + is(result, 200); + }, +}); + +declTest("sendQuery unserializable reply", { + async test(browser) { + let parent = browser.browsingContext.currentWindowGlobal; + let actorParent = parent.getActor("TestWindow"); + ok(actorParent, "JSWindowActorParent should have value"); + + try { + await actorParent.sendQuery("noncloneReply", {}); + ok(false, "expected noncloneReply to be rejected"); + } catch (error) { + ok( + error.message.includes("message reply cannot be cloned"), + "Error should have the correct message" + ); + } + }, +}); diff --git a/dom/ipc/tests/JSWindowActor/browser_uri_combination.js b/dom/ipc/tests/JSWindowActor/browser_uri_combination.js new file mode 100644 index 0000000000..ce46a3e3b6 --- /dev/null +++ b/dom/ipc/tests/JSWindowActor/browser_uri_combination.js @@ -0,0 +1,81 @@ +add_task(function specify_both() { + // Specifying both should throw. + + SimpleTest.doesThrow(() => { + ChromeUtils.registerWindowActor("TestWindow", { + parent: { + moduleURI: "resource://testing-common/TestWindowParent.jsm", + }, + child: { + moduleURI: "resource://testing-common/TestWindowChild.jsm", + esModuleURI: "resource://testing-common/TestWindowChild.sys.mjs", + }, + }); + }, "Should throw if both moduleURI and esModuleURI are specified."); + + SimpleTest.doesThrow(() => { + ChromeUtils.registerWindowActor("TestWindow", { + parent: { + esModuleURI: "resource://testing-common/TestWindowParent.sys.mjs", + }, + child: { + moduleURI: "resource://testing-common/TestWindowChild.jsm", + esModuleURI: "resource://testing-common/TestWindowChild.sys.mjs", + }, + }); + }, "Should throw if both moduleURI and esModuleURI are specified."); + + SimpleTest.doesThrow(() => { + ChromeUtils.registerWindowActor("TestWindow", { + parent: { + moduleURI: "resource://testing-common/TestWindowParent.jsm", + esModuleURI: "resource://testing-common/TestWindowParent.sys.mjs", + }, + child: { + moduleURI: "resource://testing-common/TestWindowChild.jsm", + }, + }); + }, "Should throw if both moduleURI and esModuleURI are specified."); + + SimpleTest.doesThrow(() => { + ChromeUtils.registerWindowActor("TestWindow", { + parent: { + moduleURI: "resource://testing-common/TestWindowParent.jsm", + esModuleURI: "resource://testing-common/TestWindowParent.sys.mjs", + }, + child: { + esModuleURI: "resource://testing-common/TestWindowChild.sys.mjs", + }, + }); + }, "Should throw if both moduleURI and esModuleURI are specified."); +}); + +add_task(function specify_mixed() { + // Mixing JSM and ESM should work. + + try { + ChromeUtils.registerWindowActor("TestWindow", { + parent: { + moduleURI: "resource://testing-common/TestWindowParent.jsm", + }, + child: { + esModuleURI: "resource://testing-common/TestWindowChild.sys.mjs", + }, + }); + } finally { + ChromeUtils.unregisterWindowActor("TestWindow"); + } + + try { + ChromeUtils.registerWindowActor("TestWindow", { + parent: { + esModuleURI: "resource://testing-common/TestWindowParent.sys.mjs", + }, + child: { + moduleURI: "resource://testing-common/TestWindowChild.jsm", + }, + }); + } finally { + ChromeUtils.unregisterWindowActor("TestWindow"); + } +}); diff --git a/dom/ipc/tests/JSWindowActor/file_dummyChromePage.html b/dom/ipc/tests/JSWindowActor/file_dummyChromePage.html new file mode 100644 index 0000000000..c50eddd41f --- /dev/null +++ b/dom/ipc/tests/JSWindowActor/file_dummyChromePage.html @@ -0,0 +1 @@ +<!doctype html> diff --git a/dom/ipc/tests/JSWindowActor/file_mediaPlayback.html b/dom/ipc/tests/JSWindowActor/file_mediaPlayback.html new file mode 100644 index 0000000000..a6979287e2 --- /dev/null +++ b/dom/ipc/tests/JSWindowActor/file_mediaPlayback.html @@ -0,0 +1,2 @@ +<!DOCTYPE html> +<audio src="audio.ogg" controls loop> diff --git a/dom/ipc/tests/JSWindowActor/head.js b/dom/ipc/tests/JSWindowActor/head.js new file mode 100644 index 0000000000..cfabd40aac --- /dev/null +++ b/dom/ipc/tests/JSWindowActor/head.js @@ -0,0 +1,82 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Provide infrastructure for JSWindowActor tests. + */ + +const URL = "about:blank"; +const TEST_URL = "http://test2.example.org/"; +let windowActorOptions = { + jsm: { + parent: { + moduleURI: "resource://testing-common/TestWindowParent.jsm", + }, + child: { + moduleURI: "resource://testing-common/TestWindowChild.jsm", + }, + }, + "sys.mjs": { + parent: { + esModuleURI: "resource://testing-common/TestWindowParent.sys.mjs", + }, + child: { + esModuleURI: "resource://testing-common/TestWindowChild.sys.mjs", + }, + }, +}; + +function declTest(name, cfg) { + declTestWithOptions(name, cfg, "jsm"); + declTestWithOptions(name, cfg, "sys.mjs"); +} + +function declTestWithOptions(name, cfg, fileExt) { + let { + url = "about:blank", + allFrames = false, + includeChrome = false, + matches, + remoteTypes, + messageManagerGroups, + events, + observers, + test, + } = cfg; + + // Build the actor options object which will be used to register & unregister + // our window actor. + let actorOptions = { + parent: { ...windowActorOptions[fileExt].parent }, + child: { ...windowActorOptions[fileExt].child, events, observers }, + }; + actorOptions.allFrames = allFrames; + actorOptions.includeChrome = includeChrome; + if (matches !== undefined) { + actorOptions.matches = matches; + } + if (remoteTypes !== undefined) { + actorOptions.remoteTypes = remoteTypes; + } + if (messageManagerGroups !== undefined) { + actorOptions.messageManagerGroups = messageManagerGroups; + } + + // Add a new task for the actor test declared here. + add_task(async function () { + info("Entering test: " + name); + + // Register our actor, and load a new tab with the relevant URL + ChromeUtils.registerWindowActor("TestWindow", actorOptions); + try { + await BrowserTestUtils.withNewTab(url, async browser => { + info("browser ready"); + await Promise.resolve(test(browser, window, fileExt)); + }); + } finally { + // Unregister the actor after the test is complete. + ChromeUtils.unregisterWindowActor("TestWindow"); + info("Exiting test: " + name); + } + }); +} diff --git a/dom/ipc/tests/blob_verify.sjs b/dom/ipc/tests/blob_verify.sjs new file mode 100644 index 0000000000..c979192bf0 --- /dev/null +++ b/dom/ipc/tests/blob_verify.sjs @@ -0,0 +1,26 @@ +const CC = Components.Constructor; +const BinaryInputStream = CC( + "@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream" +); +const BinaryOutputStream = CC( + "@mozilla.org/binaryoutputstream;1", + "nsIBinaryOutputStream", + "setOutputStream" +); + +function handleRequest(request, response) { + var bodyStream = new BinaryInputStream(request.bodyInputStream); + var bodyBytes = []; + let bodyAvail; + while ((bodyAvail = bodyStream.available()) > 0) { + Array.prototype.push.apply(bodyBytes, bodyStream.readByteArray(bodyAvail)); + } + + var bos = new BinaryOutputStream(response.bodyOutputStream); + + response.processAsync(); + bos.writeByteArray(bodyBytes); + response.finish(); +} diff --git a/dom/ipc/tests/browser.toml b/dom/ipc/tests/browser.toml new file mode 100644 index 0000000000..cd2129e9c1 --- /dev/null +++ b/dom/ipc/tests/browser.toml @@ -0,0 +1,90 @@ +[DEFAULT] +support-files = [ + "file_disableScript.html", + "file_domainPolicy_base.html", + "file_cancel_content_js.html", + "../../media/test/short.mp4", + "../../media/test/owl.mp3", +] + +["browser_CrashService_crash.js"] +skip-if = ["!crashreporter"] + +["browser_ProcessPriorityManager.js"] +# The Process Priority Manager is only enabled for Windows, Linux, and MacOS so far. +# Bug 1522879. +# However, you can still run browser_ProcessPriorityManager.js locally on other +# OSes. This will test the priority manager infrastructure but not actually +# change the priority. +skip-if = ["os == 'android'"] +support-files = [ + "file_cross_frame.html", + "file_dummy.html", + "../../tests/browser/file_coop_coep.html", + "../../tests/browser/file_coop_coep.html^headers^", +] + +["browser_bug1646088.js"] +support-files = ["file_dummy.html"] + +["browser_bug1686194.js"] +support-files = ["file_dummy.html"] + +["browser_cancel_content_js.js"] + +["browser_child_clipboard_restricted.js"] + +["browser_content_shutdown_with_endless_js.js"] +support-files = [ + "file_endless_js.html", + "file_dummy.html", +] + +["browser_crash_oopiframe.js"] +skip-if = [ + "!crashreporter", + "verify", + "os == 'win'", # Bug 1775837 + "os == 'linux'", # Bug 1775837 +] + +["browser_domainPolicy.js"] + +["browser_gc_schedule.js"] +# This test is timing sensitive, timing changes due to asan/tsan/debugging +# can upset it. +skip-if = [ + "verify", + "asan", + "tsan", + "debug", + "os != 'linux'", + "bits != 64", +] + +["browser_hide_tooltip.js"] + +["browser_layers_unloaded_while_interruptingJS.js"] + +["browser_memory_distribution_telemetry.js"] +skip-if = ["true"] + +["browser_pbrowser_creation_failure.js"] + +["browser_subframesPreferUsed.js"] + +["browser_very_fission.js"] +support-files = ["file_dummy.html"] +run-if = ["widget == 'gtk'"] + +["browser_wpi_isolate_everything.js"] +support-files = ["browser_wpi_base.js"] + +["browser_wpi_isolate_high_value.js"] +skip-if = ["!fission"] # Only relevant for fission +support-files = ["browser_wpi_base.js"] + +["browser_wpi_isolate_nothing.js"] +skip-if = ["apple_catalina && debug"] # Bug 1741763; high frequency intermittent; leaked 2 windows + +support-files = ["browser_wpi_base.js"] diff --git a/dom/ipc/tests/browser_CrashService_crash.js b/dom/ipc/tests/browser_CrashService_crash.js new file mode 100644 index 0000000000..0bcbf95410 --- /dev/null +++ b/dom/ipc/tests/browser_CrashService_crash.js @@ -0,0 +1,72 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Ensures that content crashes are reported to the crash service +// (nsICrashService and CrashManager.sys.mjs). + +/* eslint-disable mozilla/no-arbitrary-setTimeout */ +SimpleTest.requestFlakyTimeout("untriaged"); +SimpleTest.requestCompleteLog(); + +add_task(async function () { + let tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + forceNewProcess: true, + }); + + SimpleTest.expectChildProcessCrash(); + + let crashMan = Services.crashmanager; + + // First, clear the crash record store. + info("Waiting for pruneOldCrashes"); + var future = new Date(Date.now() + 1000 * 60 * 60 * 24); + await crashMan.pruneOldCrashes(future); + + var crashDateMS = Date.now(); + + let crashPromise = BrowserTestUtils.crashFrame(tab.linkedBrowser); + + // Finally, poll for the new crash record. + await new Promise((resolve, reject) => { + function tryGetCrash() { + info("Waiting for getCrashes"); + crashMan.getCrashes().then( + function (crashes) { + if (crashes.length) { + is(crashes.length, 1, "There should be only one record"); + var crash = crashes[0]; + ok( + crash.isOfType( + crashMan.processTypes[Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT], + crashMan.CRASH_TYPE_CRASH + ), + "Record should be a content crash" + ); + ok(!!crash.id, "Record should have an ID"); + ok(!!crash.crashDate, "Record should have a crash date"); + var dateMS = crash.crashDate.valueOf(); + var twoMin = 1000 * 60 * 2; + ok( + crashDateMS - twoMin <= dateMS && dateMS <= crashDateMS + twoMin, + `Record's crash date should be nowish: ` + + `now=${crashDateMS} recordDate=${dateMS}` + ); + resolve(); + } else { + setTimeout(tryGetCrash, 1000); + } + }, + function (err) { + reject(err); + } + ); + } + setTimeout(tryGetCrash, 1000); + }); + + await crashPromise; + + await BrowserTestUtils.removeTab(tab); +}); diff --git a/dom/ipc/tests/browser_ProcessPriorityManager.js b/dom/ipc/tests/browser_ProcessPriorityManager.js new file mode 100644 index 0000000000..e1532a53ca --- /dev/null +++ b/dom/ipc/tests/browser_ProcessPriorityManager.js @@ -0,0 +1,963 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const PRIORITY_SET_TOPIC = + "process-priority-manager:TEST-ONLY:process-priority-set"; + +// Copied from Hal.cpp +const PROCESS_PRIORITY_FOREGROUND = "FOREGROUND"; +const PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE = "BACKGROUND_PERCEIVABLE"; +const PROCESS_PRIORITY_BACKGROUND = "BACKGROUND"; + +// This is how many milliseconds we'll wait for a process priority +// change before we assume that it's just not happening. +const WAIT_FOR_CHANGE_TIME_MS = 2000; + +// A convenience function for getting the child ID from a browsing context. +function browsingContextChildID(bc) { + return bc.currentWindowGlobal?.domProcess.childID; +} + +/** + * This class is responsible for watching process priority changes, and + * mapping them to tabs in a single window. + */ +class TabPriorityWatcher { + /** + * Constructing a TabPriorityWatcher should happen before any tests + * start when there's only a single tab in the window. + * + * Callers must call `destroy()` on any instance that is constructed + * when the test is completed. + * + * @param tabbrowser (<tabbrowser>) + * The tabbrowser (gBrowser) for the window to be tested. + */ + constructor(tabbrowser) { + this.tabbrowser = tabbrowser; + Assert.equal( + tabbrowser.tabs.length, + 1, + "TabPriorityWatcher must be constructed in a window " + + "with a single tab to start." + ); + + // This maps from childIDs to process priorities. + this.priorityMap = new Map(); + + // The keys in this map are childIDs we're not expecting to change. + // Each value is an array of priorities we've seen the childID changed + // to since it was added to the map. If the array is empty, there + // have been no changes. + this.noChangeChildIDs = new Map(); + + Services.obs.addObserver(this, PRIORITY_SET_TOPIC); + } + + /** + * Cleans up lingering references for an instance of + * TabPriorityWatcher to avoid leaks. This should be called when + * finishing the test. + */ + destroy() { + Services.obs.removeObserver(this, PRIORITY_SET_TOPIC); + } + + /** + * This returns a Promise that resolves when the process with + * the given childID reaches the given priority. + * This will eventually time out if that priority is never reached. + * + * @param childID + * The childID of the process to wait on. + * @param expectedPriority (String) + * One of the PROCESS_PRIORITY_ constants defined at the + * top of this file. + * @return Promise + * @resolves undefined + * Once the browser reaches the expected priority. + */ + async waitForPriorityChange(childID, expectedPriority) { + await TestUtils.waitForCondition(() => { + let currentPriority = this.priorityMap.get(childID); + if (currentPriority == expectedPriority) { + Assert.ok( + true, + `Process with child ID ${childID} reached expected ` + + `priority: ${currentPriority}` + ); + return true; + } + return false; + }, `Waiting for process with child ID ${childID} to reach priority ${expectedPriority}`); + } + + /** + * Returns a Promise that resolves after a duration of + * WAIT_FOR_CHANGE_TIME_MS. During that time, if the process + * with the passed in child ID changes priority, a test + * failure will be registered. + * + * @param childID + * The childID of the process that we expect to not change priority. + * @return Promise + * @resolves undefined + * Once the WAIT_FOR_CHANGE_TIME_MS duration has passed. + */ + async ensureNoPriorityChange(childID) { + this.noChangeChildIDs.set(childID, []); + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(resolve => setTimeout(resolve, WAIT_FOR_CHANGE_TIME_MS)); + let priorities = this.noChangeChildIDs.get(childID); + Assert.deepEqual( + priorities, + [], + `Should have seen no process priority changes for child ID ${childID}` + ); + this.noChangeChildIDs.delete(childID); + } + + /** + * This returns a Promise that resolves when all of the processes + * of the browsing contexts in the browsing context tree + * of a particular <browser> have reached a particular priority. + * This will eventually time out if that priority is never reached. + * + * @param browser (<browser>) + * The <browser> to get the BC tree from. + * @param expectedPriority (String) + * One of the PROCESS_PRIORITY_ constants defined at the + * top of this file. + * @return Promise + * @resolves undefined + * Once the browser reaches the expected priority. + */ + async waitForBrowserTreePriority(browser, expectedPriority) { + let childIDs = new Set( + browser.browsingContext + .getAllBrowsingContextsInSubtree() + .map(browsingContextChildID) + ); + let promises = []; + for (let childID of childIDs) { + let currentPriority = this.priorityMap.get(childID); + + promises.push( + currentPriority == expectedPriority + ? this.ensureNoPriorityChange(childID) + : this.waitForPriorityChange(childID, expectedPriority) + ); + } + + await Promise.all(promises); + } + + /** + * Synchronously returns the priority of a particular child ID. + * + * @param childID + * The childID to get the content process priority for. + * @return String + * The priority of the child ID's process. + */ + currentPriority(childID) { + return this.priorityMap.get(childID); + } + + /** + * A utility function that takes a string passed via the + * PRIORITY_SET_TOPIC observer notification and extracts the + * childID and priority string. + * + * @param ppmDataString (String) + * The string data passed through the PRIORITY_SET_TOPIC observer + * notification. + * @return Object + * An object with the following properties: + * + * childID (Number) + * The ID of the content process that changed priority. + * + * priority (String) + * The priority that the content process was set to. + */ + parsePPMData(ppmDataString) { + let [childIDStr, priority] = ppmDataString.split(":"); + return { + childID: parseInt(childIDStr, 10), + priority, + }; + } + + /** nsIObserver **/ + observe(subject, topic, data) { + if (topic != PRIORITY_SET_TOPIC) { + Assert.ok(false, "TabPriorityWatcher is observing the wrong topic"); + return; + } + + let { childID, priority } = this.parsePPMData(data); + if (this.noChangeChildIDs.has(childID)) { + this.noChangeChildIDs.get(childID).push(priority); + } + this.priorityMap.set(childID, priority); + } +} + +let gTabPriorityWatcher; + +add_setup(async function () { + // We need to turn on testMode for the process priority manager in + // order to receive the observer notifications that this test relies on. + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.ipc.processPriorityManager.testMode", true], + ["dom.ipc.processPriorityManager.enabled", true], + ], + }); + gTabPriorityWatcher = new TabPriorityWatcher(gBrowser); +}); + +registerCleanupFunction(() => { + gTabPriorityWatcher.destroy(); + gTabPriorityWatcher = null; +}); + +/** + * Utility function that switches the current tabbrowser from one + * tab to another, and ensures that the tab that goes into the background + * has (or reaches) a particular content process priority. + * + * It is expected that the fromTab and toTab belong to two separate content + * processes. + * + * @param Object + * An object with the following properties: + * + * fromTab (<tab>) + * The tab that will be switched from to the toTab. The fromTab + * is the one that will be going into the background. + * + * toTab (<tab>) + * The tab that will be switched to from the fromTab. The toTab + * is presumed to start in the background, and will enter the + * foreground. + * + * fromTabExpectedPriority (String) + * The priority that the content process for the fromTab is + * expected to be (or reach) after the tab goes into the background. + * This should be one of the PROCESS_PRIORITY_ strings defined at the + * top of the file. + * + * @return Promise + * @resolves undefined + * Once the tab switch is complete, and the two content processes for the + * tabs have reached the expected priority levels. + */ +async function assertPriorityChangeOnBackground({ + fromTab, + toTab, + fromTabExpectedPriority, +}) { + let fromBrowser = fromTab.linkedBrowser; + let toBrowser = toTab.linkedBrowser; + + // If the tabs aren't running in separate processes, none of the + // rest of this is going to work. + Assert.notEqual( + toBrowser.frameLoader.remoteTab.osPid, + fromBrowser.frameLoader.remoteTab.osPid, + "Tabs should be running in separate processes." + ); + + let fromPromise = gTabPriorityWatcher.waitForBrowserTreePriority( + fromBrowser, + fromTabExpectedPriority + ); + let toPromise = gTabPriorityWatcher.waitForBrowserTreePriority( + toBrowser, + PROCESS_PRIORITY_FOREGROUND + ); + + await BrowserTestUtils.switchTab(gBrowser, toTab); + await Promise.all([fromPromise, toPromise]); +} + +/** + * Test that if a normal tab goes into the background, + * it has its process priority lowered to PROCESS_PRIORITY_BACKGROUND. + * Additionally, test priorityHint flag sets the process priority + * appropriately to PROCESS_PRIORITY_BACKGROUND and PROCESS_PRIORITY_FOREGROUND. + */ +add_task(async function test_normal_background_tab() { + let originalTab = gBrowser.selectedTab; + + await BrowserTestUtils.withNewTab( + "https://example.com/browser/dom/ipc/tests/file_cross_frame.html", + async browser => { + let tab = gBrowser.getTabForBrowser(browser); + await assertPriorityChangeOnBackground({ + fromTab: tab, + toTab: originalTab, + fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND, + }); + + await assertPriorityChangeOnBackground({ + fromTab: originalTab, + toTab: tab, + fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND, + }); + + let origtabID = browsingContextChildID( + originalTab.linkedBrowser.browsingContext + ); + + Assert.equal( + originalTab.linkedBrowser.frameLoader.remoteTab.priorityHint, + false, + "PriorityHint of the original tab should be false by default" + ); + + // Changing renderLayers doesn't change priority of the background tab. + originalTab.linkedBrowser.preserveLayers(true); + originalTab.linkedBrowser.renderLayers = true; + await new Promise(resolve => + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + setTimeout(resolve, WAIT_FOR_CHANGE_TIME_MS) + ); + Assert.equal( + gTabPriorityWatcher.currentPriority(origtabID), + PROCESS_PRIORITY_BACKGROUND, + "Tab didn't get prioritized only due to renderLayers" + ); + + // Test when priorityHint is true, the original tab priority + // becomes PROCESS_PRIORITY_FOREGROUND. + originalTab.linkedBrowser.frameLoader.remoteTab.priorityHint = true; + Assert.equal( + gTabPriorityWatcher.currentPriority(origtabID), + PROCESS_PRIORITY_FOREGROUND, + "Setting priorityHint to true should set the original tab to foreground priority" + ); + + // Test when priorityHint is false, the original tab priority + // becomes PROCESS_PRIORITY_BACKGROUND. + originalTab.linkedBrowser.frameLoader.remoteTab.priorityHint = false; + await new Promise(resolve => + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + setTimeout(resolve, WAIT_FOR_CHANGE_TIME_MS) + ); + Assert.equal( + gTabPriorityWatcher.currentPriority(origtabID), + PROCESS_PRIORITY_BACKGROUND, + "Setting priorityHint to false should set the original tab to background priority" + ); + + let tabID = browsingContextChildID(tab.linkedBrowser.browsingContext); + + // Test when priorityHint is true, the process priority of the + // active tab remains PROCESS_PRIORITY_FOREGROUND. + tab.linkedBrowser.frameLoader.remoteTab.priorityHint = true; + Assert.equal( + gTabPriorityWatcher.currentPriority(tabID), + PROCESS_PRIORITY_FOREGROUND, + "Setting priorityHint to true should maintain the new tab priority as foreground" + ); + + // Test when priorityHint is false, the process priority of the + // active tab remains PROCESS_PRIORITY_FOREGROUND. + tab.linkedBrowser.frameLoader.remoteTab.priorityHint = false; + Assert.equal( + gTabPriorityWatcher.currentPriority(tabID), + PROCESS_PRIORITY_FOREGROUND, + "Setting priorityHint to false should maintain the new tab priority as foreground" + ); + + originalTab.linkedBrowser.preserveLayers(false); + originalTab.linkedBrowser.renderLayers = false; + } + ); +}); + +// Load a simple page on the given host into a new tab. +async function loadKeepAliveTab(host) { + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + host + "/browser/dom/ipc/tests/file_dummy.html" + ); + let childID = browsingContextChildID( + gBrowser.selectedBrowser.browsingContext + ); + + Assert.equal( + gTabPriorityWatcher.currentPriority(childID), + PROCESS_PRIORITY_FOREGROUND, + "Loading a new tab should make it prioritized" + ); + + if (SpecialPowers.useRemoteSubframes) { + // There must be only one process with a remote type for the tab we loaded + // to ensure that when we load a new page into the iframe with that host + // that it will end up in the same process as the initial tab. + let remoteType = gBrowser.selectedBrowser.remoteType; + await TestUtils.waitForCondition(() => { + return ( + ChromeUtils.getAllDOMProcesses().filter( + process => process.remoteType == remoteType + ).length == 1 + ); + }, `Waiting for there to be only one process with remote type ${remoteType}`); + } + + return { tab, childID }; +} + +const AUDIO_WAKELOCK_NAME = "audio-playing"; +const VIDEO_WAKELOCK_NAME = "video-playing"; + +// This function was copied from toolkit/content/tests/browser/head.js +function wakeLockObserved(powerManager, observeTopic, checkFn) { + return new Promise(resolve => { + function wakeLockListener() {} + wakeLockListener.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsIDOMMozWakeLockListener"]), + callback(topic, state) { + if (topic == observeTopic && checkFn(state)) { + powerManager.removeWakeLockListener(wakeLockListener.prototype); + resolve(); + } + }, + }; + powerManager.addWakeLockListener(wakeLockListener.prototype); + }); +} + +// This function was copied from toolkit/content/tests/browser/head.js +async function waitForExpectedWakeLockState( + topic, + { needLock, isForegroundLock } +) { + const powerManagerService = Cc["@mozilla.org/power/powermanagerservice;1"]; + const powerManager = powerManagerService.getService( + Ci.nsIPowerManagerService + ); + const wakelockState = powerManager.getWakeLockState(topic); + let expectedLockState = "unlocked"; + if (needLock) { + expectedLockState = isForegroundLock + ? "locked-foreground" + : "locked-background"; + } + if (wakelockState != expectedLockState) { + info(`wait until wakelock becomes ${expectedLockState}`); + await wakeLockObserved( + powerManager, + topic, + state => state == expectedLockState + ); + } + is( + powerManager.getWakeLockState(topic), + expectedLockState, + `the wakelock state for '${topic}' is equal to '${expectedLockState}'` + ); +} + +/** + * If an iframe in a foreground tab is navigated to a new page for + * a different site, then the process of the new iframe page should + * have priority PROCESS_PRIORITY_FOREGROUND. Additionally, if Fission + * is enabled, then the old iframe page's process's priority should be + * lowered to PROCESS_PRIORITY_BACKGROUND. + */ +add_task(async function test_iframe_navigate() { + // This test (eventually) loads a page from the host topHost that has an + // iframe from iframe1Host. It then navigates the iframe to iframe2Host. + let topHost = "https://example.com"; + let iframe1Host = "https://example.org"; + let iframe2Host = "https://example.net"; + + // Before we load the final test page into a tab, we need to load pages + // from both iframe hosts into tabs. This is needed so that we are testing + // the "load a new page" part of prioritization and not the "initial + // process load" part. Additionally, it ensures that the process for the + // initial iframe page doesn't shut down once we navigate away from it, + // which will also affect its prioritization. + let { tab: iframe1Tab, childID: iframe1TabChildID } = await loadKeepAliveTab( + iframe1Host + ); + let { tab: iframe2Tab, childID: iframe2TabChildID } = await loadKeepAliveTab( + iframe2Host + ); + + await BrowserTestUtils.withNewTab( + topHost + "/browser/dom/ipc/tests/file_cross_frame.html", + async browser => { + Assert.equal( + gTabPriorityWatcher.currentPriority(iframe2TabChildID), + PROCESS_PRIORITY_BACKGROUND, + "Switching to another new tab should deprioritize the old one" + ); + + let topChildID = browsingContextChildID(browser.browsingContext); + let iframe = browser.browsingContext.children[0]; + let iframe1ChildID = browsingContextChildID(iframe); + + Assert.equal( + gTabPriorityWatcher.currentPriority(topChildID), + PROCESS_PRIORITY_FOREGROUND, + "The top level page in the new tab should be prioritized" + ); + + Assert.equal( + gTabPriorityWatcher.currentPriority(iframe1ChildID), + PROCESS_PRIORITY_FOREGROUND, + "The iframe in the new tab should be prioritized" + ); + + if (SpecialPowers.useRemoteSubframes) { + // Basic process uniqueness checks for the state after all three tabs + // are initially loaded. + Assert.notEqual( + topChildID, + iframe1ChildID, + "file_cross_frame.html should be loaded into a different process " + + "than its initial iframe" + ); + + Assert.notEqual( + topChildID, + iframe2TabChildID, + "file_cross_frame.html should be loaded into a different process " + + "than the tab containing iframe2Host" + ); + + Assert.notEqual( + iframe1ChildID, + iframe2TabChildID, + "The initial iframe loaded by file_cross_frame.html should be " + + "loaded into a different process than the tab containing " + + "iframe2Host" + ); + + // Note: this assertion depends on our process selection logic. + // Specifically, that we reuse an existing process for an iframe if + // possible. + Assert.equal( + iframe1TabChildID, + iframe1ChildID, + "Both pages loaded in iframe1Host should be in the same process" + ); + } + + // Do a cross-origin navigation in the iframe in the foreground tab. + let iframe2URI = iframe2Host + "/browser/dom/ipc/tests/file_dummy.html"; + let loaded = BrowserTestUtils.browserLoaded(browser, true, iframe2URI); + await SpecialPowers.spawn( + iframe, + [iframe2URI], + async function (_iframe2URI) { + content.location = _iframe2URI; + } + ); + await loaded; + + let iframe2ChildID = browsingContextChildID(iframe); + let iframe1Priority = gTabPriorityWatcher.currentPriority(iframe1ChildID); + let iframe2Priority = gTabPriorityWatcher.currentPriority(iframe2ChildID); + + if (SpecialPowers.useRemoteSubframes) { + // Basic process uniqueness check for the state after navigating the + // iframe. There's no need to check the top level pages because they + // have not navigated. + // + // iframe1ChildID != iframe2ChildID is implied by: + // iframe1ChildID != iframe2TabChildID + // iframe2TabChildID == iframe2ChildID + // + // iframe2ChildID != topChildID is implied by: + // topChildID != iframe2TabChildID + // iframe2TabChildID == iframe2ChildID + + // Note: this assertion depends on our process selection logic. + // Specifically, that we reuse an existing process for an iframe if + // possible. If that changes, this test may need to be carefully + // rewritten, as the whole point of the test is to check what happens + // with the priority manager when an iframe shares a process with + // a page in another tab. + Assert.equal( + iframe2TabChildID, + iframe2ChildID, + "Both pages loaded in iframe2Host should be in the same process" + ); + + // Now that we've established the relationship between the various + // processes, we can finally check that the priority manager is doing + // the right thing. + Assert.equal( + iframe1Priority, + PROCESS_PRIORITY_BACKGROUND, + "The old iframe process should have been deprioritized" + ); + } else { + Assert.equal( + iframe1ChildID, + iframe2ChildID, + "Navigation should not have switched processes" + ); + } + + Assert.equal( + iframe2Priority, + PROCESS_PRIORITY_FOREGROUND, + "The new iframe process should be prioritized" + ); + } + ); + + await BrowserTestUtils.removeTab(iframe2Tab); + await BrowserTestUtils.removeTab(iframe1Tab); +}); + +/** + * Test that a cross-group navigation properly preserves the process priority. + * The goal of this test is to check that the code related to mPriorityActive in + * CanonicalBrowsingContext::ReplacedBy works correctly, but in practice the + * prioritization code in SetRenderLayers will also make this test pass, though + * that prioritization happens slightly later. + */ +add_task(async function test_cross_group_navigate() { + // This page is same-site with the page we're going to cross-group navigate to. + let coopPage = + "https://example.com/browser/dom/tests/browser/file_coop_coep.html"; + + // Load it as a top level tab so that we don't accidentally get the initial + // load prioritization. + let backgroundTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + coopPage + ); + let backgroundTabChildID = browsingContextChildID( + gBrowser.selectedBrowser.browsingContext + ); + + Assert.equal( + gTabPriorityWatcher.currentPriority(backgroundTabChildID), + PROCESS_PRIORITY_FOREGROUND, + "Loading a new tab should make it prioritized" + ); + + await BrowserTestUtils.withNewTab( + "https://example.org/browser/dom/ipc/tests/file_cross_frame.html", + async browser => { + Assert.equal( + gTabPriorityWatcher.currentPriority(backgroundTabChildID), + PROCESS_PRIORITY_BACKGROUND, + "Switching to a new tab should deprioritize the old one" + ); + + let dotOrgChildID = browsingContextChildID(browser.browsingContext); + + // Do a cross-group navigation. + BrowserTestUtils.startLoadingURIString(browser, coopPage); + await BrowserTestUtils.browserLoaded(browser); + + let coopChildID = browsingContextChildID(browser.browsingContext); + let coopPriority = gTabPriorityWatcher.currentPriority(coopChildID); + let dotOrgPriority = gTabPriorityWatcher.currentPriority(dotOrgChildID); + + Assert.equal( + backgroundTabChildID, + coopChildID, + "The same site should get loaded into the same process" + ); + Assert.notEqual( + dotOrgChildID, + coopChildID, + "Navigation should have switched processes" + ); + Assert.equal( + dotOrgPriority, + PROCESS_PRIORITY_BACKGROUND, + "The old page process should have been deprioritized" + ); + Assert.equal( + coopPriority, + PROCESS_PRIORITY_FOREGROUND, + "The new page process should be prioritized" + ); + } + ); + + await BrowserTestUtils.removeTab(backgroundTab); +}); + +/** + * Test that if a tab with video goes into the background, + * it has its process priority lowered to + * PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE if it has no audio, + * and that it has its priority remain at + * PROCESS_PRIORITY_FOREGROUND if it does have audio. + */ +add_task(async function test_video_background_tab() { + let originalTab = gBrowser.selectedTab; + + await BrowserTestUtils.withNewTab("https://example.com", async browser => { + // Let's load up a video in the tab, but mute it, so that this tab should + // reach PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE. We need to wait for the + // wakelock changes from the unmuting to get back up to the parent. + await SpecialPowers.spawn(browser, [], async () => { + let video = content.document.createElement("video"); + video.src = "https://example.net/browser/dom/ipc/tests/short.mp4"; + video.muted = true; + content.document.body.appendChild(video); + // We'll loop the video to avoid it ending before the test is done. + video.loop = true; + await video.play(); + }); + await Promise.all([ + waitForExpectedWakeLockState(AUDIO_WAKELOCK_NAME, { + needLock: false, + }), + waitForExpectedWakeLockState(VIDEO_WAKELOCK_NAME, { + needLock: true, + isForegroundLock: true, + }), + ]); + + let tab = gBrowser.getTabForBrowser(browser); + + // The tab with the muted video should reach + // PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE when backgrounded. + await assertPriorityChangeOnBackground({ + fromTab: tab, + toTab: originalTab, + fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE, + }); + + // Now switch back. The initial blank tab should reach + // PROCESS_PRIORITY_BACKGROUND when backgrounded. + await assertPriorityChangeOnBackground({ + fromTab: originalTab, + toTab: tab, + fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND, + }); + + // Let's unmute the video now. We need to wait for the wakelock change from + // the unmuting to get back up to the parent. + await Promise.all([ + waitForExpectedWakeLockState(AUDIO_WAKELOCK_NAME, { + needLock: true, + isForegroundLock: true, + }), + SpecialPowers.spawn(browser, [], async () => { + let video = content.document.querySelector("video"); + video.muted = false; + }), + ]); + + // The tab with the unmuted video should stay at + // PROCESS_PRIORITY_FOREGROUND when backgrounded. + await assertPriorityChangeOnBackground({ + fromTab: tab, + toTab: originalTab, + fromTabExpectedPriority: PROCESS_PRIORITY_FOREGROUND, + }); + + // Now switch back. The initial blank tab should reach + // PROCESS_PRIORITY_BACKGROUND when backgrounded. + await assertPriorityChangeOnBackground({ + fromTab: originalTab, + toTab: tab, + fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND, + }); + }); +}); + +/** + * Test that if a tab with a playing <audio> element goes into + * the background, the process priority does not change, unless + * that audio is muted (in which case, it reaches + * PROCESS_PRIORITY_BACKGROUND). + */ +add_task(async function test_audio_background_tab() { + let originalTab = gBrowser.selectedTab; + + await BrowserTestUtils.withNewTab("https://example.com", async browser => { + // Let's load up some audio in the tab, but mute it, so that this tab should + // reach PROCESS_PRIORITY_BACKGROUND. + await SpecialPowers.spawn(browser, [], async () => { + let audio = content.document.createElement("audio"); + audio.src = "https://example.net/browser/dom/ipc/tests/owl.mp3"; + audio.muted = true; + content.document.body.appendChild(audio); + // We'll loop the audio to avoid it ending before the test is done. + audio.loop = true; + await audio.play(); + }); + + let tab = gBrowser.getTabForBrowser(browser); + + // The tab with the muted audio should reach + // PROCESS_PRIORITY_BACKGROUND when backgrounded. + await assertPriorityChangeOnBackground({ + fromTab: tab, + toTab: originalTab, + fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND, + }); + + // Now switch back. The initial blank tab should reach + // PROCESS_PRIORITY_BACKGROUND when backgrounded. + await assertPriorityChangeOnBackground({ + fromTab: originalTab, + toTab: tab, + fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND, + }); + + // Now unmute the audio. Unfortuntely, there's a bit of a race here, + // since the wakelock on the audio element is released and then + // re-acquired if the audio reaches its end and loops around. This + // will cause an unexpected priority change on the content process. + // + // To avoid this race, we'll seek the audio back to the beginning, + // and lower its playback rate to the minimum to increase the + // likelihood that the check completes before the audio loops around. + await SpecialPowers.spawn(browser, [], async () => { + let audio = content.document.querySelector("audio"); + let seeked = ContentTaskUtils.waitForEvent(audio, "seeked"); + audio.muted = false; + // 0.25 is the minimum playback rate that still keeps the audio audible. + audio.playbackRate = 0.25; + audio.currentTime = 0; + await seeked; + }); + + // The tab with the unmuted audio should stay at + // PROCESS_PRIORITY_FOREGROUND when backgrounded. + await assertPriorityChangeOnBackground({ + fromTab: tab, + toTab: originalTab, + fromTabExpectedPriority: PROCESS_PRIORITY_FOREGROUND, + }); + + // Now switch back. The initial blank tab should reach + // PROCESS_PRIORITY_BACKGROUND when backgrounded. + await assertPriorityChangeOnBackground({ + fromTab: originalTab, + toTab: tab, + fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND, + }); + }); +}); + +/** + * Test that if a tab with a WebAudio playing goes into the background, + * the process priority does not change, unless that WebAudio context is + * suspended. + */ +add_task(async function test_web_audio_background_tab() { + let originalTab = gBrowser.selectedTab; + + await BrowserTestUtils.withNewTab("https://example.com", async browser => { + // Let's synthesize a basic square wave as WebAudio. + await SpecialPowers.spawn(browser, [], async () => { + let audioCtx = new content.AudioContext(); + let oscillator = audioCtx.createOscillator(); + oscillator.type = "square"; + oscillator.frequency.setValueAtTime(440, audioCtx.currentTime); + oscillator.connect(audioCtx.destination); + oscillator.start(); + while (audioCtx.state != "running") { + info(`wait until AudioContext starts running`); + await new Promise(r => (audioCtx.onstatechange = r)); + } + // we'll stash the AudioContext away so that it's easier to access + // in the next SpecialPowers.spawn. + content.audioCtx = audioCtx; + }); + + let tab = gBrowser.getTabForBrowser(browser); + + // The tab with the WebAudio should stay at + // PROCESS_PRIORITY_FOREGROUND when backgrounded. + await assertPriorityChangeOnBackground({ + fromTab: tab, + toTab: originalTab, + fromTabExpectedPriority: PROCESS_PRIORITY_FOREGROUND, + }); + + // Now switch back. The initial blank tab should reach + // PROCESS_PRIORITY_BACKGROUND when backgrounded. + await assertPriorityChangeOnBackground({ + fromTab: originalTab, + toTab: tab, + fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND, + }); + + // Now suspend the WebAudio. This will cause it to stop + // playing. + await SpecialPowers.spawn(browser, [], async () => { + content.audioCtx.suspend(); + }); + + // The tab with the suspended WebAudio should reach + // PROCESS_PRIORITY_BACKGROUND when backgrounded. + await assertPriorityChangeOnBackground({ + fromTab: tab, + toTab: originalTab, + fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND, + }); + + // Now switch back. The initial blank tab should reach + // PROCESS_PRIORITY_BACKGROUND when backgrounded. + await assertPriorityChangeOnBackground({ + fromTab: originalTab, + toTab: tab, + fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND, + }); + }); +}); + +/** + * Test that foreground tab's process priority isn't changed when going back to + * a bfcached session history entry. + */ +add_task(async function test_audio_background_tab() { + let page1 = "https://example.com"; + let page2 = page1 + "/?2"; + + await BrowserTestUtils.withNewTab(page1, async browser => { + let childID = browsingContextChildID(browser.browsingContext); + Assert.equal( + gTabPriorityWatcher.currentPriority(childID), + PROCESS_PRIORITY_FOREGROUND, + "Loading a new tab should make it prioritized." + ); + let loaded = BrowserTestUtils.browserLoaded(browser, false, page2); + BrowserTestUtils.startLoadingURIString(browser, page2); + await loaded; + + childID = browsingContextChildID(browser.browsingContext); + Assert.equal( + gTabPriorityWatcher.currentPriority(childID), + PROCESS_PRIORITY_FOREGROUND, + "Loading a new page should keep the tab prioritized." + ); + + let pageShowPromise = BrowserTestUtils.waitForContentEvent( + browser, + "pageshow" + ); + browser.goBack(); + await pageShowPromise; + + childID = browsingContextChildID(browser.browsingContext); + Assert.equal( + gTabPriorityWatcher.currentPriority(childID), + PROCESS_PRIORITY_FOREGROUND, + "Loading a page from the bfcache should keep the tab prioritized." + ); + }); +}); diff --git a/dom/ipc/tests/browser_bug1646088.js b/dom/ipc/tests/browser_bug1646088.js new file mode 100644 index 0000000000..18e9afb49e --- /dev/null +++ b/dom/ipc/tests/browser_bug1646088.js @@ -0,0 +1,71 @@ +let dir = getChromeDir(getResolvedURI(gTestPath)); +dir.append("file_dummy.html"); +const uriString = Services.io.newFileURI(dir).spec; + +add_task(async function () { + await BrowserTestUtils.withNewTab( + "https://example.com", + async function (browser) { + // Override the browser's `prepareToChangeRemoteness` so that we can delay + // the process switch for an indefinite amount of time. This will allow us + // to control the timing of the resolve call to trigger the bug. + let prepareToChangeCalled = Promise.withResolvers(); + let finishSwitch = Promise.withResolvers(); + let oldPrepare = browser.prepareToChangeRemoteness; + browser.prepareToChangeRemoteness = async () => { + prepareToChangeCalled.resolve(); + await oldPrepare.call(browser); + await finishSwitch.promise; + }; + + // Begin a process switch, which should cause `prepareToChangeRemoteness` to + // be called. + // NOTE: This used to avoid BrowserTestUtils.loadURI, as that call would + // previously eagerly perform a process switch meaning that the interesting + // codepath wouldn't be triggered. Nowadays the process switch codepath + // always happens during navigation as required by this test. + info("Beginning process switch into file URI process"); + let browserLoaded = BrowserTestUtils.browserLoaded(browser); + BrowserTestUtils.startLoadingURIString(browser, uriString); + await prepareToChangeCalled.promise; + + // The tab we opened is now midway through process switching. Open another + // browser within the same tab, and immediately close it after the load + // finishes. + info("Creating new tab loaded in file URI process"); + let fileProcess; + let browserParentDestroyed = Promise.withResolvers(); + await BrowserTestUtils.withNewTab( + uriString, + async function (otherBrowser) { + let remoteTab = otherBrowser.frameLoader.remoteTab; + fileProcess = remoteTab.contentProcessId; + info("Loaded test URI in pid: " + fileProcess); + + browserParentDestroyed.resolve( + TestUtils.topicObserved( + "ipc:browser-destroyed", + subject => subject === remoteTab + ) + ); + } + ); + await browserParentDestroyed.promise; + + // This browser has now been closed, which could cause the file content + // process to begin shutting down, despite us process switching into it. + // We can now allow the process switch to finish, and wait for the load to + // finish as well. + info("BrowserParent has been destroyed, finishing process switch"); + finishSwitch.resolve(); + await browserLoaded; + + info("Load complete"); + is( + browser.frameLoader.remoteTab.contentProcessId, + fileProcess, + "Should have loaded in the same file URI process" + ); + } + ); +}); diff --git a/dom/ipc/tests/browser_bug1686194.js b/dom/ipc/tests/browser_bug1686194.js new file mode 100644 index 0000000000..f6acefa2f2 --- /dev/null +++ b/dom/ipc/tests/browser_bug1686194.js @@ -0,0 +1,47 @@ +/* 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/. */ + +"use strict"; + +const TEST_PAGE = + "http://mochi.test:8888/browser/dom/ipc/tests/file_dummy.html"; + +function untilPageTitleChanged() { + return new Promise(resolve => + gBrowser.addEventListener("pagetitlechanged", resolve, { once: true }) + ); +} + +add_task(async () => { + const tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: TEST_PAGE, + }); + + const { linkedBrowser } = tab; + ok( + tab.getAttribute("label").includes("file_dummy.html"), + "The title should be the raw path" + ); + + await Promise.all([ + SpecialPowers.spawn(linkedBrowser, [], function () { + content.document.title = "Title"; + }), + untilPageTitleChanged(), + ]); + + is(tab.getAttribute("label"), "Title", "The title should change"); + + linkedBrowser.reload(); + + await untilPageTitleChanged(); + + ok( + tab.getAttribute("label").includes("file_dummy.html"), + "The title should be the raw path again" + ); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/dom/ipc/tests/browser_cancel_content_js.js b/dom/ipc/tests/browser_cancel_content_js.js new file mode 100644 index 0000000000..4cb00cd468 --- /dev/null +++ b/dom/ipc/tests/browser_cancel_content_js.js @@ -0,0 +1,65 @@ +/* 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/. */ + +"use strict"; + +requestLongerTimeout(10); + +const TEST_PAGE = + "http://mochi.test:8888/browser/dom/ipc/tests/file_cancel_content_js.html"; +const NEXT_PAGE = "http://mochi.test:8888/browser/dom/ipc/tests/"; +const JS_URI = "javascript:void(document.title = 'foo')"; + +async function test_navigation(nextPage, shouldCancel) { + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.max_script_run_time", 20], + // Force a single process so that the navigation will complete in the same + // process as the previous page which is running the long-running script. + ["dom.ipc.processCount", 1], + ["dom.ipc.processCount.webIsolated", 1], + ], + }); + let tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: TEST_PAGE, + }); + + const loopEnded = ContentTask.spawn(tab.linkedBrowser, [], async function () { + await new Promise(resolve => { + content.addEventListener("LongLoopEnded", resolve, { + once: true, + }); + }); + }); + + // Wait for the test page's long-running JS loop to start. + await ContentTask.spawn(tab.linkedBrowser, [], function () { + content.dispatchEvent(new content.Event("StartLongLoop")); + }); + + info(`navigating to ${nextPage}`); + const nextPageLoaded = BrowserTestUtils.waitForContentEvent( + tab.linkedBrowser, + "DOMTitleChanged" + ); + BrowserTestUtils.startLoadingURIString(gBrowser, nextPage); + + const result = await Promise.race([ + nextPageLoaded, + loopEnded.then(() => "timeout"), + ]); + + const timedOut = result === "timeout"; + if (shouldCancel) { + Assert.strictEqual(timedOut, false, "expected next page to be loaded"); + } else { + Assert.strictEqual(timedOut, true, "expected timeout"); + } + + BrowserTestUtils.removeTab(tab); +} + +add_task(async () => test_navigation(NEXT_PAGE, true)); +add_task(async () => test_navigation(JS_URI, false)); diff --git a/dom/ipc/tests/browser_child_clipboard_restricted.js b/dom/ipc/tests/browser_child_clipboard_restricted.js new file mode 100644 index 0000000000..be2d1bca9c --- /dev/null +++ b/dom/ipc/tests/browser_child_clipboard_restricted.js @@ -0,0 +1,93 @@ +/* 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/. */ +add_task(async function () { + // Create a new content tab to make sure the paste is cross-process. + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "data:text/html,<br>" + ); + let browser = tab.linkedBrowser; + + await SpecialPowers.spawn(browser, [], async function (arg) { + const trans = Cc["@mozilla.org/widget/transferable;1"].createInstance( + Ci.nsITransferable + ); + trans.init(null); + + const string = Cc["@mozilla.org/supports-string;1"].createInstance( + Ci.nsISupportsString + ); + string.data = "blablabla"; + + trans.addDataFlavor("text/unknown"); + trans.setTransferData("text/unknown", string); + + trans.addDataFlavor("text/plain"); + trans.setTransferData("text/plain", string); + + // Write to clipboard. + Services.clipboard.setData(trans, null, Ci.nsIClipboard.kGlobalClipboard); + }); + + // Wait for known. + for (var i = 0; i < 20; i++) { + if ( + Services.clipboard.hasDataMatchingFlavors( + ["text/plain"], + Services.clipboard.kGlobalClipboard + ) + ) { + break; + } + } + + function readClipboard(flavor) { + const trans = Cc["@mozilla.org/widget/transferable;1"].createInstance( + Ci.nsITransferable + ); + trans.init(null); + trans.addDataFlavor(flavor); + Services.clipboard.getData( + trans, + Services.clipboard.kGlobalClipboard, + SpecialPowers.wrap(window).browsingContext.currentWindowContext + ); + + let data = {}; + trans.getTransferData(flavor, data); + return data.value.QueryInterface(Ci.nsISupportsString).data; + } + + ok( + Services.clipboard.hasDataMatchingFlavors( + ["text/plain"], + Services.clipboard.kGlobalClipboard + ), + "clipboard should have text/plain" + ); + + is( + readClipboard("text/plain"), + "blablabla", + "matching string for text/plain" + ); + + ok( + !Services.clipboard.hasDataMatchingFlavors( + ["text/unknown"], + Services.clipboard.kGlobalClipboard + ), + "clipboard should not have text/unknown" + ); + + let error = undefined; + try { + readClipboard("text/unknown"); + } catch (e) { + error = e; + } + is(typeof error, "object", "reading text/unknown should fail"); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/dom/ipc/tests/browser_content_shutdown_with_endless_js.js b/dom/ipc/tests/browser_content_shutdown_with_endless_js.js new file mode 100644 index 0000000000..bdec55a12c --- /dev/null +++ b/dom/ipc/tests/browser_content_shutdown_with_endless_js.js @@ -0,0 +1,86 @@ +/* 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/. */ + +"use strict"; + +const EMPTY_PAGE = + "http://mochi.test:8888/browser/dom/ipc/tests/file_dummy.html"; + +const HANG_PAGE = + "http://mochi.test:8888/browser/dom/ipc/tests/file_endless_js.html"; + +function pushPref(name, val) { + return SpecialPowers.pushPrefEnv({ set: [[name, val]] }); +} + +async function createAndShutdownContentProcess(url) { + info("Create and shutdown a content process for " + url); + + // Launch a new process and load url. Sets up a promise that will resolve + // on shutdown. + let browserDestroyed = Promise.withResolvers(); + await BrowserTestUtils.withNewTab( + { + gBrowser, + opening: url, + waitForLoad: true, + forceNewProcess: true, + }, + async function (otherBrowser) { + let remoteTab = otherBrowser.frameLoader.remoteTab; + + ok(true, "Content process created."); + + browserDestroyed.resolve( + TestUtils.topicObserved( + "ipc:browser-destroyed", + subject => subject === remoteTab + ) + ); + + // Trigger onmessage in the content browser + await SpecialPowers.spawn(otherBrowser, [], function () { + content.postMessage("LoadedMessage", "*"); + }); + + // Give the content process some extra time before we start its shutdown. + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(resolve => setTimeout(resolve, 50)); + + // withNewTab will start the shutdown of the child process for us + } + ); + + // Now wait for it to really shut down. + // If the HANG_PAGE JS is not canceled we will hang here. + await browserDestroyed.promise; + + // If we do not hang and get here, we are fine. + ok(true, "Shutdown of content process."); +} + +add_task(async () => { + // This test is only relevant in e10s. + if (!gMultiProcessBrowser) { + ok(true, "We are not in multiprocess mode, skipping test."); + return; + } + + await pushPref("dom.abort_script_on_child_shutdown", true); + + // Ensure the process cache cannot interfere. + pushPref("dom.ipc.processPreload.enabled", false); + // Ensure we have no cached processes from previous tests. + Services.ppmm.releaseCachedProcesses(); + + // First let's do a dry run that should always succeed. + await createAndShutdownContentProcess(EMPTY_PAGE); + + // Now we will start a shutdown of our content process while our content + // script is running an endless loop. + // + // If the JS does not get interrupted on shutdown, it will cause this test + // to hang. + await createAndShutdownContentProcess(HANG_PAGE); +}); diff --git a/dom/ipc/tests/browser_crash_oopiframe.js b/dom/ipc/tests/browser_crash_oopiframe.js new file mode 100644 index 0000000000..9161b874a9 --- /dev/null +++ b/dom/ipc/tests/browser_crash_oopiframe.js @@ -0,0 +1,245 @@ +"use strict"; + +/** + * Opens a number of tabs containing an out-of-process iframe. + * + * @param numTabs the number of tabs to open. + * @returns the browsing context of the iframe in the last tab opened. + */ +async function openTestTabs(numTabs) { + let iframeBC = null; + + for (let count = 0; count < numTabs; count++) { + let tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + url: "about:blank", + }); + + // If we load example.com in an injected subframe, we assume that this + // will load in its own subprocess, which we can then crash. + iframeBC = await SpecialPowers.spawn(tab.linkedBrowser, [], async () => { + let iframe = content.document.createElement("iframe"); + iframe.setAttribute("src", "http://example.com"); + + content.document.body.appendChild(iframe); + await ContentTaskUtils.waitForEvent(iframe, "load"); + return iframe.frameLoader.browsingContext; + }); + } + + return iframeBC; +} + +/** + * Helper function for testing frame crashing. Some tabs are opened + * containing frames from example.com and then the process for + * example.com is crashed. Notifications should apply to each tab + * and all should close when one of the notifications is closed. + * + * @param numTabs the number of tabs to open. + */ +async function testFrameCrash(numTabs) { + let iframeBC = await openTestTabs(numTabs); + let browser = gBrowser.selectedBrowser; + let rootBC = browser.browsingContext; + + is(iframeBC.parent, rootBC, "oop frame has root as parent"); + + let eventFiredPromise = BrowserTestUtils.waitForEvent( + browser, + "oop-browser-crashed" + ); + + BrowserTestUtils.crashFrame( + browser, + true /* shouldShowTabCrashPage */, + true /* shouldClearMinidumps */, + iframeBC + ); + + let notificationPromise = BrowserTestUtils.waitForNotificationBar( + gBrowser, + browser, + "subframe-crashed" + ); + + info("Waiting for oop-browser-crashed event."); + await eventFiredPromise.then(event => { + ok(!event.isTopFrame, "should not be reporting top-level frame crash"); + Assert.notEqual(event.childID, 0, "childID is non-zero"); + + isnot( + event.browsingContextId, + rootBC, + "top frame browsing context id not expected." + ); + + is( + event.browsingContextId, + iframeBC.id, + "oop frame browsing context id expected." + ); + }); + + if (numTabs == 1) { + // The BrowsingContext is re-used, but the window global might still be + // getting set up at this point, so wait until it's been initialized. + let { subject: windowGlobal } = await BrowserUtils.promiseObserved( + "window-global-created", + wgp => wgp.documentURI.spec.startsWith("about:framecrashed") + ); + + is( + windowGlobal, + iframeBC.currentWindowGlobal, + "Resolved on expected window global" + ); + + let newIframeURI = await SpecialPowers.spawn(iframeBC, [], async () => { + return content.document.documentURI; + }); + + ok( + newIframeURI.startsWith("about:framecrashed"), + "The iframe is now pointing at about:framecrashed" + ); + + let title = await SpecialPowers.spawn(iframeBC, [], async () => { + await content.document.l10n.ready; + return content.document.documentElement.getAttribute("title"); + }); + ok(title, "The iframe has a non-empty tooltip."); + } + + // Next, check that the crash notification bar has appeared. + await notificationPromise; + + for (let count = 1; count <= numTabs; count++) { + let notificationBox = gBrowser.getNotificationBox(gBrowser.browsers[count]); + let notification = notificationBox.currentNotification; + ok(notification, "Notification " + count + " should be visible"); + is( + notification.getAttribute("value"), + "subframe-crashed", + "Should be showing the right notification" + count + ); + + let buttons = notification.buttonContainer.querySelectorAll( + ".notification-button" + ); + is( + buttons.length, + 1, + "Notification " + count + " should have only one button." + ); + let links = notification.supportLinkEls; + is( + links.length, + 1, + "Notification " + count + " should have only one link." + ); + ok( + notification.messageText.textContent.length, + "Notification " + count + " should have a crash msg." + ); + } + + // Press the ignore button on the visible notification. + let notificationBox = gBrowser.getNotificationBox(gBrowser.selectedBrowser); + let notification = notificationBox.currentNotification; + + // Make sure all of the notifications were closed when one of them was closed. + let closedPromises = []; + for (let count = 1; count <= numTabs; count++) { + let nb = gBrowser.getNotificationBox(gBrowser.browsers[count]); + closedPromises.push( + BrowserTestUtils.waitForMutationCondition( + nb.stack, + { childList: true }, + () => !nb.currentNotification + ) + ); + } + + notification.dismiss(); + await Promise.all(closedPromises); + + for (let count = 1; count <= numTabs; count++) { + BrowserTestUtils.removeTab(gBrowser.selectedTab); + } +} + +/** + * In this test, we crash an out-of-process iframe and + * verify that : + * 1. the "oop-browser-crashed" event is dispatched with + * the browsing context of the crashed oop subframe. + * 2. the crashed subframe is now pointing at "about:framecrashed" + * page. + */ +add_task(async function test_crashframe() { + // Open a new window with fission enabled. + ok( + SpecialPowers.useRemoteSubframes, + "This test only makes sense of we can use OOP iframes." + ); + + // Create the crash reporting directory if it doesn't yet exist, otherwise, a failure + // sometimes occurs. See bug 1687855 for fixing this. + const uAppDataPath = Services.dirsvc.get("UAppData", Ci.nsIFile).path; + let path = PathUtils.join(uAppDataPath, "Crash Reports", "pending"); + await IOUtils.makeDirectory(path, { ignoreExisting: true }); + + // Test both one tab and when four tabs are opened. + await testFrameCrash(1); + await testFrameCrash(4); +}); + +// This test checks that no notification shows when there is no minidump available. It +// simulates the steps that occur during a crash, once with a dumpID and once without. +add_task(async function test_nominidump() { + for (let dumpID of [null, "8888"]) { + let iframeBC = await openTestTabs(1); + + let childID = iframeBC.currentWindowGlobal.domProcess.childID; + + let notificationPromise; + if (dumpID) { + notificationPromise = BrowserTestUtils.waitForNotificationBar( + gBrowser, + gBrowser.selectedBrowser, + "subframe-crashed" + ); + } + + gBrowser.selectedBrowser.dispatchEvent( + new FrameCrashedEvent("oop-browser-crashed", { + browsingContextID: iframeBC, + childID, + isTopFrame: false, + bubbles: true, + }) + ); + + let bag = Cc["@mozilla.org/hash-property-bag;1"].createInstance( + Ci.nsIWritablePropertyBag + ); + bag.setProperty("abnormal", "true"); + bag.setProperty("childID", iframeBC.currentWindowGlobal.domProcess.childID); + if (dumpID) { + bag.setProperty("dumpID", dumpID); + } + + Services.obs.notifyObservers(bag, "ipc:content-shutdown"); + + await notificationPromise; + let notificationBox = gBrowser.getNotificationBox(gBrowser.selectedBrowser); + let notification = notificationBox.currentNotification; + ok( + dumpID ? notification : !notification, + "notification shown for browser with no minidump" + ); + + BrowserTestUtils.removeTab(gBrowser.selectedTab); + } +}); diff --git a/dom/ipc/tests/browser_domainPolicy.js b/dom/ipc/tests/browser_domainPolicy.js new file mode 100644 index 0000000000..988288f950 --- /dev/null +++ b/dom/ipc/tests/browser_domainPolicy.js @@ -0,0 +1,187 @@ +// This test waits for a lot of subframe loads, causing it to take a long time, +// especially with Fission enabled. +requestLongerTimeout(2); + +const BASE_FILE = + "http://mochi.test:8888/browser/dom/ipc/tests/file_domainPolicy_base.html"; +const SCRIPT_PATH = "/browser/dom/ipc/tests/file_disableScript.html"; + +const TEST_POLICY = { + exceptions: ["http://test1.example.com", "http://example.com"], + superExceptions: ["http://test2.example.org", "https://test1.example.com"], + exempt: [ + "http://test1.example.com", + "http://example.com", + "http://test2.example.org", + "http://sub1.test2.example.org", + "https://sub1.test1.example.com", + ], + notExempt: [ + "http://test2.example.com", + "http://sub1.test1.example.com", + "http://www.example.com", + "https://test2.example.com", + "https://example.com", + "http://test1.example.org", + ], +}; + +// To make sure we never leave up an activated domain policy after a failed +// test, let's make this global. +var policy; + +function activateDomainPolicy(isBlock) { + policy = Services.scriptSecurityManager.activateDomainPolicy(); + + if (isBlock === undefined) { + return; + } + + let set = isBlock ? policy.blocklist : policy.allowlist; + for (let e of TEST_POLICY.exceptions) { + set.add(makeURI(e)); + } + + let superSet = isBlock ? policy.superBlocklist : policy.superAllowlist; + for (let e of TEST_POLICY.superExceptions) { + superSet.add(makeURI(e)); + } +} + +function deactivateDomainPolicy() { + if (policy) { + policy.deactivate(); + policy = null; + } +} + +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [["browser.pagethumbnails.capturing_disabled", false]], + }); + + registerCleanupFunction(() => { + deactivateDomainPolicy(); + }); +}); + +add_task(async function test_domainPolicy() { + function test(testFunc, { activateFirst, isBlock }) { + if (activateFirst) { + activateDomainPolicy(isBlock); + } + return BrowserTestUtils.withNewTab( + { + gBrowser, + opening: BASE_FILE, + forceNewProcess: true, + }, + async browser => { + if (!activateFirst) { + activateDomainPolicy(isBlock); + } + await testFunc(browser); + deactivateDomainPolicy(); + } + ); + } + + async function testDomain(browser, domain, expectEnabled = false) { + function navigateFrame() { + let url = domain + SCRIPT_PATH; + return SpecialPowers.spawn(browser, [url], async src => { + let iframe = content.document.getElementById("root"); + await new Promise(resolve => { + iframe.addEventListener("load", resolve, { once: true }); + iframe.src = src; + }); + return iframe.browsingContext; + }); + } + + function checkScriptEnabled(bc) { + return SpecialPowers.spawn(bc, [expectEnabled], enabled => { + content.wrappedJSObject.gFiredOnclick = false; + content.document.body.dispatchEvent(new content.Event("click")); + Assert.equal( + content.wrappedJSObject.gFiredOnclick, + enabled, + `Checking script-enabled for ${content.name} (${content.location})` + ); + }); + } + + let browsingContext = await navigateFrame(); + return checkScriptEnabled(browsingContext); + } + + async function testList(browser, list, expectEnabled) { + // Run these sequentially to avoid navigating multiple domains at once. + for (let domain of list) { + await testDomain(browser, domain, expectEnabled); + } + } + + info("1. Testing simple blocklist policy"); + + info("1A. Creating child process first, activating domainPolicy after"); + await test( + async browser => { + policy.blocklist.add(Services.io.newURI("http://example.com")); + await testDomain(browser, "http://example.com"); + }, + { activateFirst: false } + ); + + info("1B. Activating domainPolicy first, creating child process after"); + await test( + async browser => { + policy.blocklist.add(Services.io.newURI("http://example.com")); + await testDomain(browser, "http://example.com"); + }, + { activateFirst: true } + ); + + info("2. Testing Blocklist-style Domain Policy"); + + info("2A. Activating domainPolicy first, creating child process after"); + await test( + async browser => { + await testList(browser, TEST_POLICY.notExempt, true); + await testList(browser, TEST_POLICY.exempt, false); + }, + { activateFirst: true, isBlock: true } + ); + + info("2B. Creating child process first, activating domainPolicy after"); + await test( + async browser => { + await testList(browser, TEST_POLICY.notExempt, true); + await testList(browser, TEST_POLICY.exempt, false); + }, + { activateFirst: false, isBlock: true } + ); + + info("3. Testing Allowlist-style Domain Policy"); + await SpecialPowers.pushPrefEnv({ set: [["javascript.enabled", false]] }); + + info("3A. Activating domainPolicy first, creating child process after"); + await test( + async browser => { + await testList(browser, TEST_POLICY.notExempt, false); + await testList(browser, TEST_POLICY.exempt, true); + }, + { activateFirst: true, isBlock: false } + ); + + info("3B. Creating child process first, activating domainPolicy after"); + await test( + async browser => { + await testList(browser, TEST_POLICY.notExempt, false); + await testList(browser, TEST_POLICY.exempt, true); + }, + { activateFirst: false, isBlock: false } + ); + + finish(); +}); diff --git a/dom/ipc/tests/browser_gc_schedule.js b/dom/ipc/tests/browser_gc_schedule.js new file mode 100644 index 0000000000..8b44c98eae --- /dev/null +++ b/dom/ipc/tests/browser_gc_schedule.js @@ -0,0 +1,379 @@ +/* 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/. */ + +"use strict"; + +const TEST_PAGE = + "http://mochi.test:8888/browser/dom/ipc/tests/file_dummy.html"; + +async function waitForGCBegin() { + var waitTopic = "garbage-collector-begin"; + var observer = {}; + + info("Waiting for " + waitTopic); + // This fixes a ReferenceError for Date, it's weird. + ok(Date.now(), "Date.now()"); + var when = await new Promise(resolve => { + observer.observe = function (subject, topic, data) { + resolve(Date.now()); + }; + + Services.obs.addObserver(observer, waitTopic); + }); + + Services.obs.removeObserver(observer, waitTopic); + + // This delay attempts to make the time stamps unique. + do { + var now = Date.now(); + } while (when + 5 > now); + + return when; +} + +async function waitForGCEnd() { + var waitTopic = "garbage-collector-end"; + var observer = {}; + + info("Waiting for " + waitTopic); + // This fixes a ReferenceError for Date, it's weird. + ok(Date.now(), "Date.now()"); + let when = await new Promise(resolve => { + observer.observe = function (subject, topic, data) { + resolve(Date.now()); + }; + + Services.obs.addObserver(observer, waitTopic); + }); + + Services.obs.removeObserver(observer, waitTopic); + + do { + var now = Date.now(); + } while (when + 5 > now); + + return when; +} + +function getProcessID() { + return Services.appinfo.processID; +} + +async function resolveInOrder(promisesAndStates) { + var order = []; + var promises = []; + + for (let p of promisesAndStates) { + promises.push( + p.promise.then(when => { + info(`Tab: ${p.tab} did ${p.state}`); + order.push({ tab: p.tab, state: p.state, when }); + }) + ); + } + + await Promise.all(promises); + + return order; +} + +// Check that the list of events returned by resolveInOrder are in a +// sensible order. +function checkOneAtATime(events) { + var cur = null; + var lastWhen = null; + + info("Checking order of events"); + for (const e of events) { + ok(e.state === "begin" || e.state === "end", "event.state is good"); + Assert.notStrictEqual(e.tab, undefined, "event.tab exists"); + + if (lastWhen) { + // We need these in sorted order so that the other checks here make + // sense. + Assert.lessOrEqual( + lastWhen, + e.when, + `Unsorted events, last: ${lastWhen}, this: ${e.when}` + ); + } + lastWhen = e.when; + + if (e.state === "begin") { + is(cur, null, `GC can begin on tab ${e.tab}`); + cur = e.tab; + } else { + is(e.tab, cur, `GC can end on tab ${e.tab}`); + cur = null; + } + } + + is(cur, null, "No GC left running"); +} + +function checkAllCompleted(events, expectTabsCompleted) { + var tabsCompleted = events.filter(e => e.state === "end").map(e => e.tab); + + for (var t of expectTabsCompleted) { + ok(tabsCompleted.includes(t), `Tab ${t} did a GC`); + } +} + +async function setupTabsAndOneForForeground(num_tabs) { + ++num_tabs; + var pids = []; + + const parent_pid = getProcessID(); + info("Parent process PID is " + parent_pid); + + const tabs = await Promise.all( + Array(num_tabs) + .fill() + .map(_ => { + return BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: TEST_PAGE, + forceNewProcess: true, + }); + }) + ); + + for (const [i, tab] of Object.entries(tabs)) { + const tab_pid = await SpecialPowers.spawn( + tab.linkedBrowser, + [], + getProcessID + ); + + info(`Tab ${i} pid is ${tab_pid}`); + isnot(parent_pid, tab_pid, `Tab ${i} is in content process`); + ok(!pids.includes(tab_pid), `Tab ${i} is in a distinct process`); + + pids.push(tab_pid); + } + + // Since calling openNewForegroundTab several times in a row doesn't update + // process priorities correctly, we need to explicitly switch tabs. + for (let tab of tabs) { + await BrowserTestUtils.switchTab(gBrowser, tab); + } + + return tabs; +} + +function doContentRunNextCollectionTimer() { + content.windowUtils.pokeGC("PAGE_HIDE"); + content.windowUtils.runNextCollectorTimer("PAGE_HIDE"); +} + +function startNextCollection( + tab, + tab_num, + waits, + fn = doContentRunNextCollectionTimer +) { + var browser = tab.linkedBrowser; + + // Finish any currently running GC. + SpecialPowers.spawn(browser, [], () => { + SpecialPowers.Cu.getJSTestingFunctions().finishgc(); + }); + + if (tab.selected) { + // One isn't expected to use the return value with foreground tab! + return {}; + } + + var waitBegin = SpecialPowers.spawn(browser, [], waitForGCBegin); + var waitEnd = SpecialPowers.spawn(browser, [], waitForGCEnd); + waits.push({ promise: waitBegin, tab: tab_num, state: "begin" }); + waits.push({ promise: waitEnd, tab: tab_num, state: "end" }); + + SpecialPowers.spawn(browser, [], fn); + + // Return these so that the abort GC test can wait for the begin. + return { waitBegin, waitEnd }; +} + +add_task(async function gcOneAtATime() { + SpecialPowers.pushPrefEnv({ + set: [["javascript.options.concurrent_multiprocess_gcs.max", 1]], + }); + + const num_tabs = 12; + var tabs = await setupTabsAndOneForForeground(num_tabs); + + info("Tabs ready, Asking for GCs"); + var waits = []; + for (var i = 0; i < num_tabs; i++) { + startNextCollection(tabs[i], i, waits); + } + + let order = await resolveInOrder(waits); + // We need these in the order they actually occurred, so far that's how + // they're returned, but we'll sort them to be sure. + order.sort((e1, e2) => e1.when - e2.when); + checkOneAtATime(order); + checkAllCompleted( + order, + Array.from({ length: num_tabs }, (_, n) => n) + ); + + for (var tab of tabs) { + BrowserTestUtils.removeTab(tab); + } + + SpecialPowers.popPrefEnv(); +}); + +add_task(async function gcAbort() { + SpecialPowers.pushPrefEnv({ + set: [["javascript.options.concurrent_multiprocess_gcs.max", 1]], + }); + + const num_tabs = 2; + var tabs = await setupTabsAndOneForForeground(num_tabs); + + info("Tabs ready, Asking for GCs"); + var waits = []; + + var tab0Waits = startNextCollection(tabs[0], 0, waits, () => { + SpecialPowers.Cu.getJSTestingFunctions().gcslice(1); + }); + await tab0Waits.waitBegin; + + // Tab 0 has started a GC. Now we schedule a GC in tab one. It must not + // begin yet (but we don't check that, gcOneAtATime is assumed to check + // this. + startNextCollection(tabs[1], 1, waits); + + // Request that tab 0 abort, this test checks that tab 1 can now begin. + SpecialPowers.spawn(tabs[0].linkedBrowser, [], () => { + SpecialPowers.Cu.getJSTestingFunctions().abortgc(); + }); + + let order = await resolveInOrder(waits); + // We need these in the order they actually occurred, so far that's how + // they're returned, but we'll sort them to be sure. + order.sort((e1, e2) => e1.when - e2.when); + checkOneAtATime(order); + checkAllCompleted( + order, + Array.from({ length: num_tabs }, (_, n) => n) + ); + + for (var tab of tabs) { + BrowserTestUtils.removeTab(tab); + } + + SpecialPowers.popPrefEnv(); +}); + +add_task(async function gcJSInitiatedDuring() { + SpecialPowers.pushPrefEnv({ + set: [["javascript.options.concurrent_multiprocess_gcs.max", 1]], + }); + + const num_tabs = 3; + var tabs = await setupTabsAndOneForForeground(num_tabs); + + info("Tabs ready, Asking for GCs"); + var waits = []; + + // Start a GC on tab 0 to consume the scheduler's "token". Zeal mode 10 + // will cause it to run in many slices. + var tab0Waits = startNextCollection(tabs[0], 0, waits, () => { + if (SpecialPowers.Cu.getJSTestingFunctions().gczeal) { + SpecialPowers.Cu.getJSTestingFunctions().gczeal(10); + } + SpecialPowers.Cu.getJSTestingFunctions().gcslice(1); + }); + await tab0Waits.waitBegin; + info("GC on tab 0 has begun"); + + // Request a GC in tab 1, this will be blocked by the ongoing GC in tab 0. + var tab1Waits = startNextCollection(tabs[1], 1, waits); + + // Force a GC to start in tab 1. This won't wait for tab 0. + SpecialPowers.spawn(tabs[1].linkedBrowser, [], () => { + SpecialPowers.Cu.getJSTestingFunctions().gcslice(1); + }); + + await tab1Waits.waitBegin; + info("GC on tab 1 has begun"); + + // The GC in tab 0 should still be running. + var state = await SpecialPowers.spawn(tabs[0].linkedBrowser, [], () => { + return SpecialPowers.Cu.getJSTestingFunctions().gcstate(); + }); + info("State of Tab 0 GC is " + state); + isnot(state, "NotActive", "GC is active in tab 0"); + + // Let the GCs complete, verify that a GC in a 3rd tab can acquire a token. + startNextCollection(tabs[2], 2, waits); + + let order = await resolveInOrder(waits); + info("All GCs finished"); + checkAllCompleted( + order, + Array.from({ length: num_tabs }, (_, n) => n) + ); + + for (var tab of tabs) { + BrowserTestUtils.removeTab(tab); + } + + SpecialPowers.popPrefEnv(); +}); + +add_task(async function gcJSInitiatedBefore() { + SpecialPowers.pushPrefEnv({ + set: [["javascript.options.concurrent_multiprocess_gcs.max", 1]], + }); + + const num_tabs = 8; + var tabs = await setupTabsAndOneForForeground(num_tabs); + + info("Tabs ready"); + var waits = []; + + // Start a GC on tab 0 to consume the scheduler's first "token". Zeal mode 10 + // will cause it to run in many slices. + info("Force a JS-initiated GC in tab 0"); + var tab0Waits = startNextCollection(tabs[0], 0, waits, () => { + if (SpecialPowers.Cu.getJSTestingFunctions().gczeal) { + SpecialPowers.Cu.getJSTestingFunctions().gczeal(10); + } + SpecialPowers.Cu.getJSTestingFunctions().gcslice(1); + }); + await tab0Waits.waitBegin; + + info("Request GCs in remaining tabs"); + for (var i = 1; i < num_tabs; i++) { + startNextCollection(tabs[i], i, waits); + } + + // The GC in tab 0 should still be running. + var state = await SpecialPowers.spawn(tabs[0].linkedBrowser, [], () => { + return SpecialPowers.Cu.getJSTestingFunctions().gcstate(); + }); + info("State is " + state); + isnot(state, "NotActive", "GC is active in tab 0"); + + let order = await resolveInOrder(waits); + // We need these in the order they actually occurred, so far that's how + // they're returned, but we'll sort them to be sure. + order.sort((e1, e2) => e1.when - e2.when); + checkOneAtATime(order); + checkAllCompleted( + order, + Array.from({ length: num_tabs }, (_, n) => n) + ); + + for (var tab of tabs) { + BrowserTestUtils.removeTab(tab); + } + + SpecialPowers.popPrefEnv(); +}); diff --git a/dom/ipc/tests/browser_hide_tooltip.js b/dom/ipc/tests/browser_hide_tooltip.js new file mode 100644 index 0000000000..1b7f7c56b9 --- /dev/null +++ b/dom/ipc/tests/browser_hide_tooltip.js @@ -0,0 +1,38 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test_hiding_tooltip() { + let page1 = "data:text/html,<html title='title'><body>page 1<body></html>"; + let page2 = "data:text/html,<html><body>page 2</body></html>"; + + let tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: page1, + }); + + let popup = new Promise(function (resolve) { + window.addEventListener("popupshown", resolve, { once: true }); + }); + // Fire a mousemove to trigger the tooltip. + EventUtils.synthesizeMouseAtCenter(gBrowser.selectedBrowser, { + type: "mousemove", + }); + await popup; + + let hiding = new Promise(function (resolve) { + window.addEventListener("popuphiding", resolve, { once: true }); + }); + let loaded = BrowserTestUtils.browserLoaded( + gBrowser.selectedBrowser, + false, + page2 + ); + BrowserTestUtils.startLoadingURIString(gBrowser, page2); + await loaded; + await hiding; + + ok(true, "Should have hidden the tooltip"); + BrowserTestUtils.removeTab(tab); +}); diff --git a/dom/ipc/tests/browser_layers_unloaded_while_interruptingJS.js b/dom/ipc/tests/browser_layers_unloaded_while_interruptingJS.js new file mode 100644 index 0000000000..9903055858 --- /dev/null +++ b/dom/ipc/tests/browser_layers_unloaded_while_interruptingJS.js @@ -0,0 +1,32 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test_check_layers_cleared() { + let initialTab = gBrowser.selectedTab; + await BrowserTestUtils.withNewTab("about:blank", async browser => { + await ContentTask.spawn(browser, null, () => { + return new Promise(resolve => { + content.requestAnimationFrame(() => { + content.setTimeout( + "let start = performance.now(); while (performance.now() < start + 5000);" + ); + resolve(); + }); + }); + }); + let layersCleared = BrowserTestUtils.waitForEvent( + window, + "MozLayerTreeCleared" + ); + let startWaiting = performance.now(); + await BrowserTestUtils.switchTab(gBrowser, initialTab); + await layersCleared; + Assert.less( + performance.now(), + startWaiting + 2000, + "MozLayerTreeCleared should be dispatched while the script is still running" + ); + }); +}); diff --git a/dom/ipc/tests/browser_memory_distribution_telemetry.js b/dom/ipc/tests/browser_memory_distribution_telemetry.js new file mode 100644 index 0000000000..f4568fe05f --- /dev/null +++ b/dom/ipc/tests/browser_memory_distribution_telemetry.js @@ -0,0 +1,93 @@ +"use strict"; + +const { TelemetrySession } = ChromeUtils.importESModule( + "resource://gre/modules/TelemetrySession.sys.mjs" +); + +const DUMMY_PAGE_DATA_URI = `data:text/html, + <html> + <head> + <meta charset="utf-8"/> + <title>Dummy</title> + </head> + <body> + <h1 id='header'>Just a regular everyday normal page.</h1> + </body> + </html>`; + +/** + * Tests the MEMORY_DISTRIBUTION_AMONG_CONTENT probe by opening a few tabs, then triggering + * the memory probes and waiting for the "gather-memory-telemetry-finished" notification. + */ +add_task(async function test_memory_distribution() { + waitForExplicitFinish(); + + if (SpecialPowers.getIntPref("dom.ipc.processCount", 1) < 2) { + ok(true, "Skip this test if e10s-multi is disabled."); + finish(); + return; + } + + Services.telemetry.canRecordExtended = true; + + let histogram = Services.telemetry.getKeyedHistogramById( + "MEMORY_DISTRIBUTION_AMONG_CONTENT" + ); + histogram.clear(); + + let tab1 = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + DUMMY_PAGE_DATA_URI + ); + let tab2 = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + DUMMY_PAGE_DATA_URI + ); + let tab3 = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + DUMMY_PAGE_DATA_URI + ); + + let finishedGathering = new Promise(resolve => { + let obs = function () { + Services.obs.removeObserver(obs, "gather-memory-telemetry-finished"); + resolve(); + }; + Services.obs.addObserver(obs, "gather-memory-telemetry-finished"); + }); + + TelemetrySession.getPayload(); + + await finishedGathering; + + let s = histogram.snapshot(); + ok("0 - 10 tabs" in s, "We should have some samples by now in this bucket."); + for (var key in s) { + is(key, "0 - 10 tabs"); + let fewTabsSnapshot = s[key]; + Assert.greater( + fewTabsSnapshot.sum, + 0, + "Zero difference between all the content processes is unlikely, what happened?" + ); + Assert.less( + fewTabsSnapshot.sum, + 80, + "20 percentage difference on average is unlikely, what happened?" + ); + let values = fewTabsSnapshot.values; + for (let [bucket, value] of Object.entries(values)) { + if (bucket >= 10) { + // If this check fails it means that one of the content processes uses at least 20% more or 20% less than the mean. + is(value, 0, "All the buckets above 10 should be empty"); + } + } + } + + histogram.clear(); + + BrowserTestUtils.removeTab(tab3); + BrowserTestUtils.removeTab(tab2); + BrowserTestUtils.removeTab(tab1); + finish(); +}); diff --git a/dom/ipc/tests/browser_pbrowser_creation_failure.js b/dom/ipc/tests/browser_pbrowser_creation_failure.js new file mode 100644 index 0000000000..d4f3b8fdd5 --- /dev/null +++ b/dom/ipc/tests/browser_pbrowser_creation_failure.js @@ -0,0 +1,57 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test_subframe_pbrowser_creation_failure() { + await BrowserTestUtils.withNewTab( + "https://example.com/document-builder.sjs?html=<iframe></iframe>", + async browser => { + let bcid = await SpecialPowers.spawn(browser, [], () => { + return content.document.body.querySelector("iframe").browsingContext.id; + }); + + // We currently have no known way to trigger PBrowser creation failure, + // other than to use this custom pref for the purpose. + info(`enabling failPBrowserCreation for browsingContext: ${bcid}`); + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.tabs.remote.testOnly.failPBrowserCreation.enabled", true], + [ + "browser.tabs.remote.testOnly.failPBrowserCreation.browsingContext", + `${bcid}`, + ], + ], + }); + + let eventFiredPromise = BrowserTestUtils.waitForEvent( + browser, + "oop-browser-crashed" + ); + + info("triggering navigation which will fail pbrowser creation"); + await SpecialPowers.spawn(browser, [], () => { + content.document.body.querySelector("iframe").src = + "https://example.org/document-builder.sjs?html=frame"; + }); + + info("Waiting for oop-browser-crashed event."); + let event = await eventFiredPromise; + ok(!event.isTopFrame, "should be reporting subframe crash"); + Assert.equal( + event.childID, + 0, + "childID should be zero, as no process actually crashed" + ); + is(event.browsingContextId, bcid, "bcid should match"); + + let { subject: windowGlobal } = await BrowserUtils.promiseObserved( + "window-global-created", + wgp => wgp.documentURI.spec.startsWith("about:framecrashed") + ); + is(windowGlobal.browsingContext.id, bcid, "bcid is correct"); + + await SpecialPowers.popPrefEnv(); + } + ); +}); diff --git a/dom/ipc/tests/browser_subframesPreferUsed.js b/dom/ipc/tests/browser_subframesPreferUsed.js new file mode 100644 index 0000000000..f2f9ed2593 --- /dev/null +++ b/dom/ipc/tests/browser_subframesPreferUsed.js @@ -0,0 +1,82 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +ok( + Services.appinfo.fissionAutostart, + "this test requires fission to function!" +); + +function documentURL(origin, html) { + let params = new URLSearchParams(); + params.append("html", html.trim()); + return `${origin}/document-builder.sjs?${params.toString()}`; +} + +async function singleTest(preferUsed) { + info(`running test with preferUsed=${preferUsed}`); + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.ipc.processCount.webIsolated", 4], + ["browser.tabs.remote.subframesPreferUsed", preferUsed], + ], + }); + + const TEST_URL = documentURL( + "https://example.com", + `<iframe src=${JSON.stringify( + documentURL("https://example.org", `<h1>iframe</h1>`) + )}></iframe>` + ); + + await BrowserTestUtils.withNewTab(TEST_URL, async browser1 => { + is(browser1.browsingContext.children.length, 1); + let topProc1 = browser1.browsingContext.currentWindowGlobal.domProcess; + let frameProc1 = + browser1.browsingContext.children[0].currentWindowGlobal.domProcess; + isnot( + topProc1.childID, + frameProc1.childID, + "the frame should be in a separate process" + ); + + await BrowserTestUtils.withNewTab(TEST_URL, async browser2 => { + is(browser2.browsingContext.children.length, 1); + let topProc2 = browser2.browsingContext.currentWindowGlobal.domProcess; + let frameProc2 = + browser2.browsingContext.children[0].currentWindowGlobal.domProcess; + isnot( + topProc2.childID, + frameProc2.childID, + "the frame should be in a separate process" + ); + + // Compare processes used for the two tabs. + isnot( + topProc1.childID, + topProc2.childID, + "the toplevel windows should be loaded in separate processes" + ); + if (preferUsed) { + is( + frameProc1.childID, + frameProc2.childID, + "the iframes should load in the same process with subframesPreferUsed" + ); + } else { + isnot( + frameProc1.childID, + frameProc2.childID, + "the iframes should load in different processes without subframesPreferUsed" + ); + } + }); + }); +} + +add_task(async function test_preferUsed() { + await singleTest(true); +}); + +add_task(async function test_noPreferUsed() { + await singleTest(false); +}); diff --git a/dom/ipc/tests/browser_very_fission.js b/dom/ipc/tests/browser_very_fission.js new file mode 100644 index 0000000000..582fe00133 --- /dev/null +++ b/dom/ipc/tests/browser_very_fission.js @@ -0,0 +1,38 @@ +/* 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/. */ + +"use strict"; + +// This test creates a large number of content processes as a +// regression test for bug 1635451. + +const TEST_PAGE = + "http://mochi.test:8888/browser/dom/ipc/tests/file_dummy.html"; + +const NUM_TABS = 256; + +add_task(async () => { + let promises = []; + for (let i = 0; i < NUM_TABS; ++i) { + promises.push( + BrowserTestUtils.openNewForegroundTab({ + gBrowser, + opening: TEST_PAGE, + waitForLoad: true, + forceNewProcess: true, + }) + ); + } + + let tabs = []; + for (const p of promises) { + tabs.push(await p); + } + + ok(true, "All of the tabs loaded"); + + for (const t of tabs) { + BrowserTestUtils.removeTab(t); + } +}); diff --git a/dom/ipc/tests/browser_wpi_base.js b/dom/ipc/tests/browser_wpi_base.js new file mode 100644 index 0000000000..7a01c9a161 --- /dev/null +++ b/dom/ipc/tests/browser_wpi_base.js @@ -0,0 +1,305 @@ +// This test is fission-only! Make that clear before continuing, to avoid +// confusing failures. +ok( + Services.appinfo.fissionAutostart, + "this test requires fission to function!" +); + +requestLongerTimeout(2); + +const WebContentIsolationStrategy = { + IsolateNothing: 0, + IsolateEverything: 1, + IsolateHighValue: 2, +}; + +const COM_ORIGIN = "https://example.com"; +const ORG_ORIGIN = "https://example.org"; +const MOZ_ORIGIN = "https://www.mozilla.org"; + +// Helper for building document-builder.sjs URLs which have specific headers & +// HTML content. +function documentURL(origin, headers, html) { + let params = new URLSearchParams(); + params.append("html", html.trim()); + for (const [key, value] of Object.entries(headers)) { + params.append("headers", `${key}:${value}`); + } + return `${origin}/document-builder.sjs?${params.toString()}`; +} + +async function testTreeRemoteTypes(name, testpage) { + // Use document-builder.sjs to build up the expected document tree. + function buildURL(path, page) { + let html = `<h1>${path}</h1>`; + for (let i = 0; i < page.children.length; ++i) { + const inner = buildURL(`${path}[${i}]`, page.children[i]); + html += `<iframe src=${JSON.stringify(inner)}></iframe>`; + } + return documentURL(page.origin, page.headers, html); + } + const url = buildURL(name, testpage); + + // Load the tab and confirm that properties of the loaded documents match + // expectation. + await BrowserTestUtils.withNewTab(url, async browser => { + let stack = [ + { + path: name, + bc: browser.browsingContext, + ...testpage, + }, + ]; + + while (stack.length) { + const { path, bc, remoteType, children, origin } = stack.pop(); + is( + Services.scriptSecurityManager.createContentPrincipal( + bc.currentWindowGlobal.documentURI, + {} + ).originNoSuffix, + origin, + `Frame ${path} has expected originNoSuffix` + ); + is( + bc.currentWindowGlobal.domProcess.remoteType, + remoteType, + `Frame ${path} has expected remote type` + ); + is( + bc.children.length, + children.length, + `Frame ${path} has the expected number of children` + ); + for (let i = 0; i < bc.children.length; ++i) { + stack.push({ + path: `${path}[${i}]`, + bc: bc.children[i], + ...children[i], + }); + } + } + }); +} + +function mkTestPage({ + comRemoteType, + orgRemoteType, + mozRemoteType, + topOrigin, + topHeaders = {}, + frameHeaders = {}, +}) { + const topRemoteType = { + [COM_ORIGIN]: comRemoteType, + [ORG_ORIGIN]: orgRemoteType, + [MOZ_ORIGIN]: mozRemoteType, + }[topOrigin]; + + const innerChildren = [ + { + origin: COM_ORIGIN, + headers: frameHeaders, + remoteType: comRemoteType, + children: [], + }, + { + origin: ORG_ORIGIN, + headers: frameHeaders, + remoteType: orgRemoteType, + children: [], + }, + { + origin: MOZ_ORIGIN, + headers: frameHeaders, + remoteType: mozRemoteType, + children: [], + }, + ]; + + return { + origin: topOrigin, + headers: topHeaders, + remoteType: topRemoteType, + children: [ + { + origin: COM_ORIGIN, + headers: frameHeaders, + remoteType: comRemoteType, + children: [...innerChildren], + }, + { + origin: ORG_ORIGIN, + headers: frameHeaders, + remoteType: orgRemoteType, + children: [...innerChildren], + }, + { + origin: MOZ_ORIGIN, + headers: frameHeaders, + remoteType: mozRemoteType, + children: [...innerChildren], + }, + ], + }; +} + +const heuristics = [ + { + name: "coop", + setup_com: async expected => { + // Set the COOP header, and load + await testTreeRemoteTypes( + "com_set_coop", + mkTestPage({ + topOrigin: COM_ORIGIN, + topHeaders: { "Cross-Origin-Opener-Policy": "same-origin" }, + comRemoteType: expected.com_high, + orgRemoteType: expected.org_normal, + mozRemoteType: expected.moz_normal, + }) + ); + }, + run_extra_test: async expected => { + // Load with both the COOP and COEP headers set. + await testTreeRemoteTypes( + "com_coop_coep", + mkTestPage({ + topOrigin: COM_ORIGIN, + topHeaders: { + "Cross-Origin-Opener-Policy": "same-origin", + "Cross-Origin-Embedder-Policy": "require-corp", + }, + frameHeaders: { + "Cross-Origin-Embedder-Policy": "require-corp", + "Cross-Origin-Resource-Policy": "cross-origin", + }, + comRemoteType: expected.com_coop_coep, + orgRemoteType: expected.org_coop_coep, + mozRemoteType: expected.moz_coop_coep, + }) + ); + }, + }, + { + name: "hasSavedLogin", + setup_com: async expected => { + // add .com to the password manager + let LoginInfo = new Components.Constructor( + "@mozilla.org/login-manager/loginInfo;1", + Ci.nsILoginInfo, + "init" + ); + await Services.logins.addLoginAsync( + new LoginInfo(COM_ORIGIN, "", null, "username", "password", "", "") + ); + + // Init login detection service to trigger fetching logins + let loginDetection = Cc[ + "@mozilla.org/login-detection-service;1" + ].createInstance(Ci.nsILoginDetectionService); + loginDetection.init(); + + await TestUtils.waitForCondition(() => { + let x = loginDetection.isLoginsLoaded(); + return x; + }, "waiting for loading logins from the password manager"); + }, + }, + { + name: "isLoggedIn", + setup_com: async expected => { + let p = new Promise(resolve => { + Services.obs.addObserver(function obs() { + Services.obs.removeObserver( + obs, + "passwordmgr-form-submission-detected" + ); + resolve(); + }, "passwordmgr-form-submission-detected"); + }); + + const TEST_URL = documentURL( + COM_ORIGIN, + {}, + `<form> + <input value="username"> + <input type="password" value="password"> + <input type="submit"> + </form>` + ); + + // submit the form to simulate the login behavior + await BrowserTestUtils.withNewTab(TEST_URL, async browser => { + await SpecialPowers.spawn(browser, [], async () => { + content.document.querySelector("form").submit(); + }); + }); + await p; + }, + }, +]; + +async function do_tests(expected) { + for (let heuristic of heuristics) { + info(`Starting ${heuristic.name} test`); + // Clear all site-specific data, as we don't want to have any high-value site + // permissions from any previous iterations. + await new Promise(resolve => + Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, resolve) + ); + + // Loads for basic URLs with no special headers set. + await testTreeRemoteTypes( + "basic_com", + mkTestPage({ + topOrigin: COM_ORIGIN, + comRemoteType: expected.com_normal, + orgRemoteType: expected.org_normal, + mozRemoteType: expected.moz_normal, + }) + ); + + await testTreeRemoteTypes( + "basic_org", + mkTestPage({ + topOrigin: ORG_ORIGIN, + comRemoteType: expected.com_normal, + orgRemoteType: expected.org_normal, + mozRemoteType: expected.moz_normal, + }) + ); + + info(`Setting up ${heuristic.name} test`); + await heuristic.setup_com(expected); + + // Load again after the heuristic is triggered + info(`Running ${heuristic.name} tests after setup`); + await testTreeRemoteTypes( + `com_after_${heuristic.name}`, + mkTestPage({ + topOrigin: COM_ORIGIN, + comRemoteType: expected.com_high, + orgRemoteType: expected.org_normal, + mozRemoteType: expected.moz_normal, + }) + ); + + // Load again with a .org toplevel + await testTreeRemoteTypes( + `org_after_${heuristic.name}`, + mkTestPage({ + topOrigin: ORG_ORIGIN, + comRemoteType: expected.com_high, + orgRemoteType: expected.org_normal, + mozRemoteType: expected.moz_normal, + }) + ); + + // Run heuristic dependent tests + if (heuristic.run_extra_test) { + info(`Running extra tests for ${heuristic.name}`); + await heuristic.run_extra_test(expected); + } + } +} diff --git a/dom/ipc/tests/browser_wpi_isolate_everything.js b/dom/ipc/tests/browser_wpi_isolate_everything.js new file mode 100644 index 0000000000..e902fec9d0 --- /dev/null +++ b/dom/ipc/tests/browser_wpi_isolate_everything.js @@ -0,0 +1,27 @@ +// Import this in order to use `do_tests()`. +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/dom/ipc/tests/browser_wpi_base.js", + this +); + +add_task(async function test_isolate_everything() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.tabs.remote.separatedMozillaDomains", "mozilla.org"], + [ + "fission.webContentIsolationStrategy", + WebContentIsolationStrategy.IsolateEverything, + ], + ], + }); + + await do_tests({ + com_normal: "webIsolated=https://example.com", + org_normal: "webIsolated=https://example.org", + moz_normal: "privilegedmozilla", + com_high: "webIsolated=https://example.com", + com_coop_coep: "webCOOP+COEP=https://example.com", + org_coop_coep: "webCOOP+COEP=https://example.org", + moz_coop_coep: "privilegedmozilla", + }); +}); diff --git a/dom/ipc/tests/browser_wpi_isolate_high_value.js b/dom/ipc/tests/browser_wpi_isolate_high_value.js new file mode 100644 index 0000000000..bf6b99d5f5 --- /dev/null +++ b/dom/ipc/tests/browser_wpi_isolate_high_value.js @@ -0,0 +1,27 @@ +// Import this in order to use `do_tests()`. +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/dom/ipc/tests/browser_wpi_base.js", + this +); + +add_task(async function test_isolate_high_value() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.tabs.remote.separatedMozillaDomains", "mozilla.org"], + [ + "fission.webContentIsolationStrategy", + WebContentIsolationStrategy.IsolateHighValue, + ], + ], + }); + + await do_tests({ + com_normal: "web", + org_normal: "web", + moz_normal: "privilegedmozilla", + com_high: "webIsolated=https://example.com", + com_coop_coep: "webCOOP+COEP=https://example.com", + org_coop_coep: "webCOOP+COEP=https://example.org", + moz_coop_coep: "privilegedmozilla", + }); +}); diff --git a/dom/ipc/tests/browser_wpi_isolate_nothing.js b/dom/ipc/tests/browser_wpi_isolate_nothing.js new file mode 100644 index 0000000000..afd5e51640 --- /dev/null +++ b/dom/ipc/tests/browser_wpi_isolate_nothing.js @@ -0,0 +1,27 @@ +// Import this in order to use `do_tests()`. +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/dom/ipc/tests/browser_wpi_base.js", + this +); + +add_task(async function test_isolate_nothing() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.tabs.remote.separatedMozillaDomains", "mozilla.org"], + [ + "fission.webContentIsolationStrategy", + WebContentIsolationStrategy.IsolateNothing, + ], + ], + }); + + await do_tests({ + com_normal: "web", + org_normal: "web", + moz_normal: "privilegedmozilla", + com_high: "web", + com_coop_coep: "webCOOP+COEP=https://example.com", + org_coop_coep: "webCOOP+COEP=https://example.org", + moz_coop_coep: "privilegedmozilla", + }); +}); diff --git a/dom/ipc/tests/chrome.toml b/dom/ipc/tests/chrome.toml new file mode 100644 index 0000000000..79f3e2fb22 --- /dev/null +++ b/dom/ipc/tests/chrome.toml @@ -0,0 +1,6 @@ +[DEFAULT] +skip-if = ["os == 'android'"] +support-files = ["process_error.xhtml"] + +["test_process_error.xhtml"] +skip-if = ["!crashreporter"] diff --git a/dom/ipc/tests/file_broadcast_currenturi_onload.html b/dom/ipc/tests/file_broadcast_currenturi_onload.html new file mode 100644 index 0000000000..b92c46c944 --- /dev/null +++ b/dom/ipc/tests/file_broadcast_currenturi_onload.html @@ -0,0 +1,66 @@ + +<!doctype html> +<body> +<script> + +const url = new URL(location.href); + +// Create a popup to broadcast the load's completion to the test document. +// +// NOTE: We're using a popup to ensure that the new document has the same origin +// (http://mochi.test:8888/) as the original test document, so that we can use a +// BroadcastChannel to communicate with the test page. We can't use an iframe as +// the mixed content blocker will prevent embedding this URL in https:// +// documents. +function sendPayload(payload) { + let broadcastURL = new URL(url.pathname, "http://mochi.test:8888/"); + broadcastURL.search = "?payload=" + encodeURIComponent(JSON.stringify(payload)); + window.open(broadcastURL.href); +} + +async function getURIs() { + // Run the test and fetch the relevant information. + const browsingContext = SpecialPowers.wrap(window).browsingContext; + let [docURI, curURI] = await SpecialPowers.spawnChrome( + [browsingContext.id], async id => { + let bc = BrowsingContext.get(id); + return [ + bc.currentWindowGlobal.documentURI.spec, + bc.currentURI.spec, + ]; + } + ); + return { location: location.href, docURI, curURI }; +} + +addEventListener("load", async e => { + // If a payload parameter was included, just send the message. + const payloadStr = url.searchParams.get("payload"); + if (payloadStr) { + const chan = new BroadcastChannel("test_broadcast_onload"); + chan.postMessage(JSON.parse(payloadStr)); + window.close(); + return; + } + + // collect the initial set of URIs + const result1 = await getURIs(); + + const pushstateURL = new URL("after_pushstate", url.href); + history.pushState({}, "After PushState!", pushstateURL.href); + await new Promise(resolve => setTimeout(resolve, 0)); + + // Collect the set of URIs after pushstate + const result2 = await getURIs(); + + window.location.hash = "#after_hashchange"; + await new Promise(resolve => setTimeout(resolve, 0)); + + // Collect the set of URIs after a hash change + const result3 = await getURIs(); + + sendPayload([result1, result2, result3]); + window.close(); +}); +</script> +</body> diff --git a/dom/ipc/tests/file_cancel_content_js.html b/dom/ipc/tests/file_cancel_content_js.html new file mode 100644 index 0000000000..d2caf03c6a --- /dev/null +++ b/dom/ipc/tests/file_cancel_content_js.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> + <head> + <title>Wait for it...</title> + </head> + <body> + Try to go to another page. + <script> + addEventListener("StartLongLoop", function() { + setTimeout(() => { + const start = Date.now(); + while (Date.now() - start < 7500); + window.dispatchEvent(new CustomEvent("LongLoopEnded")); + }); + }); + </script> + </body> +</html> diff --git a/dom/ipc/tests/file_cross_frame.html b/dom/ipc/tests/file_cross_frame.html new file mode 100644 index 0000000000..b52d920dd0 --- /dev/null +++ b/dom/ipc/tests/file_cross_frame.html @@ -0,0 +1,12 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Different-origin iframe</title> +</head> +<body> +<iframe id="testIFrame" src="https://example.org/browser/dom/ipc/tests/file_dummy.html"></iframe> +<i>I am a web page</i> +</div> +</body> +</html> diff --git a/dom/ipc/tests/file_disableScript.html b/dom/ipc/tests/file_disableScript.html new file mode 100644 index 0000000000..f4888cd586 --- /dev/null +++ b/dom/ipc/tests/file_disableScript.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> +<head> +<script> +var gFiredOnload = false; +var gFiredOnclick = false; +</script> +</head> +<body onload="gFiredOnload = true;" onclick="gFiredOnclick = true;"> +</body> +</html> diff --git a/dom/ipc/tests/file_domainPolicy_base.html b/dom/ipc/tests/file_domainPolicy_base.html new file mode 100644 index 0000000000..6e3ec7aec4 --- /dev/null +++ b/dom/ipc/tests/file_domainPolicy_base.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html> +<head> +</head> +<body> +<iframe id="root" name="root"/> +</body> +</html> diff --git a/dom/ipc/tests/file_dummy.html b/dom/ipc/tests/file_dummy.html new file mode 100644 index 0000000000..c8701dae7d --- /dev/null +++ b/dom/ipc/tests/file_dummy.html @@ -0,0 +1,7 @@ +<!doctype html> +<html> +<head><meta charset="utf-8"></head> +<body> + <h1>This is a dummy file</h1> +</body> +</html> diff --git a/dom/ipc/tests/file_endless_js.html b/dom/ipc/tests/file_endless_js.html new file mode 100644 index 0000000000..926fb1d8ab --- /dev/null +++ b/dom/ipc/tests/file_endless_js.html @@ -0,0 +1,17 @@ +<!doctype html> +<html> +<head><meta charset="utf-8"></head> +<script> + function hang(m) { + let i = 1; + while (i > 0) { + i = Date.now(); + } + } + + onmessage = hang; +</script> +<body> + <h1>This is an endless JS loop</h1> +</body> +</html> diff --git a/dom/ipc/tests/mochitest.toml b/dom/ipc/tests/mochitest.toml new file mode 100644 index 0000000000..b1b347408d --- /dev/null +++ b/dom/ipc/tests/mochitest.toml @@ -0,0 +1,30 @@ +[DEFAULT] + +["test_Preallocated.html"] +skip-if = [ + "os == 'android'", + "tsan", # Bug 1525959. tsan: Bug 1683730 +] + +["test_bcg_processes.html"] +skip-if = [ + "http3", + "http2", +] + +["test_browsingcontext_currenturi.html"] +support-files = ["file_broadcast_currenturi_onload.html"] +skip-if = [ + "http3", + "http2", +] + +["test_temporaryfile_stream.html"] +skip-if = ["os == 'android'"] +support-files = [ + "blob_verify.sjs", + "!/dom/canvas/test/captureStream_common.js", +] + +["test_window_open_discarded_bc.html"] +skip-if = ["os == 'android'"] diff --git a/dom/ipc/tests/process_error.xhtml b/dom/ipc/tests/process_error.xhtml new file mode 100644 index 0000000000..3d57a3f456 --- /dev/null +++ b/dom/ipc/tests/process_error.xhtml @@ -0,0 +1,60 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + orient="vertical"> + + <browser id="thebrowser" type="content" remote="true" /> + <script type="application/javascript"><![CDATA[ + const {BrowserTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/BrowserTestUtils.sys.mjs" + ); + + const ok = window.arguments[0].ok; + const is = window.arguments[0].is; + const done = window.arguments[0].done; + const SimpleTest = window.arguments[0].SimpleTest; + + // Parse test options. + const url = new URL(document.location); + const crashType = url.searchParams.get("crashType"); + + // Allow the browser to get connected before using the messageManager to cause + // a crash: + addEventListener("DOMContentLoaded", () => { + let browser = document.getElementById('thebrowser'); + + let observerPromise = new Promise(resolve => { + let crashObserver = (subject, topic, data) => { + is(topic, 'ipc:content-shutdown', 'Received correct observer topic.'); + ok(subject instanceof Ci.nsIPropertyBag2, + 'Subject implements nsIPropertyBag2.'); + + var dumpID; + if ('nsICrashReporter' in Ci) { + dumpID = subject.getPropertyAsAString('dumpID'); + ok(dumpID, "dumpID is present and not an empty string"); + } + + Services.obs.removeObserver(crashObserver, 'ipc:content-shutdown'); + resolve(); + } + + Services.obs.addObserver(crashObserver, 'ipc:content-shutdown'); + }); + + let browsingContextId = browser.frameLoader.browsingContext.id; + + let eventFiredPromise = BrowserTestUtils.waitForEvent(browser, "oop-browser-crashed"); + let eventPromise = eventFiredPromise.then(event => { + is(event.browsingContextId, browsingContextId, + "Expected the right browsing context id on the oop-browser-crashed event."); + }) + + BrowserTestUtils.crashFrame(browser, true, false, /* Default browsing context */ null, { crashType }); + + Promise.all([observerPromise, eventPromise]).then(done); + }); + ]]></script> + +</window> diff --git a/dom/ipc/tests/test_Preallocated.html b/dom/ipc/tests/test_Preallocated.html new file mode 100644 index 0000000000..fb719995f5 --- /dev/null +++ b/dom/ipc/tests/test_Preallocated.html @@ -0,0 +1,51 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test that the preallocated process starts up. +--> +<head> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="../browserElementTestHelpers.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<script type="application/javascript"> +"use strict"; + +SimpleTest.waitForExplicitFinish(); + +function expectProcessCreated() { + /* eslint-env mozilla/chrome-script */ + return new Promise(resolve => { + function parentExpectProcessCreated() { + let topic = "ipc:content-initializing"; + let obs = { observe() { + Services.obs.removeObserver(obs, topic); + sendAsyncMessage("process-created"); + }}; + Services.obs.addObserver(obs, topic); + } + + let helper = SpecialPowers.loadChromeScript(parentExpectProcessCreated); + SimpleTest.registerCleanupFunction(function() { helper.destroy(); }); + helper.addMessageListener("process-created", resolve); + }); +} + +expectProcessCreated().then(() => { + ok(true, "Process creation detected."); + SimpleTest.finish(); +}); + +// Kill existing preallocated process. +SpecialPowers.pushPrefEnv({"set": [["dom.ipc.processPrelaunch.enabled", false]]}).then(() => { + // Make sure we have the capacity to launch preallocated process. + SpecialPowers.pushPrefEnv({"set": [["dom.ipc.processCount", 100]]}).then(() => { + // Enable preallocated process and run the test. + SpecialPowers.pushPrefEnv({"set": [["dom.ipc.processPrelaunch.enabled", true]]}); + }); +}); +</script> +</body> +</html> diff --git a/dom/ipc/tests/test_bcg_processes.html b/dom/ipc/tests/test_bcg_processes.html new file mode 100644 index 0000000000..8f68aa4a89 --- /dev/null +++ b/dom/ipc/tests/test_bcg_processes.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<head> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<script type="application/javascript"> +"use strict"; + +add_task(async function main_test() { + await SpecialPowers.pushPrefEnv({ + set: [["dom.ipc.processCount.webIsolated", 10]], + }); + + let frame1 = document.createElement("iframe"); + frame1.src = "http://example.com"; + document.body.appendChild(frame1); + await new Promise(resolve => { + frame1.addEventListener("load", resolve, { once: true }) + }); + info("frame 1 loaded"); + + let frame2 = document.createElement("iframe"); + frame2.src = "http://example.com"; + document.body.appendChild(frame2); + await new Promise(resolve => { + frame2.addEventListener("load", resolve, { once: true }) + }); + info("frame 2 loaded"); + + let id1 = await SpecialPowers.spawn(frame1, [], () => { + return ChromeUtils.domProcessChild.childId; + }); + let id2 = await SpecialPowers.spawn(frame2, [], () => { + return ChromeUtils.domProcessChild.childId; + }); + + is(id1, id2, "childID for example.com subframes should match"); +}); + +</script> +</body> +</html> diff --git a/dom/ipc/tests/test_blob_sliced_from_child_process.js b/dom/ipc/tests/test_blob_sliced_from_child_process.js new file mode 100644 index 0000000000..8a7a56088d --- /dev/null +++ b/dom/ipc/tests/test_blob_sliced_from_child_process.js @@ -0,0 +1,140 @@ +"use strict"; + +const { XPCShellContentUtils } = ChromeUtils.importESModule( + "resource://testing-common/XPCShellContentUtils.sys.mjs" +); + +XPCShellContentUtils.init(this); + +function childFrameScript() { + /* eslint-env mozilla/frame-script */ + "use strict"; + + const messageName = "test:blob-slice-test"; + const blobData = ["So", " ", "many", " ", "blobs!"]; + const blobType = "text/plain"; + + let blob = new Blob(blobData, { type: blobType }); + + let firstSliceStart = blobData[0].length + blobData[1].length; + let firstSliceEnd = firstSliceStart + blobData[2].length; + + let slice = blob.slice(firstSliceStart, firstSliceEnd, blobType); + + let secondSliceStart = blobData[2].indexOf("a"); + let secondSliceEnd = secondSliceStart + 2; + + slice = slice.slice(secondSliceStart, secondSliceEnd, blobType); + + sendAsyncMessage(messageName, { blob }); + sendAsyncMessage(messageName, { slice }); +} + +add_task(async function test() { + let page = await XPCShellContentUtils.loadContentPage( + "data:text/html,<!DOCTYPE HTML><html><body></body></html>", + { + remote: true, + } + ); + + page.loadFrameScript(childFrameScript); + + const messageName = "test:blob-slice-test"; + const blobData = ["So", " ", "many", " ", "blobs!"]; + const blobText = blobData.join(""); + const blobType = "text/plain"; + + const sliceText = "an"; + + let receivedBlob = false; + let receivedSlice = false; + + let resolveBlob, resolveSlice; + let blobPromise = new Promise(resolve => { + resolveBlob = resolve; + }); + let slicePromise = new Promise(resolve => { + resolveSlice = resolve; + }); + + let mm = page.browser.messageManager; + mm.addMessageListener(messageName, function (message) { + if ("blob" in message.data) { + equal(receivedBlob, false, "Have not yet received Blob"); + equal(receivedSlice, false, "Have not yet received Slice"); + + receivedBlob = true; + + let blob = message.data.blob; + + ok(Blob.isInstance(blob), "Received a Blob"); + equal(blob.size, blobText.length, "Blob has correct size"); + equal(blob.type, blobType, "Blob has correct type"); + + let slice = blob.slice( + blobText.length - blobData[blobData.length - 1].length, + blob.size, + blobType + ); + + ok(Blob.isInstance(slice), "Slice returned a Blob"); + equal( + slice.size, + blobData[blobData.length - 1].length, + "Slice has correct size" + ); + equal(slice.type, blobType, "Slice has correct type"); + + let reader = new FileReader(); + reader.onload = function () { + equal( + reader.result, + blobData[blobData.length - 1], + "Slice has correct data" + ); + + resolveBlob(); + }; + reader.readAsText(slice); + } else if ("slice" in message.data) { + equal(receivedBlob, true, "Already received Blob"); + equal(receivedSlice, false, "Have not yet received Slice"); + + receivedSlice = true; + + let slice = message.data.slice; + + ok(Blob.isInstance(slice), "Received a Blob for slice"); + equal(slice.size, sliceText.length, "Slice has correct size"); + equal(slice.type, blobType, "Slice has correct type"); + + let reader = new FileReader(); + reader.onload = function () { + equal(reader.result, sliceText, "Slice has correct data"); + + let slice2 = slice.slice(1, 2, blobType); + + ok(Blob.isInstance(slice2), "Slice returned a Blob"); + equal(slice2.size, 1, "Slice has correct size"); + equal(slice2.type, blobType, "Slice has correct type"); + + let reader2 = new FileReader(); + reader2.onload = function () { + equal(reader2.result, sliceText[1], "Slice has correct data"); + + resolveSlice(); + }; + reader2.readAsText(slice2); + }; + reader.readAsText(slice); + } else { + ok(false, "Received a bad message: " + JSON.stringify(message.data)); + } + }); + + await blobPromise; + await slicePromise; + + await page.close(); +}); diff --git a/dom/ipc/tests/test_blob_sliced_from_parent_process.js b/dom/ipc/tests/test_blob_sliced_from_parent_process.js new file mode 100644 index 0000000000..e196a6986c --- /dev/null +++ b/dom/ipc/tests/test_blob_sliced_from_parent_process.js @@ -0,0 +1,167 @@ +"use strict"; + +const { XPCShellContentUtils } = ChromeUtils.importESModule( + "resource://testing-common/XPCShellContentUtils.sys.mjs" +); + +XPCShellContentUtils.init(this); + +function childFrameScript() { + /* eslint-env mozilla/frame-script */ + const messageName = "test:blob-slice-test"; + const blobData = ["So", " ", "many", " ", "blobs!"]; + const blobText = blobData.join(""); + const blobType = "text/plain"; + + const sliceText = "an"; + + function info(msg) { + sendAsyncMessage(messageName, { op: "info", msg }); + } + + function ok(condition, name, diag) { + sendAsyncMessage(messageName, { op: "ok", condition, name, diag }); + } + + function is(a, b, name) { + let pass = a == b; + let diag = pass ? "" : "got " + a + ", expected " + b; + ok(pass, name, diag); + } + + function finish(result) { + sendAsyncMessage(messageName, { op: "done", result }); + } + + function grabAndContinue(arg) { + testGenerator.next(arg); + } + + function* testSteps() { + addMessageListener(messageName, grabAndContinue); + let message = yield undefined; + + let blob = message.data; + + ok(Blob.isInstance(blob), "Received a Blob"); + is(blob.size, blobText.length, "Blob has correct length"); + is(blob.type, blobType, "Blob has correct type"); + + info("Reading blob"); + + let reader = new FileReader(); + reader.addEventListener("load", grabAndContinue); + reader.readAsText(blob); + + yield undefined; + + is(reader.result, blobText, "Blob has correct data"); + + let firstSliceStart = blobData[0].length + blobData[1].length; + let firstSliceEnd = firstSliceStart + blobData[2].length; + + let slice = blob.slice(firstSliceStart, firstSliceEnd, blobType); + + ok(Blob.isInstance(slice), "Slice returned a Blob"); + is(slice.size, blobData[2].length, "Slice has correct length"); + is(slice.type, blobType, "Slice has correct type"); + + info("Reading slice"); + + reader = new FileReader(); + reader.addEventListener("load", grabAndContinue); + reader.readAsText(slice); + + yield undefined; + + is(reader.result, blobData[2], "Slice has correct data"); + + let secondSliceStart = blobData[2].indexOf("a"); + let secondSliceEnd = secondSliceStart + sliceText.length; + + slice = slice.slice(secondSliceStart, secondSliceEnd, blobType); + + ok(Blob.isInstance(slice), "Second slice returned a Blob"); + is(slice.size, sliceText.length, "Second slice has correct length"); + is(slice.type, blobType, "Second slice has correct type"); + + info("Sending second slice"); + finish(slice); + } + + let testGenerator = testSteps(); + testGenerator.next(); +} + +add_task(async function test() { + let page = await XPCShellContentUtils.loadContentPage( + "data:text/html,<!DOCTYPE HTML><html><body></body></html>", + { + remote: true, + } + ); + + page.loadFrameScript(childFrameScript); + + const messageName = "test:blob-slice-test"; + const blobData = ["So", " ", "many", " ", "blobs!"]; + const blobType = "text/plain"; + + const sliceText = "an"; + + await new Promise(resolve => { + function grabAndContinue(arg) { + testGenerator.next(arg); + } + + function* testSteps() { + let slice = yield undefined; + + ok(Blob.isInstance(slice), "Received a Blob"); + equal(slice.size, sliceText.length, "Slice has correct size"); + equal(slice.type, blobType, "Slice has correct type"); + + let reader = new FileReader(); + reader.onload = grabAndContinue; + reader.readAsText(slice); + yield undefined; + + equal(reader.result, sliceText, "Slice has correct data"); + resolve(); + } + + let testGenerator = testSteps(); + testGenerator.next(); + + let mm = page.browser.messageManager; + mm.addMessageListener(messageName, function (message) { + let data = message.data; + switch (data.op) { + case "info": { + info(data.msg); + break; + } + + case "ok": { + ok(data.condition, data.name + " - " + data.diag); + break; + } + + case "done": { + testGenerator.next(data.result); + break; + } + + default: { + ok(false, "Unknown op: " + data.op); + resolve(); + } + } + }); + + let blob = new Blob(blobData, { type: blobType }); + mm.sendAsyncMessage(messageName, blob); + }); + + await page.close(); +}); diff --git a/dom/ipc/tests/test_browsingcontext_currenturi.html b/dom/ipc/tests/test_browsingcontext_currenturi.html new file mode 100644 index 0000000000..b03af96b20 --- /dev/null +++ b/dom/ipc/tests/test_browsingcontext_currenturi.html @@ -0,0 +1,131 @@ +<!DOCTYPE HTML> +<html> +<head> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<iframe id="tls1frame" src="https://tls1.example.com/"></iframe> + +<script> +"use strict"; + +add_task(async function test_frame() { + let win = SpecialPowers.wrap(window); + info(`id=${win.browsingContext.id}`); + let [docURI, curURI] = await SpecialPowers.spawnChrome([win.browsingContext.id], async id => { + let bc = BrowsingContext.get(id); + return [ + bc.currentWindowGlobal.documentURI.spec, + bc.currentURI.spec, + ]; + }); + info(`docURI=${docURI}, curURI=${curURI}`); + is(window.location.href, curURI, "curURI has the expected value"); + is(window.location.href, docURI, "documentURI has the expected value"); +}); + +add_task(async function test_tls1_frame() { + let expframe = SpecialPowers.wrap(document.getElementById("tls1frame")); + let [docURI, curURI] = await SpecialPowers.spawnChrome( + [expframe.browsingContext.id], async id => { + const { TestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" + ); + + let bc = BrowsingContext.get(id); + + // awkwardly wait for the current window global to update to the error page. + // would be nice to do just about anything else here... + await TestUtils.waitForCondition( + () => + bc.currentURI && bc.currentURI.spec != "about:blank" && + bc.currentWindowGlobal && bc.currentWindowGlobal.documentURI.spec != "about:blank", + "waiting for current window global to be non-initial"); + + info(`currentWindowGlobal has updated in the parent!`); + return [ + bc.currentWindowGlobal.documentURI.spec, + bc.currentURI.spec, + ]; + }); + + info(`docURI=${docURI}, curURI=${curURI}`); + is(curURI, "https://tls1.example.com/", "curURI has expected value"); + ok(docURI.startsWith("about:neterror"), "documentURI starts with about:neterror"); +}); + +let BROADCAST_ONLOAD_URL = + new URL("file_broadcast_currenturi_onload.html", location.href); + +async function broadcastLoadTest(baseURI, callback) { + // Bug 1746646: Make mochitests work with TCP enabled (cookieBehavior = 5) + // Acquire storage access permission here so that the BroadcastChannel used to + // communicate with the opened windows works in xorigin tests. Otherwise, + // the iframe containing this page is isolated from first-party storage access, + // which isolates BroadcastChannel communication. + if (isXOrigin) { + await SpecialPowers.pushPrefEnv({ + set: [["privacy.partition.always_partition_third_party_non_cookie_storage", false]], + }); + SpecialPowers.wrap(document).notifyUserGestureActivation(); + await SpecialPowers.addPermission( + "storageAccessAPI", + true, + window.location.href + ); + await SpecialPowers.wrap(document).requestStorageAccess(); + } + let loaded = new Promise(resolve => { + let chan = new BroadcastChannel("test_broadcast_onload"); + chan.onmessage = event => { + resolve(event.data); + }; + }); + let srcURL = new URL(BROADCAST_ONLOAD_URL.pathname, baseURI); + callback(srcURL.href); + + let results = await loaded; + for (let { location, curURI, docURI } of results) { + info(`location=${location}, docURI=${docURI}, curURI=${curURI}`); + is(location, curURI, "curURI has expected value"); + is(location, docURI, "documentURI has expected value"); + } +} + +async function normalFrameLoadTest(base) { + await broadcastLoadTest(base, src => { + let frame = document.createElement("iframe"); + frame.src = src; + document.body.appendChild(frame); + }); +} + +async function normalPopupLoadTest(base, flags = "") { + await broadcastLoadTest(base, src => { + window.open(src, null, flags); + }); +} + +add_task(async function test_sameorigin_frame() { + await normalFrameLoadTest(location.href); +}) + +add_task(async function test_crossorigin_frame() { + await normalFrameLoadTest("https://example.com"); +}); + +add_task(async function test_sameorigin_popup() { + await normalPopupLoadTest(location.href); + await normalPopupLoadTest(location.href, "noopener"); +}); + +add_task(async function test_crossorigin_popup() { + await normalPopupLoadTest("https://example.com"); + await normalPopupLoadTest("https://example.com", "noopener"); +}); + +</script> +</body> +</html> diff --git a/dom/ipc/tests/test_bug1086684.js b/dom/ipc/tests/test_bug1086684.js new file mode 100644 index 0000000000..8a34906686 --- /dev/null +++ b/dom/ipc/tests/test_bug1086684.js @@ -0,0 +1,99 @@ +"use strict"; + +const { XPCShellContentUtils } = ChromeUtils.importESModule( + "resource://testing-common/XPCShellContentUtils.sys.mjs" +); + +XPCShellContentUtils.init(this); + +const childFramePath = "/file_bug1086684.html"; +const childFrameURL = "http://example.com" + childFramePath; + +const childFrameContents = `<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8"> +</head> +<body> +<div id="content"> + <input type="file" id="f"> +</div> +</body> +</html>`; + +const server = XPCShellContentUtils.createHttpServer({ + hosts: ["example.com"], +}); +server.registerPathHandler(childFramePath, (request, response) => { + response.write(childFrameContents); +}); + +function childFrameScript() { + /* eslint-env mozilla/frame-script */ + "use strict"; + + let { MockFilePicker } = ChromeUtils.importESModule( + "resource://testing-common/MockFilePicker.sys.mjs" + ); + + function parentReady(message) { + MockFilePicker.init(content); + MockFilePicker.setFiles([message.data.file]); + MockFilePicker.returnValue = MockFilePicker.returnOK; + + let input = content.document.getElementById("f"); + input.addEventListener("change", () => { + MockFilePicker.cleanup(); + let value = input.value; + message.target.sendAsyncMessage("testBug1086684:childDone", { value }); + }); + + input.focus(); + input.click(); + } + + addMessageListener("testBug1086684:parentReady", function (message) { + parentReady(message); + }); +} + +add_task(async function () { + Services.prefs.setBoolPref("dom.security.https_first", false); + let page = await XPCShellContentUtils.loadContentPage(childFrameURL, { + remote: true, + }); + + page.loadFrameScript(childFrameScript); + + await new Promise(resolve => { + let test; + function* testStructure(mm) { + let value; + + function testDone(msg) { + test.next(msg.data.value); + } + + mm.addMessageListener("testBug1086684:childDone", testDone); + + let blob = new Blob([]); + let file = new File([blob], "helloworld.txt", { type: "text/plain" }); + + mm.sendAsyncMessage("testBug1086684:parentReady", { file }); + value = yield; + + // Note that the "helloworld.txt" passed in above doesn't affect the + // 'value' getter. Because we're mocking a file using a blob, we ask the + // blob for its path, which is the empty string. + equal(value, "", "got the right answer and didn't crash"); + + resolve(); + } + + test = testStructure(page.browser.messageManager); + test.next(); + }); + + await page.close(); + Services.prefs.clearUserPref("dom.security.https_first"); +}); diff --git a/dom/ipc/tests/test_child_docshell.js b/dom/ipc/tests/test_child_docshell.js new file mode 100644 index 0000000000..ee79a509dc --- /dev/null +++ b/dom/ipc/tests/test_child_docshell.js @@ -0,0 +1,90 @@ +"use strict"; + +const { XPCShellContentUtils } = ChromeUtils.importESModule( + "resource://testing-common/XPCShellContentUtils.sys.mjs" +); + +XPCShellContentUtils.init(this); + +add_task(async function test() { + let page = await XPCShellContentUtils.loadContentPage("about:blank", { + remote: true, + }); + + await new Promise(resolve => { + let mm = page.browser.messageManager; + mm.addMessageListener("chromeEventHandler", function (msg) { + var result = msg.json; + equal( + result.processType, + Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT, + "The frame script is running in a real distinct child process" + ); + ok( + result.hasCorrectInterface, + "docshell.chromeEventHandler has EventTarget interface" + ); + }); + + mm.addMessageListener("DOMWindowCreatedReceived", function (msg) { + ok(true, "the chrome event handler looks functional"); + var result = msg.json; + ok( + result.stableChromeEventHandler, + "docShell.chromeEventHandler is stable" + ); + ok(result.iframeHasNewDocShell, "iframe spawns a new docShell"); + ok( + result.iframeHasSameChromeEventHandler, + "but iframe has the same chrome event handler" + ); + resolve(); + }); + + // Inject a frame script in the child process: + page.loadFrameScript(async function () { + /* eslint-env mozilla/frame-script */ + var chromeEventHandler = docShell.chromeEventHandler; + sendAsyncMessage("chromeEventHandler", { + processType: Services.appinfo.processType, + hasCorrectInterface: + chromeEventHandler && EventTarget.isInstance(chromeEventHandler), + }); + + /* + Ensure that this chromeEventHandler actually works, + by creating a new window and listening for its DOMWindowCreated event + */ + chromeEventHandler.addEventListener( + "DOMWindowCreated", + function listener(evt) { + if (evt.target == content.document) { + return; + } + chromeEventHandler.removeEventListener("DOMWindowCreated", listener); + let new_win = evt.target.defaultView; + let new_docShell = new_win.docShell; + sendAsyncMessage("DOMWindowCreatedReceived", { + stableChromeEventHandler: + chromeEventHandler === docShell.chromeEventHandler, + iframeHasNewDocShell: new_docShell !== docShell, + iframeHasSameChromeEventHandler: + new_docShell.chromeEventHandler === chromeEventHandler, + }); + } + ); + + if (content.document.readyState != "complete") { + await new Promise(res => + addEventListener("load", res, { once: true, capture: true }) + ); + } + + let iframe = content.document.createElement("iframe"); + iframe.setAttribute("src", "data:text/html,foo"); + content.document.documentElement.appendChild(iframe); + }); + }); + + await page.close(); +}); diff --git a/dom/ipc/tests/test_process_error.xhtml b/dom/ipc/tests/test_process_error.xhtml new file mode 100644 index 0000000000..d122e7fedd --- /dev/null +++ b/dom/ipc/tests/test_process_error.xhtml @@ -0,0 +1,22 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script> + SimpleTest.waitForExplicitFinish(); + SimpleTest.expectChildProcessCrash(); + + var w = window.browsingContext.topChromeWindow.openDialog('process_error.xhtml', '_blank', 'chrome,resizable=yes,width=400,height=600', window); + + function done() + { + w.close(); + SimpleTest.finish(); + } + </script> + + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;" /> +</window> diff --git a/dom/ipc/tests/test_sharedMap.js b/dom/ipc/tests/test_sharedMap.js new file mode 100644 index 0000000000..2a6c3a7142 --- /dev/null +++ b/dom/ipc/tests/test_sharedMap.js @@ -0,0 +1,377 @@ +"use strict"; + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +const { XPCShellContentUtils } = ChromeUtils.importESModule( + "resource://testing-common/XPCShellContentUtils.sys.mjs" +); + +const PROCESS_COUNT_PREF = "dom.ipc.processCount"; + +const remote = AppConstants.platform !== "android"; + +XPCShellContentUtils.init(this); + +let contentPage; + +async function readBlob(key, sharedData = Services.cpmm.sharedData) { + const { ExtensionUtils } = ChromeUtils.importESModule( + "resource://gre/modules/ExtensionUtils.sys.mjs" + ); + + let reader = new FileReader(); + reader.readAsText(sharedData.get(key)); + await ExtensionUtils.promiseEvent(reader, "loadend"); + return reader.result; +} + +function getKey(key, sharedData = Services.cpmm.sharedData) { + return sharedData.get(key); +} + +function hasKey(key, sharedData = Services.cpmm.sharedData) { + return sharedData.has(key); +} + +function getContents(sharedMap = Services.cpmm.sharedData) { + return { + keys: Array.from(sharedMap.keys()), + values: Array.from(sharedMap.values()), + entries: Array.from(sharedMap.entries()), + getValues: Array.from(sharedMap.keys(), key => sharedMap.get(key)), + }; +} + +function checkMap(contents, expected) { + expected = Array.from(expected); + + equal(contents.keys.length, expected.length, "Got correct number of keys"); + equal( + contents.values.length, + expected.length, + "Got correct number of values" + ); + equal( + contents.entries.length, + expected.length, + "Got correct number of entries" + ); + + for (let [i, [key, val]] of contents.entries.entries()) { + equal(key, contents.keys[i], `keys()[${i}] matches entries()[${i}]`); + deepEqual( + val, + contents.values[i], + `values()[${i}] matches entries()[${i}]` + ); + } + + expected.sort(([a], [b]) => a.localeCompare(b)); + contents.entries.sort(([a], [b]) => a.localeCompare(b)); + + for (let [i, [key, val]] of contents.entries.entries()) { + equal( + key, + expected[i][0], + `expected[${i}].key matches entries()[${i}].key` + ); + deepEqual( + val, + expected[i][1], + `expected[${i}].value matches entries()[${i}].value` + ); + } +} + +function checkParentMap(expected) { + info("Checking parent map"); + checkMap(getContents(Services.ppmm.sharedData), expected); +} + +async function checkContentMaps(expected, parentOnly = false) { + info("Checking in-process content map"); + checkMap(getContents(Services.cpmm.sharedData), expected); + + if (!parentOnly) { + info("Checking out-of-process content map"); + let contents = await contentPage.spawn([], getContents); + checkMap(contents, expected); + } +} + +async function loadContentPage() { + let page = await XPCShellContentUtils.loadContentPage("data:text/html,", { + remote, + }); + registerCleanupFunction(() => page.close()); + return page; +} + +add_setup(async function () { + // Start with one content process so that we can increase the number + // later and test the behavior of a fresh content process. + Services.prefs.setIntPref(PROCESS_COUNT_PREF, 1); + + contentPage = await loadContentPage(); +}); + +add_task(async function test_sharedMap() { + let { sharedData } = Services.ppmm; + + info("Check that parent and child maps are both initially empty"); + + checkParentMap([]); + await checkContentMaps([]); + + let expected = [ + ["foo-a", { foo: "a" }], + ["foo-b", { foo: "b" }], + ["bar-c", null], + ["bar-d", 42], + ]; + + function setKey(key, val) { + sharedData.set(key, val); + expected = expected.filter(([k]) => k != key); + expected.push([key, val]); + } + function deleteKey(key) { + sharedData.delete(key); + expected = expected.filter(([k]) => k != key); + } + + for (let [key, val] of expected) { + sharedData.set(key, val); + } + + info( + "Add some entries, test that they are initially only available in the parent" + ); + + checkParentMap(expected); + await checkContentMaps([]); + + info("Flush. Check that changes are visible in both parent and children"); + + sharedData.flush(); + + checkParentMap(expected); + await checkContentMaps(expected); + + info( + "Add another entry. Check that it is initially only available in the parent" + ); + + let oldExpected = Array.from(expected); + + setKey("baz-a", { meh: "meh" }); + + // When we do several checks in a row, we can't check the values in + // the content process, since the async checks may allow the idle + // flush task to run, and update it before we're ready. + + checkParentMap(expected); + checkContentMaps(oldExpected, true); + + info( + "Add another entry. Check that both new entries are only available in the parent" + ); + + setKey("baz-a", { meh: 12 }); + + checkParentMap(expected); + checkContentMaps(oldExpected, true); + + info( + "Delete an entry. Check that all changes are only visible in the parent" + ); + + deleteKey("foo-b"); + + checkParentMap(expected); + checkContentMaps(oldExpected, true); + + info( + "Flush. Check that all entries are available in both parent and children" + ); + + sharedData.flush(); + + checkParentMap(expected); + await checkContentMaps(expected); + + info("Test that entries are automatically flushed on idle:"); + + info( + "Add a new entry. Check that it is initially only available in the parent" + ); + + // Test the idle flush task. + oldExpected = Array.from(expected); + + setKey("thing", "stuff"); + + checkParentMap(expected); + checkContentMaps(oldExpected, true); + + info( + "Wait for an idle timeout. Check that changes are now visible in all children" + ); + + await new Promise(resolve => ChromeUtils.idleDispatch(resolve)); + + checkParentMap(expected); + await checkContentMaps(expected); + + // Test that has() rebuilds map after a flush. + sharedData.set("grick", true); + sharedData.flush(); + equal( + await contentPage.spawn(["grick"], hasKey), + true, + "has() should see key after flush" + ); + + sharedData.set("grack", true); + sharedData.flush(); + equal( + await contentPage.spawn(["gruck"], hasKey), + false, + "has() should return false for nonexistent key" + ); +}); + +add_task(async function test_blobs() { + let { sharedData } = Services.ppmm; + + let text = [ + "The quick brown fox jumps over the lazy dog", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit", + "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + ]; + let blobs = text.map(str => new Blob([str])); + + let data = { foo: { bar: "baz" } }; + + sharedData.set("blob0", blobs[0]); + sharedData.set("blob1", blobs[1]); + sharedData.set("data", data); + + equal( + await readBlob("blob0", sharedData), + text[0], + "Expected text for blob0 in parent ppmm" + ); + + sharedData.flush(); + + equal( + await readBlob("blob0", sharedData), + text[0], + "Expected text for blob0 in parent ppmm" + ); + equal( + await readBlob("blob1", sharedData), + text[1], + "Expected text for blob1 in parent ppmm" + ); + + equal( + await readBlob("blob0"), + text[0], + "Expected text for blob0 in parent cpmm" + ); + equal( + await readBlob("blob1"), + text[1], + "Expected text for blob1 in parent cpmm" + ); + + equal( + await contentPage.spawn(["blob0"], readBlob), + text[0], + "Expected text for blob0 in child 1 cpmm" + ); + equal( + await contentPage.spawn(["blob1"], readBlob), + text[1], + "Expected text for blob1 in child 1 cpmm" + ); + + // Start a second child process + Services.prefs.setIntPref(PROCESS_COUNT_PREF, 2); + + let page2 = await loadContentPage(); + + equal( + await page2.spawn(["blob0"], readBlob), + text[0], + "Expected text for blob0 in child 2 cpmm" + ); + equal( + await page2.spawn(["blob1"], readBlob), + text[1], + "Expected text for blob1 in child 2 cpmm" + ); + + sharedData.set("blob0", blobs[2]); + + equal( + await readBlob("blob0", sharedData), + text[2], + "Expected text for blob0 in parent ppmm" + ); + + sharedData.flush(); + + equal( + await readBlob("blob0", sharedData), + text[2], + "Expected text for blob0 in parent ppmm" + ); + equal( + await readBlob("blob1", sharedData), + text[1], + "Expected text for blob1 in parent ppmm" + ); + + equal( + await readBlob("blob0"), + text[2], + "Expected text for blob0 in parent cpmm" + ); + equal( + await readBlob("blob1"), + text[1], + "Expected text for blob1 in parent cpmm" + ); + + equal( + await contentPage.spawn(["blob0"], readBlob), + text[2], + "Expected text for blob0 in child 1 cpmm" + ); + equal( + await contentPage.spawn(["blob1"], readBlob), + text[1], + "Expected text for blob1 in child 1 cpmm" + ); + + equal( + await page2.spawn(["blob0"], readBlob), + text[2], + "Expected text for blob0 in child 2 cpmm" + ); + equal( + await page2.spawn(["blob1"], readBlob), + text[1], + "Expected text for blob1 in child 2 cpmm" + ); + + deepEqual( + await page2.spawn(["data"], getKey), + data, + "Expected data for data key in child 2 cpmm" + ); +}); diff --git a/dom/ipc/tests/test_temporaryfile_stream.html b/dom/ipc/tests/test_temporaryfile_stream.html new file mode 100644 index 0000000000..9fa76a2155 --- /dev/null +++ b/dom/ipc/tests/test_temporaryfile_stream.html @@ -0,0 +1,84 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Send an nsTemporaryFileInputStream cross-process</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/dom/canvas/test/captureStream_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"> +<div id="content"> +</div> +<script class="testbody" type="text/javascript"> +function startTest() { + var canvas = document.createElement("canvas"); + canvas.width = canvas.height = 100; + document.getElementById("content").appendChild(canvas); + + + // eslint-disable-next-line no-undef + var helper = new CaptureStreamTestHelper2D(100, 100); + helper.drawColor(canvas, helper.red); + + var stream = canvas.captureStream(0); + + var blob; + + let mediaRecorder = new MediaRecorder(stream); + is(mediaRecorder.stream, stream, + "Media recorder stream = canvas stream at the start of recording"); + + mediaRecorder.onwarning = () => ok(false, "warning unexpectedly fired"); + + mediaRecorder.onerror = () => ok(false, "Recording failed"); + + mediaRecorder.ondataavailable = ev => { + is(blob, undefined, "Should only get one dataavailable event"); + blob = ev.data; + }; + + mediaRecorder.onstart = () => { + info("Got 'start' event"); + // We just want one frame encoded, to see that the recorder produces something readable. + mediaRecorder.stop(); + }; + + mediaRecorder.onstop = () => { + info("Got 'stop' event"); + ok(blob, "Should have gotten a data blob"); + var xhr = new XMLHttpRequest(); + xhr.open("POST", "blob_verify.sjs", true); + xhr.onload = () => { + var video = document.createElement("video"); + video.id = "recorded-video"; + video.src = URL.createObjectURL(xhr.response); + video.play(); + video.onerror = err => { + ok(false, "Should be able to play the recording. Got error. code=" + video.error.code); + SimpleTest.finish(); + }; + document.getElementById("content").appendChild(video); + helper.pixelMustBecome(video, helper.red, { + threshold: 128, + infoString: "Should become red", + }).then(SimpleTest.finish); + }; + xhr.onerror = () => { + ok(false, "XHR error"); + SimpleTest.finish(); + }; + xhr.responseType = "blob"; + xhr.send(blob); + }; + + mediaRecorder.start(); + is(mediaRecorder.state, "recording", "Media recorder should be recording"); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({set: [["media.recorder.max_memory", 1]]}, startTest); +</script> +</pre> +</body> +</html> diff --git a/dom/ipc/tests/test_window_open_discarded_bc.html b/dom/ipc/tests/test_window_open_discarded_bc.html new file mode 100644 index 0000000000..4cd81463e0 --- /dev/null +++ b/dom/ipc/tests/test_window_open_discarded_bc.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<html> +<head> + <title>Discard a new BrowsingContext during window.open nested event loop</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script> +add_task(async function() { + const TOPIC = "dangerous:test-only:new-browser-child-ready"; + + let found = false; + function observer(subject, topic, data) { + let win = SpecialPowers.wrap(subject); + if (SpecialPowers.compare(win.opener, window)) { + found = true; + SpecialPowers.removeObserver(observer, TOPIC); + + win.close(); + // window.close() is not synchronous, so we need to wait for the + // BrowsingContext to actually become discarded after we call it, to + // make sure that the window provider actually has a discarded BC at the + // end of its own nested event loop. + SpecialPowers.Services.tm.spinEventLoopUntil( + "Test(test_window_open_discarded_bc.html:add_task)", + () => !win.opener + ); + } + } + SpecialPowers.addObserver(observer, TOPIC); + + let win = window.open(); + + is(found, true, "Our observer should have fired for the new window"); + is(win, null, "window.open() should return null when new window is already closed"); +}); +</script> +</body> +</html> diff --git a/dom/ipc/tests/xpcshell.toml b/dom/ipc/tests/xpcshell.toml new file mode 100644 index 0000000000..bc4c75e4b0 --- /dev/null +++ b/dom/ipc/tests/xpcshell.toml @@ -0,0 +1,16 @@ +[DEFAULT] + +["test_blob_sliced_from_child_process.js"] +skip-if = ["os == 'android' && processor == 'x86_64'"] + +["test_blob_sliced_from_parent_process.js"] +skip-if = ["os == 'android' && processor == 'x86_64'"] + +["test_bug1086684.js"] +skip-if = ["os == 'android' && processor == 'x86_64'"] + +["test_child_docshell.js"] +skip-if = ["os == 'android' && processor == 'x86_64'"] + +["test_sharedMap.js"] +skip-if = ["os == 'android' && processor == 'x86_64'"] |