diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /dom/ipc | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/ipc')
206 files changed, 52620 insertions, 0 deletions
diff --git a/dom/ipc/BrowserBridgeChild.cpp b/dom/ipc/BrowserBridgeChild.cpp new file mode 100644 index 0000000000..3e3be33388 --- /dev/null +++ b/dom/ipc/BrowserBridgeChild.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/. */ + +#ifdef ACCESSIBILITY +# ifdef XP_WIN +# include "mozilla/a11y/ProxyAccessible.h" +# include "mozilla/a11y/ProxyWrappers.h" +# endif +# 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; + +namespace mozilla::dom { + +BrowserBridgeChild::BrowserBridgeChild(BrowsingContext* aBrowsingContext, + TabId aId, const LayersId& aLayersId) + : mId{aId}, mLayersId{aLayersId}, mBrowsingContext(aBrowsingContext) {} + +BrowserBridgeChild::~BrowserBridgeChild() { +#if defined(ACCESSIBILITY) && defined(XP_WIN) + if (mEmbeddedDocAccessible) { + mEmbeddedDocAccessible->Shutdown(); + } +#endif +} + +already_AddRefed<BrowserBridgeHost> BrowserBridgeChild::FinishInit( + nsFrameLoader* aFrameLoader) { + MOZ_DIAGNOSTIC_ASSERT(!mFrameLoader); + mFrameLoader = aFrameLoader; + + RefPtr<Element> owner = mFrameLoader->GetOwnerContent(); + nsCOMPtr<nsIDocShell> docShell = do_GetInterface(owner->GetOwnerGlobal()); + MOZ_DIAGNOSTIC_ASSERT(docShell); + + nsDocShell::Cast(docShell)->OOPChildLoadStarted(this); + +#if defined(ACCESSIBILITY) + if (a11y::DocAccessible* docAcc = + a11y::GetExistingDocAccessible(owner->OwnerDoc())) { + if (a11y::Accessible* 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) { + Unused << SendActivate(aActionId); +} + +void BrowserBridgeChild::Deactivate(bool aWindowLowering, uint64_t aActionId) { + Unused << SendDeactivate(aWindowLowering, aActionId); +} + +void BrowserBridgeChild::SetIsUnderHiddenEmbedderElement( + bool aIsUnderHiddenEmbedderElement) { + Unused << SendSetIsUnderHiddenEmbedderElement(aIsUnderHiddenEmbedderElement); +} + +/*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::RecvSetEmbeddedDocAccessibleCOMProxy( + const a11y::IDispatchHolder& aCOMProxy) { +#if defined(ACCESSIBILITY) && defined(XP_WIN) + MOZ_ASSERT(!aCOMProxy.IsNull()); + if (mEmbeddedDocAccessible) { + mEmbeddedDocAccessible->Shutdown(); + } + RefPtr<IDispatch> comProxy(aCOMProxy.Get()); + mEmbeddedDocAccessible = + new a11y::RemoteIframeDocProxyAccessibleWrap(comProxy); +#endif + 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->ScrollFrameRectIntoView(frame, 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 (!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 (auto* docShell = + nsDocShell::Cast(mBrowsingContext->GetParent()->GetDocShell())) { + docShell->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(); +} + +} // namespace mozilla::dom diff --git a/dom/ipc/BrowserBridgeChild.h b/dom/ipc/BrowserBridgeChild.h new file mode 100644 index 0000000000..c6e33300e8 --- /dev/null +++ b/dom/ipc/BrowserBridgeChild.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_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 { + +namespace a11y { +class RemoteIframeDocProxyAccessibleWrap; +} + +namespace 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); + + void SetIsUnderHiddenEmbedderElement(bool aIsUnderHiddenEmbedderElement); + + already_AddRefed<BrowserBridgeHost> FinishInit(nsFrameLoader* aFrameLoader); + +#if defined(ACCESSIBILITY) && defined(XP_WIN) + a11y::RemoteIframeDocProxyAccessibleWrap* GetEmbeddedDocAccessible() { + return mEmbeddedDocAccessible; + } +#endif + + 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); + + mozilla::ipc::IPCResult RecvSetEmbeddedDocAccessibleCOMProxy( + const IDispatchHolder& aCOMProxy); + + mozilla::ipc::IPCResult RecvMaybeFireEmbedderLoadEvents( + EmbedderElementEventType aFireEventAtEmbeddingElement); + + mozilla::ipc::IPCResult RecvIntrinsicSizeOrRatioChanged( + const Maybe<IntrinsicSize>& aIntrinsicSize, + const Maybe<AspectRatio>& aIntrinsicRatio); + + 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) && defined(XP_WIN) + RefPtr<a11y::RemoteIframeDocProxyAccessibleWrap> mEmbeddedDocAccessible; +#endif +}; + +} // namespace dom +} // namespace mozilla + +#endif // !defined(mozilla_dom_BrowserBridgeParent_h) diff --git a/dom/ipc/BrowserBridgeHost.cpp b/dom/ipc/BrowserBridgeHost.cpp new file mode 100644 index 0000000000..975deb344a --- /dev/null +++ b/dom/ipc/BrowserBridgeHost.cpp @@ -0,0 +1,88 @@ +/* -*- 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(); +} + +void BrowserBridgeHost::LoadURL(nsDocShellLoadState* aLoadState) { + MOZ_ASSERT(aLoadState); + Unused << mBridge->SendLoadURL(aLoadState); +} + +void BrowserBridgeHost::ResumeLoad(uint64_t aPendingSwitchId) { + Unused << mBridge->SendResumeLoad(aPendingSwitchId); +} + +void BrowserBridgeHost::DestroyStart() { DestroyComplete(); } + +void BrowserBridgeHost::DestroyComplete() { + if (!mBridge) { + return; + } + + Unused << mBridge->Send__delete__(mBridge); + 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..0c93100f12 --- /dev/null +++ b/dom/ipc/BrowserBridgeHost.h @@ -0,0 +1,70 @@ +/* -*- 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 { + +namespace 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; + + 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 dom +} // namespace mozilla + +#endif // mozilla_dom_BrowserBridgeHost_h diff --git a/dom/ipc/BrowserBridgeParent.cpp b/dom/ipc/BrowserBridgeParent.cpp new file mode 100644 index 0000000000..4639db64e4 --- /dev/null +++ b/dom/ipc/BrowserBridgeParent.cpp @@ -0,0 +1,247 @@ +/* -*- 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" +#endif + +#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"); + + RefPtr<CanonicalBrowsingContext> browsingContext = + CanonicalBrowsingContext::Get(aWindowInit.context().mBrowsingContextId); + if (!browsingContext || browsingContext->IsDiscarded()) { + return NS_ERROR_UNEXPECTED; + } + + // 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) + CanonicalBrowsingContext* ancestor = browsingContext->GetParent(); + while (ancestor) { + if (NS_WARN_IF(ancestor->IsDiscarded())) { + return NS_ERROR_UNEXPECTED; + } + ancestor = ancestor->GetParent(); + } + + // 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(); + 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; + } + + // 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(); + + 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) { + mBrowserParent->Destroy(); + mBrowserParent->SetBrowserBridgeParent(nullptr); + mBrowserParent = nullptr; + } +} + +IPCResult BrowserBridgeParent::RecvShow(const OwnerShowInfo& aOwnerInfo) { + mBrowserParent->AttachLayerManager(); + 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(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::RecvRenderLayers( + const bool& aEnabled, const layers::LayersObserverEpoch& aEpoch) { + Unused << mBrowserParent->SendRenderLayers(aEnabled, aEpoch); + return IPC_OK(); +} + +IPCResult BrowserBridgeParent::RecvNavigateByKey( + const bool& aForward, const bool& aForDocumentNavigation) { + Unused << mBrowserParent->SendNavigateByKey(aForward, aForDocumentNavigation); + return IPC_OK(); +} + +IPCResult BrowserBridgeParent::RecvDispatchSynthesizedMouseEvent( + const WidgetMouseEvent& aEvent) { + if (aEvent.mMessage != eMouseMove || + aEvent.mReason != WidgetMouseEvent::eSynthesized) { + return IPC_FAIL(this, "Unexpected event type"); + } + + WidgetMouseEvent event = aEvent; + // 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(); +} + +IPCResult BrowserBridgeParent::RecvSetIsUnderHiddenEmbedderElement( + const bool& aIsUnderHiddenEmbedderElement) { + Unused << mBrowserParent->SendSetIsUnderHiddenEmbedderElement( + aIsUnderHiddenEmbedderElement); + return IPC_OK(); +} + +#ifdef ACCESSIBILITY +IPCResult BrowserBridgeParent::RecvSetEmbedderAccessible( + PDocAccessibleParent* aDoc, uint64_t aID) { + mEmbedderAccessibleDoc = static_cast<a11y::DocAccessibleParent*>(aDoc); + mEmbedderAccessibleID = aID; + if (auto embeddedBrowser = GetBrowserParent()) { + a11y::DocAccessibleParent* childDocAcc = + embeddedBrowser->GetTopLevelDocAccessible(); + if (childDocAcc && !childDocAcc->IsShutdown()) { + // 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(childDocAcc, aID, + /* aCreating */ false); + } + } + return IPC_OK(); +} +#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..9140e2a60f --- /dev/null +++ b/dom/ipc/BrowserBridgeParent.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_BrowserBridgeParent_h +#define mozilla_dom_BrowserBridgeParent_h + +#include "mozilla/dom/PBrowserBridgeParent.h" +#include "mozilla/Tuple.h" +#include "mozilla/dom/ipc/IdType.h" +#include "mozilla/dom/WindowGlobalTypes.h" + +namespace mozilla { + +namespace a11y { +class DocAccessibleParent; +} + +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 accessible for this iframe's embedder OuterDocAccessible. + * This returns the actor for the containing document and the unique id of + * the embedder accessible within that document. + */ + Tuple<a11y::DocAccessibleParent*, uint64_t> GetEmbedderAccessible() { + return Tuple<a11y::DocAccessibleParent*, uint64_t>(mEmbedderAccessibleDoc, + mEmbedderAccessibleID); + } +#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 RecvRenderLayers(const bool& aEnabled, + const LayersObserverEpoch& aEpoch); + + mozilla::ipc::IPCResult RecvNavigateByKey(const bool& aForward, + const bool& aForDocumentNavigation); + + 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 RecvSetIsUnderHiddenEmbedderElement( + const bool& aIsUnderHiddenEmbedderElement); + +#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..9438f34689 --- /dev/null +++ b/dom/ipc/BrowserChild.cpp @@ -0,0 +1,4020 @@ +/* -*- 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 "BrowserChild.h" + +#ifdef ACCESSIBILITY +# include "mozilla/a11y/DocAccessibleChild.h" +#endif +#include <algorithm> +#include <utility> + +#include "BackgroundChild.h" +#include "BrowserParent.h" +#include "ClientLayerManager.h" +#include "ContentChild.h" +#include "DocumentInlines.h" +#include "EventStateManager.h" +#include "FrameLayerBuilder.h" +#include "GeckoProfiler.h" +#include "Layers.h" +#include "MMPrinter.h" +#include "PermissionMessageUtils.h" +#include "PuppetWidget.h" +#include "StructuredCloneData.h" +#include "UnitTransforms.h" +#include "Units.h" +#include "VRManagerChild.h" +#include "ipc/nsGUIEventIPC.h" +#include "js/JSON.h" +#include "mozilla/AsyncEventDispatcher.h" +#include "mozilla/ClearOnShutdown.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/Preferences.h" +#include "mozilla/PresShell.h" +#include "mozilla/ProcessHangMonitor.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StaticPrefs_fission.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TextEvents.h" +#include "mozilla/TouchEvents.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/DocGroup.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/JSWindowActorChild.h" +#include "mozilla/dom/LoadURIOptionsBinding.h" +#include "mozilla/dom/MessageManagerBinding.h" +#include "mozilla/dom/MouseEventBinding.h" +#include "mozilla/dom/Nullable.h" +#include "mozilla/dom/PBrowser.h" +#include "mozilla/dom/PaymentRequestChild.h" +#include "mozilla/dom/PointerEventHandler.h" +#include "mozilla/dom/SessionStoreListener.h" +#include "mozilla/dom/WindowGlobalChild.h" +#include "mozilla/dom/WindowProxyHolder.h" +#include "mozilla/gfx/CrossProcessPaint.h" +#include "mozilla/gfx/Matrix.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/ipc/PBackgroundChild.h" +#include "mozilla/ipc/URIUtils.h" +#include "mozilla/layers/APZCCallbackHelper.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/LayerTransactionChild.h" +#include "mozilla/layers/ShadowLayers.h" +#include "mozilla/layers/WebRenderLayerManager.h" +#include "mozilla/plugins/PPluginWidgetChild.h" +#include "nsBrowserStatusFilter.h" +#include "nsColorPickerProxy.h" +#include "nsCommandParams.h" +#include "nsContentPermissionHelper.h" +#include "nsContentUtils.h" +#include "nsDeviceContext.h" +#include "nsDocShell.h" +#include "nsDocShellLoadState.h" +#include "nsEmbedCID.h" +#include "nsExceptionHandler.h" +#include "nsFilePickerProxy.h" +#include "nsFocusManager.h" +#include "nsGlobalWindow.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 "nsIScriptError.h" +#include "nsISecureBrowserUI.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 "nsPointerHashKeys.h" +#include "nsPrintfCString.h" +#include "nsQueryActor.h" +#include "nsQueryObject.h" +#include "nsRefreshDriver.h" +#include "nsSandboxFlags.h" +#include "nsString.h" +#include "nsTHashtable.h" +#include "nsThreadManager.h" +#include "nsThreadUtils.h" +#include "nsViewManager.h" +#include "nsViewportInfo.h" +#include "nsWebBrowser.h" +#include "nsWindowWatcher.h" +#include "nsIXULRuntime.h" + +#ifdef XP_WIN +# include "mozilla/plugins/PluginWidgetChild.h" +#endif + +#ifdef MOZ_WAYLAND +# include "nsAppRunner.h" +#endif + +#ifdef NS_PRINTING +# include "nsIPrintSession.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::docshell; +using namespace mozilla::widget; +using mozilla::layers::GeckoContentController; + +NS_IMPL_ISUPPORTS(ContentListener, nsIDOMEventListener) + +static const char BEFORE_FIRST_PAINT[] = "before-first-paint"; + +typedef nsDataHashtable<nsUint64HashKey, BrowserChild*> BrowserChildMap; +static BrowserChildMap* sBrowserChildren; +StaticMutex sBrowserChildrenMutex; + +already_AddRefed<Document> BrowserChild::GetTopLevelDocument() const { + nsCOMPtr<Document> doc; + WebNavigation()->GetDocument(getter_AddRefs(doc)); + 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; +} + +NS_IMETHODIMP +ContentListener::HandleEvent(Event* aEvent) { + RemoteDOMEvent remoteEvent; + remoteEvent.mEvent = aEvent; + NS_ENSURE_STATE(remoteEvent.mEvent); + mBrowserChild->SendEvent(remoteEvent); + return NS_OK; +} + +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()}, + mDidFakeShow(false), + mTriedBrowserInit(false), + mOrientation(hal::eScreenOrientation_PortraitPrimary), + mVsyncChild(nullptr), + mIgnoreKeyPressEvent(false), + mHasValidInnerSize(false), + mDestroyed(false), + mDynamicToolbarMaxHeight(0), + mUniqueId(aTabId), + mIsTopLevel(aIsTopLevel), + mHasSiblings(false), + mIsTransparent(false), + mIPCOpen(false), + mDidSetRealShowInfo(false), + mDidLoadURLInit(false), + mSkipKeyPress(false), + mLayersObserverEpoch{1}, +#if defined(XP_WIN) && defined(ACCESSIBILITY) + mNativeWindowHandle(0), +#endif +#if defined(ACCESSIBILITY) + mTopLevelDocAccessibleChild(nullptr), +#endif + mShouldSendWebProgressEventsToParent(false), + mRenderLayers(true), + mPendingDocShellIsActive(false), + mPendingDocShellReceivedMessage(false), + mPendingRenderLayers(false), + mPendingRenderLayersReceivedMessage(false), + mPendingLayersObserverEpoch{0}, + mPendingDocShellBlockers(0), + mCancelContentJSEpoch(0), + mWidgetNativeData(0) { + mozilla::HoldJSObjects(this); + + nsWeakPtr weakPtrThis(do_GetWeakReference( + static_cast<nsIBrowserChild*>(this))); // for capture by the lambda + mSetAllowedTouchBehaviorCallback = + [weakPtrThis](uint64_t aInputBlockId, + const nsTArray<TouchBehaviorFlags>& aFlags) { + if (nsCOMPtr<nsIBrowserChild> browserChild = + do_QueryReferent(weakPtrThis)) { + static_cast<BrowserChild*>(browserChild.get()) + ->SetAllowedTouchBehavior(aInputBlockId, aFlags); + } + }; + + // preloaded BrowserChild should not be added to child map + if (mUniqueId) { + MOZ_ASSERT(NestedBrowserChildMap().find(mUniqueId) == + NestedBrowserChildMap().end()); + NestedBrowserChildMap()[mUniqueId] = this; + } + mCoalesceMouseMoveEvents = + Preferences::GetBool("dom.event.coalesce_mouse_move"); + if (mCoalesceMouseMoveEvents) { + mCoalescedMouseEventFlusher = new CoalescedMouseMoveFlusher(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 && doc->IsTopLevelContentDocument()) { + 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); + } +} + +void BrowserChild::SetAllowedTouchBehavior( + uint64_t aInputBlockId, + const nsTArray<TouchBehaviorFlags>& aTargets) const { + if (mApzcTreeManager) { + mApzcTreeManager->SetAllowedTouchBehavior(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?"); + + // Set the tab context attributes then pass to docShell + NotifyTabContextUpdated(); + + // 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); + } + + docShell->SetAffectPrivateSessionLifetime( + mBrowsingContext->UsePrivateBrowsing() || + mChromeFlags & nsIWebBrowserChrome::CHROME_PRIVATE_LIFETIME); + +#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 nsGlobalWindow::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); + + if (window->GetCurrentInnerWindow()) { + window->SetKeyboardIndicators(ShowFocusRings()); + } else { + // Skip ShouldShowFocusRing check if no inner window is available + window->SetInitialKeyboardIndicators(ShowFocusRings()); + } + + // 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 !defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_THUNDERBIRD) && \ + !defined(MOZ_SUITE) + mSessionStoreListener = new TabListener(docShell, nullptr); + rv = mSessionStoreListener->Init(); + NS_ENSURE_SUCCESS(rv, rv); +#endif + + // 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; +} + +void BrowserChild::NotifyTabContextUpdated() { + nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation()); + MOZ_ASSERT(docShell); + + if (!docShell) { + return; + } + + // Set SANDBOXED_AUXILIARY_NAVIGATION flag if this is a receiver page. + if (!PresentationURL().IsEmpty()) { + // Return value of setting synced field should be checked. See bug 1656492. + Unused << mBrowsingContext->SetSandboxFlags(SANDBOXED_AUXILIARY_NAVIGATION); + } +} + +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(mWebBrowserChrome) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mStatusFilter) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mWebNav) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowsingContext) + 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(mWebBrowserChrome) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStatusFilter) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWebNav) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowsingContext) +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(nsIEmbeddingSiteWindow) + 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::RemoteSizeShellTo(int32_t aWidth, int32_t aHeight, + int32_t aShellItemWidth, + int32_t aShellItemHeight) { + nsCOMPtr<nsIDocShell> ourDocShell = do_GetInterface(WebNavigation()); + nsCOMPtr<nsIBaseWindow> docShellAsWin(do_QueryInterface(ourDocShell)); + NS_ENSURE_STATE(docShellAsWin); + + int32_t width, height; + docShellAsWin->GetSize(&width, &height); + + uint32_t flags = 0; + if (width == aWidth) { + flags |= nsIEmbeddingSiteWindow::DIM_FLAGS_IGNORE_CX; + } + + if (height == aHeight) { + flags |= nsIEmbeddingSiteWindow::DIM_FLAGS_IGNORE_CY; + } + + bool sent = SendSizeShellTo(flags, aWidth, aHeight, aShellItemWidth, + aShellItemHeight); + + return sent ? NS_OK : NS_ERROR_FAILURE; +} + +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(nsString(aStatusText)); + } + return NS_OK; +} + +NS_IMETHODIMP +BrowserChild::SetDimensions(uint32_t aFlags, int32_t aX, int32_t aY, + int32_t aCx, int32_t aCy) { + // 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 the current value for the non-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 ignore its value. + int32_t x, y, cx, cy; + GetDimensions(aFlags, &x, &y, &cx, &cy); + + if (x == aX) { + aFlags |= nsIEmbeddingSiteWindow::DIM_FLAGS_IGNORE_X; + } + + if (y == aY) { + aFlags |= nsIEmbeddingSiteWindow::DIM_FLAGS_IGNORE_Y; + } + + if (cx == aCx) { + aFlags |= nsIEmbeddingSiteWindow::DIM_FLAGS_IGNORE_CX; + } + + if (cy == aCy) { + aFlags |= nsIEmbeddingSiteWindow::DIM_FLAGS_IGNORE_CY; + } + + double scale = mPuppetWidget ? mPuppetWidget->GetDefaultScale().scale : 1.0; + + Unused << SendSetDimensions(aFlags, aX, aY, aCx, aCy, scale); + + return NS_OK; +} + +NS_IMETHODIMP +BrowserChild::GetDimensions(uint32_t aFlags, int32_t* aX, int32_t* aY, + int32_t* aCx, int32_t* aCy) { + ScreenIntRect rect = GetOuterRect(); + 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::SetFocus() { return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +BrowserChild::GetVisibility(bool* aVisibility) { + *aVisibility = true; + return NS_OK; +} + +NS_IMETHODIMP +BrowserChild::SetVisibility(bool aVisibility) { + // should the platform support this? Bug 666365 + return NS_OK; +} + +NS_IMETHODIMP +BrowserChild::GetTitle(nsAString& aTitle) { + NS_WARNING("BrowserChild::GetTitle not supported in BrowserChild"); + + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +BrowserChild::SetTitle(const nsAString& aTitle) { + // JavaScript sends the "DOMTitleChanged" event to the parent + // via the message manager. + return NS_OK; +} + +NS_IMETHODIMP +BrowserChild::GetSiteWindow(void** aSiteWindow) { + NS_WARNING("BrowserChild::GetSiteWindow not supported in BrowserChild"); + + return NS_ERROR_NOT_IMPLEMENTED; +} + +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) { + if (aIID.Equals(NS_GET_IID(nsIWebBrowserChrome3))) { + return GetWebBrowserChrome(reinterpret_cast<nsIWebBrowserChrome3**>(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, + bool aWidthSpecified, nsIURI* aURI, + const nsAString& aName, const nsACString& aFeatures, + bool aForceNoOpener, bool aForceNoReferrer, + nsDocShellLoadState* aLoadState, bool* aWindowIsNew, + BrowsingContext** aReturn) { + *aReturn = nullptr; + + RefPtr<BrowsingContext> parent = aOpenWindowInfo->GetParent(); + + int32_t openLocation = nsWindowWatcher::GetWindowOpenLocation( + parent->GetDOMWindow(), aChromeFlags, aCalledFromJS, aWidthSpecified, + 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(this, aOpenWindowInfo, aChromeFlags, + aCalledFromJS, aWidthSpecified, aURI, aName, + aFeatures, aForceNoOpener, aForceNoReferrer, + 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 (mSessionStoreListener) { + mSessionStoreListener->RemoveListeners(); + mSessionStoreListener = 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}; + } +} + +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(); + } + } + + CompositorBridgeChild* compositorChild = CompositorBridgeChild::Get(); + if (compositorChild) { + compositorChild->CancelNotifyAfterRemotePaint(this); + } + + 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( + WillChangeProcessResolver&& aResolve) { + if (mWebBrowser) { + mWebBrowser->SetWillChangeProcess(); + } + aResolve(true); + 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()); + MOZ_ASSERT(docShell); + if (!docShell) { + NS_WARNING("WebNavigation does not have a docshell"); + return IPC_OK(); + } + docShell->LoadURI(aLoadState, true); + + nsDocShell::Cast(docShell)->MaybeClearStorageAccessFlag(); + + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::URL, spec); + 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(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvCloneDocumentTreeIntoSelf( + const MaybeDiscarded<BrowsingContext>& aSourceBC) { + if (NS_WARN_IF(aSourceBC.IsNullOrDiscarded())) { + return IPC_OK(); + } + nsCOMPtr<Document> sourceDocument = aSourceBC.get()->GetDocument(); + if (NS_WARN_IF(!sourceDocument)) { + return IPC_OK(); + } + + nsCOMPtr<nsIDocShell> ourDocShell = do_GetInterface(WebNavigation()); + if (NS_WARN_IF(!ourDocShell)) { + return IPC_OK(); + } + + nsCOMPtr<nsIContentViewer> cv; + ourDocShell->GetContentViewer(getter_AddRefs(cv)); + if (NS_WARN_IF(!cv)) { + return IPC_OK(); + } + + RefPtr<Document> clone; + { + AutoPrintEventDispatcher dispatcher(*sourceDocument); + nsAutoScriptBlocker scriptBlocker; + bool hasInProcessCallbacks = false; + clone = sourceDocument->CreateStaticClone(ourDocShell, cv, + &hasInProcessCallbacks); + if (NS_WARN_IF(!clone)) { + return IPC_OK(); + } + } + + // Since the clone document is not parsed-created, we need to initialize + // layout manually. This is usually done in ReflowPrintObject for non-remote + // documents. + if (RefPtr<PresShell> ps = clone->GetPresShell()) { + if (!ps->DidInitialize()) { + nsresult rv = ps->Initialize(); + Unused << NS_WARN_IF(NS_FAILED(rv)); + } + } + + 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); + 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()); + + mOrientation = aDimensionInfo.orientation(); + 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; + + // 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::RecvSetIsUnderHiddenEmbedderElement( + const bool& aIsUnderHiddenEmbedderElement) { + if (RefPtr<PresShell> presShell = GetTopLevelPresShell()) { + presShell->SetIsUnderHiddenEmbedderElement(aIsUnderHiddenEmbedderElement); + } + 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) { + 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(); + CSSRect zoomToRect = CalculateRectToZoomTo(document, aPoint); + // 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, zoomToRect, DEFAULT_BEHAVIOR); + } +} + +mozilla::ipc::IPCResult BrowserChild::RecvHandleTap( + const GeckoContentController::TapType& aType, + const LayoutDevicePoint& aPoint, const Modifiers& aModifiers, + const ScrollableLayerGuid& aGuid, const uint64_t& aInputBlockId) { + // 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) { + return IPC_OK(); + } + if (!presShell->GetPresContext()) { + 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) { + mAPZEventState->ProcessSingleTap(point, scale, aModifiers, 1); + } + break; + case GeckoContentController::TapType::eDoubleTap: + HandleDoubleTap(point, aModifiers, aGuid); + break; + case GeckoContentController::TapType::eSecondTap: + if (mBrowserChildMessageManager) { + mAPZEventState->ProcessSingleTap(point, scale, aModifiers, 2); + } + 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) { + // 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); +} + +bool BrowserChild::NotifyAPZStateChange( + const ViewID& aViewId, + const layers::GeckoContentController::APZStateChange& aChange, + const int& aArg) { + mAPZEventState->ProcessAPZStateChange(aViewId, aChange, aArg); + 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. + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + observerService->NotifyObservers(nullptr, "APZ:TransformEnd", nullptr); + } + return true; +} + +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, 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::RecvSetKeyboardIndicators( + const UIStateChangeType& aShowFocusRings) { + nsCOMPtr<nsPIDOMWindowOuter> window = do_GetInterface(WebNavigation()); + NS_ENSURE_TRUE(window, IPC_OK()); + window->SetKeyboardIndicators(aShowFocusRings); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvStopIMEStateManagement() { + IMEStateManager::StopIMEStateManagement(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvMouseEvent( + const nsString& aType, const float& aX, const float& aY, + const int32_t& aButton, const int32_t& aClickCount, + const int32_t& aModifiers) { + // 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(); + APZCCallbackHelper::DispatchMouseEvent(presShell, aType, CSSPoint(aX, aY), + aButton, aClickCount, aModifiers, + MouseEvent_Binding::MOZ_SOURCE_UNKNOWN, + 0 /* Use the default value here. */); + return IPC_OK(); +} + +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); +} + +ScreenRect BrowserChild::GetTopLevelViewportVisibleRectInBrowserCoords() const { + return mTopLevelViewportVisibleRectInBrowserCoords; +} + +void BrowserChild::FlushAllCoalescedMouseData() { + MOZ_ASSERT(mCoalesceMouseMoveEvents); + + // Move all entries from mCoalescedMouseData to mToBeDispatchedMouseData. + for (auto iter = mCoalescedMouseData.Iter(); !iter.Done(); iter.Next()) { + CoalescedMouseData* data = iter.UserData(); + 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.LookupOrAdd(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 = new CoalescedMouseData(); + mCoalescedMouseData.Put(aEvent.pointerId, newData); + 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. + UniquePtr<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->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()) { + Unused << postLayerization.release(); + } +} + +mozilla::ipc::IPCResult BrowserChild::RecvNormalPriorityRealMouseButtonEvent( + const WidgetMouseEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId) { + return RecvRealMouseButtonEvent(aEvent, aGuid, aInputBlockId); +} + +// In case handling repeated mouse wheel takes much time, we skip firing current +// wheel event if it may be coalesced to the next one. +bool BrowserChild::MaybeCoalesceWheelEvent(const WidgetWheelEvent& aEvent, + const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId, + bool* aIsNextWheelEvent) { + MOZ_ASSERT(aIsNextWheelEvent); + if (aEvent.mMessage == eWheel) { + GetIPCChannel()->PeekMessages( + [aIsNextWheelEvent](const IPC::Message& aMsg) -> bool { + if (aMsg.type() == mozilla::dom::PBrowser::Msg_MouseWheelEvent__ID) { + *aIsNextWheelEvent = true; + } + return false; // Stop peeking. + }); + // We only coalesce the current event when + // 1. It's eWheel (we don't coalesce eOperationStart and eWheelOperationEnd) + // 2. It's not the first wheel event. + // 3. It's not the last wheel event. + // 4. It's dispatched before the last wheel event was processed + + // the processing time of the last event. + // This way pages spending lots of time in wheel listeners get wheel + // events coalesced more aggressively. + // 5. It has same attributes as the coalesced wheel event which is not yet + // fired. + if (!mLastWheelProcessedTimeFromParent.IsNull() && *aIsNextWheelEvent && + aEvent.mTimeStamp < (mLastWheelProcessedTimeFromParent + + mLastWheelProcessingDuration) && + (mCoalescedWheelData.IsEmpty() || + mCoalescedWheelData.CanCoalesce(aEvent, aGuid, aInputBlockId))) { + mCoalescedWheelData.Coalesce(aEvent, aGuid, aInputBlockId); + return true; + } + } + return false; +} + +nsEventStatus BrowserChild::DispatchWidgetEventViaAPZ(WidgetGUIEvent& aEvent) { + aEvent.ResetWaitingReplyFromRemoteProcessState(); + return APZCCallbackHelper::DispatchWidgetEvent(aEvent); +} + +void BrowserChild::MaybeDispatchCoalescedWheelEvent() { + if (mCoalescedWheelData.IsEmpty()) { + return; + } + 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()); + UniquePtr<DisplayportSetListener> postLayerization = + APZCCallbackHelper::SendSetTargetAPZCNotification( + mPuppetWidget, document, aEvent, aGuid.mLayersId, aInputBlockId); + if (postLayerization && postLayerization->Register()) { + Unused << postLayerization.release(); + } + } + + 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->ProcessWheelEvent(localEvent, aInputBlockId); + } +} + +mozilla::ipc::IPCResult BrowserChild::RecvMouseWheelEvent( + const WidgetWheelEvent& aEvent, const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId) { + bool isNextWheelEvent = false; + if (MaybeCoalesceWheelEvent(aEvent, aGuid, aInputBlockId, + &isNextWheelEvent)) { + return IPC_OK(); + } + if (isNextWheelEvent) { + // Update mLastWheelProcessedTimeFromParent so that we can compare the end + // time of the current event with the dispatched time of the next event. + mLastWheelProcessedTimeFromParent = aEvent.mTimeStamp; + mozilla::TimeStamp beforeDispatchingTime = TimeStamp::Now(); + MaybeDispatchCoalescedWheelEvent(); + DispatchWheelEvent(aEvent, aGuid, aInputBlockId); + mLastWheelProcessingDuration = (TimeStamp::Now() - beforeDispatchingTime); + mLastWheelProcessedTimeFromParent += mLastWheelProcessingDuration; + } else { + // This is the last wheel event. Set mLastWheelProcessedTimeFromParent to + // null moment to avoid coalesce the next incoming wheel event. + mLastWheelProcessedTimeFromParent = TimeStamp(); + MaybeDispatchCoalescedWheelEvent(); + 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)); + + 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(); + if (StaticPrefs::layout_css_touch_action_enabled()) { + allowedTouchBehaviors = + APZCCallbackHelper::SendSetAllowedTouchBehaviorNotification( + mPuppetWidget, document, localEvent, aInputBlockId, + mSetAllowedTouchBehaviorCallback); + } + UniquePtr<DisplayportSetListener> postLayerization = + APZCCallbackHelper::SendSetTargetAPZCNotification( + mPuppetWidget, document, localEvent, aGuid.mLayersId, + aInputBlockId); + if (postLayerization && postLayerization->Register()) { + Unused << postLayerization.release(); + } + } + + // 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(); + } + + 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 (!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(nsIWidget::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 nsIWidget::NativeKeyBindingsForSingleLineEditor: + case nsIWidget::NativeKeyBindingsForMultiLineEditor: + case nsIWidget::NativeKeyBindingsForRichTextEditor: + 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(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::RecvFlushTabState( + const uint32_t& aFlushId, const bool& aIsFinal) { + UpdateSessionStore(aFlushId, aIsFinal); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvUpdateEpoch(const uint32_t& aEpoch) { + mSessionStoreListener->SetEpoch(aEpoch); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvUpdateSHistory( + const bool& aImmediately) { + if (mSessionStoreListener) { + mSessionStoreListener->UpdateSHistoryChanges(aImmediately); + } + 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) { + if (SkipRepeatedKeyEvent(aEvent)) { + return IPC_OK(); + } + + MOZ_ASSERT( + aEvent.mMessage != eKeyPress || aEvent.AreAllEditCommandsInitialized(), + "eKeyPress event should have native key binding information"); + + // If content code called preventDefault() on a keydown event, then we don't + // want to process any following keypress events. + if (aEvent.mMessage == eKeyPress && mIgnoreKeyPressEvent) { + return IPC_OK(); + } + + WidgetKeyboardEvent localEvent(aEvent); + localEvent.mWidget = mPuppetWidget; + localEvent.mUniqueId = aEvent.mUniqueId; + 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 a response is desired from the content process, resend the key event. + if (aEvent.WantReplyFromContentProcess()) { + // 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, peventDefault() 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(); + } + // 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); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvNormalPriorityRealKeyEvent( + const WidgetKeyboardEvent& aEvent) { + return RecvRealKeyEvent(aEvent); +} + +mozilla::ipc::IPCResult BrowserChild::RecvCompositionEvent( + const WidgetCompositionEvent& aEvent) { + WidgetCompositionEvent localEvent(aEvent); + localEvent.mWidget = mPuppetWidget; + DispatchWidgetEventViaAPZ(localEvent); + Unused << SendOnEventNeedingAckHandled(aEvent.mMessage); + 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); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvNormalPrioritySelectionEvent( + const WidgetSelectionEvent& aEvent) { + return RecvSelectionEvent(aEvent); +} + +mozilla::ipc::IPCResult BrowserChild::RecvPasteTransferable( + const IPCDataTransfer& aDataTransfer, const bool& aIsPrivateData, + nsIPrincipal* aRequestingPrincipal, + const nsContentPolicyType& aContentPolicyType) { + 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( + aDataTransfer, aIsPrivateData, aRequestingPrincipal, aContentPolicyType, + trans, nullptr, this); + 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 uint32_t&, + const IAccessibleHolder&) { + 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 + +PColorPickerChild* BrowserChild::AllocPColorPickerChild(const nsString&, + const nsString&) { + MOZ_CRASH("unused"); + return nullptr; +} + +bool BrowserChild::DeallocPColorPickerChild(PColorPickerChild* aColorPicker) { + nsColorPickerProxy* picker = static_cast<nsColorPickerProxy*>(aColorPicker); + NS_RELEASE(picker); + return true; +} + +PFilePickerChild* BrowserChild::AllocPFilePickerChild(const nsString&, + const int16_t&) { + MOZ_CRASH("unused"); + return nullptr; +} + +bool BrowserChild::DeallocPFilePickerChild(PFilePickerChild* actor) { + nsFilePickerProxy* filePicker = static_cast<nsFilePickerProxy*>(actor); + NS_RELEASE(filePicker); + return true; +} + +PVsyncChild* BrowserChild::AllocPVsyncChild() { + RefPtr<dom::VsyncChild> actor = new VsyncChild(); + // There still has one ref-count after return, and it will be released in + // DeallocPVsyncChild(). + return actor.forget().take(); +} + +bool BrowserChild::DeallocPVsyncChild(PVsyncChild* aActor) { + MOZ_ASSERT(aActor); + + // This actor already has one ref-count. Please check AllocPVsyncChild(). + RefPtr<VsyncChild> actor = dont_AddRef(static_cast<VsyncChild*>(aActor)); + return true; +} + +RefPtr<VsyncChild> 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 (!IsWaylandDisabled() && !mVsyncChild) { + PVsyncChild* actor = SendPVsyncConstructor(); + mVsyncChild = static_cast<VsyncChild*>(actor); + } +#endif + return mVsyncChild; +} + +mozilla::ipc::IPCResult BrowserChild::RecvActivateFrameEvent( + const nsString& aType, const bool& capture) { + nsCOMPtr<nsPIDOMWindowOuter> window = do_GetInterface(WebNavigation()); + NS_ENSURE_TRUE(window, IPC_OK()); + nsCOMPtr<EventTarget> chromeHandler = window->GetChromeEventHandler(); + NS_ENSURE_TRUE(chromeHandler, IPC_OK()); + RefPtr<ContentListener> listener = new ContentListener(this); + chromeHandler->AddEventListener(aType, listener, capture); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvLoadRemoteScript( + const nsString& 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 nsString& 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; + UnpackClonedMessageDataForChild(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 mozilla::Maybe<uint64_t>& aSourceOuterWindowID, + 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) { + aCallback( + PrintPreviewResultInfo(0, 0, false, false, false)); // signal error + } + }); + + RefPtr<nsGlobalWindowOuter> sourceWindow; + if (aSourceOuterWindowID) { + sourceWindow = + nsGlobalWindowOuter::GetOuterWindowWithId(aSourceOuterWindowID.value()); + 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->GetNewPrintSettings(getter_AddRefs(printSettings)); + if (NS_WARN_IF(!printSettings)) { + return IPC_OK(); + } + printSettingsSvc->DeserializeToPrintSettings(aPrintData, printSettings); + + nsCOMPtr<nsIDocShell> docShellToCloneInto; + if (aSourceOuterWindowID) { + docShellToCloneInto = do_GetInterface(WebNavigation()); + if (NS_WARN_IF(!docShellToCloneInto)) { + return IPC_OK(); + } + } + + sourceWindow->Print(printSettings, + /* 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 uint64_t& aOuterWindowID, + const PrintData& aPrintData) { +#ifdef NS_PRINTING + RefPtr<nsGlobalWindowOuter> outerWindow = + nsGlobalWindowOuter::GetOuterWindowWithId(aOuterWindowID); + 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->GetNewPrintSettings(getter_AddRefs(printSettings)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return IPC_OK(); + } + + nsCOMPtr<nsIPrintSession> printSession = + do_CreateInstance("@mozilla.org/gfx/printsession;1", &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return IPC_OK(); + } + + printSettings->SetPrintSession(printSession); + printSettingsSvc->DeserializeToPrintSettings(aPrintData, printSettings); + { + IgnoredErrorResult rv; + outerWindow->Print(printSettings, + /* 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 == false); + 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, const layers::LayersObserverEpoch& aEpoch) { + if (mPendingDocShellBlockers > 0) { + mPendingRenderLayersReceivedMessage = true; + mPendingRenderLayers = aEnabled; + mPendingLayersObserverEpoch = aEpoch; + return IPC_OK(); + } + + // Since requests to change the rendering state come in from both the hang + // monitor channel and the PContent channel, we have an ordering problem. This + // code ensures that we respect the order in which the requests were made and + // ignore stale requests. + if (mLayersObserverEpoch >= aEpoch) { + return IPC_OK(); + } + mLayersObserverEpoch = aEpoch; + + 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(mLayersObserverEpoch); + } + }); + + if (aEnabled) { + ProcessHangMonitor::MaybeStartPaintWhileInterruptingJS(); + } + + if (mCompositorOptions) { + MOZ_ASSERT(mPuppetWidget); + RefPtr<LayerManager> lm = mPuppetWidget->GetLayerManager(); + MOZ_ASSERT(lm); + + // We send the current layer observer epoch to the compositor so that + // BrowserParent knows whether a layer update notification corresponds to + // the latest RecvRenderLayers request that was made. + lm->SetLayersObserverEpoch(mLayersObserverEpoch); + } + + mRenderLayers = aEnabled; + + if (aEnabled && IsVisible()) { + // This request is a no-op. + // In this case, we still want a MozLayerTreeReady notification to fire + // in the parent (so that it knows that the child has updated its epoch). + // PaintWhileInterruptingJSNoOp does that. + if (IPCOpen()) { + Unused << SendPaintWhileInterruptingJSNoOp(mLayersObserverEpoch); + } + return IPC_OK(); + } + + // FIXME(emilio): Probably / maybe this shouldn't be needed? See the comment + // in MakeVisible(), having the two separate states is not great. + UpdateVisibility(); + + if (!aEnabled) { + 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()) { + FrameLayerBuilder::InvalidateAllLayersForFrame( + nsLayoutUtils::GetDisplayRootFrame(root)); + 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->Paint(view, view->GetBounds(), PaintFlags::PaintLayers); + } + } + 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(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvHandledWindowedPluginKeyEvent( + const NativeEventData& aKeyEventData, const bool& aIsConsumed) { + if (NS_WARN_IF(!mPuppetWidget)) { + return IPC_OK(); + } + mPuppetWidget->HandledWindowedPluginKeyEvent(aKeyEventData, aIsConsumed); + return IPC_OK(); +} + +bool BrowserChild::InitBrowserChildMessageManager() { + 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->Get(uint64_t(aLayersId))); + sBrowserChildren->Put(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->HasLayerManager() || + mPuppetWidget->GetLayerManager()->GetBackendType() == + layers::LayersBackend::LAYERS_BASIC); + 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(); + RefPtr<LayerManager> lm = mPuppetWidget->GetLayerManager(); + MOZ_ASSERT(lm); + lm->SetLayersObserverEpoch(mLayersObserverEpoch); + } else { + NS_WARNING("Fallback to BasicLayerManager"); + 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); + + bool success = false; + if (mCompositorOptions->UseWebRender()) { + success = mPuppetWidget->CreateRemoteLayerManager( + [&](LayerManager* aLayerManager) -> bool { + MOZ_ASSERT(aLayerManager->AsWebRenderLayerManager()); + nsCString error; + return aLayerManager->AsWebRenderLayerManager()->Initialize( + aCompositorChild, wr::AsPipelineId(mLayersId), + &mTextureFactoryIdentifier, error); + }); + } else { + nsTArray<LayersBackend> ignored; + PLayerTransactionChild* shadowManager = + aCompositorChild->SendPLayerTransactionConstructor(ignored, + GetLayersId()); + if (shadowManager && + shadowManager->SendGetTextureFactoryIdentifier( + &mTextureFactoryIdentifier) && + mTextureFactoryIdentifier.mParentBackend != + LayersBackend::LAYERS_NONE) { + success = true; + } + if (!success) { + // Since no LayerManager is associated with the tab's widget, we will + // never have an opportunity to destroy the PLayerTransaction on the next + // device or compositor reset. Therefore, we make sure to forcefully close + // it here. Failure to do so will cause the next layer tree to fail to + // attach due since the compositor requires the old layer tree to be + // disassociated. + if (shadowManager) { + static_cast<LayerTransactionChild*>(shadowManager)->Destroy(); + shadowManager = nullptr; + } + NS_WARNING("failed to allocate layer transaction"); + } else { + success = mPuppetWidget->CreateRemoteLayerManager( + [&](LayerManager* aLayerManager) -> bool { + ShadowLayerForwarder* lf = aLayerManager->AsShadowForwarder(); + lf->SetShadowManager(shadowManager); + lf->IdentifyTextureHost(mTextureFactoryIdentifier); + return true; + }); + } + } + return success; +} + +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); + 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) { + nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation()); + if (docShell) { + // 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) { + if (nsIFrame* root = presShell->GetRootFrame()) { + root->InvalidateFrame(); + } + } + } + } + + return IPC_OK(); +} + +bool BrowserChild::IsVisible() { + return mPuppetWidget && mPuppetWidget->IsVisible(); +} + +void BrowserChild::UpdateVisibility() { + bool shouldBeVisible = mIsTopLevel ? mRenderLayers : mEffectsInfo.IsVisible(); + bool isVisible = IsVisible(); + + if (shouldBeVisible != isVisible) { + if (shouldBeVisible) { + MakeVisible(); + } else { + MakeHidden(); + } + } +} + +void BrowserChild::MakeVisible() { + if (IsVisible()) { + return; + } + + if (mPuppetWidget) { + mPuppetWidget->Show(true); + } + + nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation()); + if (!docShell) { + return; + } + + // The browser / tab-switcher is responsible of fixing the browsingContext + // state up explicitly via SetDocShellIsActive, which propagates to children + // automatically. + // + // We need it not to be observable, as this used via RecvRenderLayers and co., + // for stuff like async tab warming. + // + // We don't want to go through the docshell 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... + if (RefPtr<PresShell> presShell = docShell->GetPresShell()) { + presShell->SetIsActive(true); + } +} + +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 && mPuppetWidget->HasLayerManager()) { + ClearCachedResources(); + } + + if (nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation())) { + // Hide all plugins in this tab. 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 (nsPresContext* presContext = presShell->GetPresContext()) { + nsRootPresContext* rootPresContext = presContext->GetRootPresContext(); + nsIFrame* rootFrame = presShell->GetRootFrame(); + rootPresContext->ComputePluginGeometryUpdates(rootFrame, nullptr, + nullptr); + rootPresContext->ApplyPluginGeometryUpdates(); + } + presShell->SetIsActive(false); + } + } + + if (mPuppetWidget) { + mPuppetWidget->Show(false); + } +} + +NS_IMETHODIMP +BrowserChild::GetMessageManager(ContentFrameMessageManager** aResult) { + RefPtr<ContentFrameMessageManager> mm(mBrowserChildMessageManager); + mm.forget(aResult); + return *aResult ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +BrowserChild::GetWebBrowserChrome(nsIWebBrowserChrome3** aWebBrowserChrome) { + nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation()); + if (nsCOMPtr<nsIWebBrowserChrome3> chrome = + do_QueryActor("WebBrowserChrome", docShell->GetDocument())) { + chrome.forget(aWebBrowserChrome); + } else { + // TODO: remove fallback + NS_IF_ADDREF(*aWebBrowserChrome = mWebBrowserChrome); + } + + return NS_OK; +} + +NS_IMETHODIMP +BrowserChild::SetWebBrowserChrome(nsIWebBrowserChrome3* aWebBrowserChrome) { + mWebBrowserChrome = aWebBrowserChrome; + return NS_OK; +} + +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; +} + +void BrowserChild::SetTabId(const TabId& aTabId) { + MOZ_ASSERT(mUniqueId == 0); + + mUniqueId = aTabId; + NestedBrowserChildMap()[mUniqueId] = this; +} + +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 (!BuildClonedMessageDataForChild(Manager(), aData, data)) { + return false; + } + return SendSyncMessage(PromiseFlatString(aMessage), data, aRetVal); +} + +nsresult BrowserChild::DoSendAsyncMessage(const nsAString& aMessage, + StructuredCloneData& aData) { + ClonedMessageData data; + if (!BuildClonedMessageDataForChild(Manager(), 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); + + nsTArray<RefPtr<BrowserChild>> list; + if (!sBrowserChildren) { + return list; + } + + for (auto iter = sBrowserChildren->Iter(); !iter.Done(); iter.Next()) { + list.AppendElement(iter.Data()); + } + + return list; +} + +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<LayerManager> lm = mPuppetWidget->GetLayerManager(); + MOZ_ASSERT(lm); + + lm->DidComposite(aTransactionId, aCompositeStart, aCompositeEnd); +} + +void BrowserChild::DidRequestComposite(const TimeStamp& aCompositeReqStart, + const TimeStamp& aCompositeReqEnd) { + nsCOMPtr<nsIDocShell> docShellComPtr = do_GetInterface(WebNavigation()); + if (!docShellComPtr) { + return; + } + + nsDocShell* docShell = static_cast<nsDocShell*>(docShellComPtr.get()); + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + + if (timelines && timelines->HasConsumer(docShell)) { + // Since we're assuming that it's impossible for content JS to directly + // trigger a synchronous paint, we can avoid capturing a stack trace here, + // which means we won't run into JS engine reentrancy issues like bug + // 1310014. + timelines->AddMarkerForDocShell( + docShell, "CompositeForwardTransaction", aCompositeReqStart, + MarkerTracingType::START, MarkerStackRequest::NO_STACK); + timelines->AddMarkerForDocShell(docShell, "CompositeForwardTransaction", + aCompositeReqEnd, MarkerTracingType::END, + MarkerStackRequest::NO_STACK); + } +} + +void BrowserChild::ClearCachedResources() { + MOZ_ASSERT(mPuppetWidget); + RefPtr<LayerManager> lm = mPuppetWidget->GetLayerManager(); + MOZ_ASSERT(lm); + + lm->ClearCachedResources(); + + if (nsCOMPtr<Document> document = GetTopLevelDocument()) { + nsPresContext* presContext = document->GetPresContext(); + if (presContext) { + presContext->NotifyPaintStatusReset(); + } + } +} + +void BrowserChild::InvalidateLayers() { + MOZ_ASSERT(mPuppetWidget); + RefPtr<LayerManager> lm = mPuppetWidget->GetLayerManager(); + MOZ_ASSERT(lm); + + FrameLayerBuilder::InvalidateAllLayers(lm); +} + +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()); + + // 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(); + RefPtr<LayerManager> lm = mPuppetWidget->GetLayerManager(); + MOZ_ASSERT(lm); + lm->SetLayersObserverEpoch(mLayersObserverEpoch); + + nsCOMPtr<Document> doc(GetTopLevelDocument()); + doc->NotifyLayerManagerRecreated(); +} + +void BrowserChild::ReinitRenderingForDeviceReset() { + InvalidateLayers(); + + RefPtr<LayerManager> lm = mPuppetWidget->GetLayerManager(); + if (WebRenderLayerManager* wlm = lm->AsWebRenderLayerManager()) { + wlm->DoDestroy(/* aIsSync */ true); + } else if (ClientLayerManager* clm = lm->AsClientLayerManager()) { + if (ShadowLayerForwarder* fwd = clm->AsShadowForwarder()) { + // Force the LayerTransactionChild to synchronously shutdown. It is + // okay to do this early, we'll simply stop sending messages. This + // step is necessary since otherwise the compositor will think we + // are trying to attach two layer trees to the same ID. + fwd->SynchronouslyShutdown(); + } + } else { + if (mLayersConnected.isNothing()) { + return; + } + } + + // 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<LayerManager> lm = mPuppetWidget->GetLayerManager(); + MOZ_ASSERT(lm); + lm->UpdatePartialPrerenderedAnimations(aJankedAnimations); +} + +mozilla::ipc::IPCResult BrowserChild::RecvRequestNotifyAfterRemotePaint() { + // Get the CompositorBridgeChild instance for this content thread. + CompositorBridgeChild* compositor = CompositorBridgeChild::Get(); + + // Tell the CompositorBridgeChild that, when it gets a RemotePaintIsReady + // message that it should forward it us so that we can bounce it to our + // BrowserParent. + compositor->RequestNotifyAfterRemotePaint(this); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvUIResolutionChanged( + const float& aDpi, const int32_t& aRounding, const double& aScale) { + nsCOMPtr<Document> document(GetTopLevelDocument()); + if (!document) { + return IPC_OK(); + } + + ScreenIntSize oldScreenSize = GetInnerSize(); + if (aDpi > 0) { + mPuppetWidget->UpdateBackingScaleCache(aDpi, aRounding, aScale); + } + RefPtr<nsPresContext> presContext = document->GetPresContext(); + if (presContext) { + presContext->UIResolutionChangedSync(); + } + + 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); + } + + 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(0, &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::RecvSetWidgetNativeData( + const WindowsHandle& aWidgetNativeData) { + mWidgetNativeData = aWidgetNativeData; + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserChild::RecvReleaseAllPointerCapture() { + PointerEventHandler::ReleaseAllPointerCapture(); + return IPC_OK(); +} + +mozilla::plugins::PPluginWidgetChild* BrowserChild::AllocPPluginWidgetChild() { +#ifdef XP_WIN + return new mozilla::plugins::PluginWidgetChild(); +#else + MOZ_ASSERT_UNREACHABLE("AllocPPluginWidgetChild only supports Windows"); + return nullptr; +#endif +} + +bool BrowserChild::DeallocPPluginWidgetChild( + mozilla::plugins::PPluginWidgetChild* aActor) { + delete aActor; + return true; +} + +#ifdef XP_WIN +nsresult BrowserChild::CreatePluginWidget(nsIWidget* aParent, + nsIWidget** aOut) { + *aOut = nullptr; + mozilla::plugins::PluginWidgetChild* child = + static_cast<mozilla::plugins::PluginWidgetChild*>( + SendPPluginWidgetConstructor()); + if (!child) { + NS_ERROR("couldn't create PluginWidgetChild"); + return NS_ERROR_UNEXPECTED; + } + nsCOMPtr<nsIWidget> pluginWidget = + nsIWidget::CreatePluginProxyWidget(this, child); + if (!pluginWidget) { + NS_ERROR("couldn't create PluginWidgetProxy"); + return NS_ERROR_UNEXPECTED; + } + + nsWidgetInitData initData; + initData.mWindowType = eWindowType_plugin_ipc_content; + initData.clipChildren = true; + initData.clipSiblings = true; + nsresult rv = pluginWidget->Create( + aParent, nullptr, LayoutDeviceIntRect(0, 0, 0, 0), &initData); + if (NS_FAILED(rv)) { + NS_WARNING("Creating native plugin widget on the chrome side failed."); + } + pluginWidget.forget(aOut); + return rv; +} +#endif // XP_WIN + +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 Some(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( + const layers::LayersObserverEpoch& aEpoch) { + if (!IPCOpen() || !mPuppetWidget || !mPuppetWidget->HasLayerManager()) { + // 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(true /* aEnabled */, aEpoch); +} + +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::BeginSendingWebProgressEventsToParent() { + mShouldSendWebProgressEventsToParent = true; + return NS_OK; +} + +nsresult BrowserChild::GetHasSiblings(bool* aHasSiblings) { + *aHasSiblings = mHasSiblings; + return NS_OK; +} + +nsresult BrowserChild::SetHasSiblings(bool aHasSiblings) { + mHasSiblings = aHasSiblings; + return NS_OK; +} + +NS_IMETHODIMP BrowserChild::OnStateChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aStateFlags, + nsresult aStatus) { + if (!IPCOpen() || !mShouldSendWebProgressEventsToParent) { + return NS_OK; + } + + nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation()); + if (!docShell) { + 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; + } + + RefPtr<Document> document; + if (nsCOMPtr<nsPIDOMWindowOuter> outerWindow = do_GetInterface(docShell)) { + document = outerWindow->GetExtantDoc(); + } else { + return NS_OK; + } + + Maybe<WebProgressData> webProgressData; + Maybe<WebProgressStateChangeData> stateChangeData; + RequestData requestData; + + MOZ_TRY(PrepareProgressListenerData(aWebProgress, aRequest, webProgressData, + requestData)); + + if (webProgressData->isTopLevel()) { + stateChangeData.emplace(); + + stateChangeData->isNavigating() = docShell->GetIsNavigating(); + stateChangeData->mayEnableCharacterEncodingMenu() = + docShell->GetMayEnableCharacterEncodingMenu(); + stateChangeData->charsetAutodetected() = docShell->GetCharsetAutodetected(); + + 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; + } + + Maybe<WebProgressData> webProgressData; + RequestData requestData; + + nsresult rv = PrepareProgressListenerData(aWebProgress, aRequest, + webProgressData, requestData); + NS_ENSURE_SUCCESS(rv, rv); + + Unused << SendOnProgressChange(webProgressData, requestData, aCurSelfProgress, + aMaxSelfProgress, 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<nsIWebNavigation> webNav = WebNavigation(); + nsCOMPtr<nsIDocShell> docShell = do_GetInterface(webNav); + if (!docShell) { + return NS_OK; + } + + RefPtr<Document> document; + if (nsCOMPtr<nsPIDOMWindowOuter> outerWindow = do_GetInterface(docShell)) { + document = outerWindow->GetExtantDoc(); + } else { + return NS_OK; + } + + if (!document) { + return NS_OK; + } + + Maybe<WebProgressData> webProgressData; + RequestData requestData; + + MOZ_TRY(PrepareProgressListenerData(aWebProgress, aRequest, webProgressData, + requestData)); + + Maybe<WebProgressLocationChangeData> locationChangeData; + + bool canGoBack = false; + bool canGoForward = false; + + MOZ_TRY(webNav->GetCanGoBack(&canGoBack)); + MOZ_TRY(webNav->GetCanGoForward(&canGoForward)); + + if (aWebProgress && webProgressData->isTopLevel()) { + 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->charsetAutodetected() = + docShell->GetCharsetAutodetected(); + + 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; + } + + Maybe<WebProgressData> webProgressData; + RequestData requestData; + + nsresult rv = PrepareProgressListenerData(aWebProgress, aRequest, + webProgressData, requestData); + + NS_ENSURE_SUCCESS(rv, rv); + + const nsString message(aMessage); + + Unused << SendOnStatusChange(webProgressData, requestData, aStatus, message); + + 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, + int32_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::PrepareProgressListenerData( + nsIWebProgress* aWebProgress, nsIRequest* aRequest, + Maybe<WebProgressData>& aWebProgressData, RequestData& aRequestData) { + if (aWebProgress) { + aWebProgressData.emplace(); + + bool isTopLevel = false; + nsresult rv = aWebProgress->GetIsTopLevel(&isTopLevel); + NS_ENSURE_SUCCESS(rv, rv); + aWebProgressData->isTopLevel() = isTopLevel; + + bool isLoadingDocument = false; + rv = aWebProgress->GetIsLoadingDocument(&isLoadingDocument); + NS_ENSURE_SUCCESS(rv, rv); + aWebProgressData->isLoadingDocument() = isLoadingDocument; + + uint32_t loadType = 0; + rv = aWebProgress->GetLoadType(&loadType); + NS_ENSURE_SUCCESS(rv, rv); + aWebProgressData->loadType() = loadType; + } + + nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); + if (channel) { + nsCOMPtr<nsIURI> uri; + nsresult rv = channel->GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + aRequestData.requestURI() = uri; + + rv = channel->GetOriginalURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + aRequestData.originalRequestURI() = uri; + + nsCOMPtr<nsIClassifiedChannel> classifiedChannel = + do_QueryInterface(channel); + if (classifiedChannel) { + nsAutoCString matchedList; + rv = classifiedChannel->GetMatchedList(matchedList); + NS_ENSURE_SUCCESS(rv, rv); + aRequestData.matchedList() = std::move(matchedList); + } + } + return NS_OK; +} + +bool BrowserChild::UpdateSessionStore(uint32_t aFlushId, bool aIsFinal) { + if (!mSessionStoreListener) { + return false; + } + RefPtr<ContentSessionStore> store = mSessionStoreListener->GetSessionStore(); + + Maybe<nsCString> docShellCaps; + if (store->IsDocCapChanged()) { + docShellCaps.emplace(store->GetDocShellCaps()); + } + + Maybe<bool> privatedMode; + if (store->IsPrivateChanged()) { + privatedMode.emplace(store->GetPrivateModeEnabled()); + } + + nsTArray<int32_t> positionDescendants; + nsTArray<nsCString> positions; + if (store->IsScrollPositionChanged()) { + store->GetScrollPositions(positions, positionDescendants); + } + + nsTArray<InputFormData> inputs; + nsTArray<CollectedInputDataValue> idVals, xPathVals; + if (store->IsFormDataChanged()) { + inputs = store->GetInputs(idVals, xPathVals); + } + + nsTArray<nsCString> origins; + nsTArray<nsString> keys, values; + bool isFullStorage = false; + if (store->IsStorageUpdated()) { + isFullStorage = store->GetAndClearStorageChanges(origins, keys, values); + } + + Unused << SendSessionStoreUpdate( + docShellCaps, privatedMode, positions, positionDescendants, inputs, + idVals, xPathVals, origins, keys, values, isFullStorage, + store->GetAndClearSHistoryChanged(), aFlushId, aIsFinal, + mSessionStoreListener->GetEpoch()); + return true; +} + +#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) { + if (!IPCOpen()) { + return; + } + + Maybe<WebProgressData> webProgressData; + RequestData requestData; + nsresult rv = PrepareProgressListenerData(nullptr, aChannel, webProgressData, + requestData); + NS_ENSURE_SUCCESS_VOID(rv); + + Unused << SendNotifyContentBlockingEvent(aEvent, requestData, aBlocked, + PromiseFlatCString(aTrackingOrigin), + aTrackingFullHashes, aReason); +} + +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(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() { + nsCOMPtr<nsIEventTarget> target = EventTargetFor(TaskCategory::Other); + return target.forget(); +} + +nsresult BrowserChildMessageManager::Dispatch( + TaskCategory aCategory, already_AddRefed<nsIRunnable>&& aRunnable) { + return DispatcherTrait::Dispatch(aCategory, std::move(aRunnable)); +} + +nsISerialEventTarget* BrowserChildMessageManager::EventTargetFor( + TaskCategory aCategory) const { + return DispatcherTrait::EventTargetFor(aCategory); +} + +AbstractThread* BrowserChildMessageManager::AbstractMainThreadFor( + TaskCategory aCategory) { + return DispatcherTrait::AbstractMainThreadFor(aCategory); +} diff --git a/dom/ipc/BrowserChild.h b/dom/ipc/BrowserChild.h new file mode 100644 index 0000000000..5b948499cf --- /dev/null +++ b/dom/ipc/BrowserChild.h @@ -0,0 +1,936 @@ +/* -*- 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 "nsIEmbeddingSiteWindow.h" +#include "nsIWebBrowserChromeFocus.h" +#include "nsIDOMEventListener.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/CoalescedWheelData.h" +#include "mozilla/dom/MessageManagerCallback.h" +#include "mozilla/dom/VsyncChild.h" +#include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/EventForwards.h" +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/APZCCallbackHelper.h" +#include "mozilla/layers/CompositorOptions.h" +#include "mozilla/layers/GeckoContentControllerTypes.h" +#include "nsIWebBrowserChrome3.h" +#include "mozilla/dom/ipc/IdType.h" +#include "AudioChannelService.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 { +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 ContentSessionStore; +class TabListener; +class RequestData; +class WebProgressData; + +class BrowserChildMessageManager : public ContentFrameMessageManager, + public nsIMessageSender, + public DispatcherTrait, + 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; + + virtual Nullable<WindowProxyHolder> GetContent(ErrorResult& aError) override; + virtual already_AddRefed<nsIDocShell> GetDocShell( + ErrorResult& aError) override; + virtual 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. + virtual nsresult Dispatch(mozilla::TaskCategory aCategory, + already_AddRefed<nsIRunnable>&& aRunnable) override; + + virtual nsISerialEventTarget* EventTargetFor( + mozilla::TaskCategory aCategory) const override; + + virtual AbstractThread* AbstractMainThreadFor( + mozilla::TaskCategory aCategory) override; + + RefPtr<BrowserChild> mBrowserChild; + + protected: + ~BrowserChildMessageManager(); +}; + +class ContentListener final : public nsIDOMEventListener { + public: + explicit ContentListener(BrowserChild* aBrowserChild) + : mBrowserChild(aBrowserChild) {} + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMEVENTLISTENER + protected: + ~ContentListener() = default; + BrowserChild* mBrowserChild; +}; + +/** + * 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 nsIEmbeddingSiteWindow, + public nsIWebBrowserChromeFocus, + public nsIInterfaceRequestor, + public nsIWindowProvider, + public nsSupportsWeakReference, + public nsIBrowserChild, + public nsIObserver, + public nsIWebProgressListener2, + public TabContext, + public nsITooltipListener, + public mozilla::ipc::IShmemAllocator { + typedef mozilla::widget::PuppetWidget PuppetWidget; + typedef mozilla::dom::ClonedMessageData ClonedMessageData; + typedef mozilla::dom::CoalescedMouseData CoalescedMouseData; + typedef mozilla::dom::CoalescedWheelData CoalescedWheelData; + typedef mozilla::layers::APZEventState APZEventState; + typedef mozilla::layers::SetAllowedTouchBehaviorCallback + SetAllowedTouchBehaviorCallback; + typedef mozilla::layers::TouchBehaviorFlags 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; } + + const TabId GetTabId() const { + MOZ_ASSERT(mUniqueId != 0); + return mUniqueId; + } + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_NSIWEBBROWSERCHROME + NS_DECL_NSIEMBEDDINGSITEWINDOW + 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; } + + /** + * 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 RecvResumeLoad(const uint64_t& aPendingSwitchID, + const ParentShowInfo&); + + mozilla::ipc::IPCResult RecvCloneDocumentTreeIntoSelf( + const MaybeDiscarded<BrowsingContext>& aSourceBC); + + 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); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY + mozilla::ipc::IPCResult RecvMouseEvent(const nsString& aType, const float& aX, + const float& aY, + const int32_t& aButton, + const int32_t& aClickCount, + const int32_t& aModifiers); + + 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); + + 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); + + mozilla::ipc::IPCResult RecvNormalPriorityRealKeyEvent( + const mozilla::WidgetKeyboardEvent& aEvent); + + 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 RecvFlushTabState(const uint32_t& aFlushId, + const bool& aIsFinal); + + mozilla::ipc::IPCResult RecvUpdateEpoch(const uint32_t& aEpoch); + + mozilla::ipc::IPCResult RecvUpdateSHistory(const bool& aImmediately); + + 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 RecvSetIsUnderHiddenEmbedderElement( + const bool& aIsUnderHiddenEmbedderElement); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY + mozilla::ipc::IPCResult RecvPasteTransferable( + const IPCDataTransfer& aDataTransfer, const bool& aIsPrivateData, + nsIPrincipal* aRequestingPrincipal, + const nsContentPolicyType& aContentPolicyType); + + mozilla::ipc::IPCResult RecvActivateFrameEvent(const nsString& aType, + const bool& aCapture); + + mozilla::ipc::IPCResult RecvLoadRemoteScript(const nsString& aURL, + const bool& aRunInGlobalScope); + + mozilla::ipc::IPCResult RecvAsyncMessage(const nsString& 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 uint32_t&, + const IAccessibleHolder&); + bool DeallocPDocAccessibleChild(PDocAccessibleChild*); +#endif + + PColorPickerChild* AllocPColorPickerChild(const nsString& aTitle, + const nsString& aInitialColor); + + bool DeallocPColorPickerChild(PColorPickerChild* aActor); + + PFilePickerChild* AllocPFilePickerChild(const nsString& aTitle, + const int16_t& aMode); + + virtual PVsyncChild* AllocPVsyncChild(); + + virtual bool DeallocPVsyncChild(PVsyncChild* aActor); + + RefPtr<VsyncChild> GetVsyncChild(); + + bool DeallocPFilePickerChild(PFilePickerChild* aActor); + + nsIWebNavigation* WebNavigation() const { return mWebNav; } + + PuppetWidget* WebWidget() { return mPuppetWidget; } + + bool IsTransparent() const { return mIsTransparent; } + + const EffectsInfo& GetEffectsInfo() const { return mEffectsInfo; } + + hal::ScreenOrientation GetOrientation() const { return mOrientation; } + + void SetBackgroundColor(const nscolor& aColor); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual mozilla::ipc::IPCResult RecvUpdateEffects( + const EffectsInfo& aEffects); + + void RequestEditCommands(nsIWidget::NativeKeyBindingsType aType, + const WidgetKeyboardEvent& aEvent, + nsTArray<CommandInt>& aCommands); + + bool IsVisible(); + + /** + * Signal to this BrowserChild that it should be made visible: + * activated widget, retained layer tree, etc. (Respectively, + * made not visible.) + */ + MOZ_CAN_RUN_SCRIPT void UpdateVisibility(); + MOZ_CAN_RUN_SCRIPT void MakeVisible(); + void MakeHidden(); + + 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 DidRequestComposite(const TimeStamp& aCompositeReqStart, + const TimeStamp& aCompositeReqEnd); + + void ClearCachedResources(); + void InvalidateLayers(); + 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); + + mozilla::ipc::IPCResult RecvHandledWindowedPluginKeyEvent( + const mozilla::NativeEventData& aKeyEventData, const bool& aIsConsumed); + + mozilla::ipc::IPCResult RecvPrintPreview( + const PrintData& aPrintData, + const mozilla::Maybe<uint64_t>& aSourceOuterWindowID, + PrintPreviewResolver&& aCallback); + + mozilla::ipc::IPCResult RecvExitPrintPreview(); + + mozilla::ipc::IPCResult RecvPrint(const uint64_t& aOuterWindowID, + const PrintData& aPrintData); + + mozilla::ipc::IPCResult RecvUpdateNativeWindowHandle( + const uintptr_t& aNewHandle); + + mozilla::ipc::IPCResult RecvWillChangeProcess( + WillChangeProcessResolver&& aResolve); + /** + * Native widget remoting protocol for use with windowed plugins with e10s. + */ + PPluginWidgetChild* AllocPPluginWidgetChild(); + + bool DeallocPPluginWidgetChild(PPluginWidgetChild* aActor); + +#ifdef XP_WIN + nsresult CreatePluginWidget(nsIWidget* aParent, nsIWidget** aOut); +#endif + + 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); + + 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); + + void SetAllowedTouchBehavior( + uint64_t aInputBlockId, const nsTArray<TouchBehaviorFlags>& aFlags) const; + + bool UpdateFrame(const layers::RepaintRequest& aRequest); + bool NotifyAPZStateChange( + const ViewID& aViewId, + const layers::GeckoContentController_APZStateChange& aChange, + const int& aArg); + 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(const layers::LayersObserverEpoch& aEpoch); + + nsresult CanCancelContentJS(nsIRemoteTab::NavigationType aNavigationType, + int32_t aNavigationIndex, nsIURI* aNavigationURI, + int32_t aEpoch, bool* aCanCancel); + + layers::LayersObserverEpoch LayersObserverEpoch() const { + return mLayersObserverEpoch; + } + +#if defined(XP_WIN) && defined(ACCESSIBILITY) + uintptr_t GetNativeWindowHandle() const { return mNativeWindowHandle; } +#endif + + BrowsingContext* GetBrowsingContext() const { return mBrowsingContext; } + +#if defined(ACCESSIBILITY) + void SetTopLevelDocAccessibleChild(PDocAccessibleChild* aTopLevelChild) { + mTopLevelDocAccessibleChild = aTopLevelChild; + } + + PDocAccessibleChild* GetTopLevelDocAccessibleChild() { + return mTopLevelDocAccessibleChild; + } +#endif + + // The HANDLE object for the widget this BrowserChild in. + WindowsHandle WidgetNativeData() { return mWidgetNativeData; } + + // 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. + mozilla::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 HandleRealMouseButtonEvent(const WidgetMouseEvent& aEvent, + const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId); + + void SetCancelContentJSEpoch(int32_t aEpoch) { + mCancelContentJSEpoch = aEpoch; + } + + bool UpdateSessionStore(uint32_t aFlushId, bool aIsFinal = false); + +#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); + + protected: + virtual ~BrowserChild(); + + mozilla::ipc::IPCResult RecvDestroy(); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY + mozilla::ipc::IPCResult RecvRenderLayers( + const bool& aEnabled, const layers::LayersObserverEpoch& aEpoch); + + mozilla::ipc::IPCResult RecvNavigateByKey(const bool& aForward, + const bool& aForDocumentNavigation); + + mozilla::ipc::IPCResult RecvRequestNotifyAfterRemotePaint(); + + mozilla::ipc::IPCResult RecvSuppressDisplayport(const bool& aEnabled); + + mozilla::ipc::IPCResult RecvScrollbarPreferenceChanged(ScrollbarPreference); + + mozilla::ipc::IPCResult RecvSetKeyboardIndicators( + const UIStateChangeType& aShowFocusRings); + + mozilla::ipc::IPCResult RecvStopIMEStateManagement(); + + mozilla::ipc::IPCResult RecvAllowScriptsToClose(); + + mozilla::ipc::IPCResult RecvSetWidgetNativeData( + const WindowsHandle& aWidgetNativeData); + + mozilla::ipc::IPCResult RecvReleaseAllPointerCapture(); + + private: + void HandleDoubleTap(const CSSPoint& aPoint, const Modifiers& aModifiers, + const ScrollableLayerGuid& aGuid); + + // Notify others that our TabContext has been updated. + // + // You should call this after calling TabContext::SetTabContext(). We also + // call this during Init(). + void NotifyTabContextUpdated(); + + 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(); + + void SetTabId(const TabId& aTabId); + + ScreenIntRect GetOuterRect(); + + void SetUnscaledInnerSize(const CSSSize& aSize) { + mUnscaledInnerSize = aSize; + } + + bool SkipRepeatedKeyEvent(const WidgetKeyboardEvent& aEvent); + + void UpdateRepeatedKeyEventEndTime(const WidgetKeyboardEvent& aEvent); + + bool MaybeCoalesceWheelEvent(const WidgetWheelEvent& aEvent, + const ScrollableLayerGuid& aGuid, + const uint64_t& aInputBlockId, + bool* aIsNextWheelEvent); + + void MaybeDispatchCoalescedWheelEvent(); + + /** + * 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 PrepareProgressListenerData(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + Maybe<WebProgressData>& aWebProgressData, + RequestData& aRequestData); + already_AddRefed<nsIWebBrowserChrome3> GetWebBrowserChromeActor(); + + class DelayedDeleteRunnable; + + RefPtr<BrowserChildMessageManager> mBrowserChildMessageManager; + nsCOMPtr<nsIWebBrowserChrome3> mWebBrowserChrome; + 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; + EffectsInfo mEffectsInfo; + bool mDidFakeShow; + bool mTriedBrowserInit; + hal::ScreenOrientation mOrientation; + + RefPtr<VsyncChild> mVsyncChild; + + bool mIgnoreKeyPressEvent; + RefPtr<APZEventState> mAPZEventState; + SetAllowedTouchBehaviorCallback mSetAllowedTouchBehaviorCallback; + bool mHasValidInnerSize; + bool mDestroyed; + + // 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; + + // Whether or not this browser is the child part of the top level PBrowser + // actor in a remote browser. + bool mIsTopLevel; + + // Whether or not this tab has siblings (other tabs in the same window). + // This is one factor used when choosing to allow or deny a non-system + // script's attempt to resize the window. + bool mHasSiblings; + + // 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; + + bool mIsTransparent; + + bool mIPCOpen; + CSSSize mUnscaledInnerSize; + bool mDidSetRealShowInfo; + bool mDidLoadURLInit; + + bool mSkipKeyPress; + + // 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; + RefPtr<CoalescedMouseMoveFlusher> mCoalescedMouseEventFlusher; + + RefPtr<layers::IAPZCTreeManager> mApzcTreeManager; + RefPtr<TabListener> mSessionStoreListener; + + // The most recently seen layer observer epoch in RecvSetDocShellIsActive. + layers::LayersObserverEpoch mLayersObserverEpoch; + +#if defined(XP_WIN) && defined(ACCESSIBILITY) + // The handle associated with the native window that contains this tab + uintptr_t mNativeWindowHandle; +#endif // defined(XP_WIN) + +#if defined(ACCESSIBILITY) + PDocAccessibleChild* mTopLevelDocAccessibleChild; +#endif + bool mCoalesceMouseMoveEvents; + + bool mShouldSendWebProgressEventsToParent; + + // Whether we are rendering to the compositor or not. + bool mRenderLayers; + + // In some circumstances, a DocShell might be in a state where it is + // "blocked", and we should not attempt to change its active state or + // the underlying PresShell state until the DocShell becomes unblocked. + // It is possible, however, for the parent process to send commands to + // change those states while the DocShell is blocked. We store those + // states temporarily as "pending", and only apply them once the DocShell + // is no longer blocked. + bool mPendingDocShellIsActive; + bool mPendingDocShellReceivedMessage; + bool mPendingRenderLayers; + bool mPendingRenderLayersReceivedMessage; + layers::LayersObserverEpoch mPendingLayersObserverEpoch; + // When mPendingDocShellBlockers is greater than 0, the DocShell is blocked, + // and once it reaches 0, it is no longer blocked. + uint32_t mPendingDocShellBlockers; + int32_t mCancelContentJSEpoch; + + WindowsHandle mWidgetNativeData; + + Maybe<LayoutDeviceToLayoutDeviceMatrix4x4> mChildToParentConversionMatrix; + ScreenRect mTopLevelViewportVisibleRectInBrowserCoords; + +#ifdef XP_WIN + // Should only be accessed on main thread. + Maybe<bool> mWindowSupportsProtectedMedia; +#endif + + 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..6815775f21 --- /dev/null +++ b/dom/ipc/BrowserHost.cpp @@ -0,0 +1,263 @@ +/* -*- 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 "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; +} + +a11y::DocAccessibleParent* BrowserHost::GetTopLevelDocAccessible() const { + return mRoot->GetTopLevelDocAccessible(); +} + +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) { + if (!mRoot) { + *aHasLayers = false; + return NS_OK; + } + *aHasLayers = mRoot->GetHasLayers(); + return NS_OK; +} + +/* void resolutionChanged (); */ +NS_IMETHODIMP +BrowserHost::NotifyResolutionChanged(void) { + if (!mRoot) { + return NS_OK; + } + VisitAll([](BrowserParent* aBrowserParent) { + aBrowserParent->NotifyResolutionChanged(); + }); + return NS_OK; +} + +/* void deprioritize (); */ +NS_IMETHODIMP +BrowserHost::Deprioritize(void) { + if (!mRoot) { + return NS_OK; + } + VisitAll( + [](BrowserParent* aBrowserParent) { aBrowserParent->Deprioritize(); }); + 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 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); +} + +/* boolean startApzAutoscroll (in float aAnchorX, in float aAnchorY, in nsViewID + * aScrollId, in uint32_t aPresShellId); */ +NS_IMETHODIMP +BrowserHost::StartApzAutoscroll(float aAnchorX, float aAnchorY, + nsViewID aScrollId, uint32_t aPresShellId, + bool* _retval) { + if (!mRoot) { + return NS_OK; + } + *_retval = + mRoot->StartApzAutoscroll(aAnchorX, aAnchorY, aScrollId, aPresShellId); + return NS_OK; +} + +/* void stopApzAutoscroll (in nsViewID aScrollId, in uint32_t aPresShellId); */ +NS_IMETHODIMP +BrowserHost::StopApzAutoscroll(nsViewID aScrollId, uint32_t aPresShellId) { + if (!mRoot) { + return NS_OK; + } + mRoot->StopApzAutoscroll(aScrollId, aPresShellId); + 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; + } + if (StaticPrefs::dom_ipc_cancel_content_js_when_navigating()) { + 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..fd3a6a4973 --- /dev/null +++ b/dom/ipc/BrowserHost.h @@ -0,0 +1,108 @@ +/* -*- 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; + + 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..5ede68022d --- /dev/null +++ b/dom/ipc/BrowserParent.cpp @@ -0,0 +1,4125 @@ +/* -*- 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" + +#ifdef ACCESSIBILITY +# include "mozilla/a11y/DocAccessibleParent.h" +# include "mozilla/a11y/Platform.h" +# include "mozilla/a11y/ProxyAccessibleBase.h" +# include "nsAccessibilityService.h" +#endif +#include "mozilla/dom/BrowserHost.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/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/RemoteWebProgress.h" +#include "mozilla/dom/RemoteWebProgressRequest.h" +#include "mozilla/dom/SessionHistoryEntry.h" +#include "mozilla/dom/SessionStoreUtils.h" +#include "mozilla/dom/SessionStoreUtilsBinding.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/Hal.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/plugins/PPluginWidgetParent.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/MiscEvents.h" +#include "mozilla/MouseEvents.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/StaticPrefs_dom.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 "GeckoProfiler.h" +#include "MMPrinter.h" +#include "SessionStoreFunctions.h" +#include "mozilla/dom/CrashReport.h" +#include "nsISecureBrowserUI.h" +#include "nsIXULRuntime.h" +#include "VsyncSource.h" + +#ifdef XP_WIN +# include "mozilla/plugins/PluginWidgetParent.h" +# 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 + +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; +using mozilla::StaticAutoPtr; +using mozilla::Unused; + +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; +/* static */ +BrowserParent* BrowserParent::sPointerLockedRemoteTarget = 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::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), + mRemoteLayerTreeOwner{}, + mLayerTreeEpoch{1}, + mChildToParentConversionMatrix{}, + mRect(0, 0, 0, 0), + mDimensions(0, 0), + mOrientation(0), + mDPI(0), + mRounding(0), + mDefaultScale(0), + mUpdatedDimensions(false), + mSizeMode(nsSizeMode_Normal), + mClientOffset{}, + mChromeOffset{}, + mCreatingWindow(false), + mDelayedFrameScripts{}, + mCursor(eCursorInvalid), + mCustomCursor{}, + mCustomCursorHotspotX(0), + mCustomCursorHotspotY(0), + mVerifyDropLinks{}, + mVsyncParent(nullptr), + mMarkedDestroying(false), + mIsDestroyed(false), + mRemoteTargetSetsCursor(false), + mPreserveLayers(false), + mRenderLayers(true), + mActiveInPriorityManager(false), + mHasLayers(false), + mHasPresented(false), + mIsReadyToHandleInputEvents(false), + mIsMouseEnterIntoWidgetEventSuppressed(false), + mSuspendedProgressEvents(false) { + MOZ_ASSERT(aManager); + // 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(); +} + +BrowserParent::~BrowserParent() = default; + +/* static */ +BrowserParent* BrowserParent::GetFocused() { return sFocus; } + +/* static */ +BrowserParent* BrowserParent::GetLastMouseRemoteTarget() { + return sLastMouseRemoteTarget; +} + +/* static */ +BrowserParent* BrowserParent::GetPointerLockedRemoteTarget() { + return sPointerLockedRemoteTarget; +} + +/*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->Put(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()) { + nsViewManager* vm = presShell->GetViewManager(); + nsCOMPtr<nsIWidget> widget; + vm->GetRootWidget(getter_AddRefs(widget)); + return widget.forget(); + } + } + 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 iter = docs.ConstIter(); !iter.Done(); iter.Next()) { + auto doc = static_cast<a11y::DocAccessibleParent*>(iter.Get()->GetKey()); + // 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()) { + return doc; + } + } + + MOZ_ASSERT(docs.Count() == 0, + "If there isn't a top level accessible doc " + "there shouldn't be an accessible doc at all!"); +#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(kNameSpaceID_None, nsGkAtoms::name, name); + bool isTransparent = + nsContentUtils::IsChromeDoc(mFrameElement->OwnerDoc()) && + mFrameElement->HasAttr(kNameSpaceID_None, 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(); + TryCacheDPIAndScale(); + + // Try to send down WidgetNativeData, now that this BrowserParent is + // associated with a widget. + nsCOMPtr<nsIWidget> widget = GetTopLevelWidget(); + if (widget) { + WindowsHandle widgetNativeData = reinterpret_cast<WindowsHandle>( + widget->GetNativeData(NS_NATIVE_SHAREABLE_WINDOW)); + if (widgetNativeData) { + Unused << SendSetWidgetNativeData(widgetNativeData); + } + } + + 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); + } + + UpdateVsyncParentVsyncSource(); + + 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::DestroyInternal() { + UnsetTopLevelWebFocus(this); + UnsetLastMouseRemoteTarget(this); + UnsetPointerLockedRemoteTarget(this); + PointerEventHandler::ReleasePointerCaptureRemoteTarget(this); + PresShell::ReleaseCapturingRemoteTarget(this); + + RemoveWindowListeners(); + +#ifdef ACCESSIBILITY + if (a11y::DocAccessibleParent* tabDoc = GetTopLevelDocAccessible()) { + tabDoc->Destroy(); + } +#endif + + // 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__(). + Unused << SendDestroy(); + +#ifdef XP_WIN + // Let all PluginWidgets know we are tearing down. Prevents + // these objects from sending async events after the child side + // is shut down. + const ManagedContainer<PPluginWidgetParent>& kids = + ManagedPPluginWidgetParent(); + for (auto iter = kids.ConstIter(); !iter.Done(); iter.Next()) { + static_cast<mozilla::plugins::PluginWidgetParent*>(iter.Get()->GetKey()) + ->ParentDestroy(); + } +#endif +} + +void BrowserParent::Destroy() { + // Aggressively release the window to avoid leaking the world in shutdown + // corner cases. + mBrowserDOMWindow = nullptr; + + if (mIsDestroyed) { + return; + } + + DestroyInternal(); + + 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::RecvEnsureLayersConnected( + CompositorOptions* aCompositorOptions) { + if (mRemoteLayerTreeOwner.IsInitialized()) { + mRemoteLayerTreeOwner.EnsureLayersConnected(aCompositorOptions); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::Recv__delete__() { + Manager()->NotifyTabDestroyed(mTabId, mMarkedDestroying); + return IPC_OK(); +} + +void BrowserParent::ActorDestroy(ActorDestroyReason why) { + ContentProcessManager::GetSingleton()->UnregisterRemoteFrame(mTabId); + + if (mRemoteLayerTreeOwner.IsInitialized()) { + // 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(mRemoteLayerTreeOwner.GetLayersId()); + mRemoteLayerTreeOwner.Destroy(); + } + + // Even though BrowserParent::Destroy calls this, we need to do it here too in + // case of a crash. + BrowserParent::UnsetTopLevelWebFocus(this); + BrowserParent::UnsetLastMouseRemoteTarget(this); + BrowserParent::UnsetPointerLockedRemoteTarget(this); + PointerEventHandler::ReleasePointerCaptureRemoteTarget(this); + PresShell::ReleaseCapturingRemoteTarget(this); + + 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()); + + auto* bridge = GetBrowserBridgeParent(); + if (bridge && bridge->CanSend() && !mBrowsingContext->IsDiscarded()) { + MOZ_ASSERT(!mBrowsingContext->IsTop()); + + // Set the owner process of the root context belonging to a crashed + // process to the embedding process, since we'll be showing the crashed + // page in that process. + mBrowsingContext->SetOwnerProcessId( + bridge->Manager()->Manager()->ChildID()); + MOZ_ALWAYS_SUCCEEDS(mBrowsingContext->SetCurrentInnerWindowId(0)); + + // Tell the browser bridge to show the subframe crashed page. + Unused << bridge->SendSubFrameCrashed(); + } + } + } + + mFrameLoader = nullptr; +} + +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::RecvSizeShellTo( + const uint32_t& aFlags, const int32_t& aWidth, const int32_t& aHeight, + const int32_t& aShellItemWidth, const int32_t& aShellItemHeight) { + NS_ENSURE_TRUE(mFrameElement, IPC_OK()); + + nsCOMPtr<nsIDocShell> docShell = mFrameElement->OwnerDoc()->GetDocShell(); + NS_ENSURE_TRUE(docShell, IPC_OK()); + + nsCOMPtr<nsIDocShellTreeOwner> treeOwner; + nsresult rv = docShell->GetTreeOwner(getter_AddRefs(treeOwner)); + NS_ENSURE_SUCCESS(rv, IPC_OK()); + + int32_t width = aWidth; + int32_t height = aHeight; + + if (aFlags & nsIEmbeddingSiteWindow::DIM_FLAGS_IGNORE_CX) { + width = mDimensions.width; + } + + if (aFlags & nsIEmbeddingSiteWindow::DIM_FLAGS_IGNORE_CY) { + height = mDimensions.height; + } + + nsCOMPtr<nsIAppWindow> appWin(do_GetInterface(treeOwner)); + NS_ENSURE_TRUE(appWin, IPC_OK()); + appWin->SizeShellToWithLimit(width, height, aShellItemWidth, + aShellItemHeight); + + 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(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvEvent(const RemoteDOMEvent& aEvent) { + RefPtr<Event> event = aEvent.mEvent; + NS_ENSURE_TRUE(event, IPC_OK()); + + RefPtr<EventTarget> target = mFrameElement; + NS_ENSURE_TRUE(target, IPC_OK()); + + event->SetOwner(target); + + target->DispatchEvent(*event); + return IPC_OK(); +} + +bool BrowserParent::SendLoadRemoteScript(const nsString& aURL, + const bool& aRunInGlobalScope) { + if (mCreatingWindow) { + mDelayedFrameScripts.AppendElement( + FrameScriptInfo(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; + } + nsCString spec; + aLoadState->URI()->GetSpec(spec); + if (mCreatingWindow) { + // Don't send the message if the child wants to load its own URL. + return; + } + + Unused << SendLoadURL(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); + + Unused << SendDynamicToolbarMaxHeightChanged( + widget->GetDynamicToolbarMaxHeight()); +#endif +} + +bool BrowserParent::AttachLayerManager() { + return !!mRemoteLayerTreeOwner.AttachLayerManager(); +} + +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.AttachLayerManager()) { + return false; + } + + mSizeMode = aOwnerInfo.sizeMode(); + Unused << SendShow(GetShowInfo(), aOwnerInfo); + return true; +} + +mozilla::ipc::IPCResult BrowserParent::RecvSetDimensions( + const uint32_t& aFlags, const int32_t& aX, const int32_t& aY, + const int32_t& aCx, const int32_t& aCy, const double& aScale) { + MOZ_ASSERT(!(aFlags & nsIEmbeddingSiteWindow::DIM_FLAGS_SIZE_INNER), + "We should never see DIM_FLAGS_SIZE_INNER here!"); + + 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()); + + // We only care about the parameters that actually changed, see more details + // in `BrowserChild::SetDimensions()`. + // Note that `BrowserChild::SetDimensions()` may be called before receiving + // our `SendUIResolutionChanged()` call. Therefore, if given each cordinate + // 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 `GetUnscaledDevicePixelsPerCSSPixel()`. + double currentScale; + treeOwnerAsWin->GetUnscaledDevicePixelsPerCSSPixel(¤tScale); + + int32_t x = aX; + int32_t y = aY; + if (aFlags & nsIEmbeddingSiteWindow::DIM_FLAGS_POSITION) { + if (aFlags & nsIEmbeddingSiteWindow::DIM_FLAGS_IGNORE_X) { + int32_t unused; + treeOwnerAsWin->GetPosition(&x, &unused); + } else if (aScale != currentScale) { + x = x * currentScale / aScale; + } + + if (aFlags & nsIEmbeddingSiteWindow::DIM_FLAGS_IGNORE_Y) { + int32_t unused; + treeOwnerAsWin->GetPosition(&unused, &y); + } else if (aScale != currentScale) { + y = y * currentScale / aScale; + } + } + + int32_t cx = aCx; + int32_t cy = aCy; + if (aFlags & nsIEmbeddingSiteWindow::DIM_FLAGS_SIZE_OUTER) { + if (aFlags & nsIEmbeddingSiteWindow::DIM_FLAGS_IGNORE_CX) { + int32_t unused; + treeOwnerAsWin->GetSize(&cx, &unused); + } else if (aScale != currentScale) { + cx = cx * currentScale / aScale; + } + + if (aFlags & nsIEmbeddingSiteWindow::DIM_FLAGS_IGNORE_CY) { + int32_t unused; + treeOwnerAsWin->GetSize(&unused, &cy); + } else if (aScale != currentScale) { + cy = cy * currentScale / aScale; + } + } + + if (aFlags & nsIEmbeddingSiteWindow::DIM_FLAGS_POSITION && + aFlags & nsIEmbeddingSiteWindow::DIM_FLAGS_SIZE_OUTER) { + treeOwnerAsWin->SetPositionAndSize(x, y, cx, cy, nsIBaseWindow::eRepaint); + return IPC_OK(); + } + + if (aFlags & nsIEmbeddingSiteWindow::DIM_FLAGS_POSITION) { + treeOwnerAsWin->SetPosition(x, y); + mUpdatedDimensions = false; + UpdatePosition(); + return IPC_OK(); + } + + if (aFlags & nsIEmbeddingSiteWindow::DIM_FLAGS_SIZE_OUTER) { + treeOwnerAsWin->SetSize(cx, cy, true); + return IPC_OK(); + } + + MOZ_ASSERT(false, "Unknown flags!"); + return IPC_FAIL_NO_REASON(this); +} + +nsresult BrowserParent::UpdatePosition() { + RefPtr<nsFrameLoader> frameLoader = GetFrameLoader(); + if (!frameLoader) { + return NS_OK; + } + nsIntRect windowDims; + NS_ENSURE_SUCCESS(frameLoader->GetWindowDimensions(windowDims), + NS_ERROR_FAILURE); + UpdateDimensions(windowDims, mDimensions); + return NS_OK; +} + +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; + } + + hal::ScreenConfiguration config; + hal::GetCurrentScreenConfiguration(&config); + hal::ScreenOrientation orientation = config.orientation(); + LayoutDeviceIntPoint clientOffset = GetClientOffset(); + LayoutDeviceIntPoint chromeOffset = !GetBrowserBridgeParent() + ? -GetChildProcessOffset() + : LayoutDeviceIntPoint(); + + if (!mUpdatedDimensions || mOrientation != orientation || + mDimensions != size || !mRect.IsEqualEdges(rect) || + clientOffset != mClientOffset || chromeOffset != mChromeOffset) { + mUpdatedDimensions = true; + mRect = rect; + mDimensions = size; + mOrientation = orientation; + mClientOffset = clientOffset; + mChromeOffset = chromeOffset; + + Unused << SendUpdateDimensions(GetDimensionInfo()); + } +} + +DimensionInfo BrowserParent::GetDimensionInfo() { + nsCOMPtr<nsIWidget> widget = GetWidget(); + MOZ_ASSERT(widget); + CSSToLayoutDeviceScale widgetScale = widget->GetDefaultScale(); + + LayoutDeviceIntRect devicePixelRect = ViewAs<LayoutDevicePixel>( + mRect, PixelCastJustification::LayoutDeviceIsScreenForTabDims); + LayoutDeviceIntSize devicePixelSize = ViewAs<LayoutDevicePixel>( + mDimensions, PixelCastJustification::LayoutDeviceIsScreenForTabDims); + + CSSRect unscaledRect = devicePixelRect / widgetScale; + CSSSize unscaledSize = devicePixelSize / widgetScale; + DimensionInfo di(unscaledRect, unscaledSize, mOrientation, mClientOffset, + mChromeOffset); + return di; +} + +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); + Unused << SendHandleAccessKey(localEvent, aCharCodes); + } +} + +void BrowserParent::Activate(uint64_t aActionId) { + LOGBROWSERFOCUS(("Activate %p", this)); + if (!mIsDestroyed) { + SetTopLevelWebFocus(this); // Intentionally inside "if" + Unused << SendActivate(aActionId); + } +} + +void BrowserParent::Deactivate(bool aWindowLowering, uint64_t aActionId) { + LOGBROWSERFOCUS(("Deactivate %p", this)); + 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 uint32_t&, + const IAccessibleHolder&) { + // Reference freed in DeallocPDocAccessibleParent. + return do_AddRef(new a11y::DocAccessibleParent()).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 uint32_t& aMsaaID, + const IAccessibleHolder& aDocCOMProxy) { + 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) { + // 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); + mozilla::ipc::IPCResult added = parentDoc->AddChildDoc(doc, aParentID); + if (!added) { +# ifdef DEBUG + return added; +# else + return IPC_OK(); +# endif + } + +# ifdef XP_WIN + MOZ_ASSERT(aDocCOMProxy.IsNull()); + a11y::WrapperFor(doc)->SetID(aMsaaID); + if (a11y::nsWinUtils::IsWindowEmulationStarted()) { + doc->SetEmulatedWindowHandle(parentDoc->GetEmulatedWindowHandle()); + } +# else + Unused << aDoc->SendConstructedInParentProcess(); +# endif + + return IPC_OK(); + } + + if (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(); +# ifdef XP_WIN + MOZ_ASSERT(!aDocCOMProxy.IsNull()); + RefPtr<IAccessible> proxy(aDocCOMProxy.Get()); + doc->SetCOMInterface(proxy); +# endif + a11y::ProxyCreated( + doc, a11y::Interfaces::DOCUMENT | a11y::Interfaces::HYPERTEXT); +# ifdef XP_WIN + // This *must* be called after ProxyCreated because WrapperFor will fail + // before that. + a11y::AccessibleWrap* wrapper = a11y::WrapperFor(doc); + MOZ_ASSERT(wrapper); + wrapper->SetID(aMsaaID); +# endif + a11y::DocAccessibleParent* embedderDoc; + uint64_t embedderID; + Tie(embedderDoc, embedderID) = doc->GetRemoteEmbedder(); + // 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 (embedderDoc) { + MOZ_ASSERT(embedderID); + mozilla::ipc::IPCResult added = + embedderDoc->AddChildDoc(doc, embedderID, + /* aCreating */ false); + 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); + } + + doc->SetTopLevel(); + a11y::DocManager::RemoteDocAdded(doc); +# ifdef XP_WIN + a11y::WrapperFor(doc)->SetID(aMsaaID); + MOZ_ASSERT(!aDocCOMProxy.IsNull()); + + RefPtr<IAccessible> proxy(aDocCOMProxy.Get()); + doc->SetCOMInterface(proxy); + doc->MaybeInitWindowEmulation(); + if (a11y::Accessible* outerDoc = doc->OuterDocOfRemoteBrowser()) { + doc->SendParentCOMProxy(outerDoc); + } +# endif + } + return IPC_OK(); +} +#endif + +PFilePickerParent* BrowserParent::AllocPFilePickerParent(const nsString& aTitle, + const int16_t& aMode) { + return new FilePickerParent(aTitle, aMode); +} + +bool BrowserParent::DeallocPFilePickerParent(PFilePickerParent* actor) { + delete actor; + return true; +} + +IPCResult BrowserParent::RecvIndexedDBPermissionRequest( + nsIPrincipal* aPrincipal, IndexedDBPermissionRequestResolver&& aResolve) { + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIPrincipal> principal(aPrincipal); + if (!principal) { + return IPC_FAIL_NO_REASON(this); + } + + if (NS_WARN_IF(!mFrameElement)) { + return IPC_FAIL_NO_REASON(this); + } + + RefPtr<indexedDB::PermissionRequestHelper> actor = + new indexedDB::PermissionRequestHelper(mFrameElement, principal, + aResolve); + + mozilla::Result permissionOrErr = actor->PromptIfNeeded(); + if (permissionOrErr.isErr()) { + return IPC_FAIL_NO_REASON(this); + } + + if (permissionOrErr.inspect() != + indexedDB::PermissionRequestBase::kPermissionPrompt) { + aResolve(permissionOrErr.inspect()); + } + + return IPC_OK(); +} + +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"); + } + + // 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(); + UpdateVsyncParentVsyncSource(); + return mVsyncParent.get(); +} + +bool BrowserParent::DeallocPVsyncParent(PVsyncParent* aActor) { + MOZ_ASSERT(aActor); + mVsyncParent = nullptr; + return true; +} + +void BrowserParent::UpdateVsyncParentVsyncSource() { + if (!mVsyncParent) { + return; + } + + if (nsCOMPtr<nsIWidget> widget = GetWidget()) { + mVsyncParent->UpdateVsyncSource(widget->GetVsyncSource()); + } +} + +void BrowserParent::SendMouseEvent(const nsAString& aType, float aX, float aY, + int32_t aButton, int32_t aClickCount, + int32_t aModifiers) { + if (!mIsDestroyed) { + Unused << PBrowserParent::SendMouseEvent(nsString(aType), aX, aY, aButton, + aClickCount, aModifiers); + } +} + +void BrowserParent::MouseEnterIntoWidget() { + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (widget) { + // When we mouseenter the remote target, the remote target's cursor should + // become the current cursor. When we mouseexit, we stop. + mRemoteTargetSetsCursor = true; + if (mCursor != eCursorInvalid) { + widget->SetCursor(mCursor, mCustomCursor, mCustomCursorHotspotX, + mCustomCursorHotspotY); + } + } + + // 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.mRefPoint); + + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (widget) { + // 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; + if (mCursor != eCursorInvalid) { + widget->SetCursor(mCursor, mCustomCursor, mCustomCursorHotspotX, + mCustomCursorHotspotY); + } + } 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 + ? SendRealMouseButtonEvent(localEvent, guid, blockId) + : SendNormalPriorityRealMouseButtonEvent(localEvent, guid, blockId); + NS_WARNING_ASSERTION( + ret, "SendRealMouseButtonEvent(eMouseEnterIntoWidget) 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; + } + + 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) { + 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) { + 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) { + 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::RecvRequestNativeKeyBindings( + const uint32_t& aType, const WidgetKeyboardEvent& aEvent, + nsTArray<CommandInt>* aCommands) { + MOZ_ASSERT(aCommands); + MOZ_ASSERT(aCommands->IsEmpty()); + + nsIWidget::NativeKeyBindingsType keyBindingsType = + static_cast<nsIWidget::NativeKeyBindingsType>(aType); + switch (keyBindingsType) { + case nsIWidget::NativeKeyBindingsForSingleLineEditor: + case nsIWidget::NativeKeyBindingsForMultiLineEditor: + case nsIWidget::NativeKeyBindingsForRichTextEditor: + 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(); + } + + if (localEvent.InitEditCommandsFor(keyBindingsType)) { + *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) { + 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 uint32_t& aModifierFlags, const uint64_t& aObserverId) { + AutoSynthesizedEventResponder responder(this, aObserverId, "mouseevent"); + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (widget) { + widget->SynthesizeNativeMouseEvent(aPoint, aNativeMessage, aModifierFlags, + responder.GetObserver()); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvSynthesizeNativeMouseMove( + const LayoutDeviceIntPoint& aPoint, const uint64_t& aObserverId) { + 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) { + 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) { + 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::RecvSynthesizeNativeTouchTap( + const LayoutDeviceIntPoint& aPoint, const bool& aLongTap, + const uint64_t& aObserverId) { + 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) { + AutoSynthesizedEventResponder responder(this, aObserverId, "cleartouch"); + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (widget) { + widget->ClearNativeTouchSequence(responder.GetObserver()); + } + return IPC_OK(); +} + +void BrowserParent::SendRealKeyEvent(WidgetKeyboardEvent& aEvent) { + if (mIsDestroyed || !mIsReadyToHandleInputEvents) { + return; + } + aEvent.mRefPoint = TransformParentToChild(aEvent.mRefPoint); + + if (aEvent.mMessage == eKeyPress) { + // XXX Should we do this only when input context indicates an editor having + // focus and the key event won't cause inputting text? + aEvent.InitAllEditCommands(); + } else { + aEvent.PreventNativeKeyBindings(); + } + DebugOnly<bool> ret = + Manager()->IsInputPriorityEventEnabled() + ? PBrowserParent::SendRealKeyEvent(aEvent) + : PBrowserParent::SendNormalPriorityRealKeyEvent(aEvent); + + NS_WARNING_ASSERTION(ret, "PBrowserParent::SendRealKeyEvent() failed"); + MOZ_ASSERT(!ret || aEvent.HasBeenPostedToRemoteProcess()); +} + +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) { + 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) + : PBrowserParent::SendNormalPriorityHandleTap( + aType, TransformParentToChild(aPoint), aModifiers, aGuid, + aInputBlockId); +} + +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::UnpackClonedMessageDataForParent(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::UnpackClonedMessageDataForParent(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, + const nsCString& aCursorData, const uint32_t& aWidth, + const uint32_t& aHeight, 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) { + if (aHeight * aStride != aCursorData.Length() || + aStride < aWidth * gfx::BytesPerPixel(aFormat)) { + return IPC_FAIL(this, "Invalid custom cursor data"); + } + const gfx::IntSize size(aWidth, aHeight); + RefPtr<gfx::DataSourceSurface> customCursor = + gfx::CreateDataSourceSurfaceFromData( + size, aFormat, + reinterpret_cast<const uint8_t*>(aCursorData.BeginReading()), + aStride); + + RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(customCursor, size); + cursorImage = image::ImageOps::CreateFromDrawable(drawable); + } + + mCursor = aCursor; + mCustomCursor = cursorImage; + mCustomCursorHotspotX = aHotspotX; + mCustomCursorHotspotY = aHotspotY; + + if (!mRemoteTargetSetsCursor) { + return IPC_OK(); + } + + widget->SetCursor(aCursor, cursorImage, aHotspotX, aHotspotY); + 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(); + + xulBrowserWindow->ShowTooltip(aX, aY, aTooltip, aDirection, el); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvHideTooltip() { + 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(); + } + + 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(); + } + 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(); + } + 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(); + } + 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(); + } + + 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(); + } + mContentCache.AssignContent(aContentCache, widget, &aIMENotification); + mContentCache.MaybeNotifyIME(widget, aIMENotification); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvOnEventNeedingAckHandled( + const EventMessage& aMessage) { + // 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); + return IPC_OK(); +} + +void BrowserParent::HandledWindowedPluginKeyEvent( + const NativeEventData& aKeyEventData, bool aIsConsumed) { + DebugOnly<bool> ok = + SendHandledWindowedPluginKeyEvent(aKeyEventData, aIsConsumed); + NS_WARNING_ASSERTION(ok, "SendHandledWindowedPluginKeyEvent failed"); +} + +mozilla::ipc::IPCResult BrowserParent::RecvOnWindowedPluginKeyEvent( + const NativeEventData& aKeyEventData) { + nsCOMPtr<nsIWidget> widget = GetWidget(); + if (NS_WARN_IF(!widget)) { + // Notifies the plugin process of the key event being not consumed by us. + HandledWindowedPluginKeyEvent(aKeyEventData, false); + return IPC_OK(); + } + nsresult rv = widget->OnWindowedPluginKeyEvent(aKeyEventData, this); + if (NS_WARN_IF(NS_FAILED(rv))) { + // Notifies the plugin process of the key event being not consumed by us. + HandledWindowedPluginKeyEvent(aKeyEventData, false); + return IPC_OK(); + } + + // If the key event is posted to another process, we need to wait a call + // of HandledWindowedPluginKeyEvent(). So, nothing to do here in this case. + if (rv == NS_SUCCESS_EVENT_HANDLED_ASYNCHRONOUSLY) { + return IPC_OK(); + } + + // Otherwise, the key event is handled synchronously. Let's notify the + // plugin process of the key event's result. + bool consumed = (rv == NS_SUCCESS_EVENT_CONSUMED); + HandledWindowedPluginKeyEvent(aKeyEventData, consumed); + + 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 LayoutDeviceIntPoint& aPoint) { + LayoutDeviceToLayoutDeviceMatrix4x4 matrix = + GetChildToParentConversionMatrix(); + if (!matrix.Invert()) { + return LayoutDeviceIntPoint(0, 0); + } + return TransformPoint(aPoint, matrix); +} + +LayoutDevicePoint BrowserParent::TransformParentToChild( + const LayoutDevicePoint& aPoint) { + LayoutDeviceToLayoutDeviceMatrix4x4 matrix = + GetChildToParentConversionMatrix(); + if (!matrix.Invert()) { + return LayoutDevicePoint(0.0, 0.0); + } + return TransformPoint(aPoint, matrix); +} + +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) { + mChildToParentConversionMatrix = aMatrix; + 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) { + NS_ENSURE_TRUE(mFrameElement, 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. + Document* doc = mFrameElement->OwnerDoc(); + nsPresContext* presContext = doc->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; + } + } + + EventDispatcher::Dispatch(mFrameElement, 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. + // XXX If there were two or more remote processes, this may be called + // twice or more for a keyboard event, that must be a bug. But how to + // detect if received event has already been handled? + + MOZ_ASSERT(aEvent.mMessage == eKeyPress); + 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()) { + nsPresContext* presContext = presShell->GetPresContext(); + NS_ENSURE_TRUE(presContext, IPC_OK()); + + EventDispatcher::Dispatch(mFrameElement, 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 Maybe<WebProgressData>& aWebProgressData, + const RequestData& aRequestData, const uint32_t aStateFlags, + const nsresult aStatus, + const Maybe<WebProgressStateChangeData>& aStateChangeData) { + if (mSuspendedProgressEvents) { + return IPC_OK(); + } + + nsCOMPtr<nsIBrowser> browser = GetBrowser(); + if (!GetBrowsingContext()->GetWebProgress() || !browser) { + return IPC_OK(); + } + + nsCOMPtr<nsIWebProgress> webProgress; + nsCOMPtr<nsIRequest> request; + ReconstructWebProgressAndRequest(aWebProgressData, aRequestData, + getter_AddRefs(webProgress), + getter_AddRefs(request)); + + if (aWebProgressData && aWebProgressData->isTopLevel() && + aStateChangeData.isSome()) { + Unused << browser->SetIsNavigating(aStateChangeData->isNavigating()); + Unused << browser->SetMayEnableCharacterEncodingMenu( + aStateChangeData->mayEnableCharacterEncodingMenu()); + Unused << browser->SetCharsetAutodetected( + aStateChangeData->charsetAutodetected()); + Unused << browser->UpdateForStateChange(aStateChangeData->charset(), + aStateChangeData->documentURI(), + aStateChangeData->contentType()); + } else if (aStateChangeData.isSome()) { + return IPC_FAIL( + this, + "Unexpected WebProgressStateChangeData for non-top-level WebProgress"); + } + + GetBrowsingContext()->Top()->GetWebProgress()->OnStateChange( + webProgress, request, aStateFlags, aStatus); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvOnProgressChange( + const Maybe<WebProgressData>& aWebProgressData, + const RequestData& aRequestData, const int32_t aCurSelfProgress, + const int32_t aMaxSelfProgress, const int32_t aCurTotalProgress, + const int32_t aMaxTotalProgress) { + if (mSuspendedProgressEvents) { + return IPC_OK(); + } + + if (!GetBrowsingContext()->GetWebProgress()) { + return IPC_OK(); + } + + nsCOMPtr<nsIWebProgress> webProgress; + nsCOMPtr<nsIRequest> request; + ReconstructWebProgressAndRequest(aWebProgressData, aRequestData, + getter_AddRefs(webProgress), + getter_AddRefs(request)); + + GetBrowsingContext()->Top()->GetWebProgress()->OnProgressChange( + webProgress, request, aCurSelfProgress, aMaxSelfProgress, + aCurTotalProgress, aMaxTotalProgress); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvOnLocationChange( + const Maybe<WebProgressData>& aWebProgressData, + const RequestData& aRequestData, nsIURI* aLocation, const uint32_t aFlags, + const bool aCanGoBack, const bool aCanGoForward, + const Maybe<WebProgressLocationChangeData>& aLocationChangeData) { + if (mSuspendedProgressEvents) { + return IPC_OK(); + } + + nsCOMPtr<nsIBrowser> browser = GetBrowser(); + if (!GetBrowsingContext()->GetWebProgress() || !browser) { + return IPC_OK(); + } + + nsCOMPtr<nsIWebProgress> webProgress; + nsCOMPtr<nsIRequest> request; + ReconstructWebProgressAndRequest(aWebProgressData, aRequestData, + getter_AddRefs(webProgress), + getter_AddRefs(request)); + + Unused << browser->UpdateWebNavigationForLocationChange(aCanGoBack, + aCanGoForward); + + if (aWebProgressData && aWebProgressData->isTopLevel() && + aLocationChangeData.isSome()) { + Unused << browser->SetIsNavigating(aLocationChangeData->isNavigating()); + Unused << browser->UpdateForLocationChange( + aLocation, aLocationChangeData->charset(), + aLocationChangeData->mayEnableCharacterEncodingMenu(), + aLocationChangeData->charsetAutodetected(), + aLocationChangeData->documentURI(), aLocationChangeData->title(), + aLocationChangeData->contentPrincipal(), + aLocationChangeData->contentPartitionedPrincipal(), + aLocationChangeData->csp(), aLocationChangeData->referrerInfo(), + aLocationChangeData->isSyntheticDocument(), + aLocationChangeData->requestContextID().isSome(), + aLocationChangeData->requestContextID().valueOr(0), + aLocationChangeData->contentType()); + } + + GetBrowsingContext()->Top()->GetWebProgress()->OnLocationChange( + webProgress, 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 (!(aFlags & nsIWebProgressListener::LOCATION_CHANGE_SAME_DOCUMENT)) { + GetBrowsingContext()->UpdateSecurityState(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvOnStatusChange( + const Maybe<WebProgressData>& aWebProgressData, + const RequestData& aRequestData, const nsresult aStatus, + const nsString& aMessage) { + if (mSuspendedProgressEvents) { + return IPC_OK(); + } + + if (!GetBrowsingContext()->GetWebProgress()) { + return IPC_OK(); + } + + nsCOMPtr<nsIWebProgress> webProgress; + nsCOMPtr<nsIRequest> request; + ReconstructWebProgressAndRequest(aWebProgressData, aRequestData, + getter_AddRefs(webProgress), + getter_AddRefs(request)); + + GetBrowsingContext()->Top()->GetWebProgress()->OnStatusChange( + webProgress, request, aStatus, 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) { + 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); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvSetAllowDeprecatedTls(bool value) { + Preferences::SetBool("security.tls.version.enable-deprecated", value); + 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(); +} + +void BrowserParent::ReconstructWebProgressAndRequest( + const Maybe<WebProgressData>& aWebProgressData, + const RequestData& aRequestData, nsIWebProgress** aOutWebProgress, + nsIRequest** aOutRequest) { + MOZ_DIAGNOSTIC_ASSERT(aOutWebProgress, + "aOutWebProgress should never be null"); + MOZ_DIAGNOSTIC_ASSERT(aOutRequest, "aOutRequest should never be null"); + + nsCOMPtr<nsIWebProgress> webProgress; + if (aWebProgressData) { + webProgress = new RemoteWebProgress(aWebProgressData->loadType(), + aWebProgressData->isLoadingDocument(), + aWebProgressData->isTopLevel()); + } else { + webProgress = new RemoteWebProgress(0, false, false); + } + webProgress.forget(aOutWebProgress); + + if (aRequestData.requestURI()) { + nsCOMPtr<nsIRequest> request = MakeAndAddRef<RemoteWebProgressRequest>( + aRequestData.requestURI(), aRequestData.originalRequestURI(), + aRequestData.matchedList()); + request.forget(aOutRequest); + } else { + *aOutRequest = nullptr; + } +} + +mozilla::ipc::IPCResult BrowserParent::RecvSessionStoreUpdate( + const Maybe<nsCString>& aDocShellCaps, const Maybe<bool>& aPrivatedMode, + nsTArray<nsCString>&& aPositions, nsTArray<int32_t>&& aPositionDescendants, + const nsTArray<InputFormData>& aInputs, + const nsTArray<CollectedInputDataValue>& aIdVals, + const nsTArray<CollectedInputDataValue>& aXPathVals, + nsTArray<nsCString>&& aOrigins, nsTArray<nsString>&& aKeys, + nsTArray<nsString>&& aValues, const bool aIsFullStorage, + const bool aNeedCollectSHistory, const uint32_t& aFlushId, + const bool& aIsFinal, const uint32_t& aEpoch) { + UpdateSessionStoreData data; + if (aDocShellCaps.isSome()) { + data.mDocShellCaps.Construct() = aDocShellCaps.value(); + } + if (aPrivatedMode.isSome()) { + data.mIsPrivate.Construct() = aPrivatedMode.value(); + } + if (aPositions.Length() != 0) { + data.mPositions.Construct(std::move(aPositions)); + data.mPositionDescendants.Construct(std::move(aPositionDescendants)); + } + if (aIdVals.Length() != 0) { + SessionStoreUtils::ComposeInputData(aIdVals, data.mId.Construct()); + } + if (aXPathVals.Length() != 0) { + SessionStoreUtils::ComposeInputData(aXPathVals, data.mXpath.Construct()); + } + if (aInputs.Length() != 0) { + nsTArray<int> descendants, numId, numXPath; + nsTArray<nsString> innerHTML; + nsTArray<nsCString> url; + for (const InputFormData& input : aInputs) { + descendants.AppendElement(input.descendants); + numId.AppendElement(input.numId); + numXPath.AppendElement(input.numXPath); + innerHTML.AppendElement(input.innerHTML); + url.AppendElement(input.url); + } + + data.mInputDescendants.Construct(std::move(descendants)); + data.mNumId.Construct(std::move(numId)); + data.mNumXPath.Construct(std::move(numXPath)); + data.mInnerHTML.Construct(std::move(innerHTML)); + data.mUrl.Construct(std::move(url)); + } + // In normal case, we only update the storage when needed. + // However, we need to reset the session storage(aOrigins.Length() will be 0) + // if the usage is over the "browser_sessionstore_dom_storage_limit". + // In this case, aIsFullStorage is true. + if (aOrigins.Length() != 0 || aIsFullStorage) { + data.mStorageOrigins.Construct(std::move(aOrigins)); + data.mStorageKeys.Construct(std::move(aKeys)); + data.mStorageValues.Construct(std::move(aValues)); + data.mIsFullStorage.Construct() = aIsFullStorage; + } + + nsCOMPtr<nsISessionStoreFunctions> funcs = + do_ImportModule("resource://gre/modules/SessionStoreFunctions.jsm"); + NS_ENSURE_TRUE(funcs, IPC_OK()); + nsCOMPtr<nsIXPConnectWrappedJS> wrapped = do_QueryInterface(funcs); + AutoJSAPI jsapi; + MOZ_ALWAYS_TRUE(jsapi.Init(wrapped->GetJSObjectGlobal())); + JS::Rooted<JS::Value> dataVal(jsapi.cx()); + bool ok = ToJSValue(jsapi.cx(), data, &dataVal); + NS_ENSURE_TRUE(ok, IPC_OK()); + + nsresult rv = funcs->UpdateSessionStore(mFrameElement, mBrowsingContext, + aFlushId, aIsFinal, aEpoch, dataVal, + aNeedCollectSHistory); + NS_ENSURE_SUCCESS(rv, IPC_OK()); + + return IPC_OK(); +} + +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(); +} + +bool BrowserParent::HandleQueryContentEvent(WidgetQueryContentEvent& aEvent) { + nsCOMPtr<nsIWidget> textInputHandlingWidget = GetTextInputHandlingWidget(); + if (!textInputHandlingWidget) { + return true; + } + if (NS_WARN_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) { + if (mIsDestroyed) { + return false; + } + + 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::SendPasteTransferable( + const IPCDataTransfer& aDataTransfer, const bool& aIsPrivateData, + nsIPrincipal* aRequestingPrincipal, + const nsContentPolicyType& aContentPolicyType) { + return PBrowserParent::SendPasteTransferable( + aDataTransfer, aIsPrivateData, aRequestingPrincipal, aContentPolicyType); +} + +/* 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; + } +} + +/* static */ +void BrowserParent::UnsetPointerLockedRemoteTarget( + BrowserParent* aBrowserParent) { + if (sPointerLockedRemoteTarget == aBrowserParent) { + sPointerLockedRemoteTarget = nullptr; + } +} + +mozilla::ipc::IPCResult BrowserParent::RecvRequestIMEToCommitComposition( + const bool& aCancel, bool* aIsCommitted, nsString* aCommittedString) { + nsCOMPtr<nsIWidget> widget = GetTextInputHandlingWidget(); + if (!widget) { + *aIsCommitted = false; + return IPC_OK(); + } + + *aIsCommitted = mContentCache.RequestIMEToCommitComposition( + widget, aCancel, *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(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvSetNativeChildOfShareableWindow( + const uintptr_t& aChildWindow) { +#if defined(XP_WIN) + nsCOMPtr<nsIWidget> widget = GetTopLevelWidget(); + if (widget) { + // Note that this call will probably cause a sync native message to the + // process that owns the child window. + widget->SetNativeData(NS_NATIVE_CHILD_OF_SHAREABLE_WINDOW, aChildWindow); + } + return IPC_OK(); +#else + MOZ_ASSERT_UNREACHABLE( + "BrowserParent::RecvSetNativeChildOfShareableWindow not implemented!"); + return IPC_FAIL_NO_REASON(this); +#endif +} + +mozilla::ipc::IPCResult BrowserParent::RecvDispatchFocusToTopLevelWindow() { + if (nsCOMPtr<nsIWidget> widget = GetTopLevelWidget()) { + widget->SetFocus(nsIWidget::Raise::No, CallerType::System); + } + 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; +} + +PColorPickerParent* BrowserParent::AllocPColorPickerParent( + const nsString& aTitle, const nsString& aInitialColor) { + return new ColorPickerParent(aTitle, aInitialColor); +} + +bool BrowserParent::DeallocPColorPickerParent(PColorPickerParent* actor) { + delete actor; + return true; +} + +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; + } + + nsCOMPtr<nsIWidget> widget = GetWidget(); + + if (widget) { + mDPI = widget->GetDPI(); + mRounding = widget->RoundsWidgetCoordinatesTo(); + mDefaultScale = widget->GetDefaultScale(); + } +} + +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 (mActiveInPriorityManager != aEnabled) { + mActiveInPriorityManager = aEnabled; + // Let's inform the priority manager. This operation can end up with the + // changing of the process priority. + ProcessPriorityManager::TabActivityChanged(this, aEnabled); + } + + if (aEnabled == mRenderLayers) { + if (aEnabled && mHasLayers && mPreserveLayers) { + // RenderLayers might be called when we've been preserving layers, + // and already had layers uploaded. In that case, the MozLayerTreeReady + // event will not naturally arrive, which can confuse the front-end + // layer. So we fire the event here. + RefPtr<BrowserParent> self = this; + LayersObserverEpoch epoch = mLayerTreeEpoch; + NS_DispatchToMainThread(NS_NewRunnableFunction( + "dom::BrowserParent::RenderLayers", [self, epoch]() { + MOZ_ASSERT(NS_IsMainThread()); + self->LayerTreeUpdate(epoch, true); + })); + } + + return; + } + + // Preserve layers means that attempts to stop rendering layers + // will be ignored. + if (!aEnabled && mPreserveLayers) { + return; + } + + mRenderLayers = aEnabled; + + SetRenderLayersInternal(aEnabled); +} + +void BrowserParent::SetRenderLayersInternal(bool aEnabled) { + // Increment the epoch so that layer tree updates from previous + // RenderLayers requests are ignored. + mLayerTreeEpoch = mLayerTreeEpoch.Next(); + + Unused << SendRenderLayers(aEnabled, mLayerTreeEpoch); + + // Ask the child to repaint using the PHangMonitor channel/thread (which may + // be less congested). + if (aEnabled) { + Manager()->PaintTabWhileInterruptingJS(this, mLayerTreeEpoch); + } +} + +void BrowserParent::PreserveLayers(bool aPreserveLayers) { + mPreserveLayers = aPreserveLayers; +} + +void BrowserParent::NotifyResolutionChanged() { + if (!mIsDestroyed) { + // 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); + } +} + +void BrowserParent::Deprioritize() { + if (mActiveInPriorityManager) { + ProcessPriorityManager::TabActivityChanged(this, false); + mActiveInPriorityManager = false; + } +} + +bool BrowserParent::StartApzAutoscroll(float aAnchorX, float aAnchorY, + nsViewID aScrollId, + uint32_t aPresShellId) { + if (!AsyncPanZoomEnabled()) { + return false; + } + + bool success = false; + if (mRemoteLayerTreeOwner.IsInitialized()) { + layers::LayersId layersId = mRemoteLayerTreeOwner.GetLayersId(); + if (nsCOMPtr<nsIWidget> widget = GetWidget()) { + ScrollableLayerGuid guid(layersId, aPresShellId, aScrollId); + + // The anchor coordinates that are passed in are relative to the origin + // of the screen, but we are sending them to APZ which only knows about + // coordinates relative to the widget, so convert them accordingly. + CSSPoint anchorCss{aAnchorX, aAnchorY}; + LayoutDeviceIntPoint anchor = + RoundedToInt(anchorCss * widget->GetDefaultScale()); + anchor -= widget->WidgetToScreenOffset(); + + success = widget->StartAsyncAutoscroll( + ViewAs<ScreenPixel>( + anchor, PixelCastJustification::LayoutDeviceIsScreenForBounds), + guid); + } + } + return success; +} + +void BrowserParent::StopApzAutoscroll(nsViewID aScrollId, + uint32_t aPresShellId) { + if (!AsyncPanZoomEnabled()) { + return; + } + + if (mRemoteLayerTreeOwner.IsInitialized()) { + layers::LayersId layersId = mRemoteLayerTreeOwner.GetLayersId(); + if (nsCOMPtr<nsIWidget> widget = GetWidget()) { + ScrollableLayerGuid guid(layersId, aPresShellId, aScrollId); + + widget->StopAsyncAutoscroll(guid); + } + } +} + +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(const LayersObserverEpoch& aEpoch, + bool aActive) { + // 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. + // + // XXX: Should we still be updating |mHasLayers|? + if (GetBrowserBridgeParent()) { + return; + } + + // Ignore updates from old epochs. They might tell us that layers are + // available when we've already sent a message to clear them. We can't trust + // the update in that case since layers could disappear anytime after that. + if (aEpoch != mLayerTreeEpoch || mIsDestroyed) { + return; + } + + RefPtr<EventTarget> target = mFrameElement; + if (!target) { + NS_WARNING("Could not locate target for layer tree message."); + return; + } + + mHasLayers = aActive; + + RefPtr<Event> event = NS_NewDOMEvent(mFrameElement, nullptr, nullptr); + if (aActive) { + mHasPresented = true; + event->InitEvent(u"MozLayerTreeReady"_ns, true, false); + } else { + event->InitEvent(u"MozLayerTreeCleared"_ns, true, false); + } + event->SetTrusted(true); + event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true; + mFrameElement->DispatchEvent(*event); +} + +mozilla::ipc::IPCResult BrowserParent::RecvPaintWhileInterruptingJSNoOp( + const LayersObserverEpoch& aEpoch) { + // We sent a PaintWhileInterruptingJS message when layers were already + // visible. In this case, we should act as if an update occurred even though + // we already have the layers. + LayerTreeUpdate(aEpoch, true); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvRemotePaintIsReady() { + RefPtr<EventTarget> target = mFrameElement; + if (!target) { + NS_WARNING("Could not locate target for MozAfterRemotePaint message."); + return IPC_OK(); + } + + RefPtr<Event> event = NS_NewDOMEvent(mFrameElement, nullptr, nullptr); + event->InitEvent(u"MozAfterRemotePaint"_ns, false, false); + event->SetTrusted(true); + event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true; + mFrameElement->DispatchEvent(*event); + return IPC_OK(); +} + +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(); +} + +mozilla::plugins::PPluginWidgetParent* +BrowserParent::AllocPPluginWidgetParent() { +#ifdef XP_WIN + return new mozilla::plugins::PluginWidgetParent(); +#else + MOZ_ASSERT_UNREACHABLE("AllocPPluginWidgetParent only supports Windows"); + return nullptr; +#endif +} + +bool BrowserParent::DeallocPPluginWidgetParent( + mozilla::plugins::PPluginWidgetParent* aActor) { + delete aActor; + return true; +} + +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; +} + +class FakeChannel final : public nsIChannel, + public nsIAuthPromptCallback, + public nsIInterfaceRequestor, + public nsILoadContext { + public: + FakeChannel(const nsCString& aUri, uint64_t aCallbackId, Element* aElement) + : mCallbackId(aCallbackId), mElement(aElement) { + NS_NewURI(getter_AddRefs(mUri), aUri); + } + + NS_DECL_ISUPPORTS + +#define NO_IMPL \ + override { return NS_ERROR_NOT_IMPLEMENTED; } + NS_IMETHOD GetName(nsACString&) NO_IMPL; + NS_IMETHOD IsPending(bool*) NO_IMPL; + NS_IMETHOD GetStatus(nsresult*) NO_IMPL; + NS_IMETHOD Cancel(nsresult) NO_IMPL; + NS_IMETHOD GetCanceled(bool* aCanceled) NO_IMPL; + NS_IMETHOD Suspend() NO_IMPL; + NS_IMETHOD Resume() NO_IMPL; + NS_IMETHOD GetLoadGroup(nsILoadGroup**) NO_IMPL; + NS_IMETHOD SetLoadGroup(nsILoadGroup*) NO_IMPL; + NS_IMETHOD SetLoadFlags(nsLoadFlags) NO_IMPL; + NS_IMETHOD GetLoadFlags(nsLoadFlags*) NO_IMPL; + NS_IMETHOD GetTRRMode(nsIRequest::TRRMode* aTRRMode) NO_IMPL; + NS_IMETHOD SetTRRMode(nsIRequest::TRRMode aMode) NO_IMPL; + NS_IMETHOD GetIsDocument(bool*) NO_IMPL; + NS_IMETHOD GetOriginalURI(nsIURI**) NO_IMPL; + NS_IMETHOD SetOriginalURI(nsIURI*) NO_IMPL; + NS_IMETHOD GetURI(nsIURI** aUri) override { + nsCOMPtr<nsIURI> copy = mUri; + copy.forget(aUri); + return NS_OK; + } + NS_IMETHOD GetOwner(nsISupports**) NO_IMPL; + NS_IMETHOD SetOwner(nsISupports*) NO_IMPL; + NS_IMETHOD GetLoadInfo(nsILoadInfo** aLoadInfo) override { + nsCOMPtr<nsILoadInfo> copy = mLoadInfo; + copy.forget(aLoadInfo); + return NS_OK; + } + NS_IMETHOD SetLoadInfo(nsILoadInfo* aLoadInfo) override { + mLoadInfo = aLoadInfo; + return NS_OK; + } + NS_IMETHOD GetNotificationCallbacks( + nsIInterfaceRequestor** aRequestor) override { + NS_ADDREF(*aRequestor = this); + return NS_OK; + } + NS_IMETHOD SetNotificationCallbacks(nsIInterfaceRequestor*) NO_IMPL; + NS_IMETHOD GetSecurityInfo(nsISupports**) NO_IMPL; + NS_IMETHOD GetContentType(nsACString&) NO_IMPL; + NS_IMETHOD SetContentType(const nsACString&) NO_IMPL; + NS_IMETHOD GetContentCharset(nsACString&) NO_IMPL; + NS_IMETHOD SetContentCharset(const nsACString&) NO_IMPL; + NS_IMETHOD GetContentLength(int64_t*) NO_IMPL; + NS_IMETHOD SetContentLength(int64_t) NO_IMPL; + NS_IMETHOD Open(nsIInputStream**) NO_IMPL; + NS_IMETHOD AsyncOpen(nsIStreamListener*) NO_IMPL; + NS_IMETHOD GetContentDisposition(uint32_t*) NO_IMPL; + NS_IMETHOD SetContentDisposition(uint32_t) NO_IMPL; + NS_IMETHOD GetContentDispositionFilename(nsAString&) NO_IMPL; + NS_IMETHOD SetContentDispositionFilename(const nsAString&) NO_IMPL; + NS_IMETHOD GetContentDispositionHeader(nsACString&) NO_IMPL; + NS_IMETHOD OnAuthAvailable(nsISupports* aContext, + nsIAuthInformation* aAuthInfo) override; + NS_IMETHOD OnAuthCancelled(nsISupports* aContext, bool userCancel) override; + NS_IMETHOD GetInterface(const nsIID& uuid, void** result) override { + return QueryInterface(uuid, result); + } + NS_IMETHOD GetAssociatedWindow(mozIDOMWindowProxy**) NO_IMPL; + NS_IMETHOD GetTopWindow(mozIDOMWindowProxy**) NO_IMPL; + NS_IMETHOD GetTopFrameElement(Element** aElement) override { + RefPtr<Element> elem = mElement; + elem.forget(aElement); + return NS_OK; + } + NS_IMETHOD GetIsContent(bool*) NO_IMPL; + NS_IMETHOD GetUsePrivateBrowsing(bool*) NO_IMPL; + NS_IMETHOD SetUsePrivateBrowsing(bool) NO_IMPL; + NS_IMETHOD SetPrivateBrowsing(bool) NO_IMPL; + NS_IMETHOD GetScriptableOriginAttributes(JSContext*, + JS::MutableHandleValue) NO_IMPL; + NS_IMETHOD_(void) + GetOriginAttributes(mozilla::OriginAttributes& aAttrs) override {} + NS_IMETHOD GetUseRemoteTabs(bool*) NO_IMPL; + NS_IMETHOD SetRemoteTabs(bool) NO_IMPL; + NS_IMETHOD GetUseRemoteSubframes(bool*) NO_IMPL; + NS_IMETHOD SetRemoteSubframes(bool) NO_IMPL; + NS_IMETHOD GetUseTrackingProtection(bool*) NO_IMPL; + NS_IMETHOD SetUseTrackingProtection(bool) NO_IMPL; +#undef NO_IMPL + + protected: + ~FakeChannel() = default; + + nsCOMPtr<nsIURI> mUri; + uint64_t mCallbackId; + RefPtr<Element> mElement; + nsCOMPtr<nsILoadInfo> mLoadInfo; +}; + +NS_IMPL_ISUPPORTS(FakeChannel, nsIChannel, nsIAuthPromptCallback, nsIRequest, + nsIInterfaceRequestor, nsILoadContext); + +mozilla::ipc::IPCResult BrowserParent::RecvAsyncAuthPrompt( + const nsCString& aUri, const nsString& aRealm, + const uint64_t& aCallbackId) { + nsCOMPtr<nsIAuthPrompt2> authPrompt; + GetAuthPrompt(nsIAuthPromptProvider::PROMPT_NORMAL, + NS_GET_IID(nsIAuthPrompt2), getter_AddRefs(authPrompt)); + RefPtr<FakeChannel> channel = + new FakeChannel(aUri, aCallbackId, mFrameElement); + uint32_t promptFlags = nsIAuthInformation::AUTH_HOST; + + RefPtr<nsAuthInformationHolder> holder = + new nsAuthInformationHolder(promptFlags, aRealm, ""_ns); + + uint32_t level = nsIAuthPrompt2::LEVEL_NONE; + nsCOMPtr<nsICancelable> dummy; + nsresult rv = authPrompt->AsyncPromptAuth(channel, channel, nullptr, level, + holder, getter_AddRefs(dummy)); + + if (NS_FAILED(rv)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvInvokeDragSession( + nsTArray<IPCDataTransfer>&& aTransfers, const uint32_t& aAction, + Maybe<Shmem>&& aVisualDnDData, const uint32_t& aStride, + const gfx::SurfaceFormat& aFormat, const LayoutDeviceIntRect& aDragRect, + nsIPrincipal* aPrincipal, nsIContentSecurityPolicy* aCsp, + const CookieJarSettingsArgs& aCookieJarSettingsArgs) { + PresShell* presShell = mFrameElement->OwnerDoc()->GetPresShell(); + if (!presShell) { + Unused << Manager()->SendEndDragSession(true, true, LayoutDeviceIntPoint(), + 0); + // 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(aTransfers), aDragRect, + aPrincipal, aCsp, cookieJarSettings); + + if (!aVisualDnDData.isNothing() && aVisualDnDData.ref().IsReadable() && + aVisualDnDData.ref().Size<char>() >= aDragRect.height * aStride) { + dragStartData->SetVisualization(gfx::CreateDataSourceSurfaceFromData( + gfx::IntSize(aDragRect.width, aDragRect.height), aFormat, + aVisualDnDData.ref().get<uint8_t>(), aStride)); + } + + nsCOMPtr<nsIDragService> dragService = + do_GetService("@mozilla.org/widget/dragservice;1"); + if (dragService) { + dragService->MaybeAddChildProcess(Manager()); + } + + presShell->GetPresContext() + ->EventStateManager() + ->BeginTrackingRemoteDragGesture(mFrameElement, dragStartData); + + if (aVisualDnDData.isSome()) { + Unused << DeallocShmem(aVisualDnDData.ref()); + } + + return IPC_OK(); +} + +bool BrowserParent::AsyncPanZoomEnabled() const { + nsCOMPtr<nsIWidget> widget = GetWidget(); + return widget && widget->AsyncPanZoomEnabled(); +} + +void BrowserParent::StartPersistence( + CanonicalBrowsingContext* aContext, + nsIWebBrowserPersistDocumentReceiver* aRecv, ErrorResult& aRv) { + auto* 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) { + if (!aURI) { + return IPC_FAIL_NO_REASON(this); + } + RefPtr<nsIWidget> widget = GetWidget(); + if (NS_WARN_IF(!widget)) { + return IPC_OK(); + } + nsCOMPtr<IHistory> history = services::GetHistory(); + if (history) { + Unused << history->VisitURI(widget, aURI, aLastVisitedURI, aFlags); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvQueryVisitedState( + const nsTArray<RefPtr<nsIURI>>&& aURIs) { +#ifdef MOZ_ANDROID_HISTORY + nsCOMPtr<IHistory> history = services::GetHistory(); + if (NS_WARN_IF(!history)) { + return IPC_OK(); + } + RefPtr<nsIWidget> widget = GetWidget(); + if (NS_WARN_IF(!widget)) { + return IPC_OK(); + } + + for (size_t i = 0; i < aURIs.Length(); ++i) { + if (!aURIs[i]) { + return IPC_FAIL(this, "Received null URI"); + } + } + + GeckoViewHistory* gvHistory = static_cast<GeckoViewHistory*>(history.get()); + gvHistory->QueryVisitedState(widget, 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(); +} + +NS_IMETHODIMP +FakeChannel::OnAuthAvailable(nsISupports* aContext, + nsIAuthInformation* aAuthInfo) { + nsAuthInformationHolder* holder = + static_cast<nsAuthInformationHolder*>(aAuthInfo); + + if (!net::gNeckoChild->SendOnAuthAvailable( + mCallbackId, holder->User(), holder->Password(), holder->Domain())) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +FakeChannel::OnAuthCancelled(nsISupports* aContext, bool userCancel) { + if (!net::gNeckoChild->SendOnAuthCancelled(mCallbackId, userCancel)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +mozilla::ipc::IPCResult BrowserParent::RecvIsWindowSupportingProtectedMedia( + const uint64_t& aOuterWindowID, + IsWindowSupportingProtectedMediaResolver&& aResolve) { +#ifdef XP_WIN + bool isFxrWindow = + FxRWindowManager::GetInstance()->IsFxRWindow(aOuterWindowID); + aResolve(!isFxrWindow); +#else + 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(); +} + +bool BrowserParent::SetPointerLock() { + if (sPointerLockedRemoteTarget) { + return sPointerLockedRemoteTarget == this; + } + + sPointerLockedRemoteTarget = this; + return true; +} + +mozilla::ipc::IPCResult BrowserParent::RecvRequestPointerLock( + RequestPointerLockResolver&& aResolve) { + nsCString error; + if (!SetPointerLock()) { + error = "PointerLockDeniedInUse"; + } else { + PointerEventHandler::ReleaseAllPointerCaptureRemoteTarget(); + } + aResolve(error); + return IPC_OK(); +} + +mozilla::ipc::IPCResult BrowserParent::RecvReleasePointerLock() { + MOZ_ASSERT_IF(sPointerLockedRemoteTarget, sPointerLockedRemoteTarget == this); + UnsetPointerLockedRemoteTarget(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(); +} + +} // namespace mozilla::dom diff --git a/dom/ipc/BrowserParent.h b/dom/ipc/BrowserParent.h new file mode 100644 index 0000000000..db4ace2dc5 --- /dev/null +++ b/dom/ipc/BrowserParent.h @@ -0,0 +1,1018 @@ +/* -*- 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/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 "nsIKeyEventInPluginCallback.h" +#include "nsIRemoteTab.h" +#include "nsIWidget.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 nsIKeyEventInPluginCallback, + 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* GetPointerLockedRemoteTarget(); + + 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; } + + 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 (auto iter = browserBridges.ConstIter(); !iter.Done(); iter.Next()) { + BrowserBridgeParent* browserBridge = + static_cast<BrowserBridgeParent*>(iter.Get()->GetKey()); + 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 (auto iter = browserBridges.ConstIter(); !iter.Done(); iter.Next()) { + BrowserBridgeParent* browserBridge = + static_cast<BrowserBridgeParent*>(iter.Get()->GetKey()); + 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 RecvMoveFocus(const bool& aForward, + const bool& aForDocumentNavigation); + + mozilla::ipc::IPCResult RecvSizeShellTo(const uint32_t& aFlags, + const int32_t& aWidth, + const int32_t& aHeight, + const int32_t& aShellItemWidth, + const int32_t& aShellItemHeight); + + mozilla::ipc::IPCResult RecvDropLinks(nsTArray<nsString>&& aLinks); + + mozilla::ipc::IPCResult RecvEvent(const RemoteDOMEvent& aEvent); + + mozilla::ipc::IPCResult RecvReplyKeyEvent(const WidgetKeyboardEvent& aEvent); + + 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 Maybe<WebProgressData>& awebProgressData, + const RequestData& aRequestData, const uint32_t aStateFlags, + const nsresult aStatus, + const Maybe<WebProgressStateChangeData>& aStateChangeData); + + mozilla::ipc::IPCResult RecvOnProgressChange( + const Maybe<WebProgressData>& aWebProgressData, + const RequestData& aRequestData, const int32_t aCurSelfProgress, + const int32_t aMaxSelfProgress, const int32_t aCurTotalProgres, + const int32_t aMaxTotalProgress); + + mozilla::ipc::IPCResult RecvOnLocationChange( + const Maybe<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 Maybe<WebProgressData>& aWebProgressData, + const RequestData& aRequestData, const nsresult aStatus, + 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); + + mozilla::ipc::IPCResult RecvSetAllowDeprecatedTls(bool value); + + mozilla::ipc::IPCResult RecvNavigationFinished(); + + already_AddRefed<nsIBrowser> GetBrowser(); + + void ReconstructWebProgressAndRequest( + const Maybe<WebProgressData>& aWebProgressData, + const RequestData& aRequestData, nsIWebProgress** aOutWebProgress, + nsIRequest** aOutRequest); + + mozilla::ipc::IPCResult RecvSessionStoreUpdate( + const Maybe<nsCString>& aDocShellCaps, const Maybe<bool>& aPrivatedMode, + nsTArray<nsCString>&& aPositions, + nsTArray<int32_t>&& aPositionDescendants, + const nsTArray<InputFormData>& aInputs, + const nsTArray<CollectedInputDataValue>& aIdVals, + const nsTArray<CollectedInputDataValue>& aXPathVals, + nsTArray<nsCString>&& aOrigins, nsTArray<nsString>&& aKeys, + nsTArray<nsString>&& aValues, const bool aIsFullStorage, + const bool aNeedCollectSHistory, const uint32_t& aFlushId, + const bool& aIsFinal, const uint32_t& aEpoch); + + mozilla::ipc::IPCResult RecvIntrinsicSizeOrRatioChanged( + const Maybe<IntrinsicSize>& aIntrinsicSize, + const Maybe<AspectRatio>& aIntrinsicRatio); + + 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); + + mozilla::ipc::IPCResult RecvRequestIMEToCommitComposition( + const bool& aCancel, bool* aIsCommitted, nsString* aCommittedString); + + mozilla::ipc::IPCResult RecvGetInputContext(widget::IMEState* aIMEState); + + mozilla::ipc::IPCResult RecvSetInputContext( + const widget::InputContext& aContext, + const widget::InputContextAction& aAction); + + // See nsIKeyEventInPluginCallback + virtual void HandledWindowedPluginKeyEvent( + const NativeEventData& aKeyEventData, bool aIsConsumed) override; + + mozilla::ipc::IPCResult RecvOnWindowedPluginKeyEvent( + const NativeEventData& aKeyEventData); + + 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, + const nsCString& aUri, const uint32_t& aWidth, const uint32_t& aHeight, + 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 RecvSetNativeChildOfShareableWindow( + const uintptr_t& childWindow); + + mozilla::ipc::IPCResult RecvDispatchFocusToTopLevelWindow(); + + 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 RecvScrollRectIntoView( + const nsRect& aRect, const ScrollAxis& aVertical, + const ScrollAxis& aHorizontal, const ScrollFlags& aScrollFlags, + const int32_t& aAppUnitsPerDevPixel); + + PColorPickerParent* AllocPColorPickerParent(const nsString& aTitle, + const nsString& aInitialColor); + + bool DeallocPColorPickerParent(PColorPickerParent* aColorPicker); + + PVsyncParent* AllocPVsyncParent(); + + bool DeallocPVsyncParent(PVsyncParent* aActor); + +#ifdef ACCESSIBILITY + PDocAccessibleParent* AllocPDocAccessibleParent(PDocAccessibleParent*, + const uint64_t&, + const uint32_t&, + const IAccessibleHolder&); + bool DeallocPDocAccessibleParent(PDocAccessibleParent*); + virtual mozilla::ipc::IPCResult RecvPDocAccessibleConstructor( + PDocAccessibleParent* aDoc, PDocAccessibleParent* aParentDoc, + const uint64_t& aParentID, const uint32_t& aMsaaID, + const IAccessibleHolder& aDocCOMProxy) override; +#endif + + 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 AttachLayerManager(); + void MaybeShowFrame(); + + bool Show(const OwnerShowInfo&); + + void UpdateDimensions(const nsIntRect& aRect, const ScreenIntSize& aSize); + + DimensionInfo GetDimensionInfo(); + + nsresult UpdatePosition(); + + 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(); + + 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 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 RecvSynthesizeNativeTouchTap( + const LayoutDeviceIntPoint& aPoint, const bool& aLongTap, + const uint64_t& aObserverId); + + mozilla::ipc::IPCResult RecvClearNativeTouchSequence( + const uint64_t& aObserverId); + + void SendMouseEvent(const nsAString& aType, float aX, float aY, + int32_t aButton, int32_t aClickCount, int32_t aModifiers); + + /** + * 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); + + 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); + + 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); + + PFilePickerParent* AllocPFilePickerParent(const nsString& aTitle, + const int16_t& aMode); + + bool DeallocPFilePickerParent(PFilePickerParent* actor); + + mozilla::ipc::IPCResult RecvIndexedDBPermissionRequest( + nsIPrincipal* aPrincipal, IndexedDBPermissionRequestResolver&& aResolve); + + bool GetGlobalJSObject(JSContext* cx, JSObject** globalp); + + void StartPersistence(CanonicalBrowsingContext* aContext, + nsIWebBrowserPersistDocumentReceiver* aRecv, + ErrorResult& aRv); + + bool HandleQueryContentEvent(mozilla::WidgetQueryContentEvent& aEvent); + + bool SendPasteTransferable(const IPCDataTransfer& aDataTransfer, + const bool& aIsPrivateData, + nsIPrincipal* aRequestingPrincipal, + const nsContentPolicyType& aContentPolicyType); + + // 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 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(); + + /** + * Native widget remoting protocol for use with windowed plugins with e10s. + */ + PPluginWidgetParent* AllocPPluginWidgetParent(); + + bool DeallocPPluginWidgetParent(PPluginWidgetParent* aActor); + + PPaymentRequestParent* AllocPPaymentRequestParent(); + + bool DeallocPPaymentRequestParent(PPaymentRequestParent* aActor); + + bool SendLoadRemoteScript(const nsString& aURL, + const bool& aRunInGlobalScope); + + void LayerTreeUpdate(const LayersObserverEpoch& aEpoch, bool aActive); + + mozilla::ipc::IPCResult RecvInvokeDragSession( + nsTArray<IPCDataTransfer>&& aTransfers, const uint32_t& aAction, + Maybe<Shmem>&& aVisualDnDData, const uint32_t& aStride, + const gfx::SurfaceFormat& aFormat, const LayoutDeviceIntRect& aDragRect, + nsIPrincipal* aPrincipal, nsIContentSecurityPolicy* aCsp, + const CookieJarSettingsArgs& aCookieJarSettingsArgs); + + void AddInitialDnDDataTo(DataTransfer* aDataTransfer, + 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); + void PreserveLayers(bool aPreserveLayers); + void NotifyResolutionChanged(); + + void Deprioritize(); + + bool StartApzAutoscroll(float aAnchorX, float aAnchorY, nsViewID aScrollId, + uint32_t aPresShellId); + void StopApzAutoscroll(nsViewID aScrollId, uint32_t aPresShellId); + + // Suspend nsIWebProgressListener events. This is used to block any further + // progress events from the old process when process switching away. + void SuspendProgressEvents() { mSuspendedProgressEvents = true; } + + bool CanCancelContentJS(nsIRemoteTab::NavigationType aNavigationType, + int32_t aNavigationIndex, + nsIURI* aNavigationURI) const; + + 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); + + mozilla::ipc::IPCResult RecvAsyncAuthPrompt(const nsCString& aUri, + const nsString& aRealm, + const uint64_t& aCallbackId); + + virtual mozilla::ipc::IPCResult Recv__delete__() override; + + virtual void ActorDestroy(ActorDestroyReason why) override; + + mozilla::ipc::IPCResult RecvRemotePaintIsReady(); + + mozilla::ipc::IPCResult RecvRemoteIsReadyToHandleInputEvents(); + + mozilla::ipc::IPCResult RecvPaintWhileInterruptingJSNoOp( + const LayersObserverEpoch& aEpoch); + + mozilla::ipc::IPCResult RecvSetDimensions( + const uint32_t& aFlags, const int32_t& aX, const int32_t& aY, + const int32_t& aCx, const int32_t& aCy, 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); + + mozilla::ipc::IPCResult RecvQueryVisitedState( + const nsTArray<RefPtr<nsIURI>>&& aURIs); + + mozilla::ipc::IPCResult RecvMaybeFireEmbedderLoadEvents( + EmbedderElementEventType aFireEventAtEmbeddingElement); + + bool SetPointerLock(); + 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); + + private: + void SuppressDisplayport(bool aEnabled); + + void DestroyInternal(); + + 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(); + + private: + // This is used when APZ needs to find the BrowserParent associated with a + // layer to dispatch events. + typedef nsDataHashtable<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); + + // Keeps track of which BrowserParent requested pointer lock. + static BrowserParent* sPointerLockedRemoteTarget; + + // Unsetter for sPointerLockedRemoteTarget; only unsets if argument matches + // current sPointerLockedRemoteTarget. + static void UnsetPointerLockedRemoteTarget(BrowserParent* aBrowserParent); + + struct APZData { + bool operator==(const APZData& aOther) { + return aOther.guid == guid && aOther.blockId == blockId && + aOther.apzResponse == apzResponse; + } + + bool operator!=(const APZData& aOther) { return !(*this == aOther); } + + ScrollableLayerGuid guid; + uint64_t blockId; + nsEventStatus apzResponse; + }; + void SendRealTouchMoveEvent(WidgetTouchEvent& aEvent, APZData& aAPZData, + uint32_t aConsecutiveTouchMoveCount); + + void UpdateVsyncParentVsyncSource(); + + 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; + LayersObserverEpoch mLayerTreeEpoch; + + Maybe<LayoutDeviceToLayoutDeviceMatrix4x4> mChildToParentConversionMatrix; + + nsIntRect mRect; + ScreenIntSize mDimensions; + hal::ScreenOrientation mOrientation; + 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. + nsCursor mCursor; + nsCOMPtr<imgIContainer> mCustomCursor; + uint32_t mCustomCursorHotspotX, mCustomCursorHotspotY; + + 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 mPreserveLayers : 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; + + // Whether this is active for the ProcessPriorityManager or not. + bool mActiveInPriorityManager : 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; + + // Set to true if we're currently suspending nsIWebProgress events. + // We do this when we are process switching and want to suspend events + // from the old BrowserParent after it sent STATE_START event. + bool mSuspendedProgressEvents : 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..dd5572d0fc --- /dev/null +++ b/dom/ipc/CSPMessageUtils.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/CSPMessageUtils.h" +#include "mozilla/ipc/PBackgroundSharedTypes.h" +#include "nsSerializationHelper.h" +#include "BackgroundUtils.h" + +namespace IPC { + +using namespace mozilla::ipc; + +void ParamTraits<nsIContentSecurityPolicy*>::Write( + Message* aMsg, nsIContentSecurityPolicy* aParam) { + bool isNull = !aParam; + WriteParam(aMsg, isNull); + if (isNull) { + return; + } + + CSPInfo csp; + mozilla::Unused << NS_WARN_IF(NS_FAILED(CSPToCSPInfo(aParam, &csp))); + IPDLParamTraits<CSPInfo>::Write(aMsg, nullptr, csp); +} + +bool ParamTraits<nsIContentSecurityPolicy*>::Read( + const Message* aMsg, PickleIterator* aIter, + RefPtr<nsIContentSecurityPolicy>* aResult) { + bool isNull; + if (!ReadParam(aMsg, aIter, &isNull)) { + return false; + } + + if (isNull) { + *aResult = nullptr; + return true; + } + + CSPInfo csp; + if (!IPDLParamTraits<CSPInfo>::Read(aMsg, aIter, nullptr, &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..3877e66402 --- /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(Message* aMsg, nsIContentSecurityPolicy* aParam); + static bool Read(const Message* aMsg, PickleIterator* aIter, + 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..df33669cca --- /dev/null +++ b/dom/ipc/ClonedErrorHolder.cpp @@ -0,0 +1,352 @@ +/* -*- 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 +already_AddRefed<ClonedErrorHolder> ClonedErrorHolder::Constructor( + const GlobalObject& aGlobal, JS::Handle<JSObject*> aError, + ErrorResult& aRv) { + return Create(aGlobal.Context(), aError, aRv); +} + +// static +already_AddRefed<ClonedErrorHolder> ClonedErrorHolder::Create( + JSContext* aCx, JS::Handle<JSObject*> aError, ErrorResult& aRv) { + RefPtr<ClonedErrorHolder> ceh = new ClonedErrorHolder(); + ceh->Init(aCx, aError, aRv); + if (aRv.Failed()) { + return nullptr; + } + return ceh.forget(); +} + +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; + } + 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::RootedValue 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) { + MOZ_DIAGNOSTIC_ASSERT(uint32_t(aStr.Length()) != kVoidStringLength, + "We should not be serializing a 4GiB string"); + if (aStr.IsVoid()) { + return kVoidStringLength; + } + return aStr.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) && + 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) && + 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); + { + RefPtr<ClonedErrorHolder> ceh = new ClonedErrorHolder(); + if (!ceh->Init(aCx, aReader) || !ceh->ToErrorValue(aCx, &errorVal)) { + return nullptr; + } + } + return &errorVal.toObject(); +} + +static JS::UniqueTwoByteChars ToJSStringBuffer(JSContext* aCx, + const nsString& aStr) { + size_t nbytes = aStr.Length() * 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::MutableHandleValue 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); + } + + if (!ToJSString(aCx, mFilename, &filename) || + !ToJSString(aCx, mMessage, &message)) { + return false; + } + if (!JS::CreateError(aCx, mExnType, stack, filename, mLineNumber, mColumn, + nullptr, message, aResult)) { + return false; + } + + if (!mSourceLine.IsVoid()) { + JS::Rooted<JSObject*> errObj(aCx, &aResult.toObject()); + if (JSErrorReport* err = JS_ErrorFromException(aCx, errObj)) { + NS_ConvertUTF8toUTF16 sourceLine(mSourceLine); + if (JS::UniqueTwoByteChars buffer = ToJSStringBuffer(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; + } + + 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..1eb6af4b27 --- /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 "nsISupportsImpl.h" +#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 { + NS_INLINE_DECL_REFCOUNTING(ClonedErrorHolder) + + public: + static already_AddRefed<ClonedErrorHolder> Constructor( + const GlobalObject& aGlobal, JS::Handle<JSObject*> aError, + ErrorResult& aRv); + + static already_AddRefed<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(); + ~ClonedErrorHolder() = default; + + 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::MutableHandleValue 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 + uint32_t mColumn = 0; // 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.h b/dom/ipc/CoalescedInputData.h new file mode 100644 index 0000000000..7d1dd86e2c --- /dev/null +++ b/dom/ipc/CoalescedInputData.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_CoalescedInputData_h +#define mozilla_dom_CoalescedInputData_h + +#include "mozilla/UniquePtr.h" +#include "mozilla/layers/ScrollableLayerGuid.h" + +namespace mozilla { +namespace dom { + +template <class InputEventType> +class CoalescedInputData { + protected: + typedef mozilla::layers::ScrollableLayerGuid 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; } +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_CoalescedInputData_h diff --git a/dom/ipc/CoalescedMouseData.cpp b/dom/ipc/CoalescedMouseData.cpp new file mode 100644 index 0000000000..5e3083c85b --- /dev/null +++ b/dom/ipc/CoalescedMouseData.cpp @@ -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/. */ +#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 && + StaticPrefs::dom_w3c_pointer_events_enabled()) { + // 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->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); +} + +void CoalescedMouseMoveFlusher::WillRefresh(mozilla::TimeStamp aTime) { + MOZ_ASSERT(mRefreshDriver); + mBrowserChild->FlushAllCoalescedMouseData(); + mBrowserChild->ProcessPendingCoalescedMouseDataAndDispatchEvents(); +} + +void CoalescedMouseMoveFlusher::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 mouse move flusher"); + } +} + +void CoalescedMouseMoveFlusher::RemoveObserver() { + if (mRefreshDriver) { + mRefreshDriver->RemoveRefreshObserver(this, FlushType::Event); + mRefreshDriver = nullptr; + } +} + +CoalescedMouseMoveFlusher::CoalescedMouseMoveFlusher( + BrowserChild* aBrowserChild) + : mBrowserChild(aBrowserChild) { + MOZ_ASSERT(mBrowserChild); +} + +CoalescedMouseMoveFlusher::~CoalescedMouseMoveFlusher() { RemoveObserver(); } + +nsRefreshDriver* CoalescedMouseMoveFlusher::GetRefreshDriver() { + PresShell* presShell = mBrowserChild->GetTopLevelPresShell(); + if (!presShell || !presShell->GetPresContext() || + !presShell->GetPresContext()->RefreshDriver()) { + return nullptr; + } + return presShell->GetPresContext()->RefreshDriver(); +} diff --git a/dom/ipc/CoalescedMouseData.h b/dom/ipc/CoalescedMouseData.h new file mode 100644 index 0000000000..01ff886a1e --- /dev/null +++ b/dom/ipc/CoalescedMouseData.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_CoalescedMouseData_h +#define mozilla_dom_CoalescedMouseData_h + +#include "CoalescedInputData.h" +#include "mozilla/MouseEvents.h" +#include "nsRefreshObservers.h" + +class nsRefreshDriver; + +namespace mozilla { +namespace 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 nsARefreshObserver { + public: + explicit CoalescedMouseMoveFlusher(BrowserChild* aBrowserChild); + + virtual void WillRefresh(mozilla::TimeStamp aTime) override; + + NS_INLINE_DECL_REFCOUNTING(CoalescedMouseMoveFlusher, override) + + void StartObserver(); + void RemoveObserver(); + + private: + ~CoalescedMouseMoveFlusher(); + + nsRefreshDriver* GetRefreshDriver(); + + BrowserChild* mBrowserChild; + RefPtr<nsRefreshDriver> mRefreshDriver; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_CoalescedMouseData_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..21f27cd9f6 --- /dev/null +++ b/dom/ipc/CoalescedWheelData.h @@ -0,0 +1,30 @@ +/* -*- 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 { +namespace 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 dom +} // namespace mozilla + +#endif // mozilla_dom_CoalescedWheelData_h diff --git a/dom/ipc/ColorPickerParent.cpp b/dom/ipc/ColorPickerParent.cpp new file mode 100644 index 0000000000..deca898633 --- /dev/null +++ b/dom/ipc/ColorPickerParent.cpp @@ -0,0 +1,76 @@ +/* -*- 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(nsString(aColor)); + } + return NS_OK; +} + +NS_IMETHODIMP +ColorPickerParent::ColorPickerShownCallback::Done(const nsAString& aColor) { + if (mColorPickerParent) { + Unused << ColorPickerParent::Send__delete__(mColorPickerParent, + nsString(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)); +} + +mozilla::ipc::IPCResult ColorPickerParent::RecvOpen() { + if (!CreateColorPicker()) { + Unused << Send__delete__(this, mInitialColor); + return IPC_OK(); + } + + 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..560784af15 --- /dev/null +++ b/dom/ipc/ColorPickerParent.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_ColorPickerParent_h +#define mozilla_dom_ColorPickerParent_h + +#include "mozilla/dom/PColorPickerParent.h" +#include "nsIColorPicker.h" + +namespace mozilla { +namespace dom { + +class ColorPickerParent : public PColorPickerParent { + public: + ColorPickerParent(const nsString& aTitle, const nsString& aInitialColor) + : mTitle(aTitle), mInitialColor(aInitialColor) {} + + 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; + ColorPickerParent* mColorPickerParent; + }; + + private: + virtual ~ColorPickerParent() = default; + + bool CreateColorPicker(); + + RefPtr<ColorPickerShownCallback> mCallback; + nsCOMPtr<nsIColorPicker> mPicker; + + nsString mTitle; + nsString mInitialColor; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_ColorPickerParent_h diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp new file mode 100644 index 0000000000..f68d9a147d --- /dev/null +++ b/dom/ipc/ContentChild.cpp @@ -0,0 +1,4721 @@ +/* -*- 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 MOZ_WIDGET_GTK +# include <gdk/gdkx.h> +# include <gtk/gtk.h> +#endif + +#include "BrowserChild.h" +#include "ContentChild.h" +#include "GeckoProfiler.h" +#include "HandlerServiceChild.h" +#include "nsXPLookAndFeel.h" +#include "mozilla/Attributes.h" +#include "mozilla/BackgroundHangMonitor.h" +#include "mozilla/BenchmarkStorageChild.h" +#include "mozilla/ContentBlocking.h" +#ifdef MOZ_GLEAN +# include "mozilla/FOGIPC.h" +#endif +#include "GMPServiceChild.h" +#include "Geolocation.h" +#include "imgLoader.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/HangDetails.h" +#include "mozilla/LoadInfo.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/MemoryTelemetry.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/PerfStats.h" +#include "mozilla/PerformanceMetricsCollector.h" +#include "mozilla/PerformanceUtils.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/SpinEventLoopUntil.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StaticPrefs_fission.h" +#include "mozilla/StaticPrefs_media.h" +#include "mozilla/TelemetryIPC.h" +#include "mozilla/Unused.h" +#include "mozilla/WebBrowserPersistDocumentChild.h" +#include "mozilla/devtools/HeapSnapshotTempFileHelperChild.h" +#include "mozilla/docshell/OfflineCacheUpdateChild.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/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/PLoginReputationChild.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/WindowGlobalChild.h" +#include "mozilla/dom/WorkerDebugger.h" +#include "mozilla/dom/WorkerDebuggerManager.h" +#include "mozilla/dom/ipc/SharedMap.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/LocaleService.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/FileDescriptorSetChild.h" +#include "mozilla/ipc/FileDescriptorUtils.h" +#include "mozilla/ipc/GeckoChildProcessHost.h" +#include "mozilla/ipc/LibrarySandboxPreload.h" +#include "mozilla/ipc/PChildToParentStreamChild.h" +#include "mozilla/ipc/PParentToChildStreamChild.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" +#include "mozilla/loader/ScriptCacheActors.h" +#include "mozilla/media/MediaChild.h" +#include "mozilla/net/CaptivePortalService.h" +#include "mozilla/net/CookieServiceChild.h" +#include "mozilla/net/HttpChannelChild.h" +#include "mozilla/net/NeckoChild.h" +#include "mozilla/plugins/PluginInstanceParent.h" +#include "mozilla/plugins/PluginModuleParent.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 "nsIConsoleService.h" +#include "nsIInputStreamChannel.h" +#include "nsIOpenWindowInfo.h" +#include "nsIStringBundle.h" +#include "nsIURIMutator.h" +#include "nsQueryObject.h" +#include "nsSandboxFlags.h" + +#if !defined(XP_WIN) +# include "mozilla/Omnijar.h" +#endif + +#ifdef MOZ_GECKO_PROFILER +# include "ChildProfilerController.h" +#endif + +#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 "mozilla/Sandbox.h" +# elif defined(__OpenBSD__) +# include <err.h> +# include <sys/stat.h> +# include <unistd.h> + +# include <fstream> + +# include "SpecialSystemDirectory.h" +# include "nsILineInputStream.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 "mozilla/Unused.h" +#include "nsAnonymousTemporaryFile.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 "nsIConsoleService.h" +#include "nsIContentViewer.h" +#include "nsICycleCollectorListener.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIDragService.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIMemoryInfoDumper.h" +#include "nsIMemoryReporter.h" +#include "nsIObserverService.h" +#include "nsIScriptError.h" +#include "nsIScriptSecurityManager.h" +#include "nsJSEnvironment.h" +#include "nsMemoryInfoDumper.h" +#include "nsPluginHost.h" +#include "nsServiceManagerUtils.h" +#include "nsStyleSheetService.h" +#include "nsThreadManager.h" +#include "nsVariant.h" +#include "nsXULAppAPI.h" +#ifdef NS_PRINTING +# include "nsPrintingProxy.h" +#endif +#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 "nsIScriptSecurityManager.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" +#endif + +#ifdef XP_WIN +# include <process.h> +# define getpid _getpid +# include "mozilla/WinDllServices.h" +# include "mozilla/audio/AudioNotificationReceiver.h" +# include "mozilla/widget/AudioSession.h" +# include "mozilla/widget/WinContentSystemParameters.h" +#endif + +#if defined(XP_MACOSX) +# include "nsMacUtilsImpl.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" +#include "mozilla/dom/PPresentationChild.h" +#include "mozilla/dom/PresentationIPCService.h" +#include "mozilla/ipc/IPCStreamAlloc.h" +#include "mozilla/ipc/IPCStreamDestination.h" +#include "mozilla/ipc/IPCStreamSource.h" + +#ifdef MOZ_WEBSPEECH +# include "mozilla/dom/PSpeechSynthesisChild.h" +#endif + +#include "ClearOnShutdown.h" +#include "DomainPolicy.h" +#include "GMPServiceChild.h" +#include "GfxInfoBase.h" +#include "MMPrinter.h" +#include "ProcessUtils.h" +#include "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 "nsAppRunner.h" +#endif + +#ifdef MOZ_CODE_COVERAGE +# include "mozilla/CodeCoverageHandler.h" +#endif + +using namespace mozilla; +using namespace mozilla::docshell; +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 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::RootedValue 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::RootedValue 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.BuildClonedMessageDataForChild(mChild, 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)) +#if defined(XP_WIN) && defined(ACCESSIBILITY) + , + mMainChromeTid(0), + mMsaaID(0) +#endif + , + mIsForBrowser(false), + mIsAlive(true), + mShuttingDown(false) { + // This process is a content process, so it's clearly running in + // multiprocess mode! + nsDebugImpl::SetMultiprocessMode("Child"); + + // 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::Shutdown); + } +} + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning( \ + disable : 4722) /* Silence "destructor never returns" warning \ + */ +#endif + +ContentChild::~ContentChild() { +#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, + LookAndFeelData&& aLookAndFeelData, + nsTArray<SystemFontListEntry>&& aFontList, + const Maybe<SharedMemoryHandle>& aSharedUASheetHandle, + const uintptr_t& aSharedUASheetAddress, + nsTArray<SharedMemoryHandle>&& aSharedFontListBlocks) { + if (!sShutdownCanary) { + return IPC_OK(); + } + + mLookAndFeelData = std::move(aLookAndFeelData); + mFontList = std::move(aFontList); + mSharedFontListBlocks = std::move(aSharedFontListBlocks); +#ifdef XP_WIN + widget::WinContentSystemParameters::GetSingleton()->SetContentValues( + aXPCOMInit.systemParameters()); +#endif + + gfx::gfxVars::SetValuesForInitialize(aXPCOMInit.gfxNonDefaultVarUpdates()); + InitSharedUASheets(aSharedUASheetHandle, aSharedUASheetAddress); + InitXPCOM(std::move(aXPCOMInit), aInitialData); + InitGraphicsDeviceData(aXPCOMInit.contentDeviceData()); + + return IPC_OK(); +} + +class nsGtkNativeInitRunnable : public Runnable { + public: + nsGtkNativeInitRunnable() : Runnable("nsGtkNativeInitRunnable") {} + + NS_IMETHOD Run() override { + LookAndFeel::NativeInit(); + return NS_OK; + } +}; + +bool ContentChild::Init(MessageLoop* aIOLoop, base::ProcessId aParentPid, + const char* aParentBuildID, + UniquePtr<IPC::Channel> aChannel, 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 waylandDisabled = true; +# ifdef MOZ_WAYLAND + waylandDisabled = IsWaylandDisabled(); +# endif + if (waylandDisabled) { + 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 + + NS_ASSERTION(!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))) { + return false; + } + + if (!Open(std::move(aChannel), aParentPid, aIOLoop)) { + return false; + } + sSingleton = this; + + // If communications with the parent have broken down, take the process + // down so it's not hanging around. + GetIPCChannel()->SetAbortOnError(true); +#if defined(XP_WIN) && defined(ACCESSIBILITY) + GetIPCChannel()->SetChannelFlags(MessageChannel::REQUIRE_A11Y_REENTRY); +#endif + + // 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 (GDK_IS_X11_DISPLAY(gdk_display_get_default()) && + !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; + +#ifdef NS_PRINTING + // Force the creation of the nsPrintingProxy so that it's IPC counterpart, + // PrintingParent, is always available for printing initiated from the parent. + RefPtr<nsPrintingProxy> printingProxy = nsPrintingProxy::GetInstance(); +#endif + + 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( + TaskCategory::Other, + NS_NewRunnableFunction("RegisterPendingInputEventHangAnnotator", [] { + BackgroundHangMonitor::RegisterAnnotator( + PendingInputEventHangAnnotator::sSingleton); + })); +#endif + + return true; +} + +void ContentChild::SetProcessName(const nsACString& aName, + const nsACString* aETLDplus1) { + char* name; + if ((name = PR_GetEnv("MOZ_DEBUG_APP_PROCESS")) && aName.EqualsASCII(name)) { +#ifdef OS_POSIX + printf_stderr("\n\nCHILDCHILDCHILDCHILD\n [%s] debug me @%d\n\n", name, + getpid()); + sleep(30); +#elif defined(OS_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 + } + + mProcessName = aName; +#ifdef MOZ_GECKO_PROFILER + if (aETLDplus1) { + profiler_set_process_name(mProcessName, aETLDplus1); + } else { + profiler_set_process_name(mProcessName); + } +#endif + mozilla::ipc::SetThisProcessName(PromiseFlatCString(mProcessName).get()); +} + +static nsresult GetCreateWindowParams(nsIOpenWindowInfo* aOpenWindowInfo, + nsDocShellLoadState* aLoadState, + bool aForceNoReferrer, float* aFullZoom, + nsIReferrerInfo** aReferrerInfo, + nsIPrincipal** aTriggeringPrincipal, + nsIContentSecurityPolicy** aCsp) { + *aFullZoom = 1.0f; + 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); + + *aFullZoom = parent->FullZoom(); + return NS_OK; +} + +nsresult ContentChild::ProvideWindowCommon( + BrowserChild* aTabOpener, nsIOpenWindowInfo* aOpenWindowInfo, + uint32_t aChromeFlags, bool aCalledFromJS, bool aWidthSpecified, + nsIURI* aURI, const nsAString& aName, const nsACString& aFeatures, + bool aForceNoOpener, bool aForceNoReferrer, nsDocShellLoadState* aLoadState, + bool* aWindowIsNew, BrowsingContext** aReturn) { + MOZ_DIAGNOSTIC_ASSERT(aTabOpener, "We must have a tab opener"); + + *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(); + } + + bool sandboxFlagsPropagate = + parentSandboxFlags & SANDBOX_PROPAGATES_TO_AUXILIARY_BROWSING_CONTEXTS; + + // Check if we should load in a different process. Under Fission, we never + // want to do this, since the Fission process selection logic will handle + // everything for us. Outside of Fission, we always want to load in a + // different process if we have noopener set, but we also might if we can't + // load in the current process. + bool loadInDifferentProcess = + aForceNoOpener && StaticPrefs::dom_noopener_newprocess_enabled() && + !useRemoteSubframes && !sandboxFlagsPropagate && + !aOpenWindowInfo->GetIsForPrinting(); + if (!loadInDifferentProcess && aURI) { + // Only special-case cross-process loads if Fission is disabled. With + // Fission enabled, the initial in-process load will automatically be + // retargeted to the correct process. + if (!(parent && parent->UseRemoteSubframes())) { + nsCOMPtr<nsIWebBrowserChrome3> browserChrome3; + rv = aTabOpener->GetWebBrowserChrome(getter_AddRefs(browserChrome3)); + if (NS_SUCCEEDED(rv) && browserChrome3) { + bool shouldLoad; + rv = browserChrome3->ShouldLoadURIInThisProcess(aURI, &shouldLoad); + loadInDifferentProcess = NS_SUCCEEDED(rv) && !shouldLoad; + } + } + } + + // 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! + if (loadInDifferentProcess && !sandboxFlagsPropagate) { + float fullZoom; + nsCOMPtr<nsIPrincipal> triggeringPrincipal; + nsCOMPtr<nsIContentSecurityPolicy> csp; + nsCOMPtr<nsIReferrerInfo> referrerInfo; + rv = GetCreateWindowParams(aOpenWindowInfo, aLoadState, aForceNoReferrer, + &fullZoom, 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, aWidthSpecified, aURI, + features, fullZoom, 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); + 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 = MakeRefPtr<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; + ipcContext.openerChild() = aTabOpener; + 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. + RefPtr<nsPIDOMWindowOuter> parentWindow = + parent ? parent->GetDOMWindow() : nullptr; + if (NS_FAILED(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()); + bool hasSiblings = info.hasSiblings(); + + // 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, + aTabOpener->WebWidget()->GetDPI(), + aTabOpener->WebWidget()->RoundsWidgetCoordinatesTo(), + aTabOpener->WebWidget()->GetDefaultScale().scale); + + newChild->SetMaxTouchPoints(maxTouchPoints); + newChild->SetHasSiblings(hasSiblings); + + 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. + float fullZoom; + nsCOMPtr<nsIPrincipal> triggeringPrincipal; + nsCOMPtr<nsIContentSecurityPolicy> csp; + nsCOMPtr<nsIReferrerInfo> referrerInfo; + rv = GetCreateWindowParams(aOpenWindowInfo, aLoadState, aForceNoReferrer, + &fullZoom, getter_AddRefs(referrerInfo), + getter_AddRefs(triggeringPrincipal), + getter_AddRefs(csp)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + SendCreateWindow(aTabOpener, parent, newChild, aChromeFlags, aCalledFromJS, + aWidthSpecified, aOpenWindowInfo->GetIsForPrinting(), + aOpenWindowInfo->GetIsForWindowDotPrint(), aURI, features, + fullZoom, Principal(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([&]() { 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(const 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(*aHandle, aAddress); +} + +void ContentChild::InitXPCOM( + XPCOMInitData&& aXPCOMInit, + const mozilla::dom::ipc::StructuredCloneData& aInitialData) { + // 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. + BackgroundChild::Startup(); + +#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(); +#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; + } + + LSObject::Initialize(); + + 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()); + // XXX(Bug 1633675) The LocaleService calls could also move the arguments. + LocaleService::GetInstance()->AssignAppLocales(aXPCOMInit.appLocales()); + LocaleService::GetInstance()->AssignRequestedLocales( + aXPCOMInit.requestedLocales()); + + 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::RootedValue 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())); + + DataStorage::SetCachedStorageEntries(aXPCOMInit.dataStorage()); + + // Initialize the RemoteDecoderManager thread and its associated PBackground + // channel. + RemoteDecoderManagerChild::Init(); + + // 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(); +} + +mozilla::ipc::IPCResult ContentChild::RecvGetUntrustedModulesData( + GetUntrustedModulesDataResolver&& aResolver) { +#if defined(XP_WIN) + 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(); +#else + return IPC_FAIL(this, "Unsupported on this platform"); +#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) { +#ifdef MOZ_GECKO_PROFILER + mProfilerController = ChildProfilerController::Create(std::move(aEndpoint)); +#endif + 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(); +} + +mozilla::ipc::IPCResult ContentChild::RecvRequestPerformanceMetrics( + const nsID& aID) { + RefPtr<ContentChild> self = this; + RefPtr<AbstractThread> mainThread = AbstractThread::MainThread(); + nsTArray<RefPtr<PerformanceInfoPromise>> promises = CollectPerformanceInfo(); + + PerformanceInfoPromise::All(mainThread, promises) + ->Then( + mainThread, __func__, + [self, aID](const nsTArray<mozilla::dom::PerformanceInfo>& aResult) { + self->SendAddPerformanceMetrics(aID, aResult); + }, + []() { /* silently fails -- the parent times out + and proceeds when the data is not coming back */ + }); + + 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(); + + // Zap all the old layer managers we have lying around. + for (const auto& browserChild : tabs) { + if (browserChild->GetLayersId().IsValid()) { + browserChild->InvalidateLayers(); + } + } + + // 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(); + } + } + + RemoteDecoderManagerChild::InitForGPUProcess(std::move(aVideoManager)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvAudioDefaultDeviceChange() { +#ifdef XP_WIN + audio::AudioNotificationReceiver::NotifyDefaultDeviceChanged(); +#endif + 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 acheived when we additionally deny + // future connections, however this currently breaks WebGL so it's not done + // by default. + if (aIsSandboxEnabled && + Preferences::GetBool( + "security.sandbox.content.mac.disconnect-windowserver")) { + 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) + +# ifdef MOZ_USING_WASM_SANDBOXING + mozilla::ipc::PreloadSandboxedDynamicLibraries(); +# endif + + 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::GetCubebContext(); + } + + 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(OS_ANDROID) + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::ContentSandboxCapabilities, + static_cast<int>(SandboxInfo::Get().AsInteger())); +# endif /* XP_LINUX && !OS_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_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()) { + return IPC_FAIL(this, "Null or discarded initial BrowsingContext"); + } + + // 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(); +} + +PFileDescriptorSetChild* ContentChild::SendPFileDescriptorSetConstructor( + const FileDescriptor& aFD) { + MOZ_ASSERT(NS_IsMainThread()); + + if (IsShuttingDown()) { + return nullptr; + } + + return PContentChild::SendPFileDescriptorSetConstructor(aFD); +} + +PFileDescriptorSetChild* ContentChild::AllocPFileDescriptorSetChild( + const FileDescriptor& aFD) { + return new FileDescriptorSetChild(aFD); +} + +bool ContentChild::DeallocPFileDescriptorSetChild( + PFileDescriptorSetChild* aActor) { + delete static_cast<FileDescriptorSetChild*>(aActor); + return true; +} + +already_AddRefed<PRemoteLazyInputStreamChild> +ContentChild::AllocPRemoteLazyInputStreamChild(const nsID& aID, + const uint64_t& aSize) { + RefPtr<RemoteLazyInputStreamChild> actor = + new RemoteLazyInputStreamChild(aID, aSize); + return actor.forget(); +} + +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; +} + +PPresentationChild* ContentChild::AllocPPresentationChild() { + MOZ_CRASH("We should never be manually allocating PPresentationChild actors"); + return nullptr; +} + +bool ContentChild::DeallocPPresentationChild(PPresentationChild* aActor) { + delete aActor; + return true; +} + +mozilla::ipc::IPCResult ContentChild::RecvNotifyPresentationReceiverLaunched( + PBrowserChild* aIframe, const nsString& aSessionId) { + nsCOMPtr<nsIDocShell> docShell = + do_GetInterface(static_cast<BrowserChild*>(aIframe)->WebNavigation()); + NS_WARNING_ASSERTION(docShell, "WebNavigation failed"); + + nsCOMPtr<nsIPresentationService> service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + NS_WARNING_ASSERTION(service, "presentation service is missing"); + + Unused << NS_WARN_IF( + NS_FAILED(static_cast<PresentationIPCService*>(service.get()) + ->MonitorResponderLoading(aSessionId, docShell))); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvNotifyPresentationReceiverCleanUp( + const nsString& aSessionId) { + nsCOMPtr<nsIPresentationService> service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + NS_WARNING_ASSERTION(service, "presentation service is missing"); + + Unused << NS_WARN_IF(NS_FAILED(service->UntrackSessionInfo( + aSessionId, nsIPresentationService::ROLE_RECEIVER))); + + return IPC_OK(); +} + +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; +} + +PTestShellChild* ContentChild::AllocPTestShellChild() { + return new TestShellChild(); +} + +bool ContentChild::DeallocPTestShellChild(PTestShellChild* shell) { + delete shell; + return true; +} + +mozilla::ipc::IPCResult ContentChild::RecvPTestShellConstructor( + PTestShellChild* actor) { + return IPC_OK(); +} + +void ContentChild::UpdateCookieStatus(nsIChannel* aChannel) { + RefPtr<CookieServiceChild> csChild = CookieServiceChild::GetSingleton(); + NS_ASSERTION(csChild, "Couldn't get CookieServiceChild"); + + 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); + return IPC_OK(); +} + +PNeckoChild* ContentChild::AllocPNeckoChild() { return new NeckoChild(); } + +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(); +} + +bool ContentChild::DeallocPNeckoChild(PNeckoChild* necko) { + delete necko; + return true; +} + +PPrintingChild* ContentChild::AllocPPrintingChild() { + // The ContentParent should never attempt to allocate the nsPrintingProxy, + // which implements PPrintingChild. Instead, the nsPrintingProxy service is + // requested and instantiated via XPCOM, and the constructor of + // nsPrintingProxy sets up the IPC connection. + MOZ_CRASH("Should never get here!"); + return nullptr; +} + +bool ContentChild::DeallocPPrintingChild(PPrintingChild* printing) { + return true; +} + +PChildToParentStreamChild* ContentChild::SendPChildToParentStreamConstructor( + PChildToParentStreamChild* aActor) { + MOZ_ASSERT(NS_IsMainThread()); + + if (IsShuttingDown()) { + return nullptr; + } + + return PContentChild::SendPChildToParentStreamConstructor(aActor); +} + +PChildToParentStreamChild* ContentChild::AllocPChildToParentStreamChild() { + MOZ_CRASH("PChildToParentStreamChild actors should be manually constructed!"); +} + +bool ContentChild::DeallocPChildToParentStreamChild( + PChildToParentStreamChild* aActor) { + delete aActor; + return true; +} + +PParentToChildStreamChild* ContentChild::AllocPParentToChildStreamChild() { + return mozilla::ipc::AllocPParentToChildStreamChild(); +} + +bool ContentChild::DeallocPParentToChildStreamChild( + PParentToChildStreamChild* aActor) { + delete aActor; + return true; +} + +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_WEBSPEECH +PSpeechSynthesisChild* ContentChild::AllocPSpeechSynthesisChild() { + MOZ_CRASH("No one should be allocating PSpeechSynthesisChild actors"); +} + +bool ContentChild::DeallocPSpeechSynthesisChild(PSpeechSynthesisChild* aActor) { + delete aActor; + return true; +} +#endif + +PWebrtcGlobalChild* ContentChild::AllocPWebrtcGlobalChild() { +#ifdef MOZ_WEBRTC + auto* child = new WebrtcGlobalChild(); + return child; +#else + return nullptr; +#endif +} + +bool ContentChild::DeallocPWebrtcGlobalChild(PWebrtcGlobalChild* aActor) { +#ifdef MOZ_WEBRTC + delete static_cast<WebrtcGlobalChild*>(aActor); + return true; +#else + return false; +#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) { + nsIPrincipal* prin = aForPrincipal ? aForPrincipal.value().get() : nullptr; + SharedStyleSheetCache::Clear(prin); + 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(); +} + +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(); + + 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::RecvDataStoragePut( + const nsString& aFilename, const DataStorageItem& aItem) { + RefPtr<DataStorage> storage = DataStorage::GetFromRawFileName(aFilename); + if (storage) { + storage->Put(aItem.key(), aItem.value(), aItem.type()); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvDataStorageRemove( + const nsString& aFilename, const nsCString& aKey, + const DataStorageType& aType) { + RefPtr<DataStorage> storage = DataStorage::GetFromRawFileName(aFilename); + if (storage) { + storage->Remove(aKey, aType); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvDataStorageClear( + const nsString& aFilename) { + RefPtr<DataStorage> storage = DataStorage::GetFromRawFileName(aFilename); + if (storage) { + storage->Clear(); + } + 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(); +} + +// NOTE: This method is being run in the SystemGroup, and thus cannot directly +// touch pages. See GetSpecificMessageEventTarget. +mozilla::ipc::IPCResult ContentChild::RecvNotifyVisited( + nsTArray<VisitedQueryResult>&& aURIs) { + nsCOMPtr<IHistory> history = services::GetHistory(); + 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( + LookAndFeelData&& aLookAndFeelData, widget::ThemeChangeKind aKind) { + switch (aLookAndFeelData.type()) { + case LookAndFeelData::TLookAndFeelCache: + LookAndFeel::SetCache(aLookAndFeelData.get_LookAndFeelCache()); + break; + case LookAndFeelData::TFullLookAndFeel: + LookAndFeel::SetData(std::move(aLookAndFeelData.get_FullLookAndFeel())); + break; + default: + MOZ_ASSERT(false, "unreachable"); + } + LookAndFeel::NotifyChangedAllWindows(aKind); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvUpdateSystemParameters( + nsTArray<SystemParameterKVPair>&& aUpdates) { +#ifdef XP_WIN + widget::WinContentSystemParameters::GetSingleton()->SetContentValues( + aUpdates); +#endif + + 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::UnpackClonedMessageDataForChild(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 = + services::GetStringBundleService(); + + for (auto& descriptor : aDescriptors) { + stringBundleService->RegisterContentBundle( + descriptor.bundleURL(), descriptor.mapFile(), descriptor.mapSize()); + } + + 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::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( + nsTArray<SystemFontListEntry>&& aFontList) { + mFontList = std::move(aFontList); + gfxPlatform::GetPlatform()->UpdateFontList(true); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvRebuildFontList( + const bool& aFullRebuild) { + gfxPlatform::GetPlatform()->UpdateFontList(aFullRebuild); + 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::RecvAddPermission( + const IPC::Permission& permission) { + nsCOMPtr<nsIPermissionManager> permissionManagerIface = + services::GetPermissionManager(); + 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 = + services::GetPermissionManager(); + 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( + const uint32_t& aMainChromeTid, const uint32_t& aMsaaID) { +#ifdef ACCESSIBILITY +# ifdef XP_WIN + MOZ_ASSERT(aMainChromeTid != 0); + mMainChromeTid = aMainChromeTid; + + MOZ_ASSERT(aMsaaID != 0); + mMsaaID = aMsaaID; +# endif // XP_WIN + + // 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(); + 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) { + if (!mRemoteType.IsVoid()) { + // Preallocated processes are type PREALLOC_REMOTE_TYPE; they can become + // anything except a File: 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 + MOZ_RELEASE_ASSERT(aRemoteType != FILE_REMOTE_TYPE && + (mRemoteType == PREALLOC_REMOTE_TYPE || + (mRemoteType == DEFAULT_REMOTE_TYPE && + aRemoteType == DEFAULT_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())); + } + + auto remoteTypePrefix = RemoteTypePrefix(aRemoteType); + + // Update the process name so about:memory's process names are more obvious. + if (aRemoteType == FILE_REMOTE_TYPE) { + SetProcessName("file:// Content"_ns); + } else if (aRemoteType == EXTENSION_REMOTE_TYPE) { + SetProcessName("WebExtensions"_ns); + } else if (aRemoteType == PRIVILEGEDABOUT_REMOTE_TYPE) { + SetProcessName("Privileged Content"_ns); + } else if (aRemoteType == LARGE_ALLOCATION_REMOTE_TYPE) { + SetProcessName("Large Allocation Web Content"_ns); + } else if (RemoteTypePrefix(aRemoteType) == FISSION_WEB_REMOTE_TYPE) { + SetProcessName("Isolated Web Content"_ns); +#ifdef NIGHTLY_BUILD + // for Nightly only, and requires pref flip + if (StaticPrefs::fission_processOriginNames()) { + // Sets profiler process name + SetProcessName( + Substring(aRemoteType, FISSION_WEB_REMOTE_TYPE.Length() + 1)); + + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("Changed name of process %d from %s to %s", getpid(), + PromiseFlatCString(mRemoteType).get(), + PromiseFlatCString( + Substring(aRemoteType, FISSION_WEB_REMOTE_TYPE.Length() + 1)) + .get())); + } else +#endif + { + // The profiler can sanitize out the eTLD+1 + nsCString etld( + Substring(aRemoteType, FISSION_WEB_REMOTE_TYPE.Length() + 1)); + SetProcessName("Isolated Web Content"_ns, &etld); + } + } + // else "prealloc", "web" or "webCOOP+COEP" type -> "Web Content" already set + + mRemoteType.Assign(aRemoteType); + + // 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(); +} + +// 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::RecvInitServiceWorkers( + const ServiceWorkerConfiguration& aConfig) { + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + if (!swm) { + // browser shutdown began + return IPC_OK(); + } + swm->LoadRegistrations(aConfig.serviceWorkerRegistrations()); + return IPC_OK(); +} + +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.agentClusterId(), 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)); + + os->NotifyObservers(static_cast<nsIPropertyBag2*>(props), + "ipc:process-priority-changed", nullptr); + 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.PutEntry(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.RemoveEntry(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::RecvShutdown() { + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + if (os) { + os->NotifyObservers(ToSupports(this), "content-child-will-shutdown", + nullptr); + } + + ShutdownInternal(); + return IPC_OK(); +} + +void ContentChild::ShutdownInternal() { + // 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). + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::IPCShutdownState, "RecvShutdown"_ns); + + 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) { + os->NotifyObservers(ToSupports(this), "content-child-shutdown", nullptr); + } + +#if defined(XP_WIN) + mozilla::widget::StopAudioSession(); +#endif + + GetIPCChannel()->SetAbortOnError(false); + +#ifdef MOZ_GECKO_PROFILER + if (mProfilerController) { + nsCString shutdownProfile = + mProfilerController->GrabShutdownProfileAndShutdown(); + mProfilerController = nullptr; + // Send the shutdown profile to the parent process through our own + // message channel, which we know will survive for long enough. + Unused << SendShutdownProfile(shutdownProfile); + } +#endif + + // Start a timer that will insure we quickly exit after a reasonable + // period of time. Prevents shutdown hangs after our connection to the + // parent closes. + StartForceKillTimer(); + + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::IPCShutdownState, + "SendFinishShutdown (sending)"_ns); + bool sent = SendFinishShutdown(); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::IPCShutdownState, + sent ? "SendFinishShutdown (sent)"_ns : "SendFinishShutdown (failed)"_ns); +} + +mozilla::ipc::IPCResult ContentChild::RecvUpdateWindow( + const uintptr_t& aChildId) { +#if defined(XP_WIN) + NS_ASSERTION(aChildId, + "Expected child hwnd value for remote plugin instance."); + mozilla::plugins::PluginInstanceParent* parentInstance = + mozilla::plugins::PluginInstanceParent::LookupPluginInstanceByID( + aChildId); + if (parentInstance) { + // sync! update call to the plugin instance that forces the + // plugin to paint its child window. + if (!parentInstance->CallUpdateWindow()) { + return IPC_FAIL_NO_REASON(this); + } + } + return IPC_OK(); +#else + MOZ_ASSERT( + false, + "ContentChild::RecvUpdateWindow calls unexpected on this platform."); + return IPC_FAIL_NO_REASON(this); +#endif +} + +PContentPermissionRequestChild* +ContentChild::AllocPContentPermissionRequestChild( + const nsTArray<PermissionRequest>& aRequests, + const IPC::Principal& aPrincipal, const IPC::Principal& 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; +} + +PWebBrowserPersistDocumentChild* +ContentChild::AllocPWebBrowserPersistDocumentChild( + PBrowserChild* aBrowser, const MaybeDiscarded<BrowsingContext>& aContext) { + return new 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(); +} + +bool ContentChild::DeallocPWebBrowserPersistDocumentChild( + PWebBrowserPersistDocumentChild* aActor) { + delete aActor; + return true; +} + +mozilla::ipc::IPCResult ContentChild::RecvInvokeDragSession( + nsTArray<IPCDataTransfer>&& aTransfers, const uint32_t& aAction) { + nsCOMPtr<nsIDragService> dragService = + do_GetService("@mozilla.org/widget/dragservice;1"); + if (dragService) { + dragService->StartDragSession(); + nsCOMPtr<nsIDragSession> session; + dragService->GetCurrentSession(getter_AddRefs(session)); + if (session) { + session->SetDragAction(aAction); + // 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 < aTransfers.Length() && !hasFiles; ++i) { + auto& items = aTransfers[i].items(); + for (uint32_t j = 0; j < items.Length() && !hasFiles; ++j) { + if (items[j].data().type() == IPCDataTransferData::TIPCBlob) { + hasFiles = true; + } + } + } + + // Add the entries from the IPC to the new DataTransfer + nsCOMPtr<DataTransfer> dataTransfer = + new DataTransfer(nullptr, eDragStart, false, -1); + for (uint32_t i = 0; i < aTransfers.Length(); ++i) { + auto& items = aTransfers[i].items(); + for (uint32_t j = 0; j < items.Length(); ++j) { + const IPCDataTransferItem& item = items[j]; + RefPtr<nsVariantCC> variant = new nsVariantCC(); + if (item.data().type() == IPCDataTransferData::TnsString) { + const nsString& data = item.data().get_nsString(); + variant->SetAsAString(data); + } else if (item.data().type() == IPCDataTransferData::TShmem) { + Shmem data = item.data().get_Shmem(); + variant->SetAsACString( + nsDependentCSubstring(data.get<char>(), data.Size<char>())); + Unused << DeallocShmem(data); + } else if (item.data().type() == IPCDataTransferData::TIPCBlob) { + RefPtr<BlobImpl> blobImpl = + IPCBlobUtils::Deserialize(item.data().get_IPCBlob()); + variant->SetAsISupports(blobImpl); + } else { + 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() != IPCDataTransferData::TIPCBlob; + dataTransfer->SetDataWithPrincipalFromOtherProcess( + NS_ConvertUTF8toUTF16(item.flavor()), variant, i, + nsContentUtils::GetSystemPrincipal(), hidden); + } + } + session->SetDataTransfer(dataTransfer); + } + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvEndDragSession( + const bool& aDoneDrag, const bool& aUserCancelled, + const LayoutDeviceIntPoint& aDragEndPoint, const uint32_t& aKeyModifiers) { + nsCOMPtr<nsIDragService> dragService = + do_GetService("@mozilla.org/widget/dragservice;1"); + if (dragService) { + if (aUserCancelled) { + nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession(); + if (dragSession) { + dragSession->UserCancelled(); + } + } + static_cast<nsBaseDragService*>(dragService.get()) + ->SetDragEndPoint(aDragEndPoint); + dragService->EndDragSession(aDoneDrag, aKeyModifiers); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvPush(const nsCString& aScope, + const IPC::Principal& 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, const IPC::Principal& 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, const IPC::Principal& aPrincipal) { + PushSubscriptionChangeDispatcher dispatcher(aScope, aPrincipal); + Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObserversAndWorkers())); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvPushError( + const nsCString& aScope, const IPC::Principal& 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, const IPC::Principal& 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, + const IPC::Principal& aPrincipal, const Maybe<nsID>& aAgentClusterId) { + RefPtr<BlobImpl> blobImpl = IPCBlobUtils::Deserialize(aBlob); + MOZ_ASSERT(blobImpl); + + BlobURLProtocolHandler::AddDataEntry(aURI, aPrincipal, aAgentClusterId, + blobImpl); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvBlobURLUnregistration( + const nsCString& aURI) { + BlobURLProtocolHandler::RemoveDataEntry( + aURI, + /* aBroadcastToOtherProcesses = */ false); + return IPC_OK(); +} + +#if defined(XP_WIN) && defined(ACCESSIBILITY) +bool ContentChild::SendGetA11yContentId() { + return PContentChild::SendGetA11yContentId(&mMsaaID); +} +#endif // defined(XP_WIN) && defined(ACCESSIBILITY) + +void ContentChild::CreateGetFilesRequest(const nsAString& aDirectoryPath, + bool aRecursiveFlag, nsID& aUUID, + GetFilesHelperChild* aChild) { + MOZ_ASSERT(aChild); + MOZ_ASSERT(!mGetFilesPendingRequests.GetWeak(aUUID)); + + Unused << SendGetFilesRequest(aUUID, nsString(aDirectoryPath), + aRecursiveFlag); + mGetFilesPendingRequests.Put(aUUID, RefPtr{aChild}); +} + +void ContentChild::DeleteGetFilesRequest(nsID& aUUID, + GetFilesHelperChild* aChild) { + MOZ_ASSERT(aChild); + MOZ_ASSERT(mGetFilesPendingRequests.GetWeak(aUUID)); + + Unused << SendDeleteGetFilesRequest(aUUID); + mGetFilesPendingRequests.Remove(aUUID); +} + +mozilla::ipc::IPCResult ContentChild::RecvGetFilesResponse( + const nsID& aUUID, const GetFilesResponseResult& aResult) { + GetFilesHelperChild* child = mGetFilesPendingRequests.GetWeak(aUUID); + // This object can already been deleted in case DeleteGetFilesRequest has + // been called when the response was sending by the parent. + if (!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); + } + + mGetFilesPendingRequests.Remove(aUUID); + 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( + const Principal& 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, const nsTArray<IPCURLClassifierFeature>& aFeatures) { + return new URLClassifierLocalChild(); +} + +bool ContentChild::DeallocPURLClassifierLocalChild( + PURLClassifierLocalChild* aActor) { + MOZ_ASSERT(aActor); + delete aActor; + return true; +} + +PLoginReputationChild* ContentChild::AllocPLoginReputationChild(nsIURI* aUri) { + return new PLoginReputationChild(); +} + +bool ContentChild::DeallocPLoginReputationChild(PLoginReputationChild* 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.LookupOrAdd(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::RecvSetPluginList( + const uint32_t& aPluginEpoch, nsTArray<plugins::PluginTag>&& aPluginTags, + nsTArray<plugins::FakePluginTag>&& aFakePluginTags) { + RefPtr<nsPluginHost> host = nsPluginHost::GetInst(); + host->SetPluginsInContent(aPluginEpoch, aPluginTags, aFakePluginTags); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvShareCodeCoverageMutex( + const CrossProcessMutexHandle& aHandle) { +#ifdef MOZ_CODE_COVERAGE + CodeCoverageHandler::Init(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::RecvGetMemoryUniqueSetSize( + GetMemoryUniqueSetSizeResolver&& aResolver) { + MemoryTelemetry::Get().GetUniqueSetSize(std::move(aResolver)); + return IPC_OK(); +} + +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(), + getter_AddRefs(loadInfo)); + if (NS_FAILED(rv)) { + MOZ_DIAGNOSTIC_ASSERT(false, "LoadInfoArgsToLoadInfo failed"); + return IPC_OK(); + } + + nsCOMPtr<nsIChannel> newChannel; + MOZ_ASSERT((aArgs.loadStateLoadFlags() & + 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()); + + // 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 (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.loadStateLoadFlags()); + 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().refOr(nullptr), 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"); + + ContentBlocking::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(); +} + +void ContentChild::OnChannelReceivedMessage(const Message& aMsg) { + if (aMsg.is_sync() && !aMsg.is_reply()) { + LSObject::OnSyncMessageReceived(); + } + +#ifdef NIGHTLY_BUILD + if (nsContentUtils::IsMessageInputEvent(aMsg)) { + mPendingInputEvents++; + } +#endif +} + +#ifdef NIGHTLY_BUILD +PContentChild::Result ContentChild::OnMessageReceived(const Message& aMsg) { + if (nsContentUtils::IsMessageInputEvent(aMsg)) { + DebugOnly<uint32_t> prevEvts = mPendingInputEvents--; + MOZ_ASSERT(prevEvts > 0); + } + + return PContentChild::OnMessageReceived(aMsg); +} +#endif + +PContentChild::Result ContentChild::OnMessageReceived(const Message& aMsg, + Message*& aReply) { + Result result = PContentChild::OnMessageReceived(aMsg, aReply); + + if (aMsg.is_sync()) { + // OnMessageReceived shouldn't be called for sync replies. + MOZ_ASSERT(!aMsg.is_reply()); + + LSObject::OnSyncMessageHandled(); + } + + return result; +} + +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); + BrowsingContext::CreateFromIPC(std::move(aInit), group, nullptr); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvDiscardBrowsingContext( + const MaybeDiscarded<BrowsingContext>& aContext, + DiscardBrowsingContextResolver&& aResolve) { + if (!aContext.IsNullOrDiscarded()) { + aContext.get()->Detach(/* aFromIPC */ true); + } + + // 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::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(); + } + + 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(); + } + nsGlobalWindowOuter::Cast(window)->FocusOuter(aCallerType, aActionId); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvWindowBlur( + 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(); + } + nsGlobalWindowOuter::Cast(window)->BlurOuter(); + 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(); + } + + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + fm->RaiseWindow(window, aCallerType, aActionId); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvAdjustWindowFocus( + const MaybeDiscarded<BrowsingContext>& aContext, bool aCheckPermission, + bool aIsVisible) { + 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->AdjustWindowFocus(aContext.get(), aCheckPermission, aIsVisible); + } + 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(); + } + + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + fm->ClearFocus(window); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvSetFocusedBrowsingContext( + 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(); + } + + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + fm->SetFocusedBrowsingContextFromOtherProcess(aContext.get()); + } + 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()); + } + 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()); + } + 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(); + } + + BrowsingContext* toClear = aBrowsingContextToClear.IsDiscarded() + ? nullptr + : aBrowsingContextToClear.get(); + BrowsingContext* toFocus = aAncestorBrowsingContextToFocus.IsDiscarded() + ? nullptr + : aAncestorBrowsingContextToFocus.get(); + + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + fm->BlurFromOtherProcess(aFocusedBrowsingContext.get(), toClear, toFocus, + aIsLeavingDocument, aAdjustWidget, aActionId); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvSetupFocusedAndActive( + const MaybeDiscarded<BrowsingContext>& aFocusedBrowsingContext, + const MaybeDiscarded<BrowsingContext>& aActiveBrowsingContext) { + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + if (!aActiveBrowsingContext.IsNullOrDiscarded()) { + fm->SetActiveBrowsingContextFromOtherProcess( + aActiveBrowsingContext.get()); + } + if (!aFocusedBrowsingContext.IsNullOrDiscarded()) { + fm->SetFocusedBrowsingContextFromOtherProcess( + aFocusedBrowsingContext.get()); + } + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentChild::RecvReviseActiveBrowsingContext( + const MaybeDiscarded<BrowsingContext>& aActiveBrowsingContext, + uint64_t aActionId) { + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm && !aActiveBrowsingContext.IsNullOrDiscarded()) { + fm->ReviseActiveBrowsingContext(aActiveBrowsingContext.get(), aActionId); + } + 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(); + } + + // 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( + uint64_t innerWindowId, const nsString& entryName, + const nsString& initiatorType, UniquePtr<PerformanceTimingData>&& aData) { + auto* innerWindow = nsGlobalWindowInner::GetInnerWindowWithId(innerWindowId); + if (!innerWindow) { + return IPC_OK(); + } + + mozilla::dom::Performance* performance = innerWindow->GetPerformance(); + if (!performance) { + return IPC_OK(); + } + + performance->AsPerformanceStorage()->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(); + } + 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(); + } + 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(); + } + 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::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(nsIContentViewer::eAllowNavigation); + } else { + DispatchBeforeUnloadToSubtree(aStartingAt.get(), std::move(aResolver)); + } + return IPC_OK(); +} + +/* static */ void ContentChild::DispatchBeforeUnloadToSubtree( + BrowsingContext* aStartingAt, + const DispatchBeforeUnloadToSubtreeResolver& aResolver) { + bool resolved = false; + + aStartingAt->PreOrderWalk([&](dom::BrowsingContext* aBC) { + if (aBC->GetDocShell()) { + nsCOMPtr<nsIContentViewer> contentViewer; + aBC->GetDocShell()->GetContentViewer(getter_AddRefs(contentViewer)); + if (contentViewer && + contentViewer->DispatchBeforeUnload() == + nsIContentViewer::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(nsIContentViewer::eRequestBlockNavigation); + resolved = true; + } + } + }); + + if (!resolved) { + aResolver(nsIContentViewer::eAllowNavigation); + } +} + +mozilla::ipc::IPCResult ContentChild::RecvGoBack( + const MaybeDiscarded<BrowsingContext>& aContext, + const Maybe<int32_t>& aCancelContentJSEpoch, bool aRequireUserInteraction) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + BrowsingContext* bc = aContext.get(); + + if (auto* docShell = nsDocShell::Cast(bc->GetDocShell())) { + if (aCancelContentJSEpoch) { + docShell->SetCancelContentJSEpoch(*aCancelContentJSEpoch); + } + docShell->GoBack(aRequireUserInteraction); + + 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) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + BrowsingContext* bc = aContext.get(); + + if (auto* docShell = nsDocShell::Cast(bc->GetDocShell())) { + if (aCancelContentJSEpoch) { + docShell->SetCancelContentJSEpoch(*aCancelContentJSEpoch); + } + docShell->GoForward(aRequireUserInteraction); + + 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) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + BrowsingContext* bc = aContext.get(); + + if (auto* docShell = nsDocShell::Cast(bc->GetDocShell())) { + if (aCancelContentJSEpoch) { + docShell->SetCancelContentJSEpoch(*aCancelContentJSEpoch); + } + docShell->GotoIndex(aIndex); + + 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 (auto* 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; +} + +already_AddRefed<JSActor> ContentChild::InitJSActor( + JS::HandleObject 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->BorrowFromClonedMessageDataForChild(*aData); + } + Maybe<StructuredCloneData> stack; + if (aStack) { + stack.emplace(); + stack->BorrowFromClonedMessageDataForChild(*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) { +#ifdef MOZ_GLEAN + glean::FlushFOGData(std::move(aResolver)); +#endif + return IPC_OK(); +} + +IPCResult ContentChild::RecvUpdateMediaCodecsSupported( + RemoteDecodeIn aLocation, + const PDMFactory::MediaCodecsSupported& aSupported) { + RemoteDecoderManagerChild::SetSupported(aLocation, aSupported); + + return IPC_OK(); +} + +} // 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_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 { + if (unveil(PromiseFlatCString(pledgePath).get(), "r") == -1) { + err(1, "unveil(%s, r) failed", PromiseFlatCString(pledgePath).get()); + } + } + + return NS_OK; +} + +bool StartOpenBSDSandbox(GeckoProcessType type) { + nsAutoCString pledgeFile; + nsAutoCString unveilFile; + + 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_CACHE_HOME/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; + + default: + MOZ_ASSERT(false, "unknown process type"); + return false; + } + + 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 + +#if !defined(XP_WIN) +bool IsDevelopmentBuild() { + nsCOMPtr<nsIFile> path = mozilla::Omnijar::GetPath(mozilla::Omnijar::GRE); + // If the path doesn't exist, we're a dev build. + return path == nullptr; +} +#endif /* !XP_WIN */ + +} // 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..a179bf7147 --- /dev/null +++ b/dom/ipc/ContentChild.h @@ -0,0 +1,945 @@ +/* -*- 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_ContentChild_h +#define mozilla_dom_ContentChild_h + +#include "mozilla/Atomics.h" +#include "mozilla/dom/BlobImpl.h" +#include "mozilla/dom/GetFilesHelper.h" +#include "mozilla/dom/PContentChild.h" +#include "mozilla/dom/ProcessActor.h" +#include "mozilla/dom/RemoteType.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 "nsTHashtable.h" + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +# include "nsIFile.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 PChildToParentStreamChild; +class PFileDescriptorSetChild; +} // namespace ipc + +namespace loader { +class PScriptCacheChild; +} + +namespace widget { +enum class ThemeChangeKind : uint8_t; +} + +using mozilla::loader::PScriptCacheChild; + +#if !defined(XP_WIN) +// Returns whether or not the currently running build is an unpackaged +// developer build. This check is implemented by looking for omni.ja in the +// the obj/dist dir. We use this routine to detect when the build dir will +// use symlinks to the repo and object dir. On Windows, dev builds don't +// use symlinks. +bool IsDevelopmentBuild(); +#endif /* !XP_WIN */ + +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 mozilla::ipc::ChildToParentStreamActorManager, + public ProcessActor { + typedef mozilla::dom::ClonedMessageData ClonedMessageData; + typedef mozilla::ipc::FileDescriptor FileDescriptor; + typedef mozilla::ipc::PFileDescriptorSetChild PFileDescriptorSetChild; + + 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( + BrowserChild* aTabOpener, nsIOpenWindowInfo* aOpenWindowInfo, + uint32_t aChromeFlags, bool aCalledFromJS, bool aWidthSpecified, + nsIURI* aURI, const nsAString& aName, const nsACString& aFeatures, + bool aForceNoOpener, bool aForceNoReferrer, + nsDocShellLoadState* aLoadState, bool* aWindowIsNew, + BrowsingContext** aReturn); + + bool Init(MessageLoop* aIOLoop, base::ProcessId aParentPid, + const char* aParentBuildID, UniquePtr<IPC::Channel> aChannel, + uint64_t aChildID, bool aIsForBrowser); + + void InitXPCOM(XPCOMInitData&& aXPCOMInit, + const mozilla::dom::ipc::StructuredCloneData& aInitialData); + + void InitSharedUASheets(const 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); + + 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 void 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 RecvRequestPerformanceMetrics(const nsID& aID); + + mozilla::ipc::IPCResult RecvReinitRendering( + Endpoint<PCompositorManagerChild>&& aCompositor, + Endpoint<PImageBridgeChild>&& aImageBridge, + Endpoint<PVRManagerChild>&& aVRBridge, + Endpoint<PRemoteDecoderManagerChild>&& aVideoManager, + nsTArray<uint32_t>&& namespaces); + + mozilla::ipc::IPCResult RecvAudioDefaultDeviceChange(); + + mozilla::ipc::IPCResult RecvReinitRenderingForDeviceReset(); + + mozilla::ipc::IPCResult RecvSetProcessSandbox( + const Maybe<FileDescriptor>& aBroker); + + already_AddRefed<PRemoteLazyInputStreamChild> + AllocPRemoteLazyInputStreamChild(const nsID& aID, const uint64_t& aSize); + + 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; + + PWebBrowserPersistDocumentChild* AllocPWebBrowserPersistDocumentChild( + PBrowserChild* aBrowser, const MaybeDiscarded<BrowsingContext>& aContext); + + virtual mozilla::ipc::IPCResult RecvPWebBrowserPersistDocumentConstructor( + PWebBrowserPersistDocumentChild* aActor, PBrowserChild* aBrowser, + const MaybeDiscarded<BrowsingContext>& aContext) override; + + bool DeallocPWebBrowserPersistDocumentChild( + PWebBrowserPersistDocumentChild* aActor); + + PTestShellChild* AllocPTestShellChild(); + + bool DeallocPTestShellChild(PTestShellChild*); + + 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; + + PNeckoChild* AllocPNeckoChild(); + + bool DeallocPNeckoChild(PNeckoChild*); + + PPrintingChild* AllocPPrintingChild(); + + bool DeallocPPrintingChild(PPrintingChild*); + + PChildToParentStreamChild* AllocPChildToParentStreamChild(); + bool DeallocPChildToParentStreamChild(PChildToParentStreamChild*); + + PParentToChildStreamChild* AllocPParentToChildStreamChild(); + bool DeallocPParentToChildStreamChild(PParentToChildStreamChild*); + + PMediaChild* AllocPMediaChild(); + + bool DeallocPMediaChild(PMediaChild* aActor); + + PBenchmarkStorageChild* AllocPBenchmarkStorageChild(); + + bool DeallocPBenchmarkStorageChild(PBenchmarkStorageChild* aActor); + + PPresentationChild* AllocPPresentationChild(); + + bool DeallocPPresentationChild(PPresentationChild* aActor); + + mozilla::ipc::IPCResult RecvNotifyPresentationReceiverLaunched( + PBrowserChild* aIframe, const nsString& aSessionId); + + mozilla::ipc::IPCResult RecvNotifyPresentationReceiverCleanUp( + const nsString& aSessionId); + + mozilla::ipc::IPCResult RecvNotifyEmptyHTTPCache(); + +#ifdef MOZ_WEBSPEECH + PSpeechSynthesisChild* AllocPSpeechSynthesisChild(); + bool DeallocPSpeechSynthesisChild(PSpeechSynthesisChild* aActor); +#endif + + 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); + 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 RecvBidiKeyboardNotify(const bool& isLangRTL, + const bool& haveBidiKeyboards); + + mozilla::ipc::IPCResult RecvNotifyVisited(nsTArray<VisitedQueryResult>&&); + + mozilla::ipc::IPCResult RecvThemeChanged(LookAndFeelData&& aLookAndFeelData, + widget::ThemeChangeKind); + + mozilla::ipc::IPCResult RecvUpdateSystemParameters( + nsTArray<SystemParameterKVPair>&& aUpdates); + + // 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 RecvDataStoragePut(const nsString& aFilename, + const DataStorageItem& aItem); + + mozilla::ipc::IPCResult RecvDataStorageRemove(const nsString& aFilename, + const nsCString& aKey, + const DataStorageType& aType); + + mozilla::ipc::IPCResult RecvDataStorageClear(const nsString& aFilename); + + 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 RecvUpdateSharedData( + const FileDescriptor& aMapFile, const uint32_t& aMapSize, + nsTArray<IPCBlob>&& aBlobs, nsTArray<nsCString>&& aChangedKeys); + + mozilla::ipc::IPCResult RecvFontListChanged(); + + 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( + nsTArray<SystemFontListEntry>&& aFontList); + mozilla::ipc::IPCResult RecvRebuildFontList(const bool& aFullRebuild); + + mozilla::ipc::IPCResult RecvUpdateAppLocales( + nsTArray<nsCString>&& aAppLocales); + mozilla::ipc::IPCResult RecvUpdateRequestedLocales( + nsTArray<nsCString>&& aRequestedLocales); + + mozilla::ipc::IPCResult RecvAddPermission(const IPC::Permission& permission); + + mozilla::ipc::IPCResult RecvRemoveAllPermissions(); + + mozilla::ipc::IPCResult RecvFlushMemory(const nsString& reason); + + mozilla::ipc::IPCResult RecvActivateA11y(const uint32_t& aMainChromeTid, + const uint32_t& aMsaaID); + 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); + + // Call RemoteTypePrefix() on the result to remove URIs if you want to use + // this for telemetry. + const nsACString& GetRemoteType() const override; + + mozilla::ipc::IPCResult RecvInitServiceWorkers( + const ServiceWorkerConfiguration& aConfig); + + 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 RecvShutdown(); + + mozilla::ipc::IPCResult RecvInvokeDragSession( + nsTArray<IPCDataTransfer>&& aTransfers, const uint32_t& aAction); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY + mozilla::ipc::IPCResult RecvEndDragSession( + const bool& aDoneDrag, const bool& aUserCancelled, + const mozilla::LayoutDeviceIntPoint& aEndDragPoint, + const uint32_t& aKeyModifiers); + + mozilla::ipc::IPCResult RecvPush(const nsCString& aScope, + const IPC::Principal& aPrincipal, + const nsString& aMessageId); + + mozilla::ipc::IPCResult RecvPushWithData(const nsCString& aScope, + const IPC::Principal& aPrincipal, + const nsString& aMessageId, + nsTArray<uint8_t>&& aData); + + mozilla::ipc::IPCResult RecvPushSubscriptionChange( + const nsCString& aScope, const IPC::Principal& aPrincipal); + + mozilla::ipc::IPCResult RecvPushError(const nsCString& aScope, + const IPC::Principal& aPrincipal, + const nsString& aMessage, + const uint32_t& aFlags); + + mozilla::ipc::IPCResult RecvNotifyPushSubscriptionModifiedObservers( + const nsCString& aScope, const IPC::Principal& aPrincipal); + + mozilla::ipc::IPCResult RecvRefreshScreens( + nsTArray<ScreenDetails>&& aScreens); + + mozilla::ipc::IPCResult RecvNetworkLinkTypeChange(const uint32_t& aType); + uint32_t NetworkLinkType() const { return mNetworkLinkType; } + + // Get the directory for IndexedDB files. We query the parent for this and + // cache the value + nsString& GetIndexedDBPath(); + + ContentParentId GetID() const { return mID; } + +#if defined(XP_WIN) && defined(ACCESSIBILITY) + uint32_t GetChromeMainThreadId() const { return mMainChromeTid; } + + uint32_t GetMsaaID() const { return mMsaaID; } +#endif + + bool IsForBrowser() const { return mIsForBrowser; } + + PFileDescriptorSetChild* AllocPFileDescriptorSetChild(const FileDescriptor&); + + bool DeallocPFileDescriptorSetChild(PFileDescriptorSetChild*); + + 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); + + PWebrtcGlobalChild* AllocPWebrtcGlobalChild(); + + bool DeallocPWebrtcGlobalChild(PWebrtcGlobalChild* aActor); + + PContentPermissionRequestChild* AllocPContentPermissionRequestChild( + const nsTArray<PermissionRequest>& aRequests, + const IPC::Principal& aPrincipal, + const IPC::Principal& 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, + const IPC::Principal& aPrincipal, const Maybe<nsID>& aAgentClusterId); + + 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); + + mozilla::ipc::IPCResult RecvGetUntrustedModulesData( + GetUntrustedModulesDataResolver&& aResolver); + + mozilla::ipc::IPCResult RecvSetXPCOMProcessAttributes( + XPCOMInitData&& aXPCOMInit, const StructuredCloneData& aInitialData, + LookAndFeelData&& aLookAndFeelData, + nsTArray<SystemFontListEntry>&& aFontList, + const Maybe<base::SharedMemoryHandle>& aSharedUASheetHandle, + const uintptr_t& aSharedUASheetAddress, + nsTArray<base::SharedMemoryHandle>&& aSharedFontListBlocks); + + 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( + const CrossProcessMutexHandle& aHandle); + + mozilla::ipc::IPCResult RecvFlushCodeCoverageCounters( + FlushCodeCoverageCountersResolver&& aResolver); + + mozilla::ipc::IPCResult RecvGetMemoryUniqueSetSize( + GetMemoryUniqueSetSizeResolver&& aResolver); + + mozilla::ipc::IPCResult RecvSetInputEventQueueEnabled(); + + mozilla::ipc::IPCResult RecvFlushInputEventQueue(); + + mozilla::ipc::IPCResult RecvSuspendInputEventQueue(); + + mozilla::ipc::IPCResult RecvResumeInputEventQueue(); + + mozilla::ipc::IPCResult RecvAddDynamicScalars( + nsTArray<DynamicScalarDefinition>&& aDefs); + +#if defined(XP_WIN) && defined(ACCESSIBILITY) + bool SendGetA11yContentId(); +#endif // defined(XP_WIN) && defined(ACCESSIBILITY) + + // Get a reference to the font list passed from the chrome process, + // for use during gfx initialization. + nsTArray<mozilla::dom::SystemFontListEntry>& SystemFontList() { + return mFontList; + } + + nsTArray<base::SharedMemoryHandle>& SharedFontListBlocks() { + return mSharedFontListBlocks; + } + + // PURLClassifierChild + PURLClassifierChild* AllocPURLClassifierChild(const Principal& aPrincipal, + bool* aSuccess); + bool DeallocPURLClassifierChild(PURLClassifierChild* aActor); + + // PURLClassifierLocalChild + PURLClassifierLocalChild* AllocPURLClassifierLocalChild( + nsIURI* aUri, const nsTArray<IPCURLClassifierFeature>& aFeatures); + bool DeallocPURLClassifierLocalChild(PURLClassifierLocalChild* aActor); + + PLoginReputationChild* AllocPLoginReputationChild(nsIURI* aUri); + + bool DeallocPLoginReputationChild(PLoginReputationChild* aActor); + + PSessionStorageObserverChild* AllocPSessionStorageObserverChild(); + + bool DeallocPSessionStorageObserverChild( + PSessionStorageObserverChild* aActor); + + LookAndFeelData& 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); + + typedef std::function<void(PRFileDesc*)> AnonymousTemporaryFileCallback; + nsresult AsyncOpenAnonymousTemporaryFile( + const AnonymousTemporaryFileCallback& aCallback); + + mozilla::ipc::IPCResult RecvSetPluginList( + const uint32_t& aPluginEpoch, nsTArray<PluginTag>&& aPluginTags, + nsTArray<FakePluginTag>&& aFakePluginTags); + + 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); + + void HoldBrowsingContextGroup(BrowsingContextGroup* aBCG); + void ReleaseBrowsingContextGroup(BrowsingContextGroup* aBCG); + + // 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 + + PChildToParentStreamChild* SendPChildToParentStreamConstructor( + PChildToParentStreamChild* aActor) override; + PFileDescriptorSetChild* SendPFileDescriptorSetConstructor( + const FileDescriptor& aFD) override; + + 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; + + virtual void OnChannelReceivedMessage(const Message& aMsg) override; + + mozilla::ipc::IPCResult RecvCreateBrowsingContext( + uint64_t aGroupId, BrowsingContext::IPCInitializer&& aInit); + + mozilla::ipc::IPCResult RecvDiscardBrowsingContext( + const MaybeDiscarded<BrowsingContext>& aContext, + DiscardBrowsingContextResolver&& aResolve); + + mozilla::ipc::IPCResult RecvRegisterBrowsingContextGroup( + uint64_t aGroupId, nsTArray<SyncedContextInitializer>&& aInits); + + 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); + mozilla::ipc::IPCResult RecvRaiseWindow( + const MaybeDiscarded<BrowsingContext>& aContext, CallerType aCallerType, + uint64_t aActionId); + mozilla::ipc::IPCResult RecvAdjustWindowFocus( + const MaybeDiscarded<BrowsingContext>& aContext, bool aCheckPermission, + bool aIsVisible); + mozilla::ipc::IPCResult RecvClearFocus( + const MaybeDiscarded<BrowsingContext>& aContext); + mozilla::ipc::IPCResult RecvSetFocusedBrowsingContext( + const MaybeDiscarded<BrowsingContext>& aContext); + 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); + 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, + const MaybeDiscarded<BrowsingContext>& aActiveBrowsingContext); + mozilla::ipc::IPCResult RecvReviseActiveBrowsingContext( + const MaybeDiscarded<BrowsingContext>& aActiveBrowsingContext, + 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); + + 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( + uint64_t innerWindowId, 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); + mozilla::ipc::IPCResult RecvGoForward( + const MaybeDiscarded<BrowsingContext>& aContext, + const Maybe<int32_t>& aCancelContentJSEpoch, + bool aRequireUserInteraction); + mozilla::ipc::IPCResult RecvGoToIndex( + const MaybeDiscarded<BrowsingContext>& aContext, const int32_t& aIndex, + const Maybe<int32_t>& aCancelContentJSEpoch); + 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::HandleObject 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 RecvDispatchLocationChangeEvent( + const MaybeDiscarded<BrowsingContext>& aContext); + + mozilla::ipc::IPCResult RecvDispatchBeforeUnloadToSubtree( + const MaybeDiscarded<BrowsingContext>& aStartingAt, + DispatchBeforeUnloadToSubtreeResolver&& aResolver); + + public: + static void DispatchBeforeUnloadToSubtree( + BrowsingContext* aStartingAt, + const DispatchBeforeUnloadToSubtreeResolver& aResolver); + + private: + mozilla::ipc::IPCResult RecvFlushFOGData(FlushFOGDataResolver&& aResolver); + + mozilla::ipc::IPCResult RecvUpdateMediaCodecsSupported( + RemoteDecodeIn aLocation, + const PDMFactory::MediaCodecsSupported& aSupported); + +#ifdef NIGHTLY_BUILD + virtual PContentChild::Result OnMessageReceived(const Message& aMsg) override; +#else + using PContentChild::OnMessageReceived; +#endif + + virtual PContentChild::Result OnMessageReceived(const Message& aMsg, + Message*& aReply) override; + + nsTArray<mozilla::UniquePtr<AlertObserver>> mAlertObservers; + RefPtr<ConsoleListener> mConsoleListener; + + nsTHashtable<nsPtrHashKey<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. + nsTArray<mozilla::dom::SystemFontListEntry> mFontList; + // Temporary storage for look and feel data. + LookAndFeelData 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; + +#if defined(XP_WIN) && defined(ACCESSIBILITY) + /** + * The thread ID of the main thread in the chrome process. + */ + uint32_t mMainChromeTid; + + /** + * This is an a11y-specific unique id for the content process that is + * generated by the chrome process. + */ + uint32_t mMsaaID; +#endif // defined(XP_WIN) && defined(ACCESSIBILITY) + + 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; + +#ifdef MOZ_GECKO_PROFILER + RefPtr<ChildProfilerController> mProfilerController; +#endif + +#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; +}; + +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..8e88f743d1 --- /dev/null +++ b/dom/ipc/ContentParent.cpp @@ -0,0 +1,7516 @@ +/* -*- 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/DebugOnly.h" + +#include "base/basictypes.h" +#include "base/shared_memory.h" + +#include "ContentParent.h" +#include "ProcessUtils.h" +#include "BrowserParent.h" + +#include "chrome/common/process_watcher.h" +#include "mozilla/Result.h" +#include "mozilla/XREAppData.h" +#include "nsComponentManagerUtils.h" +#include "nsIBrowserDOMWindow.h" + +#ifdef ACCESSIBILITY +# include "mozilla/a11y/PDocAccessible.h" +#endif +#include "GeckoProfiler.h" +#include "GMPServiceParent.h" +#include "HandlerServiceParent.h" +#include "IHistory.h" +#if defined(XP_WIN) && defined(ACCESSIBILITY) +# include "mozilla/a11y/AccessibleWrap.h" +# include "mozilla/a11y/Compatibility.h" +#endif +#include <map> +#include <utility> + +#include "BrowserParent.h" +#include "ContentProcessManager.h" +#include "Geolocation.h" +#include "GfxInfoBase.h" +#include "MMPrinter.h" +#include "PreallocatedProcessManager.h" +#include "ProcessPriorityManager.h" +#include "SandboxHal.h" +#include "SourceSurfaceRawData.h" +#include "URIUtils.h" +#include "gfxPlatform.h" +#include "gfxPlatformFontList.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/ContentBlocking.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/BenchmarkStorageParent.h" +#include "mozilla/ContentBlockingUserInteraction.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Components.h" +#include "mozilla/DataStorage.h" +#ifdef MOZ_GLEAN +# include "mozilla/FOGIPC.h" +#endif +#include "mozilla/GlobalStyleSheetCache.h" +#include "mozilla/HangDetails.h" +#include "mozilla/LoginReputationIPC.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/PerformanceMetricsCollector.h" +#include "mozilla/Preferences.h" +#include "mozilla/PresShell.h" +#include "mozilla/ProcessHangMonitor.h" +#include "mozilla/ProcessHangMonitorIPC.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/ScriptPreloader.h" +#include "mozilla/Services.h" +#include "mozilla/Sprintf.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/StaticPrefs_media.h" +#include "mozilla/StaticPrefs_widget.h" +#include "mozilla/StyleSheet.h" +#include "mozilla/StyleSheetInlines.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TelemetryIPC.h" +#include "mozilla/Unused.h" +#include "mozilla/WebBrowserPersistDocumentParent.h" +#include "mozilla/devtools/HeapSnapshotTempFileHelperParent.h" +#include "mozilla/docshell/OfflineCacheUpdateParent.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/PPresentationParent.h" +#include "mozilla/dom/ParentProcessMessageManager.h" +#include "mozilla/dom/Permissions.h" +#include "mozilla/dom/PresentationParent.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/URLClassifierParent.h" +#include "mozilla/dom/WakeLock.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/embedding/printingui/PrintingParent.h" +#include "mozilla/extensions/StreamFilterParent.h" +#include "mozilla/gfx/GPUProcessManager.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/hal_sandbox/PHalParent.h" +#include "mozilla/intl/LocaleService.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/ipc/CrashReporterHost.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/FileDescriptorSetParent.h" +#include "mozilla/ipc/FileDescriptorUtils.h" +#include "mozilla/ipc/IPCStreamAlloc.h" +#include "mozilla/ipc/IPCStreamDestination.h" +#include "mozilla/ipc/IPCStreamSource.h" +#include "mozilla/ipc/IPCStreamUtils.h" +#include "mozilla/ipc/PChildToParentStreamParent.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/plugins/PluginBridge.h" +#include "mozilla/RemoteLazyInputStreamParent.h" +#include "mozilla/widget/RemoteLookAndFeel.h" +#include "mozilla/widget/ScreenManager.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 "nsDebugImpl.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDocShell.h" +#include "nsEmbedCID.h" +#include "nsFocusManager.h" +#include "nsFrameLoader.h" +#include "nsFrameMessageManager.h" +#include "nsHashPropertyBag.h" +#include "nsHyphenationManager.h" +#include "nsIAlertsService.h" +#include "nsIAppStartup.h" +#include "nsIAppWindow.h" +#include "nsIAsyncInputStream.h" +#include "nsIBidiKeyboard.h" +#include "nsICaptivePortalService.h" +#include "nsICertOverrideService.h" +#include "nsIClipboard.h" +#include "nsIContentProcess.h" +#include "nsIContentSecurityPolicy.h" +#include "nsICookie.h" +#include "nsICrashService.h" +#include "nsICycleCollectorListener.h" +#include "nsIDOMChromeWindow.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 "nsISound.h" +#include "nsIStringBundle.h" +#include "nsITimer.h" +#include "nsIURL.h" +#include "nsIWebBrowserChrome.h" +#include "nsIX509Cert.h" +#include "nsIXULRuntime.h" +#include "nsMemoryInfoDumper.h" +#include "nsMemoryReporterManager.h" +#include "nsOpenURIInFrameParams.h" +#include "nsPIWindowWatcher.h" +#include "nsPluginHost.h" +#include "nsPluginTags.h" +#include "nsQueryObject.h" +#include "nsReadableUtils.h" +#include "nsSHistory.h" +#include "nsScriptError.h" +#include "nsSerializationHelper.h" +#include "nsServiceManagerUtils.h" +#include "nsStreamUtils.h" +#include "nsStyleSheetService.h" +#include "nsThread.h" +#include "nsThreadUtils.h" +#include "nsWidgetsCID.h" +#include "nsWindowWatcher.h" +#include "prio.h" +#include "private/pprio.h" +#include "xpcpublic.h" +#include "nsOpenWindowInfo.h" + +#ifdef MOZ_WEBRTC +# include "jsapi/WebrtcGlobalParent.h" +#endif + +#if defined(XP_MACOSX) +# include "nsMacUtilsImpl.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> +#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/audio/AudioNotificationSender.h" +# include "mozilla/widget/AudioSession.h" +# include "mozilla/widget/WinContentSystemParameters.h" +# include "mozilla/WinDllServices.h" +#endif + +#ifdef ACCESSIBILITY +# include "nsAccessibilityService.h" +#endif + +#ifdef MOZ_GECKO_PROFILER +# include "nsIProfiler.h" +# include "ProfilerParent.h" +#endif + +#ifdef MOZ_CODE_COVERAGE +# include "mozilla/CodeCoverageHandler.h" +#endif + +// For VP9Benchmark::sBenchmarkFpsPref +#include "Benchmark.h" + +// XXX need another bug to move this to a common header. +#ifdef DISABLE_ASSERTS_FOR_FUZZING +# define ASSERT_UNLESS_FUZZING(...) \ + do { \ + } while (0) +#else +# define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false, __VA_ARGS__) +#endif + +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 mozilla::loader::PScriptCacheParent; +using mozilla::Telemetry::ProcessID; + +extern mozilla::LazyLogModule gFocusLog; + +#define LOGFOCUS(args) MOZ_LOG(gFocusLog, mozilla::LogLevel::Debug, args) + +// XXX Workaround for bug 986973 to maintain the existing broken semantics +template <> +struct nsIConsoleService::COMTypeInfo<nsConsoleService, void> { + static const nsIID kIID; +}; +const nsIID nsIConsoleService::COMTypeInfo<nsConsoleService, void>::kIID = + NS_ICONSOLESERVICE_IID; + +namespace mozilla { +namespace CubebUtils { +extern FileDescriptor CreateAudioIPCConnection(); +} + +namespace dom { + +LazyLogModule gProcessLog("Process"); + +static std::map<RemoteDecodeIn, PDMFactory::MediaCodecsSupported> + sCodecsSupported; + +/* static */ +LogModule* ContentParent::GetLog() { return gProcessLog; } + +#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->Unsound_IsClosed()) { + channelStr = "closed channel"; + } else { + channelStr = "open channel"; + } + numQueuedMessages = channel->Unsound_NumQueuedMessages(); + } + + 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 + +UniquePtr<nsDataHashtable<nsUint32HashKey, ContentParent*>> + ContentParent::sJSPluginContentParents; +UniquePtr<nsTArray<ContentParent*>> ContentParent::sPrivateContent; +UniquePtr<LinkedList<ContentParent>> ContentParent::sContentParents; +StaticRefPtr<ContentParent> ContentParent::sRecycledE10SProcess; +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) +UniquePtr<SandboxBrokerPolicyFactory> + ContentParent::sSandboxBrokerPolicyFactory; +#endif +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) +UniquePtr<std::vector<std::string>> ContentParent::sMacSandboxParams; +#endif + +// Whether a private docshell has been seen before. +static bool sHasSeenPrivateDocShell = false; + +// This is true when subprocess launching is enabled. This is the +// case between StartUp() and ShutDown(). +static bool sCanLaunchSubprocesses; + +// 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, +}; + +// PreallocateProcess is called by the PreallocatedProcessManager. +// ContentParent then takes this process back within GetNewOrUsedBrowserProcess. +/*static*/ RefPtr<ContentParent::LaunchPromise> +ContentParent::PreallocateProcess() { + RefPtr<ContentParent> process = new ContentParent(PREALLOC_REMOTE_TYPE); + + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("Preallocating process of type prealloc")); + return process->LaunchSubprocessAsync(PROCESS_PRIORITY_PREALLOC); +} + +/*static*/ +void ContentParent::StartUp() { + // We could launch sub processes from content process + // FIXME Bug 1023701 - Stop using ContentParent static methods in + // child process + sCanLaunchSubprocesses = true; + + 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(); + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + sSandboxBrokerPolicyFactory = MakeUnique<SandboxBrokerPolicyFactory>(); +#endif + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + sMacSandboxParams = MakeUnique<std::vector<std::string>>(); +#endif +} + +/*static*/ +void ContentParent::ShutDown() { + // No-op for now. We rely on normal process shutdown and + // ClearOnShutdown() to clean up our state. + sCanLaunchSubprocesses = false; + +#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->LookupOrAdd(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 as well as web (and webLargeAllocation, and + // webCOOP+COEP) + return StringBeginsWith(aContentProcessType, DEFAULT_REMOTE_TYPE); +} + +bool IsWebCoopCoepRemoteType(const nsACString& aContentProcessType) { + return StringBeginsWith(aContentProcessType, + WITH_COOP_COEP_REMOTE_TYPE_PREFIX); +} + +bool IsPrivilegedMozillaRemoteType(const nsACString& aContentProcessType) { + return aContentProcessType == PRIVILEGEDMOZILLA_REMOTE_TYPE; +} + +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 + int num = 0; + for (auto iter = sBrowserContentParents->Iter(); !iter.Done(); iter.Next()) { + nsTArray<ContentParent*>* contentParents = iter.Data().get(); + num += contentParents->Length(); + for (auto* cp : *contentParents) { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("%s: %zu processes", cp->mRemoteType.get(), + contentParents->Length())); + break; + } + } +#endif + // We process the toRelease array outside of the iteration to avoid modifying + // the list (via RemoveFromList()) while we're iterating it. + nsTArray<ContentParent*> toRelease; + for (auto iter = sBrowserContentParents->Iter(); !iter.Done(); iter.Next()) { + nsTArray<ContentParent*>* contentParents = iter.Data().get(); + + // Shutting down these processes will change the array so let's use another + // array for the removal. + for (auto* cp : *contentParents) { + if (cp->ManagedPBrowserParent().Count() == 0 && + !cp->HasActiveWorkerOrJSPlugin() && + cp->mRemoteType == DEFAULT_REMOTE_TYPE) { + toRelease.AppendElement(cp); + } else { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + (" Skipping %p (%s), count %d, HasActiveWorkerOrJSPlugin %d", + cp, cp->mRemoteType.get(), cp->ManagedPBrowserParent().Count(), + cp->HasActiveWorkerOrJSPlugin())); + } + } + } + + for (auto* cp : toRelease) { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + (" Shutdown %p (%s)", cp, cp->mRemoteType.get())); + PreallocatedProcessManager::Erase(cp); + // Start a soft shutdown. + cp->ShutDownProcess(SEND_SHUTDOWN_MESSAGE); + // Make sure we don't select this process for new tabs. + cp->MarkAsDead(); + // Make sure that this process is no longer accessible from JS by its + // message manager. + cp->ShutDownMessageManager(); + } +} + +/*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()); + + 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> 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)); + + return BasePrincipal::CreateContentPrincipal(origin); +} + +/*static*/ +already_AddRefed<ContentParent> ContentParent::GetUsedBrowserProcess( + const nsACString& aRemoteType, nsTArray<ContentParent*>& aContentParents, + uint32_t aMaxContentParents, bool aPreferUsed) { +#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) { + // For the preloaded browser we don't want to create a new process but + // reuse an existing one. + 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]; +#ifdef MOZ_GECKO_PROFILER + if (profiler_thread_is_being_profiled()) { + nsPrintfCString marker("Reused process %u", + (unsigned int)retval->ChildID()); + PROFILER_MARKER_TEXT("Process", DOM, {}, marker); + } +#endif + 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->StopRecycling(); + 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->StopRecycling(); + 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->StopRecycling(); + +#ifdef MOZ_GECKO_PROFILER + if (profiler_thread_is_being_profiled()) { + nsPrintfCString marker("Recycled process %u (%p)", + (unsigned int)recycled->ChildID(), recycled.get()); + PROFILER_MARKER_TEXT("Process", DOM, {}, marker); + } +#endif + 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. + RefPtr<ContentParent> preallocated; + if (aRemoteType != FILE_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(); + +#ifdef MOZ_GECKO_PROFILER + if (profiler_thread_is_being_profiled()) { + nsPrintfCString marker("Assigned preallocated process %u", + (unsigned int)preallocated->ChildID()); + PROFILER_MARKER_TEXT("Process", DOM, {}, marker); + } +#endif + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("Adopted preallocated process %p for type %s", preallocated.get(), + PromiseFlatCString(aRemoteType).get())); + + // Specialize this process for the appropriate remote type, and activate it. + preallocated->mActivateTS = TimeStamp::Now(); + preallocated->AddToPool(aContentParents); + + preallocated->mRemoteType.Assign(aRemoteType); + preallocated->mRemoteTypeIsolationPrincipal = + CreateRemoteTypeIsolationPrincipal(aRemoteType); + Unused << preallocated->SendRemoteType(preallocated->mRemoteType); + + 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())); + + // 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); + if (contentParent) { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("GetNewOrUsedProcess: Existing host process %p (launching %d)", + contentParent.get(), contentParent->IsLaunching())); + contentParent->AssertAlive(); + contentParent->StopRecycling(); + return contentParent.forget(); + } + } + + nsTArray<ContentParent*>& contentParents = GetOrCreatePool(aRemoteType); + uint32_t maxContentParents = GetMaxProcessCount(aRemoteType); + // We never want to re-use Large-Allocation processes. + if (aRemoteType == LARGE_ALLOCATION_REMOTE_TYPE && + contentParents.Length() >= maxContentParents) { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("GetNewOrUsedProcess: returning Large Used process")); + return GetNewOrUsedLaunchingBrowserProcess(DEFAULT_REMOTE_TYPE, aGroup, + aPriority, + /*aPreferUsed =*/false); + } + + // Let's try and reuse an existing process. + contentParent = GetUsedBrowserProcess(aRemoteType, contentParents, + maxContentParents, aPreferUsed); + + if (contentParent) { + // We have located a process. It may not have finished initializing, + // this will be for the caller to handle. + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("GetNewOrUsedProcess: Used process %p (launching %d)", + contentParent.get(), contentParent->IsLaunching())); + contentParent->AssertAlive(); + contentParent->StopRecycling(); + if (aGroup) { + aGroup->EnsureHostProcess(contentParent); + } + return contentParent.forget(); + } + + // 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 (!contentParent->BeginSubprocessLaunch(aPriority)) { + // Launch aborted because of shutdown. Bailout. + contentParent->LaunchSubprocessReject(); + return nullptr; + } + // Store this process for future reuse. + contentParent->AddToPool(contentParents); + + // 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); + + MOZ_ASSERT(contentParent->IsLaunching()); + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("GetNewOrUsedProcess: new process %p", contentParent.get())); + contentParent->AssertAlive(); + contentParent->StopRecycling(); + 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(LaunchError(), __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()) { + 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)) { + self->mActivateTS = TimeStamp::Now(); + return LaunchPromise::CreateAndResolve(self, __func__); + } + + self->LaunchSubprocessReject(); + return LaunchPromise::CreateAndReject(LaunchError(), __func__); + }, + [self = RefPtr{this}] { + self->LaunchSubprocessReject(); + return LaunchPromise::CreateAndReject(LaunchError(), __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*/ +already_AddRefed<ContentParent> ContentParent::GetNewOrUsedJSPluginProcess( + uint32_t aPluginID, const hal::ProcessPriority& aPriority) { + RefPtr<ContentParent> p; + if (sJSPluginContentParents) { + p = sJSPluginContentParents->Get(aPluginID); + } else { + sJSPluginContentParents = + MakeUnique<nsDataHashtable<nsUint32HashKey, ContentParent*>>(); + } + + if (p) { + return p.forget(); + } + + p = new ContentParent(aPluginID); + + if (!p->LaunchSubprocessSync(aPriority)) { + return nullptr; + } + + sJSPluginContentParents->Put(aPluginID, p); + + return p.forget(); +} + +#if defined(XP_WIN) +extern const wchar_t* kPluginWidgetContentParentProperty; + +/*static*/ +void ContentParent::SendAsyncUpdate(nsIWidget* aWidget) { + if (!aWidget || aWidget->Destroyed()) { + return; + } + // Fire off an async request to the plugin to paint its window + HWND hwnd = (HWND)aWidget->GetNativeData(NS_NATIVE_WINDOW); + NS_ASSERTION(hwnd, "Expected valid hwnd value."); + ContentParent* cp = reinterpret_cast<ContentParent*>( + ::GetPropW(hwnd, kPluginWidgetContentParentProperty)); + if (cp && cp->CanSend()) { + Unused << cp->SendUpdateWindow((uintptr_t)hwnd); + } +} +#endif // defined(XP_WIN) + +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; + + nsresult rv; + rv = PGMPService::CreateEndpoints(base::GetCurrentProcId(), OtherPid(), + &parent, &child); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, "CreateEndpoints failed"); + return IPC_FAIL_NO_REASON(this); + } + + if (!GMPServiceParent::Create(std::move(parent))) { + MOZ_ASSERT(false, "GMPServiceParent::Create failed"); + return IPC_FAIL_NO_REASON(this); + } + + if (!SendInitGMPService(std::move(child))) { + MOZ_ASSERT(false, "SendInitGMPService failed"); + return IPC_FAIL_NO_REASON(this); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvLoadPlugin( + const uint32_t& aPluginId, nsresult* aRv, uint32_t* aRunID, + Endpoint<PPluginModuleParent>* aEndpoint) { + *aRv = NS_OK; + if (!mozilla::plugins::SetupBridge(aPluginId, this, aRv, aRunID, aEndpoint)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvUngrabPointer( + const uint32_t& aTime) { +#if !defined(MOZ_WIDGET_GTK) + MOZ_CRASH("This message only makes sense on GTK platforms"); +#else + gdk_pointer_ungrab(aTime); + return IPC_OK(); +#endif +} + +static void LogFailedPrincipalValidationInfo(nsIPrincipal* aPrincipal, + const char* aMethod) { + // no need to do the dance if logging is disabled + if (MOZ_LOG_TEST(ContentParent::GetLog(), LogLevel::Error)) { + nsAutoCString spec; + if (!aPrincipal) { + spec.AssignLiteral("NullPtr"); + } else if (aPrincipal->IsSystemPrincipal()) { + spec.AssignLiteral("SystemPrincipal"); + } else if (aPrincipal->GetIsExpandedPrincipal()) { + spec.AssignLiteral("ExpandedPrincipal"); + } else if (aPrincipal->GetIsContentPrincipal()) { + aPrincipal->GetSpec(spec); + } + + MOZ_LOG(ContentParent::GetLog(), LogLevel::Error, + (" Receiving unexpected Principal (%s) within %s", spec.get(), + aMethod)); + } +} + +bool ContentParent::ValidatePrincipal( + nsIPrincipal* aPrincipal, + const EnumSet<ValidatePrincipalOptions>& aOptions) { + // 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")) { + 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 (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); +} + +mozilla::ipc::IPCResult ContentParent::RecvRemovePermission( + const IPC::Principal& aPrincipal, const nsCString& aPermissionType, + nsresult* aRv) { + if (!ValidatePrincipal(aPrincipal)) { + LogFailedPrincipalValidationInfo(aPrincipal, __func__); + } + *aRv = Permissions::RemovePermission(aPrincipal, aPermissionType); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvConnectPluginBridge( + const uint32_t& aPluginId, nsresult* aRv, + Endpoint<PPluginModuleParent>* aEndpoint) { + *aRv = NS_OK; + // We don't need to get the run ID for the plugin, since we already got it + // in the first call to SetupBridge in RecvLoadPlugin, so we pass in a dummy + // pointer and just throw it away. + uint32_t dummy = 0; + if (!mozilla::plugins::SetupBridge(aPluginId, this, aRv, &dummy, aEndpoint)) { + return IPC_FAIL(this, "SetupBridge failed"); + } + return IPC_OK(); +} + +/*static*/ +already_AddRefed<RemoteBrowser> ContentParent::CreateBrowser( + const TabContext& aContext, Element* aFrameElement, + const nsACString& aRemoteType, BrowsingContext* aBrowsingContext, + ContentParent* aOpenerContentParent) { + AUTO_PROFILER_LABEL("ContentParent::CreateBrowser", OTHER); + + if (!sCanLaunchSubprocesses) { + 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); + } + + bool isPreloadBrowser = false; + nsAutoString isPreloadBrowserStr; + if (aFrameElement->GetAttr(kNameSpaceID_None, nsGkAtoms::preloadedState, + isPreloadBrowserStr)) { + isPreloadBrowser = isPreloadBrowserStr.EqualsLiteral("preloaded"); + } + + RefPtr<ContentParent> constructorSender; + MOZ_RELEASE_ASSERT(XRE_IsParentProcess(), + "Cannot allocate BrowserParent in content process"); + if (aOpenerContentParent && aOpenerContentParent->IsAlive()) { + constructorSender = aOpenerContentParent; + } else { + if (aContext.IsJSPlugin()) { + constructorSender = GetNewOrUsedJSPluginProcess( + aContext.JSPluginId(), PROCESS_PRIORITY_FOREGROUND); + } else { + constructorSender = GetNewOrUsedBrowserProcess( + remoteType, aBrowsingContext->Group(), PROCESS_PRIORITY_FOREGROUND, + isPreloadBrowser); + } + 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 (docShell->GetAffectPrivateSessionLifetime()) { + chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_LIFETIME; + } + + 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(); + 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; + } + + 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(); + } +} + +static LookAndFeelData GetLookAndFeelData() { + if (StaticPrefs::widget_remote_look_and_feel_AtStartup()) { + return *RemoteLookAndFeel::ExtractData(); + } + return LookAndFeel::GetCache(); +} + +void ContentParent::BroadcastThemeUpdate(widget::ThemeChangeKind aKind) { + LookAndFeelData lnfData = GetLookAndFeelData(); + + for (auto* cp : AllProcesses(eLive)) { + Unused << cp->SendThemeChanged(lnfData, aKind); + } +} + +/*static */ +void ContentParent::BroadcastMediaCodecsSupportedUpdate( + RemoteDecodeIn aLocation, + const PDMFactory::MediaCodecsSupported& aSupported) { + sCodecsSupported[aLocation] = aSupported; + for (auto* cp : AllProcesses(eAll)) { + Unused << cp->SendUpdateMediaCodecsSupported(aLocation, aSupported); + } +} + +const nsACString& ContentParent::GetRemoteType() const { return mRemoteType; } + +void ContentParent::Init() { + 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); + } + } + + AddShutdownBlockers(); + + 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 (PresShell::IsAccessibilityActive()) { +# if defined(XP_WIN) + // Don't init content a11y if we detect an incompat version of JAWS in use. + if (!mozilla::a11y::Compatibility::IsOldJAWS()) { + Unused << SendActivateA11y( + ::GetCurrentThreadId(), + a11y::AccessibleWrap::GetContentProcessIdFor(ChildID())); + } +# else + Unused << SendActivateA11y(0, 0); +# endif + } +#endif // #ifdef ACCESSIBILITY + +#ifdef MOZ_GECKO_PROFILER + Unused << SendInitProfiler(ProfilerParent::CreateForProcess(OtherPid())); +#endif + + // Ensure that the default set of permissions are avaliable in the content + // process before we try to load any URIs in it. + EnsurePermissionsByKey(""_ns, ""_ns); + + RefPtr<GeckoMediaPluginServiceParent> gmps( + GeckoMediaPluginServiceParent::GetSingleton()); + gmps->UpdateContentProcessGMPCapabilities(); + + // 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(); +} + +void ContentParent::MaybeBeginShutDown(uint32_t aExpectedBrowserCount, + bool aSendShutDown) { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose, + ("MaybeBeginShutdown %p", this)); + MOZ_ASSERT(NS_IsMainThread()); + + if (ManagedPBrowserParent().Count() != aExpectedBrowserCount || + ShouldKeepProcessAlive() || TryToRecycle()) { + 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(); + StartForceKillTimer(); + + if (aSendShutDown) { + MaybeAsyncSendShutDownMessage(); + } +} + +void ContentParent::MaybeAsyncSendShutDownMessage() { + MOZ_LOG(ContentParent::GetLog(), LogLevel::Verbose, + ("MaybeAsyncSendShutDownMessage %p", this)); + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(sRecycledE10SProcess != this); + +#ifdef DEBUG + // Calling this below while the lock is acquired will deadlock. + bool shouldKeepProcessAlive = ShouldKeepProcessAlive(); +#endif + + auto lock = mRemoteWorkerActorData.Lock(); + MOZ_ASSERT_IF(!lock->mCount, !shouldKeepProcessAlive); + + if (lock->mCount) { + return; + } + + MOZ_ASSERT(!lock->mShutdownStarted); + lock->mShutdownStarted = true; + + // 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 ContentParent::ShutDownProcess(ShutDownMethod aMethod) { + // 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 (CanSend() && !mShutdownPending) { + // Stop sending input events with input priority when shutting down. + SetInputPriorityEventEnabled(false); + if (SendShutdown()) { + mShutdownPending = true; + // Start the force-kill timer if we haven't already. + StartForceKillTimer(); + } + } + // If call was not successful, the channel must have been broken + // somehow, and we will clean up the error in ActorDestroy. + return; + } + + using mozilla::dom::quota::QuotaManagerService; + + if (QuotaManagerService* qms = QuotaManagerService::GetOrCreate()) { + qms->AbortOperationsForProcess(mChildID); + } + + // If Close() fails with an error, we'll end up back in this function, but + // with aMethod = CLOSE_CHANNEL_WITH_ERROR. + + if (aMethod == CLOSE_CHANNEL && !mCalledClose) { + // Close() can only be called once: It kicks off the destruction + // sequence. + mCalledClose = true; + Close(); + } + + const ManagedContainer<POfflineCacheUpdateParent>& ocuParents = + ManagedPOfflineCacheUpdateParent(); + for (auto iter = ocuParents.ConstIter(); !iter.Done(); iter.Next()) { + RefPtr<mozilla::docshell::OfflineCacheUpdateParent> ocuParent = + static_cast<mozilla::docshell::OfflineCacheUpdateParent*>( + iter.Get()->GetKey()); + ocuParent->StopSendingMessagesToChild(); + } + + // 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(); +} + +mozilla::ipc::IPCResult ContentParent::RecvFinishShutdown() { + // At this point, we already called ShutDownProcess once with + // SEND_SHUTDOWN_MESSAGE. To actually close the channel, we call + // ShutDownProcess again with CLOSE_CHANNEL. + MOZ_ASSERT(mShutdownPending); + 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(!sPrivateContent || !sPrivateContent->Contains(this)); + MOZ_RELEASE_ASSERT(sRecycledE10SProcess != this); + if (IsForJSPlugin()) { + MOZ_RELEASE_ASSERT(!sJSPluginContentParents || + !sJSPluginContentParents->Get(mJSPluginID)); + } else { + MOZ_RELEASE_ASSERT( + !sBrowserContentParents || + !sBrowserContentParents->Contains(mRemoteType) || + !sBrowserContentParents->Get(mRemoteType)->Contains(this) || + !sCanLaunchSubprocesses); // aka in shutdown - avoid timing issues + + for (auto& group : mGroups) { + MOZ_RELEASE_ASSERT(group.GetKey()->GetHostProcess(mRemoteType) != this, + "still a host process for one of our groups?"); + } + } +} + +void ContentParent::AssertAlive() { MOZ_DIAGNOSTIC_ASSERT(!IsDead()); } + +void ContentParent::RemoveFromList() { + if (IsForJSPlugin()) { + if (sJSPluginContentParents) { + sJSPluginContentParents->Remove(mJSPluginID); + if (!sJSPluginContentParents->Count()) { + sJSPluginContentParents = nullptr; + } + } + return; + } + + if (sPrivateContent) { + sPrivateContent->RemoveElement(this); + if (!sPrivateContent->Length()) { + sPrivateContent = nullptr; + } + } + + 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 (auto& group : mGroups) { + group.GetKey()->RemoveHostProcess(this); + } + + StopRecycling(/* 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(); + + PreallocatedProcessManager::Erase(this); + +#ifdef MOZ_WIDGET_ANDROID + 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. + 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::OnChannelConnected(int32_t pid) { + MOZ_ASSERT(NS_IsMainThread()); + + SetOtherProcessId(pid); +} + +void ContentParent::ProcessingError(Result aCode, const char* aReason) { + if (MsgDropped == aCode) { + return; + } +#ifndef FUZZING + // Other errors are big deals. + KillHard(aReason); +#endif +} + +void ContentParent::ActorDestroy(ActorDestroyReason why) { + MOZ_RELEASE_ASSERT(mSelfRef); + + 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(why == NormalShutdown ? CLOSE_CHANNEL + : CLOSE_CHANNEL_WITH_ERROR); + + 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(); + + mConsoleService = nullptr; + + // 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 (mCrashReporter->GenerateCrashReport(OtherPid())) { + // Propagate `isLikelyOOM`. + Unused << props->SetPropertyAsBool(u"isLikelyOOM"_ns, + mCrashReporter->IsLikelyOOM()); + } + } + + 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. + nsCOMPtr<nsIUserIdleService> idleService = + do_GetService("@mozilla.org/widget/useridleservice;1"); + MOZ_ASSERT(idleService); + RefPtr<ParentIdleListener> listener; + for (int32_t i = mIdleListeners.Length() - 1; i >= 0; --i) { + listener = static_cast<ParentIdleListener*>(mIdleListeners[i].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? + 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(); + 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(); + +#if defined(XP_WIN) && defined(ACCESSIBILITY) + a11y::AccessibleWrap::ReleaseContentProcessIdFor(ChildID()); +#endif + +#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); + + nsTHashtable<nsRefPtrHashKey<BrowsingContextGroup>> groups; + mGroups.SwapElements(groups); + for (auto& group : groups) { + group.GetKey()->Unsubscribe(this); + } + MOZ_DIAGNOSTIC_ASSERT(mGroups.IsEmpty()); +} + +void ContentParent::ActorDealloc() { mSelfRef = nullptr; } + +bool ContentParent::TryToRecycle() { + // 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. + StopRecycling(/* 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::StopRecycling(bool aForeground) { + if (sRecycledE10SProcess != this) { + return; + } + + sRecycledE10SProcess = nullptr; + if (aForeground) { + ProcessPriorityManager::SetProcessPriority(this, + PROCESS_PRIORITY_FOREGROUND); + } +} + +bool ContentParent::HasActiveWorkerOrJSPlugin() { + if (IsForJSPlugin()) { + return true; + } + + // If we have active workers, we need to stay alive. + { + const auto lock = mRemoteWorkerActorData.Lock(); + if (lock->mCount) { + return true; + } + } + return false; +} + +bool ContentParent::ShouldKeepProcessAlive() { + if (HasActiveWorkerOrJSPlugin()) { + 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 (!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() { + // 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() { + // Something wants to keep this content process alive. + ++mNumKeepaliveCalls; +} + +void ContentParent::RemoveKeepAlive() { + MOZ_DIAGNOSTIC_ASSERT(mNumKeepaliveCalls > 0); + --mNumKeepaliveCalls; + + MaybeBeginShutDown(); +} + +void ContentParent::StartForceKillTimer() { + if (mForceKillTimer || !CanSend()) { + return; + } + + NotifyImpendingShutdown(); + + int32_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() { + return static_cast<TestShellParent*>(SendPTestShellConstructor()); +} + +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; + } + + // Windowserver access + if (!Preferences::GetBool( + "security.sandbox.content.mac.disconnect-windowserver")) { + 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 development builds, + // these are used to whitelist the repo dir and object dir respectively. + nsresult rv; + if (mozilla::IsDevelopmentBuild()) { + // 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); + + if (!ContentProcessManager::GetSingleton()) { + // Shutdown has begun, we shouldn't spawn any more child processes. + return false; + } + + std::vector<std::string> extraArgs; + extraArgs.push_back("-childID"); + char idStr[21]; + SprintfLiteral(idStr, "%" PRId64, static_cast<uint64_t>(mChildID)); + extraArgs.push_back(idStr); + extraArgs.push_back(IsForBrowser() ? "-isForBrowser" : "-notForBrowser"); + + // 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()) { + MarkAsDead(); + return false; + } + mPrefSerializer->AddSharedPrefCmdLineArgs(*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) { + extraArgs.push_back("-safeMode"); + } + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + if (IsContentSandboxEnabled()) { + AppendSandboxParams(extraArgs); + mSubprocess->DisableOSActivityMode(); + } +#endif + + nsCString parentBuildID(mozilla::PlatformBuildID()); + extraArgs.push_back("-parentBuildID"); + extraArgs.push_back(parentBuildID.get()); + + // See also ActorDealloc. + mSelfRef = this; + mLaunchYieldTS = TimeStamp::Now(); + return mSubprocess->AsyncLaunch(std::move(extraArgs)); +} + +void ContentParent::LaunchSubprocessReject() { + NS_ERROR("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(); +} + +bool ContentParent::LaunchSubprocessResolve(bool aIsSync, + ProcessPriority aPriority) { + AUTO_PROFILER_LABEL("ContentParent::LaunchSubprocess::resolve", OTHER); + + // Take the pending IPC channel. This channel will be used to open the raw IPC + // connection between this process and the launched content process. + UniquePtr<IPC::Channel> channel = mSubprocess->TakeChannel(); + if (!channel) { + // We don't have a channel, so this method must've been called already. + MOZ_ASSERT(sCreatedFirstContentProcess); + MOZ_ASSERT(!mPrefSerializer); + MOZ_ASSERT(mLifecycleState != LifecycleState::LAUNCHING); + return true; + } + + // Now that communication with the child is complete, we can cleanup + // the preference serializer. + mPrefSerializer = nullptr; + + const auto launchResumeTS = TimeStamp::Now(); +#ifdef MOZ_GECKO_PROFILER + if (profiler_thread_is_being_profiled()) { + 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); + } +#endif + + if (!sCreatedFirstContentProcess) { + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + obs->NotifyObservers(nullptr, "ipc:first-content-process-created", nullptr); + sCreatedFirstContentProcess = true; + } + + base::ProcessId procId = + base::GetProcId(mSubprocess->GetChildProcessHandle()); + Open(std::move(channel), procId); + + ContentProcessManager::GetSingleton()->AddContentProcess(this); + +#ifdef MOZ_CODE_COVERAGE + Unused << SendShareCodeCoverageMutex( + CodeCoverageHandler::Get()->GetMutexHandle(procId)); +#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()) { + 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())); + } + 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)) { + return false; + } + 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(LaunchError(), __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(LaunchError(), __func__); + }); +} + +ContentParent::ContentParent(const nsACString& aRemoteType, int32_t aJSPluginID) + : mSelfRef(nullptr), + mSubprocess(nullptr), + mLaunchTS(TimeStamp::Now()), + mLaunchYieldTS(mLaunchTS), + mActivateTS(mLaunchTS), + mIsAPreallocBlocker(false), + mRemoteType(aRemoteType), + mChildID(gContentChildID++), + mGeolocationWatchID(-1), + mJSPluginID(aJSPluginID), + mRemoteWorkerActorData("ContentParent::mRemoteWorkerActorData"), + mNumDestroyingTabs(0), + mNumKeepaliveCalls(0), + mLifecycleState(LifecycleState::LAUNCHING), + mIsForBrowser(!mRemoteType.IsEmpty()), + mCalledClose(false), + mCalledKillHard(false), + mCreatedPairedMinidumps(false), + mShutdownPending(false), + mIsRemoteInputEventQueueEnabled(false), + mIsInputPriorityEventEnabled(false), + mIsInPool(false), + mHangMonitorActor(nullptr) { + MOZ_DIAGNOSTIC_ASSERT(!IsForJSPlugin(), + "XXX(nika): How are we creating a JSPlugin?"); + + mRemoteTypeIsolationPrincipal = + CreateRemoteTypeIsolationPrincipal(aRemoteType); + + // Insert ourselves into the global linked list of ContentParent objects. + if (!sContentParents) { + sContentParents = MakeUnique<LinkedList<ContentParent>>(); + } + sContentParents->insertBack(this); + + mMessageManager = nsFrameMessageManager::NewProcessMessageManager(true); + +#if defined(XP_WIN) + if (XRE_IsParentProcess()) { + audio::AudioNotificationSender::Init(); + } + // 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 (mForceKillTimer) { + mForceKillTimer->Cancel(); + } + + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + 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::Shutdown)) { + 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()); + } + + 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()); + + nsCOMPtr<nsIClipboard> clipboard( + do_GetService("@mozilla.org/widget/clipboard;1")); + MOZ_ASSERT(clipboard, "No clipboard?"); + + rv = clipboard->SupportsSelectionClipboard( + &xpcomInit.clipboardCaps().supportsSelectionClipboard()); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + rv = clipboard->SupportsFindClipboard( + &xpcomInit.clipboardCaps().supportsFindClipboard()); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + // 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::RootedValue 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. + nsTArray<SystemFontListEntry> fontList; + gfxPlatform::GetPlatform()->ReadSystemFontList(&fontList); + + LookAndFeelData lnfData = GetLookAndFeelData(); + + // 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 = services::GetGfxInfo(); + if (gfxInfo) { + GfxInfoBase* gfxInfoRaw = static_cast<GfxInfoBase*>(gfxInfo.get()); + xpcomInit.gfxFeatureStatus() = gfxInfoRaw->GetAllFeatures(); + } + +#ifdef XP_WIN + xpcomInit.systemParameters() = + widget::WinContentSystemParameters::GetSingleton()->GetParentValues(); +#endif + + DataStorage::GetAllChildProcessData(xpcomInit.dataStorage()); + + // Send the dynamic scalar definitions to the new process. + TelemetryIPC::GetDynamicScalarDefinitions(xpcomInit.dynamicScalarDefs()); + + for (auto const& [location, supported] : sCodecsSupported) { + Unused << SendUpdateMediaCodecsSupported(location, supported); + } + + // 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(); + + SharedMemoryHandle handle; + if (sheetCache->ShareToProcess(OtherPid(), &handle)) { + sharedUASheetHandle.emplace(handle); + } else { + sharedUASheetAddress = 0; + } + + Unused << SendSetXPCOMProcessAttributes( + xpcomInit, initialData, lnfData, fontList, sharedUASheetHandle, + sharedUASheetAddress, sharedFontListBlocks); + + 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 = + services::GetStringBundleService(); + 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); + + 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, &namespaces)) { + // This can fail if we've already started shutting down the compositor + // thread. See Bug 1562763 comment 8. + 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 + + if (!ServiceWorkerParentInterceptEnabled()) { + RefPtr<ServiceWorkerRegistrar> swr = ServiceWorkerRegistrar::Get(); + MOZ_ASSERT(swr); + + nsTArray<ServiceWorkerRegistrationData> registrations; + swr->GetRegistrations(registrations); + + // Send down to the content process the permissions for each of the + // registered service worker scopes. + for (auto& registration : registrations) { + auto principalOrErr = PrincipalInfoToPrincipal(registration.principal()); + if (principalOrErr.isOk()) { + nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap(); + TransmitPermissionsForPrincipal(principal); + } + } + + Unused << SendInitServiceWorkers(ServiceWorkerConfiguration(registrations)); + } + + { + nsTArray<BlobURLRegistrationData> registrations; + BlobURLProtocolHandler::ForEachBlobURL( + [&](BlobImpl* aBlobImpl, nsIPrincipal* aPrincipal, + const Maybe<nsID>& aAgentClusterId, const nsACString& aURI, + bool aRevoked) { + nsAutoCString origin; + nsresult rv = aPrincipal->GetOrigin(origin); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + // 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 (!StringBeginsWith(origin, "moz-extension://"_ns) && + !aPrincipal->IsSystemPrincipal()) { + return true; + } + + IPCBlob ipcBlob; + rv = IPCBlobUtils::Serialize(aBlobImpl, this, ipcBlob); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + registrations.AppendElement(BlobURLRegistrationData( + nsCString(aURI), ipcBlob, aPrincipal, aAgentClusterId, 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 (auto& group : mGroups) { + group.GetKey()->Subscribe(this); + } + + // Start up nsPluginHost and run FindPlugins to cache the plugin list. + // If this isn't our first content process, just send over cached list. + RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst(); + pluginHost->SendPluginsToContent(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 || !mSubprocess->GetChildProcessHandle()) { + return -1; + } + return base::GetProcId(mSubprocess->GetChildProcessHandle()); +} + +mozilla::ipc::IPCResult ContentParent::RecvGetGfxVars( + nsTArray<GfxVarUpdate>* aVars) { + // Ensure gfxVars is initialized (for xpcshell tests). + gfxVars::Initialize(); + + *aVars = gfxVars::FetchNonDefaultVars(); + + // Now that content has initialized gfxVars, we can start listening for + // updates. + gfxVars::AddReceiver(this); + return IPC_OK(); +} + +void ContentParent::OnCompositorUnexpectedShutdown() { + GPUProcessManager* gpm = GPUProcessManager::Get(); + + Endpoint<PCompositorManagerChild> compositor; + Endpoint<PImageBridgeChild> imageBridge; + Endpoint<PVRManagerChild> vrBridge; + Endpoint<PRemoteDecoderManagerChild> videoManager; + AutoTArray<uint32_t, 3> namespaces; + + DebugOnly<bool> opened = + gpm->CreateContentBridges(OtherPid(), &compositor, &imageBridge, + &vrBridge, &videoManager, &namespaces); + MOZ_ASSERT(opened); + + 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 IPCDataTransfer& aDataTransfer, const bool& aIsPrivateData, + const IPC::Principal& aRequestingPrincipal, + const nsContentPolicyType& aContentPolicyType, + const int32_t& aWhichClipboard) { + if (!ValidatePrincipal(aRequestingPrincipal, + {ValidatePrincipalOptions::AllowNullPtr})) { + LogFailedPrincipalValidationInfo(aRequestingPrincipal, __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( + aDataTransfer, aIsPrivateData, aRequestingPrincipal, aContentPolicyType, + trans, this, nullptr); + NS_ENSURE_SUCCESS(rv, IPC_OK()); + + clipboard->SetData(trans, nullptr, aWhichClipboard); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvGetClipboard( + nsTArray<nsCString>&& aTypes, const int32_t& aWhichClipboard, + IPCDataTransfer* aDataTransfer) { + 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); + // 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); + + for (uint32_t t = 0; t < aTypes.Length(); t++) { + trans->AddDataFlavor(aTypes[t].get()); + } + + clipboard->GetData(trans, aWhichClipboard); + nsContentUtils::TransferableToIPCTransferable(trans, aDataTransfer, true, + nullptr, 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(); +} + +mozilla::ipc::IPCResult ContentParent::RecvPlaySound(nsIURI* aURI) { + // If the check here fails, it can only mean that this message was spoofed. + if (!aURI || !aURI->SchemeIs("chrome")) { + // PlaySound only accepts a valid chrome URI. + return IPC_FAIL_NO_REASON(this); + } + nsCOMPtr<nsIURL> soundURL(do_QueryInterface(aURI)); + if (!soundURL) { + return IPC_OK(); + } + + nsresult rv; + nsCOMPtr<nsISound> sound(do_GetService(NS_SOUND_CID, &rv)); + NS_ENSURE_SUCCESS(rv, IPC_OK()); + + sound->Play(soundURL); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvBeep() { + nsresult rv; + nsCOMPtr<nsISound> sound(do_GetService(NS_SOUND_CID, &rv)); + NS_ENSURE_SUCCESS(rv, IPC_OK()); + + sound->Beep(); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvPlayEventSound( + const uint32_t& aEventId) { + nsresult rv; + nsCOMPtr<nsISound> sound(do_GetService(NS_SOUND_CID, &rv)); + NS_ENSURE_SUCCESS(rv, IPC_OK()); + + sound->PlayEventSound(aEventId); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvGetIconForExtension( + const nsCString& 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) { + PreallocatedProcessManager::RemoveBlocker(mRemoteType, this); + mIsAPreallocBlocker = false; + } + 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 + +// Async shutdown blocker +NS_IMETHODIMP +ContentParent::BlockShutdown(nsIAsyncShutdownClient* aClient) { + // Make sure that our process will get scheduled. + ProcessPriorityManager::SetProcessPriority(this, PROCESS_PRIORITY_FOREGROUND); + + // Okay to call ShutDownProcess multiple times. + ShutDownProcess(SEND_SHUTDOWN_MESSAGE); + MarkAsDead(); + + 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 StaticRefPtr<nsIAsyncShutdownClient> sXPCOMShutdownClient; +static StaticRefPtr<nsIAsyncShutdownClient> sProfileBeforeChangeClient; + +static void InitClients() { + if (!sXPCOMShutdownClient) { + nsresult rv; + nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService(); + + nsCOMPtr<nsIAsyncShutdownClient> client; + rv = svc->GetXpcomWillShutdown(getter_AddRefs(client)); + sXPCOMShutdownClient = client.forget(); + ClearOnShutdown(&sXPCOMShutdownClient); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "XPCOMShutdown shutdown blocker"); + + rv = svc->GetProfileBeforeChange(getter_AddRefs(client)); + sProfileBeforeChangeClient = client.forget(); + ClearOnShutdown(&sProfileBeforeChangeClient); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), + "profileBeforeChange shutdown blocker"); + } +} + +void ContentParent::AddShutdownBlockers() { + InitClients(); + + sXPCOMShutdownClient->AddBlocker( + this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__, u""_ns); + sProfileBeforeChangeClient->AddBlocker( + this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__, u""_ns); +} + +void ContentParent::RemoveShutdownBlockers() { + Unused << sXPCOMShutdownClient->RemoveBlocker(this); + Unused << sProfileBeforeChangeClient->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")) { + // A pref changed. If it is useful to do so, inform child processes. + if (!ShouldSyncPreference(aData)) { + return NS_OK; + } + + // We know prefs are ASCII here. + NS_LossyConvertUTF16toASCII strData(aData); + + Pref pref(strData, /* isLocked */ false, Nothing(), Nothing()); + Preferences::GetPreference(&pref); + if (IsInitialized()) { + MOZ_ASSERT(mQueuedPrefs.IsEmpty()); + if (!SendPreferenceUpdate(pref)) { + return NS_ERROR_NOT_AVAILABLE; + } + } else { + MOZ_ASSERT(!IsDead()); + mQueuedPrefs.AppendElement(pref); + } + } + + 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. +# if defined(XP_WIN) + // Don't init content a11y if we detect an incompat version of JAWS in + // use. + if (!mozilla::a11y::Compatibility::IsOldJAWS()) { + Unused << SendActivateA11y( + ::GetCurrentThreadId(), + a11y::AccessibleWrap::GetContentProcessIdFor(ChildID())); + } +# else + Unused << SendActivateA11y(0, 0); +# endif + } 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")) { + if (!aData) { + return NS_ERROR_UNEXPECTED; + } + 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); + // Do not push these cookie updates to the same process they originated + // from. + if (cs->ProcessingCookie()) { + return NS_OK; + } + if (!nsCRT::strcmp(aData, u"batch-deleted")) { + nsCOMPtr<nsIArray> cookieList = do_QueryInterface(aSubject); + NS_ASSERTION(cookieList, "couldn't get cookie list"); + cs->RemoveBatchDeletedCookies(cookieList); + return NS_OK; + } + + if (!nsCRT::strcmp(aData, u"cleared")) { + cs->RemoveAll(); + return NS_OK; + } + + nsCOMPtr<nsICookie> xpcCookie = do_QueryInterface(aSubject); + NS_ASSERTION(xpcCookie, "couldn't get cookie"); + if (!nsCRT::strcmp(aData, u"deleted")) { + cs->RemoveCookie(xpcCookie); + } else if ((!nsCRT::strcmp(aData, u"added")) || + (!nsCRT::strcmp(aData, u"changed"))) { + cs->AddCookie(xpcCookie); + } + } else if (!strcmp(aTopic, NS_NETWORK_LINK_TYPE_TOPIC)) { + UpdateNetworkLinkType(); + } + + return NS_OK; +} + +/* static */ +bool ContentParent::ShouldSyncPreference(const char16_t* aData) { +#define PARENT_ONLY_PREF_LIST_ENTRY(s) \ + { s, (sizeof(s) / sizeof(char16_t)) - 1 } + struct ParentOnlyPrefListEntry { + const char16_t* mPrefBranch; + size_t mLen; + }; + // These prefs are not useful in child processes. + static const ParentOnlyPrefListEntry sParentOnlyPrefBranchList[] = { + PARENT_ONLY_PREF_LIST_ENTRY(u"app.update.lastUpdateTime."), + PARENT_ONLY_PREF_LIST_ENTRY(u"datareporting.policy."), + PARENT_ONLY_PREF_LIST_ENTRY(u"browser.safebrowsing.provider."), + PARENT_ONLY_PREF_LIST_ENTRY(u"browser.shell."), + PARENT_ONLY_PREF_LIST_ENTRY(u"browser.slowStartup."), + PARENT_ONLY_PREF_LIST_ENTRY(u"browser.startup."), + PARENT_ONLY_PREF_LIST_ENTRY(u"extensions.getAddons.cache."), + PARENT_ONLY_PREF_LIST_ENTRY(u"media.gmp-manager."), + PARENT_ONLY_PREF_LIST_ENTRY(u"media.gmp-gmpopenh264."), + PARENT_ONLY_PREF_LIST_ENTRY(u"privacy.sanitize."), + }; +#undef PARENT_ONLY_PREF_LIST_ENTRY + + for (const auto& entry : sParentOnlyPrefBranchList) { + if (NS_strncmp(entry.mPrefBranch, aData, entry.mLen) == 0) { + return false; + } + } + return true; +} + +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<PBackgroundParent>&& aEndpoint) { + if (!BackgroundParent::Alloc(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) { + ASSERT_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.openerParent()); + if (!opener) { + ASSERT_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 = aSource.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) { + if (aSource.IsNullOrDiscarded() || aTarget.IsNullOrDiscarded()) { + 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 + ->ChangeRemoteness(cp->GetRemoteType(), /* aLoadID = */ 0, + /* aReplaceBC = */ false, /* aSpecificGroupId = */ 0) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [source = RefPtr{source}](BrowserParent* aBp) { + Unused << aBp->SendCloneDocumentTreeIntoSelf(source); + }, + [](nsresult aRv) { + NS_WARNING( + nsPrintfCString("Remote clone failed: %x\n", unsigned(aRv)) + .get()); + }); + 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"); + } + + 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.openerParent()); + 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->RegisterRemoteFrame(parent)) { + return IPC_FAIL(this, "RegisterRemoteFrame Failed"); + } + } + + if (NS_WARN_IF(!parent->BindPWindowGlobalEndpoint(std::move(aWindowEp), + initialWindow))) { + return IPC_FAIL(this, "BindPWindowGlobalEndpoint failed"); + } + + 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::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. + nsCOMPtr<nsIAppStartup> appStartup = components::AppStartup::Service(); + if (mCrashReporter && !appStartup->GetShuttingDown() && + 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, nullptr, "browser"_ns)) { + mCreatedPairedMinidumps = mCrashReporter->FinalizeCrashReport(); + } + } +} + +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 = %d 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; + mForceKillTimer = nullptr; + + RemoveShutdownBlockers(); + + GeneratePairedMinidump(aReason); + + nsDependentCString reason(aReason); + 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."); + return; + } + + if (!KillProcess(otherProcessHandle, base::PROCESS_END_KILLED_BY_USER, + false)) { + NS_WARNING("failed to kill subprocess!"); + } + + if (mSubprocess) { + MOZ_LOG( + ContentParent::GetLog(), LogLevel::Verbose, + ("KillHard Subprocess: ContentParent %p mSubprocess %p handle " + "%" PRIuPTR, + this, mSubprocess, + mSubprocess ? (uintptr_t)mSubprocess->GetChildProcessHandle() : -1)); + mSubprocess->SetAlreadyDead(); + } + + // 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(); +} + +mozilla::ipc::IPCResult ContentParent::RecvAddPerformanceMetrics( + const nsID& aID, nsTArray<PerformanceInfo>&& aMetrics) { + nsresult rv = PerformanceMetricsCollector::DataReceived(aID, aMetrics); + Unused << NS_WARN_IF(NS_FAILED(rv)); + 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); +} + +PTestShellParent* ContentParent::AllocPTestShellParent() { + return new TestShellParent(); +} + +bool ContentParent::DeallocPTestShellParent(PTestShellParent* shell) { + delete shell; + return true; +} + +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; +} + +PNeckoParent* ContentParent::AllocPNeckoParent() { return new NeckoParent(); } + +bool ContentParent::DeallocPNeckoParent(PNeckoParent* necko) { + delete necko; + return true; +} + +PPrintingParent* ContentParent::AllocPPrintingParent() { +#ifdef NS_PRINTING + if (mPrintingParent) { + // Only one PrintingParent should be created per process. + return nullptr; + } + + // Create the printing singleton for this process. + mPrintingParent = new PrintingParent(); + + // Take another reference for IPDL code. + mPrintingParent.get()->AddRef(); + + return mPrintingParent.get(); +#else + MOZ_ASSERT_UNREACHABLE("Should never be created if no printing."); + return nullptr; +#endif +} + +bool ContentParent::DeallocPPrintingParent(PPrintingParent* printing) { +#ifdef NS_PRINTING + MOZ_RELEASE_ASSERT( + mPrintingParent == printing, + "Only one PrintingParent should have been created per process."); + + // Release reference taken for IPDL code. + static_cast<PrintingParent*>(printing)->Release(); + + mPrintingParent = nullptr; +#else + MOZ_ASSERT_UNREACHABLE("Should never have been created if no printing."); +#endif + return true; +} + +#ifdef NS_PRINTING +already_AddRefed<embedding::PrintingParent> ContentParent::GetPrintingParent() { + MOZ_ASSERT(mPrintingParent); + + RefPtr<embedding::PrintingParent> printingParent = mPrintingParent; + return printingParent.forget(); +} +#endif + +mozilla::ipc::IPCResult ContentParent::RecvInitStreamFilter( + const uint64_t& aChannelId, const nsString& 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(); +} + +PChildToParentStreamParent* ContentParent::AllocPChildToParentStreamParent() { + return mozilla::ipc::AllocPChildToParentStreamParent(); +} + +bool ContentParent::DeallocPChildToParentStreamParent( + PChildToParentStreamParent* aActor) { + delete aActor; + return true; +} + +PParentToChildStreamParent* ContentParent::AllocPParentToChildStreamParent() { + MOZ_CRASH( + "PParentToChildStreamParent actors should be manually constructed!"); +} + +bool ContentParent::DeallocPParentToChildStreamParent( + PParentToChildStreamParent* aActor) { + delete aActor; + return true; +} + +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 Maybe<mozilla::net::LoadInfoArgs>& aLoadInfoArgs, + const nsCString& aMimeContentType, const nsCString& aContentDisposition, + const uint32_t& aContentDispositionHint, + const nsString& 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 Maybe<LoadInfoArgs>& loadInfoArgs, const nsCString& aMimeContentType, + const nsCString& aContentDisposition, + const uint32_t& aContentDispositionHint, + const nsString& 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(); + static_cast<ExternalHelperAppParent*>(actor)->Init( + loadInfoArgs, aMimeContentType, aForceSave, aReferrer, context, + aShouldCloseWindow); + 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; +} + +PPresentationParent* ContentParent::AllocPPresentationParent() { + RefPtr<PresentationParent> actor = new PresentationParent(); + return actor.forget().take(); +} + +bool ContentParent::DeallocPPresentationParent(PPresentationParent* aActor) { + RefPtr<PresentationParent> actor = + dont_AddRef(static_cast<PresentationParent*>(aActor)); + return true; +} + +mozilla::ipc::IPCResult ContentParent::RecvPPresentationConstructor( + PPresentationParent* aActor) { + if (!static_cast<PresentationParent*>(aActor)->Init(mChildID)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +#ifdef MOZ_WEBSPEECH +PSpeechSynthesisParent* ContentParent::AllocPSpeechSynthesisParent() { + return new mozilla::dom::SpeechSynthesisParent(); +} + +bool ContentParent::DeallocPSpeechSynthesisParent( + PSpeechSynthesisParent* aActor) { + delete aActor; + return true; +} + +mozilla::ipc::IPCResult ContentParent::RecvPSpeechSynthesisConstructor( + PSpeechSynthesisParent* aActor) { + if (!static_cast<SpeechSynthesisParent*>(aActor)->SendInit()) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} +#endif + +mozilla::ipc::IPCResult ContentParent::RecvStartVisitedQueries( + const nsTArray<RefPtr<nsIURI>>& aUris) { + nsCOMPtr<IHistory> history = services::GetHistory(); + if (!history) { + return IPC_OK(); + } + for (const auto& params : aUris) { + if (NS_WARN_IF(!params)) { + continue; + } + history->RegisterVisitedCallback(params, nullptr); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvSetURITitle(nsIURI* uri, + const nsString& title) { + if (!uri) { + return IPC_FAIL_NO_REASON(this); + } + nsCOMPtr<IHistory> history = services::GetHistory(); + if (history) { + history->SetURITitle(uri, title); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvIsSecureURI( + const uint32_t& aType, nsIURI* aURI, const uint32_t& aFlags, + const OriginAttributes& aOriginAttributes, bool* aIsSecureURI) { + nsCOMPtr<nsISiteSecurityService> sss(do_GetService(NS_SSSERVICE_CONTRACTID)); + if (!sss) { + return IPC_FAIL_NO_REASON(this); + } + if (!aURI) { + return IPC_FAIL_NO_REASON(this); + } + nsresult rv = sss->IsSecureURI(aType, aURI, aFlags, aOriginAttributes, + nullptr, nullptr, aIsSecureURI); + if (NS_FAILED(rv)) { + return IPC_FAIL_NO_REASON(this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvAccumulateMixedContentHSTS( + nsIURI* aURI, const bool& aActive, + const OriginAttributes& aOriginAttributes) { + if (!aURI) { + return IPC_FAIL_NO_REASON(this); + } + nsMixedContentBlocker::AccumulateMixedContentHSTS(aURI, aActive, + aOriginAttributes); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvLoadURIExternal( + nsIURI* uri, nsIPrincipal* aTriggeringPrincipal, + const MaybeDiscarded<BrowsingContext>& aContext) { + if (aContext.IsDiscarded()) { + return IPC_OK(); + } + + nsCOMPtr<nsIExternalProtocolService> extProtService( + do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID)); + if (!extProtService) { + return IPC_OK(); + } + + if (!uri) { + return IPC_FAIL_NO_REASON(this); + } + + BrowsingContext* bc = aContext.get(); + extProtService->LoadURI(uri, aTriggeringPrincipal, bc); + 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_NO_REASON(this); + } + nsCOMPtr<nsIAlertsService> sysAlerts(components::Alerts::Service()); + if (sysAlerts) { + sysAlerts->ShowAlert(aAlert, this); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvCloseAlert(const nsString& aName) { + nsCOMPtr<nsIAlertsService> sysAlerts(components::Alerts::Service()); + if (sysAlerts) { + sysAlerts->CloseAlert(aName); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvDisableNotifications( + const IPC::Principal& aPrincipal) { + if (!ValidatePrincipal(aPrincipal)) { + LogFailedPrincipalValidationInfo(aPrincipal, __func__); + } + Unused << Notification::RemovePermission(aPrincipal); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvOpenNotificationSettings( + const IPC::Principal& aPrincipal) { + if (!ValidatePrincipal(aPrincipal)) { + LogFailedPrincipalValidationInfo(aPrincipal, __func__); + } + Unused << Notification::OpenSettings(aPrincipal); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvNotificationEvent( + const nsString& aType, const NotificationEventData& aData) { + nsCOMPtr<nsIServiceWorkerManager> swm = + mozilla::services::GetServiceWorkerManager(); + 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 nsString& 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::UnpackClonedMessageDataForParent(aData, data); + + ppm->ReceiveMessage(ppm, nullptr, aMsg, true, &data, aRetvals, + IgnoreErrors()); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvAsyncMessage( + const nsString& 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::UnpackClonedMessageDataForParent(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(); + 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; +} + +nsConsoleService* ContentParent::GetConsoleService() { + if (mConsoleService) { + return mConsoleService.get(); + } + + // XXXkhuey everything about this is terrible. + // Get the ConsoleService by CID rather than ContractID, so that we + // can cast the returned pointer to an nsConsoleService (rather than + // just an nsIConsoleService). This allows us to call the non-idl function + // nsConsoleService::LogMessageWithMode. + NS_DEFINE_CID(consoleServiceCID, NS_CONSOLESERVICE_CID); + nsCOMPtr<nsIConsoleService> consoleService(do_GetService(consoleServiceCID)); + mConsoleService = static_cast<nsConsoleService*>(consoleService.get()); + return mConsoleService.get(); +} + +mozilla::ipc::IPCResult ContentParent::RecvConsoleMessage( + const nsString& aMessage) { + RefPtr<nsConsoleService> consoleService = GetConsoleService(); + if (!consoleService) { + return IPC_OK(); + } + + RefPtr<nsConsoleMessage> msg(new nsConsoleMessage(aMessage.get())); + consoleService->LogMessageWithMode(msg, nsConsoleService::SuppressLog); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvReportFrameTimingData( + uint64_t aInnerWindowId, const nsString& entryName, + const nsString& initiatorType, UniquePtr<PerformanceTimingData>&& aData) { + RefPtr<WindowGlobalParent> parent = + WindowGlobalParent::GetByInnerWindowId(aInnerWindowId); + 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( + aInnerWindowId, entryName, initiatorType, std::move(aData)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::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) { + return RecvScriptErrorInternal(aMessage, aSourceName, aSourceLine, + aLineNumber, aColNumber, aFlags, aCategory, + aFromPrivateWindow, aFromChromeContext); +} + +mozilla::ipc::IPCResult ContentParent::RecvScriptErrorWithStack( + 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 bool& aFromChromeContext, const ClonedMessageData& aFrame) { + return RecvScriptErrorInternal( + aMessage, aSourceName, aSourceLine, aLineNumber, aColNumber, aFlags, + aCategory, aFromPrivateWindow, aFromChromeContext, &aFrame); +} + +mozilla::ipc::IPCResult ContentParent::RecvScriptErrorInternal( + 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 bool& aFromChromeContext, const ClonedMessageData* aStack) { + RefPtr<nsConsoleService> consoleService = GetConsoleService(); + if (!consoleService) { + return IPC_OK(); + } + + nsCOMPtr<nsIScriptError> msg; + + if (aStack) { + StructuredCloneData data; + UnpackClonedMessageDataForParent(*aStack, data); + + AutoJSAPI jsapi; + if (NS_WARN_IF(!jsapi.Init(xpc::PrivilegedJunkScope()))) { + MOZ_CRASH(); + } + JSContext* cx = jsapi.cx(); + + JS::RootedValue stack(cx); + ErrorResult rv; + data.Read(cx, &stack, rv); + if (rv.Failed() || !stack.isObject()) { + rv.SuppressException(); + return IPC_OK(); + } + + JS::RootedObject stackObj(cx, &stack.toObject()); + MOZ_ASSERT(JS::IsUnwrappedSavedFrame(stackObj)); + + JS::RootedObject stackGlobal(cx, JS::GetNonCCWObjectGlobal(stackObj)); + msg = new nsScriptErrorWithStack(JS::NothingHandleValue, stackObj, + stackGlobal); + } else { + msg = new nsScriptError(); + } + + nsresult rv = msg->Init(aMessage, aSourceName, aSourceLine, aLineNumber, + aColNumber, aFlags, aCategory.get(), + aFromPrivateWindow, aFromChromeContext); + if (NS_FAILED(rv)) return IPC_OK(); + + msg->SetIsForwardedFromContentProcess(true); + + consoleService->LogMessageWithMode(msg, nsConsoleService::SuppressLog); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvPrivateDocShellsExist( + const bool& aExist) { + if (!sPrivateContent) { + sPrivateContent = MakeUnique<nsTArray<ContentParent*>>(); + if (!sHasSeenPrivateDocShell) { + sHasSeenPrivateDocShell = true; + Telemetry::ScalarSet( + Telemetry::ScalarID::DOM_PARENTPROCESS_PRIVATE_WINDOW_USED, true); + } + } + if (aExist) { + sPrivateContent->AppendElement(this); + } else { + sPrivateContent->RemoveElement(this); + + // Only fire the notification if we have private and non-private + // windows: if privatebrowsing.autostart is true, all windows are + // private. + if (!sPrivateContent->Length() && + !Preferences::GetBool("browser.privatebrowsing.autostart")) { + nsCOMPtr<nsIObserverService> obs = + mozilla::services::GetObserverService(); + obs->NotifyObservers(nullptr, "last-pb-context-exited", nullptr); + sPrivateContent = nullptr; + } + } + return IPC_OK(); +} + +bool ContentParent::DoLoadMessageManagerScript(const nsAString& aURL, + bool aRunInGlobalScope) { + MOZ_ASSERT(!aRunInGlobalScope); + return SendLoadProcessScript(nsString(aURL)); +} + +nsresult ContentParent::DoSendAsyncMessage(const nsAString& aMessage, + StructuredCloneData& aHelper) { + ClonedMessageData data; + if (!BuildClonedMessageDataForParent(this, aHelper, data)) { + return NS_ERROR_DOM_DATA_CLONE_ERR; + } + if (!SendAsyncMessage(nsString(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(); +} + +bool ContentParent::ShouldContinueFromReplyTimeout() { + RefPtr<ProcessHangMonitor> monitor = ProcessHangMonitor::Get(); + return !monitor || !monitor->ShouldTimeOutCPOWs(); +} + +mozilla::ipc::IPCResult ContentParent::RecvRecordingDeviceEvents( + const nsString& aRecordingStatus, const nsString& aPageURL, + const bool& aIsAudio, const bool& aIsVideo) { + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + // recording-device-ipc-events needs to gather more information from content + // process + RefPtr<nsHashPropertyBag> props = new nsHashPropertyBag(); + props->SetPropertyAsUint64(u"childID"_ns, ChildID()); + props->SetPropertyAsBool(u"isAudio"_ns, aIsAudio); + props->SetPropertyAsBool(u"isVideo"_ns, aIsVideo); + props->SetPropertyAsAString(u"requestURL"_ns, aPageURL); + + obs->NotifyObservers((nsIPropertyBag2*)props, "recording-device-ipc-events", + aRecordingStatus.get()); + } else { + NS_WARNING( + "Could not get the Observer service for " + "ContentParent::RecvRecordingDeviceEvents."); + } + return IPC_OK(); +} + +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_NO_REASON(this)); + + RefPtr<ParentIdleListener> listener = + new ParentIdleListener(this, aObserver, aIdleTimeInS); + rv = idleService->AddIdleObserver(listener, aIdleTimeInS); + NS_ENSURE_SUCCESS(rv, IPC_FAIL_NO_REASON(this)); + 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_NO_REASON(this)); + 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(0 > mChildXSocketFdDup.get(), "Already backed up X resources??"); + if (aXSocketFd.IsValid()) { + auto rawFD = aXSocketFd.ClonePlatformHandle(); + mChildXSocketFdDup.reset(rawFD.release()); + } +#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(); +} + +PFileDescriptorSetParent* ContentParent::AllocPFileDescriptorSetParent( + const FileDescriptor& aFD) { + return new FileDescriptorSetParent(aFD); +} + +bool ContentParent::DeallocPFileDescriptorSetParent( + PFileDescriptorSetParent* aActor) { + delete static_cast<FileDescriptorSetParent*>(aActor); + return true; +} + +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; + } + + nsTArray<SystemFontListEntry> fontList; + gfxPlatform::GetPlatform()->ReadSystemFontList(&fontList); + + for (auto* cp : AllProcesses(eLive)) { + Unused << cp->SendUpdateFontList(fontList); + } +} + +already_AddRefed<mozilla::docshell::POfflineCacheUpdateParent> +ContentParent::AllocPOfflineCacheUpdateParent( + nsIURI* aManifestURI, nsIURI* aDocumentURI, + const PrincipalInfo& aLoadingPrincipalInfo, const bool& aStickDocument, + const CookieJarSettingsArgs& aCookieJarSettingsArgs) { + RefPtr<mozilla::docshell::OfflineCacheUpdateParent> update = + new mozilla::docshell::OfflineCacheUpdateParent(); + return update.forget(); +} + +mozilla::ipc::IPCResult ContentParent::RecvPOfflineCacheUpdateConstructor( + POfflineCacheUpdateParent* aActor, nsIURI* aManifestURI, + nsIURI* aDocumentURI, const PrincipalInfo& aLoadingPrincipal, + const bool& aStickDocument, + const CookieJarSettingsArgs& aCookieJarSettingsArgs) { + MOZ_ASSERT(aActor); + + RefPtr<mozilla::docshell::OfflineCacheUpdateParent> update = + static_cast<mozilla::docshell::OfflineCacheUpdateParent*>(aActor); + + nsresult rv = update->Schedule(aManifestURI, aDocumentURI, aLoadingPrincipal, + aStickDocument, aCookieJarSettingsArgs); + if (NS_FAILED(rv) && IsAlive()) { + // Inform the child of failure. + Unused << update->SendFinish(false, false); + } + + return IPC_OK(); +} + +PWebrtcGlobalParent* ContentParent::AllocPWebrtcGlobalParent() { +#ifdef MOZ_WEBRTC + return WebrtcGlobalParent::Alloc(); +#else + return nullptr; +#endif +} + +bool ContentParent::DeallocPWebrtcGlobalParent(PWebrtcGlobalParent* aActor) { +#ifdef MOZ_WEBRTC + WebrtcGlobalParent::Dealloc(static_cast<WebrtcGlobalParent*>(aActor)); + return true; +#else + return false; +#endif +} + +mozilla::ipc::IPCResult ContentParent::RecvSetOfflinePermission( + const Principal& aPrincipal) { + if (!ValidatePrincipal(aPrincipal)) { + LogFailedPrincipalValidationInfo(aPrincipal, __func__); + } + nsCOMPtr<nsIOfflineCacheUpdateService> updateService = + components::OfflineCacheUpdate::Service(); + if (!updateService) { + return IPC_FAIL_NO_REASON(this); + } + nsresult rv = updateService->AllowOfflineApp(aPrincipal); + NS_ENSURE_SUCCESS(rv, IPC_FAIL_NO_REASON(this)); + + return IPC_OK(); +} + +void ContentParent::MaybeInvokeDragSession(BrowserParent* aParent) { + // 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 && dragService->MaybeAddChildProcess(this)) { + // We need to send transferable data to child process. + nsCOMPtr<nsIDragSession> session; + dragService->GetCurrentSession(getter_AddRefs(session)); + if (session) { + nsTArray<IPCDataTransfer> dataTransfers; + RefPtr<DataTransfer> transfer = session->GetDataTransfer(); + if (!transfer) { + // Pass eDrop to get DataTransfer with external + // drag formats cached. + transfer = new DataTransfer(nullptr, eDrop, true, -1); + session->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::TransferablesToIPCTransferables( + transferables, dataTransfers, false, nullptr, this); + uint32_t action; + session->GetDragAction(&action); + mozilla::Unused << SendInvokeDragSession(dataTransfers, action); + } + } +} + +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, + const IPC::Principal& aPrincipal, const IPC::Principal& aTopLevelPrincipal, + const bool& aIsHandlingUserInput, + const bool& aMaybeUnsafePermissionDelegate, const TabId& aTabId) { + ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); + RefPtr<BrowserParent> 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; +} + +PWebBrowserPersistDocumentParent* +ContentParent::AllocPWebBrowserPersistDocumentParent( + PBrowserParent* aBrowser, const MaybeDiscarded<BrowsingContext>& aContext) { + return new WebBrowserPersistDocumentParent(); +} + +bool ContentParent::DeallocPWebBrowserPersistDocumentParent( + PWebBrowserPersistDocumentParent* aActor) { + delete aActor; + return true; +} + +mozilla::ipc::IPCResult ContentParent::CommonCreateWindow( + PBrowserParent* aThisTab, BrowsingContext* aParent, bool aSetOpener, + const uint32_t& aChromeFlags, const bool& aCalledFromJS, + const bool& aWidthSpecified, const bool& aForPrinting, + const bool& aForWindowDotPrint, nsIURI* aURIToLoad, + const nsCString& aFeatures, const float& aFullZoom, + BrowserParent* aNextRemoteBrowser, const nsString& 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(); + } + + nsCOMPtr<nsIDOMChromeWindow> rootChromeWin = do_QueryInterface(outerWin); + if (rootChromeWin) { + rootChromeWin->GetBrowserDOMWindow(getter_AddRefs(browserDOMWin)); + } + } + + aOpenLocation = nsWindowWatcher::GetWindowOpenLocation( + outerWin, aChromeFlags, aCalledFromJS, aWidthSpecified, aForPrinting); + + MOZ_ASSERT(aOpenLocation == nsIBrowserDOMWindow::OPEN_NEWTAB || + 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_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(); + } + + aResult = pwwatch->OpenWindowWithRemoteTab(thisBrowserHost, aFeatures, + aCalledFromJS, aFullZoom, 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. + // + // This involves doing a bit of gymnastics in order to get at the FrameLoader, + // so we scope this to avoid polluting the main function scope. + { + nsCOMPtr<Element> frameElement = newBrowserHost->GetOwnerElement(); + MOZ_ASSERT(frameElement); + 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& aWidthSpecified, + const bool& aForPrinting, const bool& aForPrintPreview, nsIURI* aURIToLoad, + const nsCString& aFeatures, const float& aFullZoom, + const IPC::Principal& aTriggeringPrincipal, nsIContentSecurityPolicy* aCsp, + nsIReferrerInfo* aReferrerInfo, const OriginAttributes& aOriginAttributes, + CreateWindowResolver&& aResolve) { + if (!ValidatePrincipal(aTriggeringPrincipal, + {ValidatePrincipalOptions::AllowSystem})) { + LogFailedPrincipalValidationInfo(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; + cwi.hasSiblings() = false; + + // 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 discarded. + if (aParent.IsDiscarded()) { + 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"); + } + + RefPtr<BrowsingContext> newBCOpener = newBC->GetOpener(); + if (newBCOpener && aParent.get() != newBCOpener) { + 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, aParent.get(), !!newBCOpener, aChromeFlags, aCalledFromJS, + aWidthSpecified, aForPrinting, aForPrintPreview, aURIToLoad, aFeatures, + aFullZoom, 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()); + + newTab->SwapFrameScriptsFrom(cwi.frameScripts()); + newTab->MaybeShowFrame(); + + nsCOMPtr<nsIWidget> widget = newTab->GetWidget(); + if (widget) { + cwi.dimensions() = newTab->GetDimensionInfo(); + } + + cwi.maxTouchPoints() = newTab->GetMaxTouchPoints(); + cwi.hasSiblings() = (openLocation == nsIBrowserDOMWindow::OPEN_NEWTAB); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvCreateWindowInDifferentProcess( + PBrowserParent* aThisTab, const MaybeDiscarded<BrowsingContext>& aParent, + const uint32_t& aChromeFlags, const bool& aCalledFromJS, + const bool& aWidthSpecified, nsIURI* aURIToLoad, const nsCString& aFeatures, + const float& aFullZoom, const nsString& 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 discarded. + if (NS_WARN_IF(aParent.IsDiscarded())) { + 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, aParent.get(), /* aSetOpener = */ false, aChromeFlags, + aCalledFromJS, aWidthSpecified, /* aForPrinting = */ false, + /* aForPrintPreview = */ false, aURIToLoad, aFeatures, aFullZoom, + /* 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 nsCString& aProfile) { +#ifdef MOZ_GECKO_PROFILER + nsCOMPtr<nsIProfiler> profiler( + do_GetService("@mozilla.org/tools/profiler;1")); + profiler->ReceiveShutdownProfile(aProfile); +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvGetGraphicsDeviceInitData( + ContentDeviceData* aOut) { + gfxPlatform::GetPlatform()->BuildContentDeviceData(aOut); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvGetOutputColorProfileData( + nsTArray<uint8_t>* aOutputColorProfileData) { + (*aOutputColorProfileData) = + gfxPlatform::GetPlatform()->GetPlatformCMSOutputProfileData(); + 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 mozilla::fontlist::Pointer& aFacePtr, + const gfxSparseBitSet& aMap) { + auto* fontList = gfxPlatformFontList::PlatformFontList(); + MOZ_RELEASE_ASSERT(fontList, "gfxPlatformFontList not initialized?"); + fontList->SetCharacterMap(aGeneration, aFacePtr, 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 mozilla::fontlist::Pointer& aFamilyPtr) { + auto* fontList = gfxPlatformFontList::PlatformFontList(); + MOZ_RELEASE_ASSERT(fontList, "gfxPlatformFontList not initialized?"); + fontList->SetupFamilyCharMap(aGeneration, aFamilyPtr); + 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_NO_REASON(this); + } + nsHyphenationManager::Instance()->ShareHyphDictToProcess( + aURI, Pid(), aOutHandle, aOutSize); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvGraphicsError( + const nsCString& aError) { + gfx::LogForwarder* lf = gfx::Factory::GetLogForwarder(); + if (lf) { + std::stringstream message; + message << "CP+" << aError.get(); + 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: + MOZ_ASSERT_UNREACHABLE("unknown crash guard type"); + return IPC_FAIL_NO_REASON(this); + } + + 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 nsString& 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 nsCString& aScope, const IPC::Principal& aPrincipal, + const nsString& aMessageId) { + if (!ValidatePrincipal(aPrincipal)) { + LogFailedPrincipalValidationInfo(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 nsCString& aScope, const IPC::Principal& aPrincipal, + const nsString& aMessageId, nsTArray<uint8_t>&& aData) { + if (!ValidatePrincipal(aPrincipal)) { + LogFailedPrincipalValidationInfo(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 nsCString& aScope, const IPC::Principal& aPrincipal) { + if (!ValidatePrincipal(aPrincipal)) { + LogFailedPrincipalValidationInfo(aPrincipal, __func__); + } + PushSubscriptionChangeDispatcher dispatcher(aScope, aPrincipal); + Unused << NS_WARN_IF(NS_FAILED(dispatcher.NotifyObserversAndWorkers())); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvPushError( + const nsCString& aScope, const IPC::Principal& aPrincipal, + const nsString& aMessage, const uint32_t& aFlags) { + if (!ValidatePrincipal(aPrincipal)) { + LogFailedPrincipalValidationInfo(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 nsCString& aScope, const IPC::Principal& aPrincipal) { + if (!ValidatePrincipal(aPrincipal)) { + LogFailedPrincipalValidationInfo(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 Maybe<nsID>& aAgentClusterId, ContentParent* aIgnoreThisCP) { + nsAutoCString origin; + nsresult rv = aPrincipal->GetOrigin(origin); + NS_ENSURE_SUCCESS_VOID(rv); + + uint64_t originHash = ComputeLoadedOriginHash(aPrincipal); + + bool toBeSent = StringBeginsWith(origin, "moz-extension://"_ns) || + aPrincipal->IsSystemPrincipal(); + + nsCString uri(aURI); + IPC::Principal principal(aPrincipal); + + for (auto* cp : AllProcesses(eLive)) { + if (cp != aIgnoreThisCP) { + if (!toBeSent && !cp->mLoadedOriginHashes.Contains(originHash)) { + continue; + } + + nsresult rv = cp->TransmitPermissionsForPrincipal(principal); + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + + IPCBlob ipcBlob; + rv = IPCBlobUtils::Serialize(aBlobImpl, cp, ipcBlob); + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + + Unused << cp->SendBlobURLRegistration(uri, ipcBlob, principal, + aAgentClusterId); + } + } +} + +/* static */ +void ContentParent::BroadcastBlobURLUnregistration( + const nsACString& aURI, nsIPrincipal* aPrincipal, + ContentParent* aIgnoreThisCP) { + nsAutoCString origin; + nsresult rv = aPrincipal->GetOrigin(origin); + NS_ENSURE_SUCCESS_VOID(rv); + + uint64_t originHash = ComputeLoadedOriginHash(aPrincipal); + + bool toBeSent = StringBeginsWith(origin, "moz-extension://"_ns) || + aPrincipal->IsSystemPrincipal(); + + 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 nsCString& aURI, const IPCBlob& aBlob, const Principal& aPrincipal, + const Maybe<nsID>& aAgentClusterId) { + if (!ValidatePrincipal(aPrincipal, {ValidatePrincipalOptions::AllowSystem})) { + LogFailedPrincipalValidationInfo(aPrincipal, __func__); + } + RefPtr<BlobImpl> blobImpl = IPCBlobUtils::Deserialize(aBlob); + if (NS_WARN_IF(!blobImpl)) { + return IPC_FAIL_NO_REASON(this); + } + + BlobURLProtocolHandler::AddDataEntry(aURI, aPrincipal, aAgentClusterId, + blobImpl); + BroadcastBlobURLRegistration(aURI, blobImpl, aPrincipal, aAgentClusterId, + 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 nsCString& aURI, const Principal& aPrincipal) { + if (!ValidatePrincipal(aPrincipal, {ValidatePrincipalOptions::AllowSystem})) { + LogFailedPrincipalValidationInfo(aPrincipal, __func__); + } + BlobURLProtocolHandler::RemoveDataEntry(aURI, false /* Don't broadcast */); + BroadcastBlobURLUnregistration(aURI, aPrincipal, this); + mBlobURLs.RemoveElement(aURI); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvGetA11yContentId( + uint32_t* aContentId) { +#if defined(XP_WIN) && defined(ACCESSIBILITY) + *aContentId = a11y::AccessibleWrap::GetContentProcessIdFor(ChildID()); + MOZ_ASSERT(*aContentId); + return IPC_OK(); +#else + return IPC_FAIL_NO_REASON(this); +#endif +} + +mozilla::ipc::IPCResult ContentParent::RecvA11yHandlerControl( + const uint32_t& aPid, const IHandlerControlHolder& aHandlerControl) { +#if defined(XP_WIN) && defined(ACCESSIBILITY) + MOZ_ASSERT(!aHandlerControl.IsNull()); + RefPtr<IHandlerControl> proxy(aHandlerControl.Get()); + a11y::AccessibleWrap::SetHandlerControl(aPid, std::move(proxy)); + return IPC_OK(); +#else + return IPC_FAIL_NO_REASON(this); +#endif +} + +bool ContentParent::HandleWindowsMessages(const Message& aMsg) const { + MOZ_ASSERT(aMsg.is_sync()); + +#ifdef ACCESSIBILITY + // a11y messages can be triggered by windows messages, which means if we + // allow handling windows messages while we wait for the response to a sync + // a11y message we can reenter the ipc message sending code. + if (a11y::PDocAccessible::PDocAccessibleStart < aMsg.type() && + a11y::PDocAccessible::PDocAccessibleEnd > aMsg.type()) { + return false; + } +#endif + + return true; +} + +mozilla::ipc::IPCResult ContentParent::RecvGetFilesRequest( + const nsID& aUUID, const nsString& aDirectoryPath, + const bool& aRecursiveFlag) { + MOZ_ASSERT(!mGetFilesPendingRequests.GetWeak(aUUID)); + + if (!mozilla::Preferences::GetBool("dom.filesystem.pathcheck.disabled", + false)) { + RefPtr<FileSystemSecurity> fss = FileSystemSecurity::Get(); + if (NS_WARN_IF(!fss || !fss->ContentProcessHasAccessTo(ChildID(), + aDirectoryPath))) { + return IPC_FAIL_NO_REASON(this); + } + } + + 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_NO_REASON(this); + } + return IPC_OK(); + } + + mGetFilesPendingRequests.Put(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, const layers::LayersObserverEpoch& aEpoch) { + if (!mHangMonitorActor) { + return; + } + ProcessHangMonitor::PaintWhileInterruptingJS(mHangMonitorActor, + aBrowserParent, aEpoch); +} + +void ContentParent::CancelContentJSExecutionIfRunning( + BrowserParent* aBrowserParent, nsIRemoteTab::NavigationType aNavigationType, + const CancelContentJSOptions& aCancelContentJSOptions) { + if (!mHangMonitorActor) { + return; + } + + ProcessHangMonitor::CancelContentJSExecutionIfRunning( + mHangMonitorActor, aBrowserParent, aNavigationType, + aCancelContentJSOptions); +} + +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; + rv = ssm->GetChannelResultPrincipal(aChannel, getter_AddRefs(principal)); + 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); + + rv = TransmitPermissionsForPrincipal(principal); + 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); + } + + return NS_OK; +} + +void ContentParent::TransmitBlobURLsForPrincipal(nsIPrincipal* aPrincipal) { + uint64_t originHash = ComputeLoadedOriginHash(aPrincipal); + + if (!mLoadedOriginHashes.Contains(originHash)) { + mLoadedOriginHashes.AppendElement(originHash); + + nsTArray<BlobURLRegistrationData> registrations; + BlobURLProtocolHandler::ForEachBlobURL( + [&](BlobImpl* aBlobImpl, nsIPrincipal* aBlobPrincipal, + const Maybe<nsID>& aAgentClusterId, const nsACString& aURI, + bool aRevoked) { + if (!aPrincipal->Subsumes(aBlobPrincipal)) { + return true; + } + + IPCBlob ipcBlob; + nsresult rv = IPCBlobUtils::Serialize(aBlobImpl, this, ipcBlob); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + registrations.AppendElement(BlobURLRegistrationData( + nsCString(aURI), ipcBlob, aPrincipal, aAgentClusterId, aRevoked)); + + rv = TransmitPermissionsForPrincipal(aPrincipal); + Unused << NS_WARN_IF(NS_FAILED(rv)); + return true; + }); + + if (!registrations.IsEmpty()) { + Unused << SendInitBlobURLs(registrations); + } + } +} + +void ContentParent::TransmitBlobDataIfBlobURL(nsIURI* aURI, + nsIPrincipal* aPrincipal) { + MOZ_ASSERT(aURI); + MOZ_ASSERT(aPrincipal); + + if (!IsBlobURI(aURI)) { + return; + } + + TransmitBlobURLsForPrincipal(aPrincipal); +} + +void ContentParent::EnsurePermissionsByKey(const nsCString& aKey, + const nsCString& 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.Contains(aKey)) { + return; + } + mActivePermissionKeys.PutEntry(aKey); + + 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); +} + +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(); +} + +////////////////////////////////////////////////////////////////// +// PURLClassifierParent + +PURLClassifierParent* ContentParent::AllocPURLClassifierParent( + const Principal& 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, const Principal& 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)) { + LogFailedPrincipalValidationInfo(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) { + NS_WARNING("aURI should not be null"); + return IPC_FAIL_NO_REASON(this); + } + + 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; +} + +PLoginReputationParent* ContentParent::AllocPLoginReputationParent( + nsIURI* aURI) { + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr<LoginReputationParent> actor = new LoginReputationParent(); + return actor.forget().take(); +} + +mozilla::ipc::IPCResult ContentParent::RecvPLoginReputationConstructor( + PLoginReputationParent* aActor, nsIURI* aURI) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aActor); + + if (!aURI) { + return IPC_FAIL_NO_REASON(this); + } + + auto* actor = static_cast<LoginReputationParent*>(aActor); + return actor->QueryReputation(aURI); +} + +bool ContentParent::DeallocPLoginReputationParent( + PLoginReputationParent* aActor) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aActor); + + RefPtr<LoginReputationParent> actor = + dont_AddRef(static_cast<LoginReputationParent*>(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_NO_REASON(this); + } + return IPC_OK(); +} + +bool ContentParent::DeallocPSessionStorageObserverParent( + PSessionStorageObserverParent* aActor) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aActor); + + return mozilla::dom::DeallocPSessionStorageObserverParent(aActor); +} + +mozilla::ipc::IPCResult ContentParent::RecvMaybeReloadPlugins() { + RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst(); + pluginHost->ReloadPlugins(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvDeviceReset() { + GPUProcessManager* pm = GPUProcessManager::Get(); + if (pm) { + pm->SimulateDeviceReset(); + } + + return IPC_OK(); +} + +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( + const nsACString& aSerializedCert, uint32_t aFlags, + const nsACString& aHostName, int32_t aPort, bool aIsTemporary, + AddCertExceptionResolver&& aResolver) { + nsCOMPtr<nsISupports> certObj; + nsresult rv = NS_DeserializeObject(aSerializedCert, getter_AddRefs(certObj)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIX509Cert> cert = do_QueryInterface(certObj); + if (!cert) { + rv = NS_ERROR_INVALID_ARG; + } else { + nsCOMPtr<nsICertOverrideService> overrideService = + do_GetService(NS_CERTOVERRIDE_CONTRACTID); + if (!overrideService) { + rv = NS_ERROR_FAILURE; + } else { + rv = overrideService->RememberValidityOverride(aHostName, aPort, cert, + aFlags, aIsTemporary); + } + } + } + aResolver(rv); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +ContentParent::RecvAutomaticStorageAccessPermissionCanBeGranted( + const Principal& aPrincipal, + AutomaticStorageAccessPermissionCanBeGrantedResolver&& aResolver) { + if (!ValidatePrincipal(aPrincipal)) { + LogFailedPrincipalValidationInfo(aPrincipal, __func__); + } + aResolver(Document::AutomaticStorageAccessPermissionCanBeGranted(aPrincipal)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +ContentParent::RecvStorageAccessPermissionGrantedForOrigin( + uint64_t aTopLevelWindowId, + const MaybeDiscarded<BrowsingContext>& aParentContext, + const Principal& aTrackingPrincipal, const nsCString& aTrackingOrigin, + const int& aAllowMode, + const Maybe<ContentBlockingNotifier::StorageAccessPermissionGrantedReason>& + aReason, + StorageAccessPermissionGrantedForOriginResolver&& aResolver) { + if (aParentContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + + // 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()); + } + + ContentBlocking::SaveAccessForOriginOnParentProcess( + aTopLevelWindowId, aParentContext.get_canonical(), aTrackingPrincipal, + aTrackingOrigin, aAllowMode) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [aResolver = std::move(aResolver)]( + ContentBlocking::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, const Principal& aTrackingPrincipal, + const nsCString& aTrackingOrigin, uint32_t aCookieBehavior, + const ContentBlockingNotifier::StorageAccessPermissionGrantedReason& + aReason, + CompleteAllowAccessForResolver&& aResolver) { + if (aParentContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + + ContentBlocking::CompleteAllowAccessFor( + aParentContext.get_canonical(), aTopLevelWindowId, aTrackingPrincipal, + aTrackingOrigin, aCookieBehavior, aReason, nullptr) + ->Then(GetCurrentSerialEventTarget(), __func__, + [aResolver = std::move(aResolver)]( + ContentBlocking::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::RecvStoreUserInteractionAsPermission( + const Principal& aPrincipal) { + if (!ValidatePrincipal(aPrincipal)) { + LogFailedPrincipalValidationInfo(aPrincipal, __func__); + } + ContentBlockingUserInteraction::Observe(aPrincipal); + 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::RecvGetModulesTrust( + ModulePaths&& aModPaths, bool aRunAtNormalPriority, + GetModulesTrustResolver&& aResolver) { +#if defined(XP_WIN) + 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(); +#else + return IPC_FAIL(this, "Unsupported on this platform"); +#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"); + } + + BrowsingContext::CreateFromIPC(std::move(aInit), group, this); + return IPC_OK(); +} + +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, + DiscardBrowsingContextResolver&& aResolve) { + if (!aContext.IsNullOrDiscarded()) { + RefPtr<CanonicalBrowsingContext> context = aContext.get_canonical(); + if (!CheckBrowsingContextEmbedder(context, "discard")) { + return IPC_FAIL(this, "Illegal Discard attempt"); + } + + context->Detach(/* aFromIPC */ true); + } + + // 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::RegisterRemoteWorkerActor() { + auto lock = mRemoteWorkerActorData.Lock(); + ++lock->mCount; +} + +void ContentParent::UnregisterRemoveWorkerActor() { + MOZ_ASSERT(NS_IsMainThread()); + + { + auto lock = mRemoteWorkerActorData.Lock(); + if (--lock->mCount) { + 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. + + ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); + ContentParent* cp = + cpm->GetContentProcessById(ContentParentId(context->OwnerProcessId())); + 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(); + } + CanonicalBrowsingContext* context = aContext.get_canonical(); + + ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); + ContentParent* cp = + cpm->GetContentProcessById(ContentParentId(context->OwnerProcessId())); + Unused << cp->SendWindowFocus(context, aCallerType, aActionId); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvWindowBlur( + 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(); + + ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); + ContentParent* cp = + cpm->GetContentProcessById(ContentParentId(context->OwnerProcessId())); + Unused << cp->SendWindowBlur(context); + 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(); + } + + CanonicalBrowsingContext* context = aContext.get_canonical(); + + ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); + ContentParent* cp = + cpm->GetContentProcessById(ContentParentId(context->OwnerProcessId())); + Unused << cp->SendRaiseWindow(context, aCallerType, aActionId); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvAdjustWindowFocus( + const MaybeDiscarded<BrowsingContext>& aContext, bool aCheckPermission, + bool aIsVisible) { + 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(); + BrowsingContext* parent = context->GetParent(); + if (!parent) { + return IPC_OK(); + } + + CanonicalBrowsingContext* canonicalParent = parent->Canonical(); + + ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); + ContentParent* cp = cpm->GetContentProcessById( + ContentParentId(canonicalParent->OwnerProcessId())); + Unused << cp->SendAdjustWindowFocus(context, aCheckPermission, aIsVisible); + 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(); + + ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); + ContentParent* cp = + cpm->GetContentProcessById(ContentParentId(context->OwnerProcessId())); + Unused << cp->SendClearFocus(context); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvSetFocusedBrowsingContext( + 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(); + + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + fm->SetFocusedBrowsingContextInChrome(context); + BrowserParent::UpdateFocusFromBrowsingContext(); + } + + context->Group()->EachOtherParent(this, [&](ContentParent* aParent) { + Unused << aParent->SendSetFocusedBrowsingContext(context); + }); + + 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(); + } + 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( + fm->GetActiveBrowsingContextInChrome(), aActionId); + 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(); + } + 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( + fm->GetActiveBrowsingContextInChrome(), aActionId); + 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(); + } + CanonicalBrowsingContext* context = aContext.get_canonical(); + + ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); + + ContentParent* cp = + cpm->GetContentProcessById(ContentParentId(context->OwnerProcessId())); + 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(); + } + CanonicalBrowsingContext* context = aContext.get_canonical(); + ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); + + 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) { + 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(); + } + + CanonicalBrowsingContext* focusedBrowsingContext = + aFocusedBrowsingContext.get_canonical(); + + ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); + + // 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."); + ContentParent* cp = cpm->GetContentProcessById(ContentParentId( + aBrowsingContextToClear.get_canonical()->OwnerProcessId())); + Unused << cp->SendSetFocusedElement(aBrowsingContextToClear, false); + } else if (ancestorDifferent) { + ContentParent* cp = cpm->GetContentProcessById(ContentParentId( + aAncestorBrowsingContextToFocus.get_canonical()->OwnerProcessId())); + Unused << cp->SendSetFocusedElement(aAncestorBrowsingContextToFocus, true); + } + + ContentParent* cp = cpm->GetContentProcessById( + ContentParentId(focusedBrowsingContext->OwnerProcessId())); + 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(); + + ContentProcessManager* cpm = ContentProcessManager::GetSingleton(); + + ContentParent* cp = + cpm->GetContentProcessById(ContentParentId(context->OwnerProcessId())); + 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) { + UnpackClonedMessageDataForParent(aMessage, messageFromChild); + + ClonedMessageData clonedMessageData; + if (BuildClonedMessageDataForParent(cp, 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 + // BrowisngContextGroup when unsubscribing, so we don't need to do it here. + mGroups.RemoveEntry(aGroup); +} + +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); +} + +PParentToChildStreamParent* ContentParent::SendPParentToChildStreamConstructor( + PParentToChildStreamParent* aActor) { + MOZ_ASSERT(NS_IsMainThread()); + return PContentParent::SendPParentToChildStreamConstructor(aActor); +} + +PFileDescriptorSetParent* ContentParent::SendPFileDescriptorSetConstructor( + const FileDescriptor& aFD) { + MOZ_ASSERT(NS_IsMainThread()); + return PContentParent::SendPFileDescriptorSetConstructor(aFD); +} + +mozilla::ipc::IPCResult ContentParent::RecvBlobURLDataRequest( + const nsCString& aBlobURL, nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aLoadingPrincipal, const OriginAttributes& aOriginAttributes, + const Maybe<nsID>& aAgentClusterId, + 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, aAgentClusterId, + true /* AlsoIfRevoked */)) { + aResolver(NS_ERROR_DOM_BAD_URI); + return IPC_OK(); + } + + IPCBlob ipcBlob; + nsresult rv = IPCBlobUtils::Serialize(blobImpl, this, 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<RefPtr<nsDocShellLoadState>> loadState; + Maybe<bool> reloadActiveEntry; + if (!aContext.IsDiscarded()) { + aContext.get_canonical()->NotifyOnHistoryReload( + aForceReload, canReload, loadState, reloadActiveEntry); + } + aResolver(Tuple<const bool&, const Maybe<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) { + if (!aContext.IsDiscarded()) { + aContext.get_canonical()->SessionHistoryCommit( + aLoadID, aChangeID, aLoadType, aPersist, aCloneEntryChildren); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvHistoryGo( + const MaybeDiscarded<BrowsingContext>& aContext, int32_t aOffset, + uint64_t aHistoryEpoch, bool aRequireUserInteraction, + HistoryGoResolver&& aResolveRequestedIndex) { + if (!aContext.IsDiscarded()) { + aContext.get_canonical()->HistoryGo( + aOffset, aHistoryEpoch, aRequireUserInteraction, Some(ChildID()), + std::move(aResolveRequestedIndex)); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvSessionHistoryUpdate( + const MaybeDiscarded<BrowsingContext>& aContext, const int32_t& aIndex, + const int32_t& aLength, const nsID& aChangeID) { + 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(); + context->Group()->EachParent([&](ContentParent* aParent) { + Unused << aParent->SendHistoryCommitIndexAndLength(aContext, aIndex, + aLength, aChangeID); + }); + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvSynchronizeLayoutHistoryState( + const MaybeDiscarded<BrowsingContext>& aContext, + nsILayoutHistoryState* aState) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + + SessionHistoryEntry* entry = + aContext.get_canonical()->GetActiveSessionHistoryEntry(); + if (entry) { + entry->SetLayoutHistoryState(aState); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvSessionHistoryEntryTitle( + const MaybeDiscarded<BrowsingContext>& aContext, const nsString& 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::RecvSessionHistoryEntryStoreWindowNameInContiguousEntries( + const MaybeDiscarded<BrowsingContext>& aContext, const nsString& 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::RecvGetLoadingSessionHistoryInfoFromParent( + const MaybeDiscarded<BrowsingContext>& aContext, + GetLoadingSessionHistoryInfoFromParentResolver&& aResolver) { + if (aContext.IsNullOrDiscarded()) { + return IPC_OK(); + } + + Maybe<LoadingSessionHistoryInfo> info; + int32_t requestedIndex = -1; + int32_t sessionHistoryLength = 0; + aContext.get_canonical()->GetLoadingSessionHistoryInfoFromParent( + info, &requestedIndex, &sessionHistoryLength); + aResolver( + Tuple<const mozilla::Maybe<LoadingSessionHistoryInfo>&, const int32_t&, + const int32_t&>(info, requestedIndex, sessionHistoryLength)); + + 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.IsDiscarded()) { + 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.IsDiscarded()) { + aContext.get_canonical()->ReplaceActiveSessionHistoryEntry(&aInfo); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult +ContentParent::RecvRemoveDynEntriesFromActiveSessionHistoryEntry( + const MaybeDiscarded<BrowsingContext>& aContext) { + if (!aContext.IsDiscarded()) { + aContext.get_canonical()->RemoveDynEntriesFromActiveSessionHistoryEntry(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvRemoveFromSessionHistory( + const MaybeDiscarded<BrowsingContext>& aContext) { + if (!aContext.IsDiscarded()) { + aContext.get_canonical()->RemoveFromSessionHistory(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult ContentParent::RecvHistoryReload( + const MaybeDiscarded<BrowsingContext>& aContext, + const uint32_t aReloadFlags) { + if (!aContext.IsDiscarded()) { + 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->BorrowFromClonedMessageDataForParent(*aData); + } + Maybe<StructuredCloneData> stack; + if (aStack) { + stack.emplace(); + stack->BorrowFromClonedMessageDataForParent(*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; +} + +already_AddRefed<JSActor> ContentParent::InitJSActor( + JS::HandleObject 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) { +#ifdef MOZ_GLEAN + glean::FOGData(std::move(buf)); +#endif + 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; } + +} // 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; +} diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h new file mode 100644 index 0000000000..3cb4ca0eed --- /dev/null +++ b/dom/ipc/ContentParent.h @@ -0,0 +1,1671 @@ +/* -*- 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/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/ipc/PParentToChildStreamParent.h" +#include "mozilla/ipc/PChildToParentStreamParent.h" +#include "mozilla/Attributes.h" +#include "mozilla/DataMutex.h" +#include "mozilla/FileUtils.h" +#include "mozilla/HalTypes.h" +#include "mozilla/LinkedList.h" +#include "mozilla/MemoryReportingProcess.h" +#include "mozilla/MozPromise.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" + +#include "nsClassHashtable.h" +#include "nsDataHashtable.h" +#include "nsPluginTags.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; + +namespace mozilla { +class PRemoteSpellcheckEngineParent; + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) +class SandboxBroker; +class SandboxBrokerPolicyFactory; +#endif + +class PreallocatedProcessManagerImpl; +class BenchmarkStorageParent; + +using mozilla::loader::PScriptCacheParent; + +namespace embedding { +class PrintingParent; +} + +namespace ipc { +class CrashReporterHost; +class PFileDescriptorSetParent; +class TestShellParent; +#ifdef FUZZING +class ProtocolFuzzerHelper; +#endif +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; +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 mozilla::ipc::ParentToChildStreamActorManager, + public ProcessActor { + typedef mozilla::ipc::GeckoChildProcessHost GeckoChildProcessHost; + typedef mozilla::ipc::PFileDescriptorSetParent PFileDescriptorSetParent; + 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; +#ifdef FUZZING + friend class mozilla::ipc::ProtocolFuzzerHelper; +#endif + + public: + using LaunchError = mozilla::ipc::LaunchError; + using LaunchPromise = + mozilla::MozPromise<RefPtr<ContentParent>, LaunchError, false>; + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_CONTENTPARENT_IID) + + static LogModule* GetLog(); + + /** + * Create a subprocess suitable for use later as a content process. + */ + static RefPtr<LaunchPromise> PreallocateProcess(); + + /** + * 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(); + + /** + * 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 a JS plugin. aPluginID is the id of the + * JS plugin + * (@see nsFakePlugin::mId). There is a maximum of one process per JS plugin. + */ + static already_AddRefed<ContentParent> GetNewOrUsedJSPluginProcess( + uint32_t aPluginID, const hal::ProcessPriority& aPriority); + + /** + * 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 BroadcastThemeUpdate(widget::ThemeChangeKind); + + static void BroadcastMediaCodecsSupportedUpdate( + RemoteDecodeIn aLocation, + const PDMFactory::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) { + 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); + +#if defined(XP_WIN) + /** + * Windows helper for firing off an update window request to a plugin + * instance. + * + * aWidget - the eWindowType_plugin_ipc_chrome widget associated with + * this plugin window. + */ + static void SendAsyncUpdate(nsIWidget* aWidget); +#endif + + mozilla::ipc::IPCResult RecvCreateGMPService(); + + mozilla::ipc::IPCResult RecvLoadPlugin( + const uint32_t& aPluginId, nsresult* aRv, uint32_t* aRunID, + Endpoint<PPluginModuleParent>* aEndpoint); + + mozilla::ipc::IPCResult RecvMaybeReloadPlugins(); + + mozilla::ipc::IPCResult RecvConnectPluginBridge( + const uint32_t& aPluginId, nsresult* aRv, + Endpoint<PPluginModuleParent>* aEndpoint); + + mozilla::ipc::IPCResult RecvUngrabPointer(const uint32_t& aTime); + + mozilla::ipc::IPCResult RecvRemovePermission(const IPC::Principal& aPrincipal, + const nsCString& aPermissionType, + nsresult* aRv); + + 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; + + /** 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 IsDead() const { return mLifecycleState == LifecycleState::DEAD; } + + bool IsForBrowser() const { return mIsForBrowser; } + bool IsForJSPlugin() const { + return mJSPluginID != nsFakePluginTag::NOT_JSPLUGIN; + } + + GeckoChildProcessHost* Process() const { return mSubprocess; } + + nsIContentProcessInfo* ScriptableHelper() const { return mScriptableHelper; } + + mozilla::dom::ProcessMessageManager* GetMessageManager() const { + return mMessageManager; + } + + bool NeedsPermissionsUpdate(const nsACString& aPermissionKey) const; + + /** + * 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); + + PNeckoParent* AllocPNeckoParent(); + + virtual mozilla::ipc::IPCResult RecvPNeckoConstructor( + PNeckoParent* aActor) override { + return PContentParent::RecvPNeckoConstructor(aActor); + } + + PPrintingParent* AllocPPrintingParent(); + + bool DeallocPPrintingParent(PPrintingParent* aActor); + +#if defined(NS_PRINTING) + /** + * @return the PrintingParent for this ContentParent. + */ + already_AddRefed<embedding::PrintingParent> GetPrintingParent(); +#endif + + mozilla::ipc::IPCResult RecvInitStreamFilter( + const uint64_t& aChannelId, const nsString& aAddonId, + InitStreamFilterResolver&& aResolver); + + PChildToParentStreamParent* AllocPChildToParentStreamParent(); + bool DeallocPChildToParentStreamParent(PChildToParentStreamParent* aActor); + + PParentToChildStreamParent* AllocPParentToChildStreamParent(); + bool DeallocPParentToChildStreamParent(PParentToChildStreamParent* aActor); + + PHalParent* AllocPHalParent(); + + virtual mozilla::ipc::IPCResult RecvPHalConstructor( + PHalParent* aActor) override { + return PContentParent::RecvPHalConstructor(aActor); + } + + PHeapSnapshotTempFileHelperParent* AllocPHeapSnapshotTempFileHelperParent(); + + PRemoteSpellcheckEngineParent* AllocPRemoteSpellcheckEngineParent(); + + mozilla::ipc::IPCResult RecvRecordingDeviceEvents( + const nsString& aRecordingStatus, const nsString& aPageURL, + const bool& aIsAudio, const bool& aIsVideo); + + bool CycleCollectWithLogs(bool aDumpAllTraces, + nsICycleCollectorLogSink* aSink, + nsIDumpGCAndCCLogsCallback* aCallback); + + mozilla::ipc::IPCResult RecvNotifyTabDestroying(const TabId& aTabId, + const ContentParentId& aCpId); + + already_AddRefed<POfflineCacheUpdateParent> AllocPOfflineCacheUpdateParent( + nsIURI* aManifestURI, nsIURI* aDocumentURI, + const PrincipalInfo& aLoadingPrincipalInfo, const bool& aStickDocument, + const CookieJarSettingsArgs& aCookieJarSettingsArgs); + + virtual mozilla::ipc::IPCResult RecvPOfflineCacheUpdateConstructor( + POfflineCacheUpdateParent* aActor, nsIURI* aManifestURI, + nsIURI* aDocumentURI, const PrincipalInfo& aLoadingPrincipal, + const bool& stickDocument, + const CookieJarSettingsArgs& aCookieJarSettingsArgs) override; + + mozilla::ipc::IPCResult RecvSetOfflinePermission( + const IPC::Principal& principal); + + mozilla::ipc::IPCResult RecvFinishShutdown(); + + void MaybeInvokeDragSession(BrowserParent* aParent); + + PContentPermissionRequestParent* AllocPContentPermissionRequestParent( + const nsTArray<PermissionRequest>& aRequests, + const IPC::Principal& aPrincipal, + const IPC::Principal& aTopLevelPrincipal, + const bool& aIsHandlingUserInput, + const bool& aMaybeUnsafePermissionDelegate, const TabId& aTabId); + + bool DeallocPContentPermissionRequestParent( + PContentPermissionRequestParent* actor); + + virtual bool HandleWindowsMessages(const Message& aMsg) const override; + + 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& aWidthSpecified, const bool& aForPrinting, + const bool& aForWindowDotPrint, nsIURI* aURIToLoad, + const nsCString& aFeatures, const float& aFullZoom, + const IPC::Principal& 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, + const bool& aWidthSpecified, nsIURI* aURIToLoad, + const nsCString& aFeatures, const float& aFullZoom, const nsString& aName, + nsIPrincipal* aTriggeringPrincipal, nsIContentSecurityPolicy* aCsp, + nsIReferrerInfo* aReferrerInfo, + const OriginAttributes& aOriginAttributes); + + static void BroadcastBlobURLRegistration( + const nsACString& aURI, BlobImpl* aBlobImpl, nsIPrincipal* aPrincipal, + const Maybe<nsID>& aAgentClusterId, + ContentParent* aIgnoreThisCP = nullptr); + + static void BroadcastBlobURLUnregistration( + const nsACString& aURI, nsIPrincipal* aPrincipal, + ContentParent* aIgnoreThisCP = nullptr); + + mozilla::ipc::IPCResult RecvStoreAndBroadcastBlobURLRegistration( + const nsCString& aURI, const IPCBlob& aBlob, const Principal& aPrincipal, + const Maybe<nsID>& aAgentCluster); + + mozilla::ipc::IPCResult RecvUnstoreAndBroadcastBlobURLUnregistration( + const nsCString& aURI, const Principal& aPrincipal); + + mozilla::ipc::IPCResult RecvGetA11yContentId(uint32_t* aContentId); + + mozilla::ipc::IPCResult RecvA11yHandlerControl( + const uint32_t& aPid, const IHandlerControlHolder& aHandlerControl); + + virtual int32_t Pid() const override; + + // PURLClassifierParent. + PURLClassifierParent* AllocPURLClassifierParent(const Principal& aPrincipal, + bool* aSuccess); + virtual mozilla::ipc::IPCResult RecvPURLClassifierConstructor( + PURLClassifierParent* aActor, const Principal& 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; + + PLoginReputationParent* AllocPLoginReputationParent(nsIURI* aURI); + + virtual mozilla::ipc::IPCResult RecvPLoginReputationConstructor( + PLoginReputationParent* aActor, nsIURI* aURI) override; + + bool DeallocPLoginReputationParent(PLoginReputationParent* aActor); + + 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* aBrowserParent, + const layers::LayersObserverEpoch& aEpoch); + + void CancelContentJSExecutionIfRunning( + BrowserParent* aBrowserParent, + nsIRemoteTab::NavigationType aNavigationType, + const CancelContentJSOptions& aCancelContentJSOptions); + + // 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 + // aPrincipal 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, nsIPrincipal* aPrincipal); + + 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, + 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); + mozilla::ipc::IPCResult RecvRaiseWindow( + const MaybeDiscarded<BrowsingContext>& aContext, CallerType aCallerType, + uint64_t aActionId); + mozilla::ipc::IPCResult RecvAdjustWindowFocus( + const MaybeDiscarded<BrowsingContext>& aContext, bool aCheckPermission, + bool aIsVisible); + mozilla::ipc::IPCResult RecvClearFocus( + const MaybeDiscarded<BrowsingContext>& aContext); + mozilla::ipc::IPCResult RecvSetFocusedBrowsingContext( + const MaybeDiscarded<BrowsingContext>& aContext); + 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) + + PParentToChildStreamParent* SendPParentToChildStreamConstructor( + PParentToChildStreamParent* aActor) override; + PFileDescriptorSetParent* SendPFileDescriptorSetConstructor( + const FileDescriptor& aFD) override; + + mozilla::ipc::IPCResult RecvBlobURLDataRequest( + const nsCString& aBlobURL, nsIPrincipal* pTriggeringPrincipal, + nsIPrincipal* pLoadingPrincipal, + const OriginAttributes& aOriginAttributes, + const Maybe<nsID>& aAgentClusterId, + BlobURLDataRequestResolver&& aResolver); + + protected: + bool CheckBrowsingContextEmbedder(CanonicalBrowsingContext* aBC, + const char* aOperation) const; + + void OnChannelConnected(int32_t pid) override; + + void ActorDestroy(ActorDestroyReason why) override; + void ActorDealloc() 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 UniquePtr<nsTArray<ContentParent*>> sPrivateContent; + static UniquePtr<nsDataHashtable<nsUint32HashKey, ContentParent*>> + sJSPluginContentParents; + static UniquePtr<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 UniquePtr<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& aWidthSpecified, const bool& aForPrinting, + const bool& aForWindowDotPrint, nsIURI* aURIToLoad, + const nsCString& aFeatures, const float& aFullZoom, + BrowserParent* aNextRemoteBrowser, const nsString& aName, + nsresult& aResult, nsCOMPtr<nsIRemoteTab>& aNewRemoteTab, + bool* aWindowIsNew, int32_t& aOpenLocation, + nsIPrincipal* aTriggeringPrincipal, nsIReferrerInfo* aReferrerInfo, + bool aLoadUri, nsIContentSecurityPolicy* aCsp, + const OriginAttributes& aOriginAttributes); + + explicit ContentParent(int32_t aPluginID) : ContentParent(""_ns, aPluginID) {} + explicit ContentParent(const nsACString& aRemoteType) + : ContentParent(aRemoteType, nsFakePluginTag::NOT_JSPLUGIN) {} + + ContentParent(const nsACString& aRemoteType, int32_t aPluginID); + + // 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. + */ + bool TryToRecycle(); + + /** + * 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. + */ + void StopRecycling(bool aForeground = true); + + /** + * 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 or JSPlugin + */ + bool HasActiveWorkerOrJSPlugin(); + + /** + * 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(); + + /** + * 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, + // Close the channel with error and let the subprocess clean up itself. + CLOSE_CHANNEL_WITH_ERROR, + }; + + void MaybeAsyncSendShutDownMessage(); + + /** + * 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. + * + * If aMethod is CLOSE_CHANNEL_WITH_ERROR and this is the first call + * to ShutDownProcess, then we'll close our channel using CloseWithError() + * rather than vanilla Close(). CloseWithError() indicates to IPC that this + * is an abnormal shutdown (e.g. a crash). + */ + void ShutDownProcess(ShutDownMethod aMethod); + + // Perform any steps necesssary to gracefully shtudown the message + // manager and null out mMessageManager. + void ShutDownMessageManager(); + + // 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 nsCString& aKey, const nsCString& aOrigin); + + 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::PBackgroundParent>&& aEndpoint); + + mozilla::ipc::IPCResult RecvAddMemoryReport(const MemoryReport& aReport); + mozilla::ipc::IPCResult RecvAddPerformanceMetrics( + const nsID& aID, nsTArray<PerformanceInfo>&& aMetrics); + + bool DeallocPRemoteSpellcheckEngineParent(PRemoteSpellcheckEngineParent*); + + mozilla::ipc::IPCResult RecvCloneDocumentTreeInto( + const MaybeDiscarded<BrowsingContext>& aSource, + const MaybeDiscarded<BrowsingContext>& aTarget); + + 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( + const uint32_t& aType, nsIURI* aURI, const uint32_t& aFlags, + 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); + + PTestShellParent* AllocPTestShellParent(); + + bool DeallocPTestShellParent(PTestShellParent* shell); + + PScriptCacheParent* AllocPScriptCacheParent(const FileDescOrError& cacheFile, + const bool& wantCacheData); + + bool DeallocPScriptCacheParent(PScriptCacheParent* shell); + + bool DeallocPNeckoParent(PNeckoParent* necko); + + already_AddRefed<PExternalHelperAppParent> AllocPExternalHelperAppParent( + nsIURI* aUri, const Maybe<mozilla::net::LoadInfoArgs>& aLoadInfoArgs, + const nsCString& aMimeContentType, const nsCString& aContentDisposition, + const uint32_t& aContentDispositionHint, + const nsString& 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 Maybe<LoadInfoArgs>& loadInfoArgs, + const nsCString& aMimeContentType, const nsCString& aContentDisposition, + const uint32_t& aContentDispositionHint, + const nsString& 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); + + PPresentationParent* AllocPPresentationParent(); + + bool DeallocPPresentationParent(PPresentationParent* aActor); + + virtual mozilla::ipc::IPCResult RecvPPresentationConstructor( + PPresentationParent* aActor) override; + +#ifdef MOZ_WEBSPEECH + PSpeechSynthesisParent* AllocPSpeechSynthesisParent(); + bool DeallocPSpeechSynthesisParent(PSpeechSynthesisParent* aActor); + + virtual mozilla::ipc::IPCResult RecvPSpeechSynthesisConstructor( + PSpeechSynthesisParent* aActor) override; +#endif + + PWebBrowserPersistDocumentParent* AllocPWebBrowserPersistDocumentParent( + PBrowserParent* aBrowser, + const MaybeDiscarded<BrowsingContext>& aContext); + + bool DeallocPWebBrowserPersistDocumentParent( + PWebBrowserPersistDocumentParent* aActor); + + mozilla::ipc::IPCResult RecvGetGfxVars(nsTArray<GfxVarUpdate>* aVars); + + mozilla::ipc::IPCResult RecvSetClipboard( + const IPCDataTransfer& aDataTransfer, const bool& aIsPrivateData, + const IPC::Principal& aRequestingPrincipal, + const nsContentPolicyType& aContentPolicyType, + const int32_t& aWhichClipboard); + + mozilla::ipc::IPCResult RecvGetClipboard(nsTArray<nsCString>&& aTypes, + const int32_t& aWhichClipboard, + IPCDataTransfer* aDataTransfer); + + 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 RecvPlaySound(nsIURI* aURI); + mozilla::ipc::IPCResult RecvBeep(); + mozilla::ipc::IPCResult RecvPlayEventSound(const uint32_t& aEventId); + + mozilla::ipc::IPCResult RecvGetIconForExtension(const nsCString& aFileExt, + const uint32_t& aIconSize, + nsTArray<uint8_t>* bits); + + mozilla::ipc::IPCResult RecvStartVisitedQueries( + const nsTArray<RefPtr<nsIURI>>&); + + mozilla::ipc::IPCResult RecvSetURITitle(nsIURI* uri, const nsString& title); + + mozilla::ipc::IPCResult RecvShowAlert(nsIAlertNotification* aAlert); + + mozilla::ipc::IPCResult RecvCloseAlert(const nsString& aName); + + mozilla::ipc::IPCResult RecvDisableNotifications( + const IPC::Principal& aPrincipal); + + mozilla::ipc::IPCResult RecvOpenNotificationSettings( + const IPC::Principal& aPrincipal); + + mozilla::ipc::IPCResult RecvNotificationEvent( + const nsString& aType, const NotificationEventData& aData); + + mozilla::ipc::IPCResult RecvLoadURIExternal( + nsIURI* uri, nsIPrincipal* triggeringPrincipal, + const MaybeDiscarded<BrowsingContext>& aContext); + mozilla::ipc::IPCResult RecvExtProtocolChannelConnectParent( + const uint64_t& registrarId); + + mozilla::ipc::IPCResult RecvSyncMessage( + const nsString& aMsg, const ClonedMessageData& aData, + nsTArray<StructuredCloneData>* aRetvals); + + mozilla::ipc::IPCResult RecvAsyncMessage(const nsString& 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 nsString& aMessage); + + 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& aIsFromPrivateWindow, + const uint64_t& aInnerWindowId, const bool& aIsFromChromeContext); + + mozilla::ipc::IPCResult RecvReportFrameTimingData( + uint64_t innerWindowId, const nsString& entryName, + const nsString& initiatorType, UniquePtr<PerformanceTimingData>&& aData); + + mozilla::ipc::IPCResult RecvScriptErrorWithStack( + 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& aIsFromPrivateWindow, + const bool& aIsFromChromeContext, const ClonedMessageData& aStack); + + private: + mozilla::ipc::IPCResult RecvScriptErrorInternal( + 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& aIsFromPrivateWindow, + const bool& aIsFromChromeContext, + const ClonedMessageData* aStack = nullptr); + + public: + mozilla::ipc::IPCResult RecvPrivateDocShellsExist(const bool& aExist); + + 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); + + virtual void ProcessingError(Result aCode, const char* aMsgName) override; + + mozilla::ipc::IPCResult RecvGraphicsError(const nsCString& 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); + + PFileDescriptorSetParent* AllocPFileDescriptorSetParent( + const mozilla::ipc::FileDescriptor&); + + bool DeallocPFileDescriptorSetParent(PFileDescriptorSetParent*); + + PWebrtcGlobalParent* AllocPWebrtcGlobalParent(); + bool DeallocPWebrtcGlobalParent(PWebrtcGlobalParent* aActor); + + mozilla::ipc::IPCResult RecvUpdateDropEffect(const uint32_t& aDragAction, + const uint32_t& aDropEffect); + + mozilla::ipc::IPCResult RecvShutdownProfile(const nsCString& aProfile); + + mozilla::ipc::IPCResult RecvGetGraphicsDeviceInitData( + ContentDeviceData* aOut); + + mozilla::ipc::IPCResult RecvGetOutputColorProfileData( + nsTArray<uint8_t>* aOutputColorProfileData); + + 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 mozilla::fontlist::Pointer& aFacePtr, + 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 mozilla::fontlist::Pointer& aFamilyPtr); + + 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 nsString& aCodecName, + const uint32_t& aDecodeFPS); + + mozilla::ipc::IPCResult RecvNotifyPushObservers( + const nsCString& aScope, const IPC::Principal& aPrincipal, + const nsString& aMessageId); + + mozilla::ipc::IPCResult RecvNotifyPushObserversWithData( + const nsCString& aScope, const IPC::Principal& aPrincipal, + const nsString& aMessageId, nsTArray<uint8_t>&& aData); + + mozilla::ipc::IPCResult RecvNotifyPushSubscriptionChangeObservers( + const nsCString& aScope, const IPC::Principal& aPrincipal); + + mozilla::ipc::IPCResult RecvPushError(const nsCString& aScope, + const IPC::Principal& aPrincipal, + const nsString& aMessage, + const uint32_t& aFlags); + + mozilla::ipc::IPCResult RecvNotifyPushSubscriptionModifiedObservers( + const nsCString& aScope, const IPC::Principal& aPrincipal); + + mozilla::ipc::IPCResult RecvGetFilesRequest(const nsID& aID, + const nsString& 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 RecvRecordOrigin(const uint32_t& aMetricId, + const nsCString& aOrigin); + mozilla::ipc::IPCResult RecvReportContentBlockingLog( + const IPCStream& aIPCStream); + + mozilla::ipc::IPCResult RecvBHRThreadHang(const HangDetails& aHangDetails); + + mozilla::ipc::IPCResult RecvAddCertException( + const nsACString& aSerializedCert, uint32_t aFlags, + const nsACString& aHostName, int32_t aPort, bool aIsTemporary, + AddCertExceptionResolver&& aResolver); + + mozilla::ipc::IPCResult RecvAutomaticStorageAccessPermissionCanBeGranted( + const Principal& aPrincipal, + AutomaticStorageAccessPermissionCanBeGrantedResolver&& aResolver); + + mozilla::ipc::IPCResult RecvStorageAccessPermissionGrantedForOrigin( + uint64_t aTopLevelWindowId, + const MaybeDiscarded<BrowsingContext>& aParentContext, + const Principal& aTrackingPrincipal, const nsCString& aTrackingOrigin, + const int& aAllowMode, + const Maybe< + ContentBlockingNotifier::StorageAccessPermissionGrantedReason>& + aReason, + StorageAccessPermissionGrantedForOriginResolver&& aResolver); + + mozilla::ipc::IPCResult RecvCompleteAllowAccessFor( + const MaybeDiscarded<BrowsingContext>& aParentContext, + uint64_t aTopLevelWindowId, const Principal& aTrackingPrincipal, + const nsCString& aTrackingOrigin, uint32_t aCookieBehavior, + const ContentBlockingNotifier::StorageAccessPermissionGrantedReason& + aReason, + CompleteAllowAccessForResolver&& aResolver); + + mozilla::ipc::IPCResult RecvStoreUserInteractionAsPermission( + const Principal& aPrincipal); + + 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 RecvGetModulesTrust( + ModulePaths&& aModPaths, bool aRunAtNormalPriority, + GetModulesTrustResolver&& aResolver); + + 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); + + mozilla::ipc::IPCResult RecvHistoryGo( + const MaybeDiscarded<BrowsingContext>& aContext, int32_t aOffset, + uint64_t aHistoryEpoch, bool aRequireUserInteraction, + HistoryGoResolver&& aResolveRequestedIndex); + + mozilla::ipc::IPCResult RecvSessionHistoryUpdate( + const MaybeDiscarded<BrowsingContext>& aContext, const int32_t& aIndex, + const int32_t& aLength, const nsID& aChangeID); + + mozilla::ipc::IPCResult RecvSynchronizeLayoutHistoryState( + const MaybeDiscarded<BrowsingContext>& aContext, + nsILayoutHistoryState* aState); + + mozilla::ipc::IPCResult RecvSessionHistoryEntryTitle( + const MaybeDiscarded<BrowsingContext>& aContext, const nsString& aTitle); + + mozilla::ipc::IPCResult RecvSessionHistoryEntryScrollRestorationIsManual( + const MaybeDiscarded<BrowsingContext>& aContext, const bool& aIsManual); + + mozilla::ipc::IPCResult RecvSessionHistoryEntryCacheKey( + const MaybeDiscarded<BrowsingContext>& aContext, + const uint32_t& aCacheKey); + + mozilla::ipc::IPCResult + RecvSessionHistoryEntryStoreWindowNameInContiguousEntries( + const MaybeDiscarded<BrowsingContext>& aContext, const nsString& aName); + + mozilla::ipc::IPCResult RecvGetLoadingSessionHistoryInfoFromParent( + const MaybeDiscarded<BrowsingContext>& aContext, + GetLoadingSessionHistoryInfoFromParentResolver&& aResolver); + + 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); + + mozilla::ipc::IPCResult RecvHistoryReload( + const MaybeDiscarded<BrowsingContext>& aContext, + const uint32_t aReloadFlags); + + // 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); + + 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(); + + static bool ShouldSyncPreference(const char16_t* aData); + + already_AddRefed<JSActor> InitJSActor(JS::HandleObject aMaybeActor, + const nsACString& aName, + ErrorResult& aRv) override; + mozilla::ipc::IProtocol* AsNativeActor() override { return this; } + + 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); + + void AddToPool(nsTArray<ContentParent*>&); + void RemoveFromPool(nsTArray<ContentParent*>&); + void AssertNotInPool(); + + void AssertAlive(); + + private: + // Released in ActorDealloc; deliberately not exposed to the CC. + RefPtr<ContentParent> mSelfRef; + + // 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; + nsCOMPtr<nsIPrincipal> mRemoteTypeIsolationPrincipal; + + ContentParentId mChildID; + int32_t mGeolocationWatchID; + + // This contains the id for the JS plugin (@see nsFakePluginTag) if this is + // the ContentParent for a process containing iframes for that JS plugin. If + // this is not a ContentParent for a JS plugin then it contains the value + // nsFakePluginTag::NOT_JSPLUGIN. + int32_t mJSPluginID; + + // 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; + + // `mCount` is increased when a RemoteWorkerParent actor is created for this + // ContentProcess and it is decreased when the actor is destroyed. + // + // `mShutdownStarted` is flipped to `true` when a runnable that calls + // `ShutDownProcess` is dispatched; it's needed because the corresponding + // Content Process may be shutdown if there's no remote worker actors, and + // decrementing `mCount` and the call to `ShutDownProcess` are async. So, + // when a worker is going to be spawned and we see that `mCount` is 0, + // we can decide whether or not to use that process based on the value of + // `mShutdownStarted.` + // + // It's touched on PBackground thread and on main-thread. + struct RemoteWorkerActorData { + uint32_t mCount = 0; + bool mShutdownStarted = false; + }; + + DataMutex<RemoteWorkerActorData> mRemoteWorkerActorData; + + // 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; + + // 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; + + RefPtr<nsConsoleService> mConsoleService; + nsConsoleService* GetConsoleService(); + 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. + ScopedClose mChildXSocketFdDup; +#endif + + PProcessHangMonitorParent* mHangMonitorActor; + + UniquePtr<gfx::DriverCrashGuard> mDriverCrashGuard; + UniquePtr<MemoryReportRequestHost> mMemoryReportRequest; + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + mozilla::UniquePtr<SandboxBroker> mSandboxBroker; + static mozilla::UniquePtr<SandboxBrokerPolicyFactory> + sSandboxBrokerPolicyFactory; +#endif + +#ifdef NS_PRINTING + RefPtr<embedding::PrintingParent> mPrintingParent; +#endif + + // This hashtable is used to run GetFilesHelper objects in the parent process. + // GetFilesHelper can be aborted by receiving RecvDeleteGetFilesRequest. + nsRefPtrHashtable<nsIDHashKey, GetFilesHelper> mGetFilesPendingRequests; + + nsTHashtable<nsCStringHashKey> mActivePermissionKeys; + + 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 + + nsTHashtable<nsRefPtrHashKey<BrowsingContextGroup>> mGroups; + + // 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; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(ContentParent, NS_CONTENTPARENT_IID) + +// This is the C++ version of remoteTypePrefix in E10SUtils.jsm. +const nsDependentCSubstring RemoteTypePrefix( + const nsACString& aContentProcessType); + +// This is based on isWebRemoteType in E10SUtils.jsm. +bool IsWebRemoteType(const nsACString& aContentProcessType); + +bool IsWebCoopCoepRemoteType(const nsACString& aContentProcessType); + +bool IsPrivilegedMozillaRemoteType(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..49fff36bca --- /dev/null +++ b/dom/ipc/ContentProcess.cpp @@ -0,0 +1,204 @@ +/* -*- 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" +#endif + +#if (defined(XP_WIN) || defined(XP_MACOSX)) && defined(MOZ_SANDBOX) +# include "mozilla/SandboxSettings.h" +# include "nsAppDirectoryServiceDefs.h" +# include "nsDirectoryService.h" +# include "nsDirectoryServiceDefs.h" +#endif + +#include "nsAppRunner.h" +#include "ProcessUtils.h" + +using mozilla::ipc::IOThreadChild; + +namespace mozilla::dom { + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +static void SetTmpEnvironmentVariable(nsIFile* aValue) { + // Save the TMP environment variable so that is is picked up by GetTempPath(). + // Note that we specifically write to the TMP variable, as that is the first + // variable that is checked by GetTempPath() to determine its output. + nsAutoString fullTmpPath; + nsresult rv = aValue->GetPath(fullTmpPath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + Unused << NS_WARN_IF(!SetEnvironmentVariableW(L"TMP", fullTmpPath.get())); + // We also set TEMP in case there is naughty third-party code that is + // referencing the environment variable directly. + Unused << NS_WARN_IF(!SetEnvironmentVariableW(L"TEMP", fullTmpPath.get())); +} +#endif + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +static void SetUpSandboxEnvironment() { + MOZ_ASSERT( + nsDirectoryService::gService, + "SetUpSandboxEnvironment relies on nsDirectoryService being initialized"); + + // On Windows, a sandbox-writable temp directory is used whenever the sandbox + // is enabled. + if (!IsContentSandboxEnabled()) { + return; + } + + nsCOMPtr<nsIFile> sandboxedContentTemp; + nsresult rv = nsDirectoryService::gService->Get( + NS_APP_CONTENT_PROCESS_TEMP_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(sandboxedContentTemp)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + // Change the gecko defined temp directory to our sandbox-writable one. + // Undefine returns a failure if the property is not already set. + Unused << nsDirectoryService::gService->Undefine(NS_OS_TEMP_DIR); + rv = nsDirectoryService::gService->Set(NS_OS_TEMP_DIR, sandboxedContentTemp); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + SetTmpEnvironmentVariable(sandboxedContentTemp); +} +#endif + +bool ContentProcess::Init(int aArgc, char* aArgv[]) { + Maybe<uint64_t> childID; + Maybe<bool> isForBrowser; + Maybe<const char*> parentBuildID; + char* prefsHandle = nullptr; + char* prefMapHandle = nullptr; + char* prefsLen = nullptr; + char* prefMapSize = nullptr; +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + nsCOMPtr<nsIFile> profileDir; +#endif + + for (int i = 1; i < aArgc; i++) { + if (!aArgv[i]) { + continue; + } + + if (strcmp(aArgv[i], "-appdir") == 0) { + if (++i == aArgc) { + return false; + } + nsDependentCString appDir(aArgv[i]); + mXREEmbed.SetAppDir(appDir); + + } else if (strcmp(aArgv[i], "-childID") == 0) { + if (++i == aArgc) { + return false; + } + char* str = aArgv[i]; + childID = Some(strtoull(str, &str, 10)); + if (str[0] != '\0') { + return false; + } + + } else if (strcmp(aArgv[i], "-isForBrowser") == 0) { + isForBrowser = Some(true); + + } else if (strcmp(aArgv[i], "-notForBrowser") == 0) { + isForBrowser = Some(false); + +#ifdef XP_WIN + } else if (strcmp(aArgv[i], "-prefsHandle") == 0) { + if (++i == aArgc) { + return false; + } + prefsHandle = aArgv[i]; + } else if (strcmp(aArgv[i], "-prefMapHandle") == 0) { + if (++i == aArgc) { + return false; + } + prefMapHandle = aArgv[i]; +#endif + + } else if (strcmp(aArgv[i], "-prefsLen") == 0) { + if (++i == aArgc) { + return false; + } + prefsLen = aArgv[i]; + } else if (strcmp(aArgv[i], "-prefMapSize") == 0) { + if (++i == aArgc) { + return false; + } + prefMapSize = aArgv[i]; + } else if (strcmp(aArgv[i], "-safeMode") == 0) { + gSafeMode = true; + + } else if (strcmp(aArgv[i], "-parentBuildID") == 0) { + if (++i == aArgc) { + return false; + } + parentBuildID = Some(aArgv[i]); + +#if defined(XP_MACOSX) && defined(MOZ_SANDBOX) + } else if (strcmp(aArgv[i], "-profile") == 0) { + if (++i == aArgc) { + return false; + } + bool flag; + nsresult rv = XRE_GetFileFromPath(aArgv[i], getter_AddRefs(profileDir)); + if (NS_FAILED(rv) || NS_FAILED(profileDir->Exists(&flag)) || !flag) { + NS_WARNING("Invalid profile directory passed to content process."); + profileDir = nullptr; + } +#endif /* XP_MACOSX && MOZ_SANDBOX */ + } + } + + // Did we find all the mandatory flags? + if (childID.isNothing() || isForBrowser.isNothing() || + parentBuildID.isNothing()) { + return false; + } + + ::mozilla::ipc::SharedPreferenceDeserializer deserializer; + if (!deserializer.DeserializeFromSharedMemory(prefsHandle, prefMapHandle, + prefsLen, prefMapSize)) { + return false; + } + + mContent.Init(IOThreadChild::message_loop(), ParentPid(), *parentBuildID, + IOThreadChild::TakeChannel(), *childID, *isForBrowser); + + mXREEmbed.Start(); +#if (defined(XP_MACOSX)) && defined(MOZ_SANDBOX) + mContent.SetProfileDir(profileDir); +# if defined(DEBUG) + if (IsContentSandboxEnabled()) { + AssertMacSandboxEnabled(); + } +# endif /* DEBUG */ +#endif /* XP_MACOSX && MOZ_SANDBOX */ + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + SetUpSandboxEnvironment(); +#endif + + return true; +} + +// Note: CleanUp() never gets called in non-debug builds because we exit early +// in ContentChild::ActorDestroy(). +void ContentProcess::CleanUp() { mXREEmbed.Stop(); } + +} // namespace mozilla::dom diff --git a/dom/ipc/ContentProcess.h b/dom/ipc/ContentProcess.h new file mode 100644 index 0000000000..70704a2573 --- /dev/null +++ b/dom/ipc/ContentProcess.h @@ -0,0 +1,53 @@ +/* -*- 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 "mozilla/ipc/ScopedXREEmbed.h" +#include "ContentChild.h" + +#if defined(XP_WIN) +# include "mozilla/mscom/ProcessRuntime.h" +#endif + +namespace mozilla { +namespace dom { + +/** + * ContentProcess is a singleton on the content process which represents + * the main thread where tab instances live. + */ +class ContentProcess : public mozilla::ipc::ProcessChild { + typedef mozilla::ipc::ProcessChild ProcessChild; + + public: + explicit ContentProcess(ProcessId aParentPid) : ProcessChild(aParentPid) {} + + ~ContentProcess() = default; + + virtual bool Init(int aArgc, char* aArgv[]) override; + virtual void CleanUp() override; + + private: + ContentChild mContent; + mozilla::ipc::ScopedXREEmbed mXREEmbed; + +#if defined(XP_WIN) + // This object initializes and configures COM. + mozilla::mscom::ProcessRuntime mCOMRuntime; +#endif + + ContentProcess(const ContentProcess&) = delete; + + const ContentProcess& operator=(const ContentProcess&) = delete; +}; + +} // namespace dom +} // namespace mozilla + +#endif // ifndef dom_tabs_ContentThread_h diff --git a/dom/ipc/ContentProcessManager.cpp b/dom/ipc/ContentProcessManager.cpp new file mode 100644 index 0000000000..4a663c0c8b --- /dev/null +++ b/dom/ipc/ContentProcessManager.cpp @@ -0,0 +1,135 @@ +/* -*- 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/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) { + sSingleton = new ContentProcessManager(); + ClearOnShutdown(&sSingleton); + } + return sSingleton; +} + +void ContentProcessManager::AddContentProcess(ContentParent* aChildCp) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aChildCp); + + auto entry = mContentParentMap.LookupForAdd(aChildCp->ChildID()); + MOZ_ASSERT_IF(entry, entry.Data() == aChildCp); + entry.OrInsert([&] { return 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); + + auto entry = mBrowserParentMap.LookupForAdd(aChildBp->GetTabId()); + MOZ_ASSERT_IF(entry, entry.Data() == aChildBp); + entry.OrInsert([&] { + // 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(); + return aChildBp; + }); + return !entry; +} + +void ContentProcessManager::UnregisterRemoteFrame(const TabId& aChildTabId) { + MOZ_ASSERT(NS_IsMainThread()); + + auto childBp = mBrowserParentMap.GetAndRemove(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..b823da61ed --- /dev/null +++ b/dom/ipc/ContentProcessManager.h @@ -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/. */ + +#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 "nsDataHashtable.h" + +namespace mozilla { +namespace 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; + + nsDataHashtable<nsUint64HashKey, ContentParent*> mContentParentMap; + nsDataHashtable<nsUint64HashKey, BrowserParent*> mBrowserParentMap; + + MOZ_COUNTED_DEFAULT_CTOR(ContentProcessManager); +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_ContentProcessManager_h diff --git a/dom/ipc/DOMTypes.ipdlh b/dom/ipc/DOMTypes.ipdlh new file mode 100644 index 0000000000..49b0dbfcfe --- /dev/null +++ b/dom/ipc/DOMTypes.ipdlh @@ -0,0 +1,396 @@ +/* -*- 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 IPCBlob; +include IPCStream; +include protocol PRemoteLazyInputStream; +include ProtocolTypes; + +using struct mozilla::void_t from "mozilla/ipc/IPCCore.h"; + +using moveonly struct mozilla::SerializedStructuredCloneBuffer + from "mozilla/ipc/SerializedStructuredCloneBuffer.h"; + +using class mozilla::dom::LoadingSessionHistoryInfo + from "mozilla/dom/SessionHistoryEntry.h"; + +using LayoutDeviceIntRect from "Units.h"; +using DesktopIntRect from "Units.h"; +using DesktopToLayoutDeviceScale from "Units.h"; +using CSSToLayoutDeviceScale from "Units.h"; +using CSSRect from "Units.h"; +using CSSSize from "Units.h"; +using ScreenIntSize from "Units.h"; +using mozilla::LayoutDeviceIntPoint from "Units.h"; +using nsSizeMode from "nsIWidgetListener.h"; +using ScrollbarPreference from "mozilla/ScrollbarPreferences.h"; +using hal::ScreenOrientation from "mozilla/HalScreenConfiguration.h"; +using mozilla::gfx::SurfaceFormat from "mozilla/gfx/Types.h"; +using refcounted class nsIPrincipal from "nsIPrincipal.h"; +using mozilla::dom::MaybeDiscardedBrowsingContext from "mozilla/dom/BrowsingContext.h"; +using refcounted class nsIURI from "nsIURI.h"; +using refcounted class nsIContentSecurityPolicy from "nsIContentSecurityPolicy.h"; +using refcounted class nsIInputStream from "mozilla/ipc/IPCStreamUtils.h"; +using refcounted class nsIReferrerInfo from "nsIReferrerInfo.h"; +using refcounted class nsIVariant from "nsIVariant.h"; +using class mozilla::TimeStamp from "mozilla/TimeStamp.h"; +using refcounted class mozilla::dom::BrowsingContext from "mozilla/dom/BrowsingContext.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; +}; + +union IPCDataTransferData +{ + nsString; // text + Shmem; // images using Shmem + IPCBlob; // files +}; + +struct IPCDataTransferImage +{ + uint32_t width; + uint32_t height; + uint32_t stride; + SurfaceFormat format; +}; + +struct IPCDataTransferItem +{ + nsCString flavor; + // The image details are only used when transferring images. + IPCDataTransferImage imageDetails; + IPCDataTransferData data; +}; + +struct IPCDataTransfer +{ + IPCDataTransferItem[] items; +}; + +struct ScreenDetails { + LayoutDeviceIntRect rect; + DesktopIntRect rectDisplayPix; + LayoutDeviceIntRect availRect; + DesktopIntRect availRectDisplayPix; + int32_t pixelDepth; + int32_t colorDepth; + DesktopToLayoutDeviceScale contentsScaleFactor; + CSSToLayoutDeviceScale defaultCSSScaleFactor; + float dpi; +}; + +struct DimensionInfo +{ + CSSRect rect; + CSSSize size; + ScreenOrientation orientation; + LayoutDeviceIntPoint clientOffset; + LayoutDeviceIntPoint chromeOffset; +}; + +struct FrameScriptInfo +{ + nsString url; + bool runInGlobalScope; +}; + +struct FeaturePolicyInfo +{ + nsString[] inheritedDeniedFeatureNames; + nsString[] attributeEnabledFeatureNames; + nsString declaredString; + nsIPrincipal defaultOrigin; + nsIPrincipal selfOrigin; + nsIPrincipal srcOrigin; +}; + +/** + * The information required to complete a window creation request. + */ +struct CreatedWindowInfo +{ + nsresult rv; + bool windowOpened; + FrameScriptInfo[] frameScripts; + uint32_t maxTouchPoints; + DimensionInfo dimensions; + bool hasSiblings; +}; + + +/** + * PerformanceInfo is used to pass performance info stored + * in WorkerPrivate and DocGroup instances, as well as + * memory-related information. + * + * Each (host, pid, windowId) is unique to a given DocGroup or + * Worker, and we collect the number of dispatches per Dispatch + * category and total execution duration as well as the current + * Zone JS Heap usage. + * + * This IPDL struct reflects the data collected in Performance counters, + * in addition of some memory usage information. + * + * see xpcom/threads/PerformanceCounter.h + */ + +struct MediaMemoryInfo { + uint64_t audioSize; + uint64_t videoSize; + uint64_t resourcesSize; +}; + +struct PerformanceMemoryInfo { + MediaMemoryInfo media; + uint64_t domDom; + uint64_t domStyle; + uint64_t domOther; + uint64_t GCHeapUsage; +}; + +struct CategoryDispatch +{ + // DispatchCategory value + uint16_t category; + // Number of dispatch + uint16_t count; +}; + +struct PerformanceInfo +{ + // Host of the document, if any + nsCString host; + // process id + uint32_t pid; + // window id + uint64_t windowId; + // Execution time in microseconds + uint64_t duration; + // Counter ID (unique across processes) + uint64_t counterId; + // True if the data is collected in a worker + bool isWorker; + // True if the document window is the top window + bool isTopLevel; + // Memory + PerformanceMemoryInfo memory; + // Counters per category. For workers, a single entry + CategoryDispatch[] items; +}; + +struct DocShellLoadStateInit +{ + nsIURI URI; + nsIURI OriginalURI; + nsIURI ResultPrincipalURI; + bool ResultPrincipalURIIsSome; + nsIPrincipal TriggeringPrincipal; + nsIReferrerInfo ReferrerInfo; + bool KeepResultPrincipalURIIfSet; + bool LoadReplace; + bool InheritPrincipal; + bool PrincipalIsExplicit; + nsIPrincipal PrincipalToInherit; + nsIPrincipal PartitionedPrincipalToInherit; + bool ForceAllowDataURI; + bool OriginalFrameSrc; + bool IsFormSubmission; + uint32_t LoadType; + nsString Target; + nsIURI BaseURI; + uint32_t LoadFlags; + bool FirstParty; + bool HasValidUserGestureActivation; + bool AllowFocusMove; + nsCString TypeHint; + nsString FileName; + bool IsFromProcessingFrameAttributes; + // 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. + nsIContentSecurityPolicy Csp; + + MaybeDiscardedBrowsingContext SourceBrowsingContext; + MaybeDiscardedBrowsingContext TargetBrowsingContext; + + // The TriggineringSandboxFlags are the SandboxFlags of the entity + // responsible for causing the load to occur. + uint32_t TriggeringSandboxFlags; + + nsCString? OriginalURIString; + int32_t? CancelContentJSEpoch; + + nsIInputStream PostDataStream; + nsIInputStream HeadersStream; + + nsString SrcdocData; // useless without sourcedocshell + + // Fields missing due to lack of need or serialization + // nsCOMPtr<nsIDocShell> mSourceDocShell; + // bool mIsSrcDocLoad; // useless without sourcedocshell + // nsIChannel pendingRedirectedChannel; // sent through other mechanism + + uint64_t LoadIdentifier; + + bool ChannelInitialized; + + bool TryToReplaceWithSessionHistoryLoad; + + LoadingSessionHistoryInfo? loadingSessionHistoryInfo; +}; + +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; + uint32_t classOfService; + bool? privateBrowsing; + nsCString? method; + nsIReferrerInfo referrerInfo; + TimedChannelInfo? timedChannel; + nullable PRemoteLazyInputStream uploadStream; + 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; + nsIURI; + 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; + 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..d35d77840a --- /dev/null +++ b/dom/ipc/DocShellMessageUtils.cpp @@ -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/. */ + +#include "mozilla/dom/DocShellMessageUtils.h" +#include "mozilla/dom/DOMTypes.h" +#include "mozilla/ipc/IPDLParamTraits.h" +#include "nsSerializationHelper.h" + +namespace mozilla::ipc { + +void IPDLParamTraits<nsDocShellLoadState*>::Write(IPC::Message* aMsg, + IProtocol* aActor, + nsDocShellLoadState* aParam) { + MOZ_RELEASE_ASSERT(aParam); + WriteIPDLParam(aMsg, aActor, aParam->Serialize()); +} + +bool IPDLParamTraits<nsDocShellLoadState*>::Read( + const IPC::Message* aMsg, PickleIterator* aIter, IProtocol* aActor, + RefPtr<nsDocShellLoadState>* aResult) { + dom::DocShellLoadStateInit loadState; + if (!ReadIPDLParam(aMsg, aIter, aActor, &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. + MOZ_ASSERT(loadState.URI()); + + *aResult = new nsDocShellLoadState(loadState); + return true; +} + +} // namespace mozilla::ipc diff --git a/dom/ipc/DocShellMessageUtils.h b/dom/ipc/DocShellMessageUtils.h new file mode 100644 index 0000000000..b8e1316cd6 --- /dev/null +++ b/dom/ipc/DocShellMessageUtils.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_docshell_message_utils_h__ +#define mozilla_dom_docshell_message_utils_h__ + +#include "ipc/EnumSerializer.h" +#include "nsCOMPtr.h" +#include "nsDocShellLoadState.h" +#include "nsIContentViewer.h" +#include "mozilla/ScrollbarPreferences.h" + +namespace mozilla { +namespace ipc { + +template <> +struct IPDLParamTraits<nsDocShellLoadState*> { + static void Write(IPC::Message* aMsg, IProtocol* aActor, + nsDocShellLoadState* aParam); + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, RefPtr<nsDocShellLoadState>* aResult); +}; + +} // namespace ipc +} // namespace mozilla + +namespace IPC { + +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..e7bcbb1184 --- /dev/null +++ b/dom/ipc/EffectsInfo.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_EffectsInfo_h +#define mozilla_dom_EffectsInfo_h + +#include "nsRect.h" + +namespace mozilla { +namespace 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() { *this = EffectsInfo::FullyHidden(); } + + static EffectsInfo VisibleWithinRect(const nsRect& aVisibleRect, + float aScaleX, float aScaleY) { + return EffectsInfo{aVisibleRect, aScaleX, aScaleY}; + } + static EffectsInfo FullyHidden() { return EffectsInfo{nsRect(), 1.0f, 1.0f}; } + + bool operator==(const EffectsInfo& aOther) { + return mVisibleRect == aOther.mVisibleRect && mScaleX == aOther.mScaleX && + mScaleY == aOther.mScaleY; + } + bool operator!=(const EffectsInfo& aOther) { return !(*this == aOther); } + + bool IsVisible() const { return !mVisibleRect.IsEmpty(); } + + // The visible rect of this browser relative to the root frame. If this is + // empty then the browser can be considered invisible. + nsRect mVisibleRect; + // The desired scale factors to apply to rasterized content to match + // transforms applied in ancestor browsers. + float mScaleX; + float mScaleY; + // If you add new fields here, you must also update operator== + + private: + EffectsInfo(const nsRect& aVisibleRect, float aScaleX, float aScaleY) + : mVisibleRect(aVisibleRect), mScaleX(aScaleX), mScaleY(aScaleY) {} +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_EffectsInfo_h diff --git a/dom/ipc/FilePickerParent.cpp b/dom/ipc/FilePickerParent.cpp new file mode 100644 index 0000000000..5049e03c52 --- /dev/null +++ b/dom/ipc/FilePickerParent.cpp @@ -0,0 +1,291 @@ +/* -*- 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/FileBlobImpl.h" +#include "mozilla/dom/FileSystemSecurity.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/IPCBlobUtils.h" + +using mozilla::Unused; +using namespace mozilla::dom; + +NS_IMPL_ISUPPORTS(FilePickerParent::FilePickerShownCallback, + nsIFilePickerShownCallback); + +NS_IMETHODIMP +FilePickerParent::FilePickerShownCallback::Done(int16_t 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, parent, 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(int16_t 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; + } + + Element* element = BrowserParent::GetFrom(Manager())->GetOwnerElement(); + if (!element) { + return false; + } + + nsCOMPtr<mozIDOMWindowProxy> window = element->OwnerDoc()->GetWindow(); + if (!window) { + return false; + } + + return NS_SUCCEEDED(mFilePicker->Init(window, mTitle, mMode)); +} + +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 int16_t& 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); + } + + mCallback = new FilePickerShownCallback(this); + + mFilePicker->Open(mCallback); + 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..abe9fac68c --- /dev/null +++ b/dom/ipc/FilePickerParent.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_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 { +namespace dom { + +class FilePickerParent : public PFilePickerParent { + public: + FilePickerParent(const nsString& aTitle, const int16_t& aMode) + : mTitle(aTitle), mMode(aMode), mResult(nsIFilePicker::returnOK) {} + + virtual ~FilePickerParent(); + + void Done(int16_t 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 int16_t& aCapture); + + 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; + FilePickerParent* mFilePickerParent; + }; + + private: + bool CreateFilePicker(); + + // This runnable is used to do some I/O operation on a separate thread. + class IORunnable : public Runnable { + 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; + int16_t mMode; + int16_t mResult; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_FilePickerParent_h diff --git a/dom/ipc/IdType.h b/dom/ipc/IdType.h new file mode 100644 index 0000000000..18c04051e8 --- /dev/null +++ b/dom/ipc/IdType.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_IdType_h +#define mozilla_dom_IdType_h + +#include "ipc/IPCMessageUtils.h" + +namespace IPC { +template <typename T> +struct ParamTraits; +} // namespace IPC + +namespace mozilla { +namespace 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; +}; + +typedef IdType<BrowserParent> TabId; +typedef IdType<ContentParent> ContentParentId; +} // namespace dom +} // namespace mozilla + +namespace IPC { + +template <typename T> +struct ParamTraits<mozilla::dom::IdType<T>> { + typedef mozilla::dom::IdType<T> paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mId); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return ReadParam(aMsg, aIter, &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..c90e9f7157 --- /dev/null +++ b/dom/ipc/InProcessChild.h @@ -0,0 +1,71 @@ +/* -*- 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 { +namespace 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::HandleObject 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 dom +} // namespace mozilla + +#endif // defined(mozilla_dom_InProcessChild_h) diff --git a/dom/ipc/InProcessImpl.cpp b/dom/ipc/InProcessImpl.cpp new file mode 100644 index 0000000000..d35eea501b --- /dev/null +++ b/dom/ipc/InProcessImpl.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 "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->GetIPCChannel(), 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; +} + +already_AddRefed<JSActor> InProcessParent::InitJSActor( + JS::HandleObject 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; +} + +already_AddRefed<JSActor> InProcessChild::InitJSActor( + JS::HandleObject 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) { + if (current->GetProtocolId() == PInProcessMsgStart) { + break; // Found the correct actor. + } + current = current->Manager(); + } + if (!current) { + return nullptr; // Not a 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..6a4acc7e77 --- /dev/null +++ b/dom/ipc/InProcessParent.h @@ -0,0 +1,76 @@ +/* -*- 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 { +namespace 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::HandleObject 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 dom +} // namespace mozilla + +#endif // defined(mozilla_dom_InProcessParent_h) diff --git a/dom/ipc/MMPrinter.cpp b/dom/ipc/MMPrinter.cpp new file mode 100644 index 0000000000..9a18ec657d --- /dev/null +++ b/dom/ipc/MMPrinter.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 "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 "nsFrameMessageManager.h" +#include "prenv.h" + +namespace mozilla::dom { + +LazyLogModule MMPrinter::sMMLog("MessageManager"); + +/* 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; + } + + MOZ_LOG(MMPrinter::sMMLog, LogLevel::Debug, + ("%s Message: %s in process type: %s", 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::UnpackClonedMessageDataForChild(aData, data); + + /* Read original StructuredCloneData. */ + JS::RootedValue scdContent(cx); + data.Read(cx, &scdContent, rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + return; + } + + JS::RootedString unevalObj(cx, JS_ValueToSource(cx, scdContent)); + nsAutoJSString srcString; + if (!srcString.init(cx, unevalObj)) return; + + MOZ_LOG(MMPrinter::sMMLog, LogLevel::Verbose, + (" %s", 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..c58acd6911 --- /dev/null +++ b/dom/ipc/MMPrinter.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 MMPrinter_h +#define MMPrinter_h + +#include "mozilla/dom/DOMTypes.h" +#include "nsString.h" + +namespace mozilla { +namespace 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 dom +} // namespace mozilla + +#endif /* MMPrinter_h */ diff --git a/dom/ipc/ManifestMessagesChild.jsm b/dom/ipc/ManifestMessagesChild.jsm new file mode 100644 index 0000000000..b7a8dd959a --- /dev/null +++ b/dom/ipc/ManifestMessagesChild.jsm @@ -0,0 +1,121 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 + */ +"use strict"; + +var EXPORTED_SYMBOLS = ["ManifestMessagesChild"]; + +ChromeUtils.defineModuleGetter( + this, + "ManifestObtainer", + "resource://gre/modules/ManifestObtainer.jsm" +); +ChromeUtils.defineModuleGetter( + this, + "ManifestFinder", + "resource://gre/modules/ManifestFinder.jsm" +); +ChromeUtils.defineModuleGetter( + this, + "ManifestIcons", + "resource://gre/modules/ManifestIcons.jsm" +); + +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 = 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 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 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..ad96825478 --- /dev/null +++ b/dom/ipc/MaybeDiscarded.h @@ -0,0 +1,135 @@ +/* -*- 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 { +namespace 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 dom +} // namespace mozilla + +#endif // mozilla_dom_MaybeDiscarded_h diff --git a/dom/ipc/MemMapSnapshot.cpp b/dom/ipc/MemMapSnapshot.cpp new file mode 100644 index 0000000000..7a104c420e --- /dev/null +++ b/dom/ipc/MemMapSnapshot.cpp @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MemMapSnapshot.h" + +#include "mozilla/AutoMemMap.h" +#include "mozilla/ResultExtensions.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..1a17e9c9af --- /dev/null +++ b/dom/ipc/MemMapSnapshot.h @@ -0,0 +1,53 @@ +/* -*- 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" + +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..bff321c63a --- /dev/null +++ b/dom/ipc/MemoryReportRequest.h @@ -0,0 +1,75 @@ +/* -*- 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 { +namespace 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 dom +} // namespace mozilla + +#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..90010a51b4 --- /dev/null +++ b/dom/ipc/PBrowser.ipdl @@ -0,0 +1,1048 @@ +/* -*- 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 PPluginWidget; +include protocol PRemotePrintJob; +include protocol PChildToParentStream; +include protocol PParentToChildStream; +include protocol PFileDescriptorSet; +include protocol PRemoteLazyInputStream; +include protocol PPaymentRequest; +include protocol PWindowGlobal; +include protocol PBrowserBridge; +include protocol PVsync; + +include DOMTypes; +include NeckoChannelParams; +include WindowGlobalTypes; +include IPCBlob; +include IPCStream; +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/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 mozilla::gfx::Matrix4x4 from "mozilla/gfx/Matrix.h"; +using mozilla::gfx::MaybeMatrix4x4 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 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 mozilla::layers::LayersId from "mozilla/layers/LayersTypes.h"; +using mozilla::layers::LayersObserverEpoch from "mozilla/layers/LayersTypes.h"; +using mozilla::layers::MaybeZoomConstraints from "mozilla/layers/ZoomConstraints.h"; +using mozilla::layers::GeckoContentController_TapType from "mozilla/layers/GeckoContentControllerTypes.h"; +using 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 nsCursor from "nsIWidget.h"; +using struct LookAndFeelInt 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::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 class mozilla::NativeEventData from "ipc/nsGUIEventIPC.h"; +using struct mozilla::FontRange from "ipc/nsGUIEventIPC.h"; +using mozilla::a11y::IAccessibleHolder from "mozilla/a11y/IPCTypes.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 CallerType from "mozilla/dom/BindingDeclarations.h"; +using mozilla::dom::EmbedderElementEventType from "mozilla/dom/TabMessageTypes.h"; +using refcounted class nsDocShellLoadState from "nsDocShellLoadState.h"; +using mozilla::IntrinsicSize from "nsIFrame.h"; +using mozilla::AspectRatio from "mozilla/AspectRatio.h"; + +namespace mozilla { +namespace dom { + +struct WebProgressData +{ + bool isTopLevel; + bool isLoadingDocument; + uint32_t loadType; +}; + +struct RequestData +{ + nsIURI requestURI; + nsIURI originalRequestURI; + nsCString matchedList; +}; + +struct WebProgressStateChangeData +{ + bool isNavigating; + bool mayEnableCharacterEncodingMenu; + bool charsetAutodetected; + + // The following fields are only set when the aStateFlags param passed with + // this struct is |nsIWebProgress.STATE_STOP|. + nsString contentType; + nsString charset; + nsIURI documentURI; +}; + +struct WebProgressLocationChangeData +{ + bool isNavigating; + bool isSyntheticDocument; + bool mayEnableCharacterEncodingMenu; + bool charsetAutodetected; + nsString contentType; + nsString title; + nsString charset; + nsIURI documentURI; + nsIPrincipal contentPrincipal; + nsIPrincipal contentPartitionedPrincipal; + nsIContentSecurityPolicy csp; + 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; +}; + +/** + * 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. + */ +nested(upto inside_cpow) sync refcounted protocol PBrowser +{ + manager PContent; + + manages PColorPicker; + +#ifdef ACCESSIBILITY + manages PDocAccessible; +#endif + + manages PFilePicker; + manages PPluginWidget; + manages PPaymentRequest; + 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. aMsaaID is the MSAA id for this content process, and + * is only valid on Windows. Set to 0 on other platforms. aDocCOMProxy + * is also Windows-specific and should be set to 0 on other platforms. + */ + async PDocAccessible(nullable PDocAccessible aParentDoc, uint64_t aParentAcc, + uint32_t aMsaaID, IAccessibleHolder aDocCOMProxy); +#endif + + /* + * Creates a new remoted nsIWidget connection for windowed plugins + * in e10s mode. This is always initiated from the child in response + * to windowed plugin creation. + */ + sync PPluginWidget(); + + async PPaymentRequest(); + + /** + * Create a new Vsync connection for our associated root widget + */ + async PVsync(); + + /** + * Sends an NS_NATIVE_CHILD_OF_SHAREABLE_WINDOW to be adopted by the + * widget's shareable window on the chrome side. Only used on Windows. + */ + async SetNativeChildOfShareableWindow(uintptr_t childWindow); + + /** + * When content moves focus from a native plugin window that's a child + * of the native browser window we need to move native focus to the + * browser. Otherwise the plugin window will never relinquish focus. + */ + sync DispatchFocusToTopLevelWindow(); + +parent: + /** + * When child sends this message, parent should move focus to + * the next or previous focusable element or document. + */ + async MoveFocus(bool forward, bool forDocumentNavigation); + + /** + * SizeShellTo request propagation to parent. + * + * aFlag Can indicate if one of the dimensions should be ignored. + * If only one dimension has changed it has to be indicated + * by the nsIEmbeddingSiteWindow::DIM_FLAGS_IGNORE_* flags. + * aShellItemWidth, + * aShellItemHeight On parent side we won't be able to decide the dimensions + * of the shell item parameter in the original SizeShellTo + * call so we send over its dimensions that will be used + * for the actual resize. + **/ + async SizeShellTo(uint32_t aFlag, int32_t aWidth, int32_t aHeight, + int32_t aShellItemWidth, int32_t aShellItemHeight); + + /** + * 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); + + async Event(RemoteDOMEvent aEvent); + + 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. + * + * 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) + 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. + */ + nested(inside_cpow) async OnEventNeedingAckHandled(EventMessage message); + + /** + * Notifies the parent process of native key event data received in a + * plugin process directly. + * + * aKeyEventData The native key event data. The actual type copied into + * NativeEventData depending on the caller. Please check + * PluginInstanceChild. + */ + nested(inside_cpow) async OnWindowedPluginKeyEvent(NativeEventData aKeyEventData); + + /** + * 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 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, + nsCString customCursorData, + uint32_t width, uint32_t height, + 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); + + async PFilePicker(nsString aTitle, int16_t aMode); + + /** + * Initiates an asynchronous request for one of the special indexedDB + * permissions for the provided principal. + * + * @param principal + * The principal of the request. + * + * NOTE: The principal is untrusted in the parent process. Only + * principals that can live in the content process should + * provided. + */ + async IndexedDBPermissionRequest(nsIPrincipal aPrincipal) returns (uint32_t permission); + + /** + * 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); + + /** + * Brings up the auth prompt dialog. + * Called when this is the PBrowserParent for a nested remote iframe. + * aCallbackId corresponds to an nsIAuthPromptCallback that lives in the + * root process. It will be passed back to the root process with either the + * OnAuthAvailable or OnAuthCancelled message. + */ + async AsyncAuthPrompt(nsCString uri, nsString realm, uint64_t aCallbackId); + + /** + * 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__(); + + async ReplyKeyEvent(WidgetKeyboardEvent event); + + /** + * Retrieves edit commands for the key combination represented by aEvent. + * + * @param aType One of nsIWidget::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, + 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 SynthesizeNativeTouchTap(LayoutDeviceIntPoint aPoint, + bool aLongTap, + uint64_t aObserverId); + async ClearNativeTouchSequence(uint64_t aObserverId); + + async AccessKeyNotHandled(WidgetKeyboardEvent event); + + async RegisterProtocolHandler(nsString scheme, nsIURI handlerURI, nsString title, + nsIURI documentURI); + + async OnStateChange(WebProgressData? aWebProgressData, + RequestData aRequestData, uint32_t aStateFlags, + nsresult aStatus, + WebProgressStateChangeData? aStateChangeData); + + async OnProgressChange(WebProgressData? aWebProgressData, + RequestData aRequestData, int32_t aCurSelfProgress, + int32_t aMaxSelfProgress, int32_t aCurTotalProgress, + int32_t aMaxTotalProgress); + + async OnLocationChange(WebProgressData? aWebProgressData, + RequestData aRequestData, nsIURI aLocation, + uint32_t aFlags, bool aCanGoBack, + bool aCanGoForward, + WebProgressLocationChangeData? aLocationChangeData); + + async OnStatusChange(WebProgressData? aWebProgressData, + RequestData aRequestData, nsresult aStatus, + nsString aMessage); + + async NotifyContentBlockingEvent(uint32_t aEvent, RequestData aRequestData, + bool aBlocked, nsCString aTrackingOrigin, + nsCString[] aTrackingFullHashes, + StorageAccessPermissionGrantedReason? aReason); + + async NavigationFinished(); + + async SessionStoreUpdate(nsCString? aDocShellCaps, bool? aPrivatedMode, + nsCString[] aPositions, int32_t[] aPositionDescendants, + InputFormData[] aInputs, CollectedInputDataValue[] aIdVals, + CollectedInputDataValue[] aXPathVals, + nsCString[] aOrigins, nsString[] aKeys, + nsString[] aValues, bool aIsFullStorage, + bool aNeedCollectSHistory, uint32_t aFlushId, + bool aIsFinal, uint32_t aEpoch); + + async IntrinsicSizeOrRatioChanged(IntrinsicSize? aIntrinsicSize, + AspectRatio? aIntrinsicRatio); + + /** + * 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 FlushTabState(uint32_t aFlushId, bool aIsFinal); + async UpdateEpoch(uint32_t aEpoch); + async UpdateSHistory(bool aImmediately); + async CloneDocumentTreeIntoSelf(MaybeDiscardedBrowsingContext aBc); + + /** + * Parent informs the child to release all pointer capture. + */ + prio(input) async ReleaseAllPointerCapture(); + +parent: + + /** + * Child informs the parent that the graphics objects are ready for + * compositing. This is sent when all pending changes have been + * sent to the compositor and are ready to be shown on the next composite. + * @see PCompositor + * @see RequestNotifyAfterRemotePaint + */ + async RemotePaintIsReady(); + + /** + * Child informs the parent that the content is ready to handle input + * events. This is sent when the BrowserChild is created. + */ + async RemoteIsReadyToHandleInputEvents(); + + /** + * Child informs the parent that the layer tree is already available. + */ + async PaintWhileInterruptingJSNoOp(LayersObserverEpoch aEpoch); + +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, likely through win.moveTo or resizeTo + */ + async SetDimensions(uint32_t aFlags, int32_t aX, int32_t aY, + int32_t aCx, int32_t aCy, double aScale); + + nested(inside_sync) sync DispatchWheelEvent(WidgetWheelEvent event); + nested(inside_sync) sync DispatchMouseEvent(WidgetMouseEvent event); + nested(inside_sync) sync DispatchKeyboardEvent(WidgetKeyboardEvent event); + + async InvokeDragSession(IPCDataTransfer[] transfers, uint32_t action, + Shmem? visualData, + uint32_t stride, SurfaceFormat format, + LayoutDeviceIntRect dragRect, + nsIPrincipal principal, nsIContentSecurityPolicy csp, + CookieJarSettingsArgs cookieJarSettings); + + // 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 SetAllowDeprecatedTls(bool value); + +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 ResumeLoad(uint64_t pendingSwitchID, ParentShowInfo info); + + async UpdateDimensions(DimensionInfo dimensions) compressall; + + async SizeModeChanged(nsSizeMode sizeMode); + + async ChildToParentMatrix(MaybeMatrix4x4 aMatrix, + ScreenRect aRemoteDocumentRect); + + async SetIsUnderHiddenEmbedderElement(bool aIsUnderHiddenEmbedderElement); + + async DynamicToolbarMaxHeightChanged(ScreenIntCoord height); + + async DynamicToolbarOffsetChanged(ScreenIntCoord height); + + async SetKeyboardIndicators(UIStateChangeType showFocusRings); + + /** + * StopIMEStateManagement() is called when the process loses focus and + * should stop managing IME state. + */ + async StopIMEStateManagement(); + + /** + * @see nsIDOMWindowUtils sendMouseEvent. + */ + async MouseEvent(nsString aType, + float aX, + float aY, + int32_t aButton, + int32_t aClickCount, + int32_t aModifiers); + + /** + * When two consecutive mouse move events would be added to the message queue, + * they are 'compressed' by dumping the oldest one. + */ + prio(input) async RealMouseMoveEvent(WidgetMouseEvent event, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId) compress; + async NormalPriorityRealMouseMoveEvent(WidgetMouseEvent event, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId) compress; + + /** + * 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*()`. + */ + prio(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. + */ + prio(input) async SynthMouseMoveEvent(WidgetMouseEvent event, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId); + async NormalPrioritySynthMouseMoveEvent(WidgetMouseEvent event, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId); + + prio(input) async RealMouseButtonEvent(WidgetMouseEvent event, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId); + async NormalPriorityRealMouseButtonEvent(WidgetMouseEvent event, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId); + + prio(input) async RealKeyEvent(WidgetKeyboardEvent event); + async NormalPriorityRealKeyEvent(WidgetKeyboardEvent event); + + prio(input) async MouseWheelEvent(WidgetWheelEvent event, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId); + async NormalPriorityMouseWheelEvent(WidgetWheelEvent event, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId); + + prio(input) async RealTouchEvent(WidgetTouchEvent aEvent, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId, + nsEventStatus aApzResponse); + async NormalPriorityRealTouchEvent(WidgetTouchEvent aEvent, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId, + nsEventStatus aApzResponse); + + prio(input) async HandleTap(GeckoContentController_TapType aType, LayoutDevicePoint point, + Modifiers aModifiers, ScrollableLayerGuid aGuid, + uint64_t aInputBlockId); + async NormalPriorityHandleTap(GeckoContentController_TapType aType, LayoutDevicePoint point, + Modifiers aModifiers, ScrollableLayerGuid aGuid, + uint64_t aInputBlockId); + + prio(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; + prio(input) async RealTouchMoveEvent2(WidgetTouchEvent aEvent, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId, + nsEventStatus aApzResponse) compress; + async NormalPriorityRealTouchMoveEvent2(WidgetTouchEvent aEvent, + ScrollableLayerGuid aGuid, + uint64_t aInputBlockId, + nsEventStatus aApzResponse) compress; + + /* + * 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, nsIPrincipal aPrincipal, + nsIContentSecurityPolicy csp); + + prio(input) async CompositionEvent(WidgetCompositionEvent event); + async NormalPriorityCompositionEvent(WidgetCompositionEvent event); + + prio(input) async SelectionEvent(WidgetSelectionEvent event); + async NormalPrioritySelectionEvent(WidgetSelectionEvent event); + + /** + * Call PasteTransferable via a controller on the content process + * to handle the command content event, "pasteTransferable". + */ + async PasteTransferable(IPCDataTransfer aDataTransfer, + bool aIsPrivateData, + nsIPrincipal aRequestingPrincipal, + nsContentPolicyType aContentPolicyType); + + /** + * Activate event forwarding from client to parent. + */ + async ActivateFrameEvent(nsString aType, bool capture); + + 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. + * @param aEpoch + * The layer observer epoch for this activation. This message should be + * ignored if this epoch has already been observed (via + * PaintWhileInterruptingJS). + */ + async RenderLayers(bool aEnabled, LayersObserverEpoch aEpoch); +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); + + /** + * The parent (chrome thread) requests that the child inform it when + * the graphics objects are ready to display. + * @see PCompositor + * @see RemotePaintIsReady + */ + async RequestNotifyAfterRemotePaint(); + + /** + * 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); + + /** + * HandledWindowedPluginKeyEvent() is always called after posting a native + * key event with OnWindowedPluginKeyEvent(). + * + * @param aKeyEventData The key event which was posted to the parent + * process. + * @param aIsConsumed true if aKeyEventData is consumed in the + * parent process. Otherwise, false. + */ + async HandledWindowedPluginKeyEvent(NativeEventData aKeyEventData, + bool aIsConsumed); + + /** + * 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 aSourceOuterWindowID Optionally, the ID of the nsGlobalWindowOuter + * 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, + uint64_t? aSourceOuterWindowID) 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 aOuterWindowID the ID of the outer window to print + * @param aPrintData the serialized settings to print with + */ + async Print(uint64_t aOuterWindowID, 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(); + + /** + * Pass the current handle for the current native widget to the content + * process, so it can be used by PuppetWidget. + */ + async SetWidgetNativeData(WindowsHandle aHandle); + + async WillChangeProcess() returns (bool success); + +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(nsIURI aURI, nsIURI aLastVisitedURI, + uint32_t aFlags); + + /** Fetches the visited status for an array of URIs (Android-only). */ + async QueryVisitedState(nsIURI[] aURIs); + + /** + * 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..4d41c68515 --- /dev/null +++ b/dom/ipc/PBrowserBridge.ipdl @@ -0,0 +1,129 @@ +/* -*- 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 "mozilla/LayoutMessageUtils.h"; +include "mozilla/dom/BindingIPCUtils.h"; +include "mozilla/dom/DocShellMessageUtils.h"; +include "mozilla/dom/TabMessageUtils.h"; + +using mozilla::layers::LayersObserverEpoch from "mozilla/layers/LayersTypes.h"; +using class mozilla::WidgetMouseEvent from "ipc/nsGUIEventIPC.h"; +using mozilla::a11y::IDispatchHolder from "mozilla/a11y/IPCTypes.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 CallerType from "mozilla/dom/BindingDeclarations.h"; +using nsIntRect from "nsRect.h"; +using mozilla::dom::EmbedderElementEventType from "mozilla/dom/TabMessageTypes.h"; +using refcounted class nsDocShellLoadState from "nsDocShellLoadState.h"; +using mozilla::IntrinsicSize from "nsIFrame.h"; +using mozilla::AspectRatio from "mozilla/AspectRatio.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 refcounted 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); + + /** + * Send the child the COM proxy for the embedded document accessible. + */ + async SetEmbeddedDocAccessibleCOMProxy(IDispatchHolder aCOMProxy); + + /** + * 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); + +parent: + // Destroy the remote web browser due to the nsFrameLoader going away. + async __delete__(); + + // DocShell messaging. + async LoadURL(nsDocShellLoadState aLoadState); + async ResumeLoad(uint64_t aPendingSwitchID); + + // Out of process rendering. + async Show(OwnerShowInfo info); + async ScrollbarPreferenceChanged(ScrollbarPreference pref); + async UpdateDimensions(nsIntRect rect, ScreenIntSize size) compressall; + async RenderLayers(bool aEnabled, LayersObserverEpoch aEpoch); + + async UpdateEffects(EffectsInfo aEffects); + + /** + * 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 SetIsUnderHiddenEmbedderElement(bool aIsUnderHiddenEmbedderElement); + + 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(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..0e54ad598c --- /dev/null +++ b/dom/ipc/PColorPicker.ipdl @@ -0,0 +1,27 @@ +/* -*- 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 { + +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..87293ed138 --- /dev/null +++ b/dom/ipc/PContent.ipdl @@ -0,0 +1,1865 @@ +/* -*- 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 PBackground; +include protocol PBrowser; +include protocol PCompositorManager; +include protocol PContentPermissionRequest; +include protocol PCycleCollectWithLogs; +include protocol PExternalHelperApp; +include protocol PHandlerService; +include protocol PFileDescriptorSet; +include protocol PHal; +include protocol PHeapSnapshotTempFileHelper; +include protocol PProcessHangMonitor; +include protocol PImageBridge; +include protocol PRemoteLazyInputStream; +include protocol PLoginReputation; +include protocol PMedia; +include protocol PNecko; +include protocol PStreamFilter; +include protocol PGMPContent; +include protocol PGMPService; +include protocol PPluginModule; +include protocol PGMP; +include protocol PPrinting; +include protocol PChildToParentStream; +include protocol PParentToChildStream; +include protocol POfflineCacheUpdate; +#ifdef MOZ_WEBSPEECH +include protocol PSpeechSynthesis; +#endif +include protocol PTestShell; +include protocol PRemoteSpellcheckEngine; +include protocol PWebBrowserPersistDocument; +include protocol PWebrtcGlobal; +include protocol PWindowGlobal; +include protocol PPresentation; +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 PTabContext; +include PluginTypes; +include ProtocolTypes; +include PBackgroundSharedTypes; +include PContentPermission; +include ServiceWorkerConfiguration; +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/DataStorageIPCUtils.h"; +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/ReferrerInfoUtils.h"; +include "mozilla/ipc/ByteBufUtils.h"; +include "mozilla/ipc/URIUtils.h"; +include "mozilla/PermissionDelegateIPCUtils.h"; + +using refcounted class nsIDOMGeoPosition from "nsGeoPositionIPCSerialiser.h"; +using refcounted 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 class IPC::Principal from "mozilla/dom/PermissionMessageUtils.h"; +using mozilla::a11y::IHandlerControlHolder from "mozilla/a11y/IPCTypes.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::dom::TabId from "mozilla/dom/ipc/IdType.h"; +using mozilla::dom::ContentParentId from "mozilla/dom/ipc/IdType.h"; +using mozilla::LayoutDeviceIntPoint 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"; +using moveonly mozilla::UntrustedModulesData from "mozilla/UntrustedModulesData.h"; +using moveonly mozilla::ModulePaths from "mozilla/UntrustedModulesData.h"; +using moveonly mozilla::ModulesMapResult from "mozilla/UntrustedModulesData.h"; +using mozilla::Telemetry::DiscardedData from "mozilla/TelemetryComms.h"; +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 "nsIContentViewer.h"; +using mozilla::dom::MaybeDiscardedWindowContext from "mozilla/dom/WindowContext.h"; +using mozilla::dom::WindowContextTransaction from "mozilla/dom/WindowContext.h"; +using base::SharedMemoryHandle from "base/shared_memory.h"; +using mozilla::fontlist::Pointer from "SharedFontList.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 refcounted class nsDocShellLoadState from "nsDocShellLoadState.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::ContentBlocking::StorageAccessPromptChoices from "mozilla/ContentBlocking.h"; +using JSActorMessageKind from "mozilla/dom/JSActor.h"; +using JSActorMessageMeta from "mozilla/dom/PWindowGlobal.h"; +using mozilla::PermissionDelegateHandler::DelegatedPermissionList from "mozilla/PermissionDelegateHandler.h"; +using refcounted 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::PDMFactory::MediaCodecsSupported from "PDMFactory.h"; +using mozilla::RemoteDecodeIn from "mozilla/RemoteDecoderManagerChild.h"; +using mozilla::dom::PerformanceTimingData from "mozilla/dom/PerformanceTiming.h"; +using refcounted mozilla::dom::FeaturePolicy from "mozilla/dom/FeaturePolicy.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 + +union SystemParameterValue { + bool; + float; +}; + +struct SystemParameterKVPair { + uint8_t id; + SystemParameterValue value; +}; + +struct ClipboardCapabilities { + bool supportsSelectionClipboard; + bool supportsFindClipboard; +}; + +union FileDescOrError { + FileDescriptor; + nsresult; +}; + +struct DomainPolicyClone +{ + bool active; + nsIURI[] blocklist; + nsIURI[] allowlist; + nsIURI[] superBlocklist; + 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; + nsIPrincipal principal; + nsID? agentClusterId; + bool revoked; +}; + +struct JSWindowActorEventDecl +{ + nsString name; + bool capture; + bool systemGroup; + bool allowUntrusted; + bool? passive; +}; + +struct JSWindowActorInfo +{ + nsCString name; + bool allFrames; + + // 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; + // 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 XPCOMInitData +{ + bool isOffline; + bool isConnected; + int32_t captivePortalState; + bool isLangRTL; + bool haveBidiKeyboards; + nsCString[] dictionaries; + ClipboardCapabilities clipboardCaps; + DomainPolicyClone domainPolicy; + nsIURI userContentSheetURL; + GfxVarUpdate[] gfxNonDefaultVarUpdates; + ContentDeviceData contentDeviceData; + GfxInfoFeatureStatus[] gfxFeatureStatus; + DataStorageEntry[] dataStorage; + nsCString[] appLocales; + nsCString[] requestedLocales; + DynamicScalarDefinition[] dynamicScalarDefs; + SystemParameterKVPair[] systemParameters; +}; + +struct VisitedQueryResult +{ + 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; + nsIURI targetOriginURI; + nsIPrincipal callerPrincipal; + nsIPrincipal subjectPrincipal; + nsIURI callerURI; + bool isFromPrivateWindow; + nsCString scriptLocation; + uint64_t innerWindowId; +}; + +union SyncedContextInitializer +{ + BrowsingContextInitializer; + WindowContextInitializer; +}; + +union BlobURLDataRequestResult +{ + IPCBlob; + 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. + */ +nested(upto inside_cpow) sync protocol PContent +{ + manages PBrowser; + manages PContentPermissionRequest; + manages PCycleCollectWithLogs; + manages PExternalHelperApp; + manages PFileDescriptorSet; + manages PHal; + manages PHandlerService; + manages PHeapSnapshotTempFileHelper; + manages PRemoteLazyInputStream; + manages PMedia; + manages PNecko; + manages POfflineCacheUpdate; + manages PPrinting; + manages PChildToParentStream; + manages PParentToChildStream; +#ifdef MOZ_WEBSPEECH + manages PSpeechSynthesis; +#endif + manages PTestShell; + manages PRemoteSpellcheckEngine; + manages PWebBrowserPersistDocument; + manages PWebrtcGlobal; + manages PPresentation; + manages PURLClassifier; + manages PURLClassifierLocal; + manages PScriptCache; + manages PLoginReputation; + 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); + +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: + async PFileDescriptorSet(FileDescriptor fd); + + // 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 AudioDefaultDeviceChange(); + + async NetworkLinkTypeChange(uint32_t type); + + // 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); + + async RequestPerformanceMetrics(nsID aID); + + /** + * Used by third-party modules telemetry (aka "untrusted modules" telemetry) + * to pull data from content processes. + */ + async GetUntrustedModulesData() returns (UntrustedModulesData? data); + + /** + * 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 ClearImageCache(bool privateLoader, bool chrome); + + async ClearStyleSheetCache(nsIPrincipal? aForPrincipal); + + async SetOffline(bool offline); + async SetConnectivity(bool connectivity); + async SetCaptivePortalState(int32_t aState); + + async NotifyVisited(VisitedQueryResult[] uri); + + /** + * Tell the child that the system theme has changed, and that a repaint is + * necessary. + */ + async ThemeChanged(LookAndFeelData lookAndFeelData, ThemeChangeKind aKind); + + async UpdateSystemParameters(SystemParameterKVPair[] aUpdates); + + async PreferenceUpdate(Pref pref); + async VarUpdate(GfxVarUpdate var); + + async UpdatePerfStatsCollectionMask(uint64_t aMask); + async CollectPerfStatsJSON() returns (nsCString aStats); + + async DataStoragePut(nsString aFilename, DataStorageItem aItem); + async DataStorageRemove(nsString aFilename, nsCString aKey, DataStorageType aType); + async DataStorageClear(nsString aFilename); + + async NotifyAlertsObserver(nsCString topic, nsString data); + + async GeolocationUpdate(nsIDOMGeoPosition aPosition); + + async GeolocationError(uint16_t errorCode); + + async UpdateDictionaryList(nsCString[] dictionaries); + + async UpdateFontList(SystemFontListEntry[] 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. + */ + 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(); + + async UpdateAppLocales(nsCString[] appLocales); + async UpdateRequestedLocales(nsCString[] requestedLocales); + + 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. + * @param aTid is the thread ID of the chrome process main thread. Only used + * on Windows; pass 0 on other platforms. + * @param aMsaaID is an a11y-specific unique id for the content process + * that is generated by the chrome process. Only used on + * Windows; pass 0 on other platforms. + */ + async ActivateA11y(uint32_t aMainChromeTid, uint32_t aMsaaID); + + /** + * 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); + + /** + * Send ServiceWorkerRegistrationData to child process. + */ + async InitServiceWorkers(ServiceWorkerConfiguration aConfig); + + /** + * 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, + LookAndFeelData lookAndFeeldata, + /* used on MacOSX/Linux/Android only: */ + SystemFontListEntry[] systemFontList, + SharedMemoryHandle? sharedUASheetHandle, + uintptr_t sharedUASheetAddress, + SharedMemoryHandle[] sharedFontListBlocks); + + // 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(nsIURI uri, uint32_t type); + async UnregisterSheet(nsIURI uri, uint32_t type); + + /** + * Notify idle observers in the child + */ + async NotifyIdleObserver(uint64_t observerId, nsCString topic, nsString str); + + async InvokeDragSession(IPCDataTransfer[] transfers, uint32_t action); + + async EndDragSession(bool aDoneDrag, bool aUserCancelled, + LayoutDeviceIntPoint aDragEndPoint, + uint32_t aKeyModifiers); + + async DomainSetChanged(uint32_t aSetType, uint32_t aChangeType, nsIURI aDomain); + + /** + * 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 presentation receiver has been launched with the + * correspondent iframe. + */ + async NotifyPresentationReceiverLaunched(PBrowser aIframe, nsString aSessionId); + + /** + * Notify the child that the info about a presentation receiver needs to be + * cleaned up. + */ + async NotifyPresentationReceiverCleanUp(nsString aSessionId); + + /** + * 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, Principal principal, nsString messageId); + + /** + * Send a `push` event with data to a service worker in the child. + */ + async PushWithData(nsCString scope, Principal principal, + nsString messageId, uint8_t[] data); + + /** + * Send a `pushsubscriptionchange` event to a service worker in the child. + */ + async PushSubscriptionChange(nsCString scope, Principal principal); + + async GetFilesResponse(nsID aID, GetFilesResponseResult aResult); + + async BlobURLRegistration(nsCString aURI, IPCBlob aBlob, + Principal aPrincipal, + nsID? aAgentClusterId); + + async BlobURLUnregistration(nsCString aURI); + + async GMPsChanged(GMPCapabilityData[] capabilities); + + + async PParentToChildStream(); + + async ProvideAnonymousTemporaryFile(uint64_t aID, FileDescOrError aFD); + + async SetPermissionsWithKey(nsCString aPermissionKey, Permission[] aPermissions); + + async RefreshScreens(ScreenDetails[] aScreens); + + async PRemoteLazyInputStream(nsID aID, uint64_t aSize); + + /** + * This call takes the set of plugins loaded in the chrome process, and + * sends them to the content process. However, in many cases this set will + * not have changed since the last SetPluginList message. To keep track of + * this, the chrome process increments an epoch number every time the set of + * plugins changes. The chrome process sends up the last epoch it observed. + * If the epoch last seen by the content process is the same, the content + * process ignores the update. Otherwise the content process updates its + * list and reloads its plugins. + **/ + async SetPluginList(uint32_t pluginEpoch, PluginTag[] plugins, FakePluginTag[] fakePlugins); + + async ShareCodeCoverageMutex(CrossProcessMutexHandle handle); + async FlushCodeCoverageCounters() returns (bool unused); + + async GetMemoryUniqueSetSize() returns (int64_t uss); + + /* + * 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. + */ + prio(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); + +#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); + async GoForward(MaybeDiscardedBrowsingContext aContext, int32_t? aCancelContentJSEpoch, bool aRequireUserInteraction); + async GoToIndex(MaybeDiscardedBrowsingContext aContext, int32_t aIndex, int32_t? aCancelContentJSEpoch); + 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 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); + +parent: + /** + * This is a temporary way to pass index and length through parent process. + * Used for testing. + */ + async SessionHistoryUpdate(MaybeDiscardedBrowsingContext aTopContext, + int32_t aIndex, int32_t aLength, nsID aChangeID); + + async SynchronizeLayoutHistoryState(MaybeDiscardedBrowsingContext aContext, + nsILayoutHistoryState aState); + + async SessionHistoryEntryTitle(MaybeDiscardedBrowsingContext aContext, + nsString aTitle); + + async SessionHistoryEntryScrollRestorationIsManual(MaybeDiscardedBrowsingContext aContext, + bool aIsManual); + + async SessionHistoryEntryCacheKey(MaybeDiscardedBrowsingContext aContext, + uint32_t aCacheKey); + + async SessionHistoryEntryStoreWindowNameInContiguousEntries(MaybeDiscardedBrowsingContext aContext, + nsString aName); + + async GetLoadingSessionHistoryInfoFromParent(MaybeDiscardedBrowsingContext aContext) + returns (LoadingSessionHistoryInfo? aLoadingInfo, int32_t aRequestedIndex, int32_t aLength); + + async InitBackground(Endpoint<PBackgroundParent> aEndpoint); + + async CreateGMPService(); + + async InitStreamFilter(uint64_t channelId, nsString addonId) + returns (Endpoint<PStreamFilterChild> aEndpoint); + + /** + * This call connects the content process to a plugin process. This call + * returns an endpoint for a new PluginModuleParent. The corresponding + * PluginModuleChild will be started up in the plugin process. + */ + sync LoadPlugin(uint32_t aPluginId) + returns (nsresult aResult, uint32_t aRunID, Endpoint<PPluginModuleParent> aEndpoint); + + /** + * This call is used by asynchronous plugin instantiation to notify the + * content parent that it is now safe to initiate the plugin bridge for + * the specified plugin id. The endpoint for the content process part of the + * bridge is returned. + */ + sync ConnectPluginBridge(uint32_t aPluginId) + returns (nsresult rv, Endpoint<PPluginModuleParent> aEndpoint); + + async PRemoteSpellcheckEngine(); + + async InitCrashReporter(NativeThreadId tid); + + sync IsSecureURI(uint32_t aType, nsIURI aURI, uint32_t aFlags, + OriginAttributes aOriginAttributes) + returns (bool isSecureURI); + + async AccumulateMixedContentHSTS(nsIURI aURI, bool aActive, + OriginAttributes aOriginAttributes); + + nested(inside_cpow) async PHal(); + + async PHeapSnapshotTempFileHelper(); + + async PNecko(); + + async PPrinting(); + + async PChildToParentStream(); + +#ifdef MOZ_WEBSPEECH + async PSpeechSynthesis(); +#endif + + async PMedia(); + + async PWebrtcGlobal(); + + async PPresentation(); + + async CreateAudioIPCConnection() returns (FileDescOrError fd); + + sync PURLClassifier(Principal principal) + returns (bool success); + + async PURLClassifierLocal(nsIURI uri, IPCURLClassifierFeature[] features); + + async PLoginReputation(nsIURI formURI); + + async PSessionStorageObserver(); + + async PBenchmarkStorage(); + + // Services remoting + + async StartVisitedQueries(nsIURI[] uri); + async SetURITitle(nsIURI uri, nsString title); + + async LoadURIExternal(nsIURI uri, nsIPrincipal triggeringPrincipal, MaybeDiscardedBrowsingContext browsingContext); + async ExtProtocolChannelConnectParent(uint64_t registrarId); + + // PrefService message + sync GetGfxVars() returns (GfxVarUpdate[] vars); + + sync SyncMessage(nsString aMessage, ClonedMessageData aData) + returns (StructuredCloneData[] retval); + + async ShowAlert(nsIAlertNotification alert); + + async CloseAlert(nsString name); + + async DisableNotifications(Principal principal); + + async OpenNotificationSettings(Principal 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(nsIURI uri, + LoadInfoArgs? loadInfoArgs, + nsCString aMimeContentType, + nsCString aContentDisposition, + uint32_t aContentDispositionHint, + nsString aContentDispositionFilename, + bool aForceSave, + int64_t aContentLength, + bool aWasFileChannel, + 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(IPCDataTransfer aDataTransfer, + bool aIsPrivateData, + Principal aRequestingPrincipal, + nsContentPolicyType aContentPolicyType, + int32_t aWhichClipboard); + + // Given a list of supported types, returns the clipboard data for the + // first type that matches. + sync GetClipboard(nsCString[] aTypes, int32_t aWhichClipboard) + returns (IPCDataTransfer dataTransfer); + + // Returns a list of formats supported by the clipboard + sync GetExternalClipboardFormats(int32_t aWhichClipboard, bool aPlainTextOnly) returns (nsCString[] aTypes); + + // 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); + + // 'Play', 'Beep' and 'PlayEventSound' are the only nsISound methods used in + // the content process. + async PlaySound(nsIURI aURL) compress; + async Beep() compress; + async PlayEventSound(uint32_t aEventId) compress; + + sync GetIconForExtension(nsCString aFileExt, uint32_t aIconSize) + returns (uint8_t[] bits); + + // Notify the parent of the presence or absence of private docshells + async PrivateDocShellsExist(bool aExist); + + // Tell the parent that the child has gone idle for the first time. + async FirstIdle(); + + async DeviceReset(); + + async CopyFavicon(nsIURI oldURI, nsIURI newURI, bool isPrivate); + + /** + * Notifies the parent about a recording device is starting or shutdown. + * @param recordingStatus starting or shutdown + * @param pageURL URL that request that changing the recording status + * @param isAudio recording start with microphone + * @param isVideo recording start with camera + */ + async RecordingDeviceEvents(nsString recordingStatus, + nsString pageURL, + bool isAudio, + bool isVideo); + + // 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); + + /** + * Starts an offline application cache update. + * @param manifestURI + * URI of the manifest to fetch, the application cache group ID + * @param documentURI + * URI of the document that referred the manifest + * @param loadingPrincipal + * Principal of the document that referred the manifest + * @param stickDocument + * True if the update was initiated by a document load that referred + * a manifest. + * False if the update was initiated by applicationCache.update() call. + * + * Tells the update to carry the documentURI to a potential separate + * update of implicit (master) items. + * + * Why this argument? If the document was not found in an offline cache + * before load and refers a manifest and this manifest itself has not + * been changed since the last fetch, we will not do the application + * cache group update. But we must cache the document (identified by the + * documentURI). This argument will ensure that a previously uncached + * document will get cached and that we don't re-cache a document that + * has already been cached (stickDocument=false). + * @param tabId + * To identify which tab owns the app. + */ + async POfflineCacheUpdate(nsIURI manifestURI, nsIURI documentURI, + PrincipalInfo loadingPrincipal, bool stickDocument, + CookieJarSettingsArgs cookieJarSettings); + + /** + * Sets "offline-app" permission for the principal. Called when we hit + * a web app with the manifest attribute in <html> + */ + async SetOfflinePermission(Principal principal); + + /** + * 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, + Principal aPrincipal, + Principal aTopLevelPrincipal, + bool aIsHandlingUserInput, + bool aMaybeUnsafePermissionDelegate, + TabId tabId); + + async ShutdownProfile(nsCString aProfile); + + /** + * Request graphics initialization information from the parent. + */ + sync GetGraphicsDeviceInitData() + returns (ContentDeviceData aData); + + /** + * Request a buffer containing the contents of the output color profile. + * If set, this is the file pointed to by + * gfx.color_management.display_profile, otherwise it contains a + * platform-specific default + */ + sync GetOutputColorProfileData() + returns (uint8_t[] aOutputColorProfileData); + + /** + * 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 aFacePtr + * Font-list shared-memory "pointer" to the Face record to be updated. + * A Pointer is a record of a shared-memory block index and an offset + * within that block, which each process that maps the block can convert + * into a real pointer in its address space. + * @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, Pointer aFacePtr, gfxSparseBitSet aMap); + + /** + * Ask the parent to set up the merged charmap for a family, to accelerate + * future fallback searches. + * aFamilyPtr may refer to an element of either the Families() or AliasFamilies(). + */ + async SetupFamilyCharMap(uint32_t aGeneration, Pointer aFamilyPtr); + + /** + * 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(nsIURI aURI) returns (SharedMemoryHandle aHandle, uint32_t aSize); + + async CreateWindow(PBrowser aThisTab, + MaybeDiscardedBrowsingContext aParent, + PBrowser aNewTab, + uint32_t aChromeFlags, + bool aCalledFromJS, + bool aWidthSpecified, + bool aForPrinting, + bool aForWindowDotPrint, + nsIURI aURIToLoad, + nsCString aFeatures, + float aFullZoom, + Principal aTriggeringPrincipal, + nsIContentSecurityPolicy aCsp, + nsIReferrerInfo aReferrerInfo, + OriginAttributes aOriginAttributes) + returns (CreatedWindowInfo window); + + async CreateWindowInDifferentProcess( + PBrowser aThisTab, + MaybeDiscardedBrowsingContext aParent, + uint32_t aChromeFlags, + bool aCalledFromJS, + bool aWidthSpecified, + nsIURI aURIToLoad, + nsCString aFeatures, + float aFullZoom, + nsString aName, + nsIPrincipal aTriggeringPrincipal, + nsIContentSecurityPolicy aCsp, + nsIReferrerInfo aReferrerInfo, + OriginAttributes aOriginAttributes); + + /** + * Tells the parent to ungrab the pointer on the default display. + * + * This is for GTK platforms where we have to ensure the pointer ungrab happens in the + * chrome process as that's the process that receives the pointer event. + */ + sync UngrabPointer(uint32_t time); + + sync RemovePermission(Principal principal, nsCString permissionType) returns (nsresult rv); + + /** + * 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, Principal principal, + nsString messageId); + + /** + * Notify `push-message` observers with data in the parent. + */ + async NotifyPushObserversWithData(nsCString scope, Principal principal, + nsString messageId, uint8_t[] data); + + /** + * Notify `push-subscription-change` observers in the parent. + */ + async NotifyPushSubscriptionChangeObservers(nsCString scope, + Principal principal); + + async GetFilesRequest(nsID aID, nsString aDirectory, bool aRecursiveFlag); + async DeleteGetFilesRequest(nsID aID); + + async StoreAndBroadcastBlobURLRegistration(nsCString url, IPCBlob blob, + Principal principal, + nsID? aAgentClusterId); + + async UnstoreAndBroadcastBlobURLUnregistration(nsCString url, Principal principal); + + /** + * 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); + + sync GetA11yContentId() returns (uint32_t aContentId); + async A11yHandlerControl(uint32_t aPid, + IHandlerControlHolder aHandlerControl); + + async AddMemoryReport(MemoryReport aReport); + + async MaybeReloadPlugins(); + + async BHRThreadHang(HangDetails aHangDetails); + + async AddPerformanceMetrics(nsID aID, PerformanceInfo[] aMetrics); + + /* + * Adds a certificate exception for the given hostname and port. + */ + async AddCertException(nsCString aSerializedCert, uint32_t aFlags, + nsCString aHostName, int32_t aPort, + 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(Principal 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, + Principal aTrackingPrincipal, + nsCString aTrackingOrigin, + int aAllowMode, + StorageAccessPermissionGrantedReason? aReason) + returns (bool unused); + + async CompleteAllowAccessFor(MaybeDiscardedBrowsingContext aParentContext, + uint64_t aTopLevelWindowId, + Principal aTrackingPrincipal, + nsCString aTrackingOrigin, + uint32_t aCookieBehavior, + StorageAccessPermissionGrantedReason aReason) + returns (StorageAccessPromptChoices? choice); + + async StoreUserInteractionAsPermission(Principal aPrincipal); + + /** + * 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); + + /** + * 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); + + /** + * 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); + + async HistoryGo(MaybeDiscardedBrowsingContext aContext, + int32_t aOffset, uint64_t aHistoryEpoch, bool aRequireUserInteraction) returns(int32_t requestedIndex); + + async BlobURLDataRequest(nsCString aBlobURL, + nsIPrincipal aTriggeringPrincipal, + nsIPrincipal aLoadingPrincipal, + OriginAttributes aOriginAttributes, + nsID? aAgentClusterId) + 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); + +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. + */ + async ReportFrameTimingData(uint64_t innerWindowId, 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, + Principal principal); + + /** + * Send a Push error message to all service worker clients in the parent or + * child. + */ + async PushError(nsCString scope, Principal 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. + */ + async CreateBrowsingContext(uint64_t aGroupId, BrowsingContextInitializer aInit); + + /** + * Discards the passed-in BrowsingContext. If the BrowsingContext has + * already been discarded, this message does nothing. + * The response promise is fulfilled when the process has flagged the + * BrowsingContext as discarded. + */ + async DiscardBrowsingContext(MaybeDiscardedBrowsingContext aContext) + returns (bool unused); + + async AdjustWindowFocus(MaybeDiscardedBrowsingContext aContext, + bool aCheckPermission, + bool aIsVisible); + async WindowClose(MaybeDiscardedBrowsingContext aContext, + bool aTrustedCaller); + async WindowFocus(MaybeDiscardedBrowsingContext aContext, + CallerType aCallerType, uint64_t aActionId); + async WindowBlur(MaybeDiscardedBrowsingContext aContext); + async RaiseWindow(MaybeDiscardedBrowsingContext aContext, CallerType aCallerType, uint64_t aActionId); + async ClearFocus(MaybeDiscardedBrowsingContext aContext); + async SetFocusedBrowsingContext(MaybeDiscardedBrowsingContext aContext); + 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: + 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, + MaybeDiscardedBrowsingContext aActiveBrowsingContext); + async ReviseActiveBrowsingContext(MaybeDiscardedBrowsingContext aActiveBrowsingContext, + uint64_t aActionId); +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. + 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, + FeaturePolicy aContainerFeaturePolicy); +}; + +} +} 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..182e5e9dc1 --- /dev/null +++ b/dom/ipc/PContentPermissionRequest.ipdl @@ -0,0 +1,26 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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; + +namespace mozilla { +namespace dom { + +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..f420c6ddae --- /dev/null +++ b/dom/ipc/PCycleCollectWithLogs.ipdl @@ -0,0 +1,22 @@ +/* -*- 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 { + +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..c65640d50e --- /dev/null +++ b/dom/ipc/PFilePicker.ipdl @@ -0,0 +1,53 @@ +/* -*- 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 protocol PChildToParentStream; +include protocol PFileDescriptorSet; +include protocol PParentToChildStream; +include protocol PRemoteLazyInputStream; + +include IPCBlob; + +using struct mozilla::void_t from "mozilla/ipc/IPCCore.h"; + +namespace mozilla { +namespace dom { + +struct InputBlobs +{ + IPCBlob[] blobs; +}; + +struct InputDirectory +{ + nsString directoryPath; +}; + +union MaybeInputData +{ + InputBlobs; + InputDirectory; + void_t; +}; + +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, + int16_t capture); + +child: + async __delete__(MaybeInputData data, int16_t result); +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/ipc/PInProcess.ipdl b/dom/ipc/PInProcess.ipdl new file mode 100644 index 0000000000..6b14ae3663 --- /dev/null +++ b/dom/ipc/PInProcess.ipdl @@ -0,0 +1,28 @@ +/* -*- 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 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. + */ +async refcounted protocol PInProcess +{ + manages PWindowGlobal; +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/ipc/PLoginReputation.ipdl b/dom/ipc/PLoginReputation.ipdl new file mode 100644 index 0000000000..98c508f650 --- /dev/null +++ b/dom/ipc/PLoginReputation.ipdl @@ -0,0 +1,26 @@ +/* -*- 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; + +namespace mozilla { +namespace dom { + +// PLoginReputation allows child to send URL to parent when user focuses +// on a password field. Right now this is an one way IPC call (No callback +// will return after parent receives the IPC message) since we just process +// the URL in parent (LoginReputationService) and stores the result to telemetry. +protocol PLoginReputation +{ + manager PContent; + +child: + async __delete__(); +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/ipc/PPluginWidget.ipdl b/dom/ipc/PPluginWidget.ipdl new file mode 100644 index 0000000000..1e5d308292 --- /dev/null +++ b/dom/ipc/PPluginWidget.ipdl @@ -0,0 +1,63 @@ + /* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "mozilla/dom/BindingIPCUtils.h"; +include "mozilla/GfxMessageUtils.h"; + +using mozilla::dom::CallerType from "mozilla/dom/BindingDeclarations.h"; +using nsIntRect from "nsRect.h"; + +namespace mozilla { +namespace plugins { + +/** + * PPluginWidget - a nsIWidget'ish protocol for windowed plugins in e10s. + * On windows we create native widgets in chrome which we then manage + * from content. On the566595 content side there's PluginWidgetProxy which + * implements nsIWidget. We hand this around layout and plugins code. Anything + * not dealt with via PluginWidgetProxy falls through to PuppetWidget. Native + * widget exists on the chrome side (PluginWidgetParent) attached to the + * browser window as a child. Window management calls are forwarded from + * PluginWidgetProxy to PluginWidgetParent over this interface. + * + * Note lifetime management for PluginWidgetProxy (the plugin widget) and the + * connection (PluginWidgetChild) are separated. PluginWidgetChild will + * be torn down first by the tab, followed by the deref'ing of the nsIWidget + * via layout. + */ +sync protocol PPluginWidget { + manager PBrowser; + +parent: + async __delete__(); + + /** + * Used to set the ID of a scroll capture container from the parent process, + * so that we can create a proxy container in the layer tree. + * @param aScrollCaptureId async container ID of the parent container + * @param aPluginInstanceId plugin ID on which to set the scroll capture ID + */ + sync Create() returns (nsresult aResult, uint64_t aScrollCaptureId, + uintptr_t aPluginInstanceId); + async SetFocus(bool aRaise, CallerType aCallerType); + + /** + * Returns NS_NATIVE_PLUGIN_PORT and its variants: a sharable native + * window for plugins. On Linux, this returns an XID for a socket widget + * embedded in the chrome side native window. On Windows this returns the + * native HWND of the plugin widget. + */ + sync GetNativePluginPort() returns (uintptr_t value); + + /** + * Sends an NS_NATIVE_CHILD_WINDOW to be adopted by the widget's native window + * on the chrome side. This is only currently used on Windows. + */ + sync SetNativeChildWindow(uintptr_t childWindow); +}; + +} +} diff --git a/dom/ipc/PProcessHangMonitor.ipdl b/dom/ipc/PProcessHangMonitor.ipdl new file mode 100644 index 0000000000..224b98aded --- /dev/null +++ b/dom/ipc/PProcessHangMonitor.ipdl @@ -0,0 +1,57 @@ +/* -*- 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 mozilla::layers::LayersObserverEpoch from "mozilla/layers/LayersTypes.h"; + +namespace mozilla { + +struct SlowScriptData +{ + TabId tabId; + nsCString filename; + nsString addonId; + double duration; +}; + +struct PluginHangData +{ + uint32_t pluginId; + ProcessId contentProcessId; +}; + +union HangData +{ + SlowScriptData; + PluginHangData; +}; + +protocol PProcessHangMonitor +{ +parent: + async HangEvidence(HangData data); + async ClearHang(); + +child: + async TerminateScript(bool aTerminateGlobal); + + async BeginStartingDebugger(); + async EndStartingDebugger(); + + async PaintWhileInterruptingJS(TabId tabId, LayersObserverEpoch aEpoch); + + async CancelContentJSExecutionIfRunning( + TabId tabId, NavigationType aNavigationType, + int32_t aNavigationIndex, nsCString? aNavigationURI, int32_t aEpoch); +}; + +} // namespace mozilla diff --git a/dom/ipc/PTabContext.ipdlh b/dom/ipc/PTabContext.ipdlh new file mode 100644 index 0000000000..b83f87d26b --- /dev/null +++ b/dom/ipc/PTabContext.ipdlh @@ -0,0 +1,62 @@ +/* -*- 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 UIStateChangeType from "nsPIDOMWindow.h"; +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; + + // The requested presentation URL. + // This value would be empty if the TabContext isn't created for + // presented content. + nsString presentationURL; + + // Keyboard indicator state inherited from the parent. + UIStateChangeType showFocusRings; + + // Maximum number of touch points on the screen. + uint32_t maxTouchPoints; +}; + +struct JSPluginFrameIPCTabContext +{ + uint32_t jsPluginId; +}; + +// 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; + JSPluginFrameIPCTabContext; +}; + +} +} diff --git a/dom/ipc/PURLClassifier.ipdl b/dom/ipc/PURLClassifier.ipdl new file mode 100644 index 0000000000..b003be29f7 --- /dev/null +++ b/dom/ipc/PURLClassifier.ipdl @@ -0,0 +1,23 @@ +/* -*- 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 { + +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..c835359725 --- /dev/null +++ b/dom/ipc/PURLClassifierLocal.ipdl @@ -0,0 +1,35 @@ +/* -*- 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"; + +using refcounted class nsIURI from "nsIURI.h"; + +namespace mozilla { +namespace dom { + +struct URLClassifierLocalResult +{ + nsIURI uri; + nsCString featureName; + nsCString matchingList; +}; + +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..19fdc4164d --- /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. + prio(high) async Notify(VsyncEvent aVsync, float aVsyncRate) compress; + +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..b257f64b75 --- /dev/null +++ b/dom/ipc/PWindowGlobal.ipdl @@ -0,0 +1,203 @@ +/* -*- 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/PermissionMessageUtils.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 NeckoChannelParams; + +include "mozilla/layers/LayersMessageUtils.h"; + +using JSActorMessageKind from "mozilla/dom/JSActor.h"; +using mozilla::gfx::IntRect from "mozilla/gfx/Rect.h"; +using moveonly mozilla::gfx::PaintFragment from "mozilla/gfx/CrossProcessPaint.h"; +using nscolor from "nsColor.h"; +using refcounted class nsDocShellLoadState from "nsDocShellLoadState.h"; +using mozilla::dom::XPCOMPermitUnloadAction from "nsIContentViewer.h"; +using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h"; +using mozilla::layers::LayersId from "mozilla/layers/LayersTypes.h"; +using refcounted class nsITransportSecurityInfo from "nsITransportSecurityInfo.h"; +using mozilla::UseCounters from "mozilla/UseCounter.h"; +using mozilla::dom::MaybeDiscardedWindowContext from "mozilla/dom/WindowContext.h"; +using refcounted mozilla::dom::FeaturePolicy from "mozilla/dom/FeaturePolicy.h"; + +namespace mozilla { +namespace dom { + +struct JSActorMessageMeta { + nsCString actorName; + nsString messageName; + uint64_t queryId; + JSActorMessageKind kind; +}; + +struct IPCWebShareData +{ + nsCString title; + nsCString text; + 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 refcounted 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); + + /** + * Returns the serialized security info associated with this window. + */ + async GetSecurityInfo() returns(nsCString? serializedSecInfo); + + 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(FeaturePolicy aContainerFeaturePolicy); + +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. + 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. + async UpdateDocumentPrincipal(nsIPrincipal aPrincipal); + + // Update document's `documentHasLoaded` bit in this WindowGlobal. + async UpdateDocumentHasLoaded(bool aDocumentHasLoaded); + + // Update document's 'documentHasUserInteracted' bit in this WindowGlobal. + async UpdateDocumentHasUserInteracted(bool aDocumentHasUserInteracted); + + // Update document's sandbox flags in this WindowGlobal. + async UpdateSandboxFlags(uint32_t aSandboxFlags); + + // Update document csp's fields in this WindowGlobal. + async UpdateDocumentCspSettings(bool aBlockAllMixedContent, bool aUpgradeInsecureRequests); + + // Update document's cookie settings in this WindowGlobal. + async UpdateCookieJarSettings(CookieJarSettingsArgs cookieJarSettings); + + // Update the title of the document in this WindowGlobal. + async UpdateDocumentTitle(nsString aTitle); + + async UpdateDocumentSecurityInfo(nsITransportSecurityInfo aSecurityInfo); + + // Update the document's HTTPS-Only Mode flags in this WindowGlobal. + async UpdateHttpsOnlyStatus(uint32_t aHttpsOnlyStatus); + + /// Send down initial document bit to the parent. + 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. + async SetClientInfo(IPCClientInfo aClientInfo); + + /** + * Inform the parent that the document will preload a resource if the + * network.preload pref is enabled. + */ + async UpdateDocumentWouldPreloadResources(); + + /** + * Submit load event telemetry affiliated with whether or not the document + * tree preloaded any resources. + */ + async SubmitLoadEventPreloadTelemetry(TimeStamp aNavigationStart, + TimeStamp aLoadStart, + TimeStamp aLoadEnd); + + + /** + * Submit Time-to-First-Interaction telemetry correlated with whether or not + * the document tree preloaded any resources. + */ + async SubmitTimeToFirstInteractionPreloadTelemetry(uint32_t aMillis); + + /** + * Submit Load Input Event Response time telemetry correlated with whether + * or not the document tree preloaded any resources. + */ + async SubmitLoadInputEventResponsePreloadTelemetry(uint32_t aMillis); + + // 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 nsIContentViewer::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 Destroy(); +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/ipc/PermissionMessageUtils.cpp b/dom/ipc/PermissionMessageUtils.cpp new file mode 100644 index 0000000000..2e26aaa78a --- /dev/null +++ b/dom/ipc/PermissionMessageUtils.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/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::Message* aMsg, + IProtocol* aActor, + nsIPrincipal* aParam) { + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + + Maybe<PrincipalInfo> info; + if (aParam) { + info.emplace(); + nsresult rv = PrincipalToPrincipalInfo(aParam, info.ptr()); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); + } + + WriteIPDLParam(aMsg, aActor, info); +} + +bool IPDLParamTraits<nsIPrincipal*>::Read(const IPC::Message* aMsg, + PickleIterator* aIter, + IProtocol* aActor, + RefPtr<nsIPrincipal>* aResult) { + Maybe<PrincipalInfo> info; + if (!ReadIPDLParam(aMsg, aIter, 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..e73ce7922e --- /dev/null +++ b/dom/ipc/PermissionMessageUtils.h @@ -0,0 +1,76 @@ +/* -*- 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 IPC { + +/** + * Legacy IPC::Principal type. Use nsIPrincipal directly in new IPDL code. + */ +class Principal { + friend struct mozilla::ipc::IPDLParamTraits<Principal>; + + public: + Principal() = default; + + explicit Principal(nsIPrincipal* aPrincipal) : mPrincipal(aPrincipal) {} + + operator nsIPrincipal*() const { return mPrincipal.get(); } + + Principal& operator=(const Principal& aOther) = delete; + + private: + RefPtr<nsIPrincipal> mPrincipal; +}; + +} // namespace IPC + +namespace mozilla { +namespace ipc { + +template <> +struct IPDLParamTraits<nsIPrincipal*> { + static void Write(IPC::Message* aMsg, IProtocol* aActor, + nsIPrincipal* aParam); + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, RefPtr<nsIPrincipal>* aResult); + + // Overload to support deserializing nsCOMPtr<nsIPrincipal> directly. + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, nsCOMPtr<nsIPrincipal>* aResult) { + RefPtr<nsIPrincipal> result; + if (!Read(aMsg, aIter, aActor, &result)) { + return false; + } + *aResult = std::move(result); + return true; + } +}; + +template <> +struct IPDLParamTraits<IPC::Principal> { + typedef IPC::Principal paramType; + static void Write(IPC::Message* aMsg, IProtocol* aActor, + const paramType& aParam) { + WriteIPDLParam(aMsg, aActor, aParam.mPrincipal); + } + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, paramType* aResult) { + return ReadIPDLParam(aMsg, aIter, aActor, &aResult->mPrincipal); + } +}; + +} // namespace ipc +} // namespace mozilla + +#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..9c297ca74c --- /dev/null +++ b/dom/ipc/PreallocatedProcessManager.cpp @@ -0,0 +1,428 @@ +/* -*- 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/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 <deque> + +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(); + void AllocateOnIdle(); + void AllocateNow(); + + void RereadPrefs(); + void Enable(uint32_t aProcesses); + void Disable(); + void CloseProcesses(); + + bool IsEmpty() const { + return mPreallocatedProcesses.empty() && !mLaunchInProgress; + } + + bool mEnabled; + static bool sShutdown; + bool mLaunchInProgress; + uint32_t mNumberPreallocs; + std::deque<RefPtr<ContentParent>> 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; +bool PreallocatedProcessManagerImpl::sShutdown = false; + +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), mLaunchInProgress(false), mNumberPreallocs(1) {} + +PreallocatedProcessManagerImpl::~PreallocatedProcessManagerImpl() { + // This shouldn't happen, because the promise callbacks should + // hold strong references, but let't make absolutely sure: + MOZ_RELEASE_ASSERT(!mLaunchInProgress); +} + +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); + } + // Let's prevent any new preallocated processes from starting. ContentParent + // will handle the shutdown of the existing process and the + // mPreallocatedProcesses reference will be cleared by the ClearOnShutdown + // of the manager singleton. + sShutdown = true; + } 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(); + } + if (number >= 0) { + Enable(number); + // We have one prealloc queue for all types except File now + if (static_cast<uint64_t>(number) < mPreallocatedProcesses.size()) { + CloseProcesses(); + } + } + } else { + Disable(); + } +} + +already_AddRefed<ContentParent> PreallocatedProcessManagerImpl::Take( + const nsACString& aRemoteType) { + if (!mEnabled || sShutdown) { + return nullptr; + } + RefPtr<ContentParent> process; + if (!mPreallocatedProcesses.empty()) { + process = mPreallocatedProcesses.front().forget(); + mPreallocatedProcesses.pop_front(); // holds a nullptr + + ProcessPriorityManager::SetProcessPriority(process, + PROCESS_PRIORITY_FOREGROUND); + + // We took a preallocated process. Let's try to start up a new one + // soon. + AllocateOnIdle(); + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("Use prealloc process %p", process.get())); + } + return process.forget(); +} + +void PreallocatedProcessManagerImpl::Erase(ContentParent* aParent) { + // Ensure this ContentParent isn't cached + for (auto it = mPreallocatedProcesses.begin(); + it != mPreallocatedProcesses.end(); it++) { + if (*it == aParent) { + mPreallocatedProcesses.erase(it); + break; + } + } +} + +void PreallocatedProcessManagerImpl::Enable(uint32_t aProcesses) { + mNumberPreallocs = aProcesses; + if (mEnabled) { + return; + } + + mEnabled = true; + AllocateAfterDelay(); +} + +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 mEnabled && sNumBlockers == 0 && + mPreallocatedProcesses.size() < mNumberPreallocs && !sShutdown && + (FissionAutostart() || + !ContentParent::IsMaxProcessCountReached(DEFAULT_REMOTE_TYPE)); +} + +void PreallocatedProcessManagerImpl::AllocateAfterDelay() { + if (!mEnabled) { + return; + } + + NS_DelayedDispatchToCurrentThread( + NewRunnableMethod("PreallocatedProcessManagerImpl::AllocateOnIdle", this, + &PreallocatedProcessManagerImpl::AllocateOnIdle), + StaticPrefs::dom_ipc_processPrelaunch_delayMs()); +} + +void PreallocatedProcessManagerImpl::AllocateOnIdle() { + if (!mEnabled) { + return; + } + + NS_DispatchToCurrentThreadQueue( + NewRunnableMethod("PreallocatedProcessManagerImpl::AllocateNow", this, + &PreallocatedProcessManagerImpl::AllocateNow), + EventQueuePriority::Idle); +} + +void PreallocatedProcessManagerImpl::AllocateNow() { + if (!CanAllocate()) { + if (mEnabled && !sShutdown && IsEmpty() && sNumBlockers > 0) { + // If it's too early to allocate a process let's retry later. + AllocateAfterDelay(); + } + return; + } + + RefPtr<PreallocatedProcessManagerImpl> self(this); + mLaunchInProgress = true; + + ContentParent::PreallocateProcess()->Then( + GetCurrentSerialEventTarget(), __func__, + + [self, this](const RefPtr<ContentParent>& process) { + mLaunchInProgress = false; + if (process->IsDead()) { + // 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 { + if (CanAllocate()) { + // slight perf reason for push_back - while the cpu cache + // probably has stack/etc associated with the most recent + // process created, we don't know that it has finished startup. + // If we added it to the queue on completion of startup, we + // could push_front it, but that would require a bunch more + // logic. + mPreallocatedProcesses.push_back(process); + MOZ_LOG(ContentParent::GetLog(), LogLevel::Debug, + ("Preallocated = %lu of %d processes", + (unsigned long)mPreallocatedProcesses.size(), + mNumberPreallocs)); + + // Continue prestarting processes if needed + if (mPreallocatedProcesses.size() < mNumberPreallocs) { + AllocateOnIdle(); + } + } else { + process->ShutDownProcess(ContentParent::SEND_SHUTDOWN_MESSAGE); + } + } + }, + + [self, this](ContentParent::LaunchError err) { + mLaunchInProgress = false; + }); +} + +void PreallocatedProcessManagerImpl::Disable() { + if (!mEnabled) { + return; + } + + mEnabled = false; + CloseProcesses(); +} + +void PreallocatedProcessManagerImpl::CloseProcesses() { + while (!mPreallocatedProcesses.empty()) { + RefPtr<ContentParent> process(mPreallocatedProcesses.front().forget()); + mPreallocatedProcesses.pop_front(); + 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::sShutdown) { + return nullptr; + } + return PreallocatedProcessManagerImpl::Singleton(); +} + +/* static */ +bool PreallocatedProcessManager::Enabled() { + if (auto impl = GetPPMImpl()) { + return impl->mEnabled; + } + 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..762351a429 --- /dev/null +++ b/dom/ipc/PrefsTypes.ipdlh @@ -0,0 +1,31 @@ +/* -*- 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; + 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..6e091494c2 --- /dev/null +++ b/dom/ipc/ProcessActor.cpp @@ -0,0 +1,38 @@ +/* -*- 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 "mozJSComponentLoader.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..354bb4c59e --- /dev/null +++ b/dom/ipc/ProcessHangMonitor.cpp @@ -0,0 +1,1460 @@ +/* -*- 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/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/TaskFactory.h" +#include "mozilla/Monitor.h" +#include "mozilla/plugins/PluginBridge.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_browser.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/Unused.h" +#include "mozilla/WeakPtr.h" + +#include "nsExceptionHandler.h" +#include "nsFrameLoader.h" +#include "nsIHangReport.h" +#include "nsIRemoteTab.h" +#include "nsNetUtil.h" +#include "nsQueryObject.h" +#include "nsPluginHost.h" +#include "nsThreadUtils.h" + +#include "base/task.h" +#include "base/thread.h" + +#ifdef XP_WIN +// For IsDebuggerPresent() +# include <windows.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: + explicit HangMonitorChild(ProcessHangMonitor* aMonitor); + ~HangMonitorChild() override; + + void Bind(Endpoint<PProcessHangMonitorChild>&& aEndpoint); + + typedef ProcessHangMonitor::SlowScriptAction 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 NotifyPluginHang(uint32_t aPluginId); + void NotifyPluginHangAsync(uint32_t aPluginId); + + void ClearHang(); + void ClearHangAsync(); + void ClearPaintWhileInterruptingJS(const LayersObserverEpoch& aEpoch); + + // 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( + const bool& aTerminateGlobal) override; + mozilla::ipc::IPCResult RecvBeginStartingDebugger() override; + mozilla::ipc::IPCResult RecvEndStartingDebugger() override; + + mozilla::ipc::IPCResult RecvPaintWhileInterruptingJS( + const TabId& aTabId, const LayersObserverEpoch& aEpoch) 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; + + void ActorDestroy(ActorDestroyReason aWhy) override; + + bool InterruptCallback(); + void Shutdown(); + + static HangMonitorChild* Get() { return sInstance; } + + 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; + static Maybe<Monitor> sMonitor; + + static Atomic<bool, SequentiallyConsistent> sInitializing; + + private: + void ShutdownOnThread(); + + static Atomic<HangMonitorChild*, SequentiallyConsistent> sInstance; + + const RefPtr<ProcessHangMonitor> mHangMonitor; + Monitor mMonitor; + + // Main thread-only. + bool mSentReport; + + // These fields must be accessed with mMonitor held. + bool mTerminateScript; + bool mTerminateGlobal; + bool mStartDebugger; + bool mFinishedStartingDebugger; + bool mPaintWhileInterruptingJS; + TabId mPaintWhileInterruptingJSTab; + MOZ_INIT_OUTSIDE_CTOR LayersObserverEpoch mPaintWhileInterruptingJSEpoch; + bool mCancelContentJS; + TabId mCancelContentJSTab; + nsIRemoteTab::NavigationType mCancelContentJSNavigationType; + int32_t mCancelContentJSNavigationIndex; + mozilla::Maybe<nsCString> mCancelContentJSNavigationURI; + int32_t mCancelContentJSEpoch; + JSContext* mContext; + bool mShutdownDone; + + // 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; +}; + +Maybe<Monitor> HangMonitorChild::sMonitor; + +Atomic<bool, SequentiallyConsistent> HangMonitorChild::sInitializing; + +Atomic<HangMonitorChild*, SequentiallyConsistent> 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 ID of + * the plugin which caused the hang as well as the content PID. The ID of + * a minidump taken during the hang can also be provided. + * + * @param aHangData The hang information + * @param aDumpId The ID of a minidump taken when the hang occurred + */ + void SetHangData(const HangData& aHangData, const nsAString& aDumpId) { + mHangData = aHangData; + mDumpId = aDumpId; + } + + void ClearHang() { + mHangData = HangData(); + mDumpId.Truncate(); + } + + private: + ~HangMonitoredProcess() = default; + + // Everything here is main thread-only. + HangMonitorParent* mActor; + ContentParent* mContentParent; + HangData mHangData; + nsAutoString mDumpId; +}; + +class HangMonitorParent : public PProcessHangMonitorParent, + public SupportsWeakPtr { + public: + explicit HangMonitorParent(ProcessHangMonitor* aMonitor); + ~HangMonitorParent() override; + + void Bind(Endpoint<PProcessHangMonitorParent>&& aEndpoint); + + mozilla::ipc::IPCResult RecvHangEvidence(const HangData& aHangData) override; + mozilla::ipc::IPCResult RecvClearHang() override; + + void ActorDestroy(ActorDestroyReason aWhy) override; + + void SetProcess(HangMonitoredProcess* aProcess) { mProcess = aProcess; } + + void Shutdown(); + + void PaintWhileInterruptingJS(dom::BrowserParent* aBrowserParent, + const LayersObserverEpoch& aEpoch); + void CancelContentJSExecutionIfRunning( + dom::BrowserParent* aBrowserParent, + nsIRemoteTab::NavigationType aNavigationType, + const dom::CancelContentJSOptions& aCancelContentJSOptions); + + void TerminateScript(bool aTerminateGlobal); + void BeginStartingDebugger(); + void EndStartingDebugger(); + void CleanupPluginHang(uint32_t aPluginId, bool aRemoveFiles); + + /** + * Update the dump for the specified plugin. This method is thread-safe and + * is used to replace a browser minidump with a full minidump. If aDumpId is + * empty this is a no-op. + */ + void UpdateMinidump(uint32_t aPluginId, const nsString& aDumpId); + + void Dispatch(already_AddRefed<nsIRunnable> aRunnable) { + mHangMonitor->Dispatch(std::move(aRunnable)); + } + bool IsOnThread() { return mHangMonitor->IsOnThread(); } + + private: + bool TakeBrowserMinidump(const PluginHangData& aPhd, nsString& aCrashId); + + void SendHangNotification(const HangData& aHangData, + const nsString& aBrowserDumpId, bool aTakeMinidump); + + void ClearHangNotification(); + + void PaintWhileInterruptingJSOnThread(TabId aTabId, + const LayersObserverEpoch& aEpoch); + void CancelContentJSExecutionIfRunningOnThread( + TabId aTabId, nsIRemoteTab::NavigationType aNavigationType, + int32_t aNavigationIndex, nsIURI* aNavigationURI, int32_t aEpoch); + + void ShutdownOnThread(); + + const RefPtr<ProcessHangMonitor> mHangMonitor; + + // This field is only accessed on the hang thread. + bool mIPCOpen; + + Monitor mMonitor; + + // Must be accessed with mMonitor held. + RefPtr<HangMonitoredProcess> mProcess; + bool mShutdownDone; + // Map from plugin ID to crash dump ID. Protected by + // mBrowserCrashDumpHashLock. + nsDataHashtable<nsUint32HashKey, nsString> mBrowserCrashDumpIds; + Mutex mBrowserCrashDumpHashLock; + mozilla::ipc::TaskFactory<HangMonitorParent> mMainThreadTaskFactory; +}; + +} // namespace + +/* HangMonitorChild implementation */ + +HangMonitorChild::HangMonitorChild(ProcessHangMonitor* aMonitor) + : mHangMonitor(aMonitor), + mMonitor("HangMonitorChild lock"), + mSentReport(false), + mTerminateScript(false), + mTerminateGlobal(false), + mStartDebugger(false), + mFinishedStartingDebugger(false), + mPaintWhileInterruptingJS(false), + mCancelContentJS(false), + mCancelContentJSNavigationType(nsIRemoteTab::NAVIGATE_BACK), + mCancelContentJSNavigationIndex(0), + mCancelContentJSEpoch(0), + mShutdownDone(false), + mIPCOpen(true), + mPaintWhileInterruptingJSActive(false) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!sInstance); + mContext = danger::GetJSContext(); + + BackgroundHangMonitor::RegisterAnnotator(*this); + + MOZ_ASSERT(!sMonitor.isSome()); + sMonitor.emplace("HangMonitorChild::sMonitor"); + MonitorAutoLock mal(*sMonitor); + + MOZ_ASSERT(!sInitializing); + sInitializing = true; +} + +HangMonitorChild::~HangMonitorChild() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(sInstance == this); + sInstance = nullptr; +} + +bool HangMonitorChild::InterruptCallback() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + // 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; + } + + bool paintWhileInterruptingJS; + TabId paintWhileInterruptingJSTab; + LayersObserverEpoch paintWhileInterruptingJSEpoch; + + { + MonitorAutoLock lock(mMonitor); + paintWhileInterruptingJS = mPaintWhileInterruptingJS; + paintWhileInterruptingJSTab = mPaintWhileInterruptingJSTab; + paintWhileInterruptingJSEpoch = mPaintWhileInterruptingJSEpoch; + + mPaintWhileInterruptingJS = false; + } + + if (paintWhileInterruptingJS) { + RefPtr<BrowserChild> browserChild = + BrowserChild::FindBrowserChild(paintWhileInterruptingJSTab); + if (browserChild) { + js::AutoAssertNoContentJS nojs(mContext); + browserChild->PaintWhileInterruptingJS(paintWhileInterruptingJSEpoch); + } + } + + // 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::RootedObject 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()) { + if (Document* topLevelDoc = doc->GetTopLevelContentDocument()) { + topLevelDoc->DisallowBFCaching(); + } + } + + return false; + } + } + + return true; +} + +void HangMonitorChild::AnnotateHang(BackgroundHangAnnotations& aAnnotations) { + if (mPaintWhileInterruptingJSActive) { + aAnnotations.AddAnnotation(u"PaintWhileInterruptingJS"_ns, true); + } +} + +void HangMonitorChild::Shutdown() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + BackgroundHangMonitor::UnregisterAnnotator(*this); + + MonitorAutoLock lock(mMonitor); + while (!mShutdownDone) { + mMonitor.Wait(); + } +} + +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( + const bool& aTerminateGlobal) { + MOZ_RELEASE_ASSERT(IsOnThread()); + + MonitorAutoLock lock(mMonitor); + if (aTerminateGlobal) { + mTerminateGlobal = true; + } else { + mTerminateScript = true; + } + 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, const LayersObserverEpoch& aEpoch) { + MOZ_RELEASE_ASSERT(IsOnThread()); + + { + MonitorAutoLock lock(mMonitor); + MaybeStartPaintWhileInterruptingJS(); + mPaintWhileInterruptingJS = true; + mPaintWhileInterruptingJSTab = aTabId; + mPaintWhileInterruptingJSEpoch = aEpoch; + } + + JS_RequestInterruptCallback(mContext); + + return IPC_OK(); +} + +void HangMonitorChild::MaybeStartPaintWhileInterruptingJS() { + mPaintWhileInterruptingJSActive = true; +} + +void HangMonitorChild::ClearPaintWhileInterruptingJS( + const LayersObserverEpoch& aEpoch) { + 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(); +} + +void HangMonitorChild::Bind(Endpoint<PProcessHangMonitorChild>&& aEndpoint) { + MOZ_RELEASE_ASSERT(IsOnThread()); + + MonitorAutoLock mal(*sMonitor); + + MOZ_ASSERT(!sInstance); + sInstance = this; + + DebugOnly<bool> ok = aEndpoint.Bind(this); + MOZ_ASSERT(ok); + + sInitializing = false; + mal.Notify(); +} + +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 (mTerminateGlobal) { + mTerminateGlobal = false; + return SlowScriptAction::TerminateGlobal; + } + + 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::NotifyPluginHang(uint32_t aPluginId) { + // main thread in the child + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + mSentReport = true; + + // bounce to background thread + Dispatch(NewNonOwningRunnableMethod<uint32_t>( + "HangMonitorChild::NotifyPluginHangAsync", this, + &HangMonitorChild::NotifyPluginHangAsync, aPluginId)); +} + +void HangMonitorChild::NotifyPluginHangAsync(uint32_t aPluginId) { + MOZ_RELEASE_ASSERT(IsOnThread()); + + // bounce back to parent on background thread + if (mIPCOpen) { + Unused << SendHangEvidence( + PluginHangData(aPluginId, base::GetCurrentProcId())); + } +} + +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; + mTerminateGlobal = 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), + mBrowserCrashDumpHashLock("mBrowserCrashDumpIds lock"), + mMainThreadTaskFactory(this) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); +} + +HangMonitorParent::~HangMonitorParent() { + MutexAutoLock lock(mBrowserCrashDumpHashLock); + + for (auto iter = mBrowserCrashDumpIds.Iter(); !iter.Done(); iter.Next()) { + nsString crashId = iter.UserData(); + if (!crashId.IsEmpty()) { + CrashReporter::DeleteMinidumpFilesForID(crashId); + } + } +} + +void HangMonitorParent::Shutdown() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + MonitorAutoLock lock(mMonitor); + + if (mProcess) { + mProcess->Clear(); + mProcess = nullptr; + } + + Dispatch(NewNonOwningRunnableMethod("HangMonitorParent::ShutdownOnThread", + this, + &HangMonitorParent::ShutdownOnThread)); + + 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, const LayersObserverEpoch& aEpoch) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + if (StaticPrefs::browser_tabs_remote_force_paint()) { + TabId id = aTab->GetTabId(); + Dispatch(NewNonOwningRunnableMethod<TabId, LayersObserverEpoch>( + "HangMonitorParent::PaintWhileInterruptingJSOnThread", this, + &HangMonitorParent::PaintWhileInterruptingJSOnThread, id, aEpoch)); + } +} + +void HangMonitorParent::PaintWhileInterruptingJSOnThread( + TabId aTabId, const LayersObserverEpoch& aEpoch) { + MOZ_RELEASE_ASSERT(IsOnThread()); + + if (mIPCOpen) { + Unused << SendPaintWhileInterruptingJS(aTabId, aEpoch); + } +} + +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::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 HangData& aHangData, + const nsString& aBrowserDumpId, + bool aTakeMinidump) { + // chrome process, main thread + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + nsString dumpId; + if ((aHangData.type() == HangData::TPluginHangData) && aTakeMinidump) { + // We've been handed a partial minidump; complete it with plugin and + // content process dumps. + const PluginHangData& phd = aHangData.get_PluginHangData(); + + plugins::TakeFullMinidump(phd.pluginId(), phd.contentProcessId(), + aBrowserDumpId, dumpId); + UpdateMinidump(phd.pluginId(), dumpId); + } else { + // We already have a full minidump; go ahead and use it. + dumpId = aBrowserDumpId; + } + + mProcess->SetHangData(aHangData, 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(); +} + +// Take a minidump of the browser process if one wasn't already taken for the +// plugin that caused the hang. Return false if a dump was already available or +// true if new one has been taken. +bool HangMonitorParent::TakeBrowserMinidump(const PluginHangData& aPhd, + nsString& aCrashId) { + MutexAutoLock lock(mBrowserCrashDumpHashLock); + if (!mBrowserCrashDumpIds.Get(aPhd.pluginId(), &aCrashId)) { + nsCOMPtr<nsIFile> browserDump; + if (CrashReporter::TakeMinidump(getter_AddRefs(browserDump), true)) { + if (!CrashReporter::GetIDFromMinidump(browserDump, aCrashId) || + aCrashId.IsEmpty()) { + browserDump->Remove(false); + NS_WARNING( + "Failed to generate timely browser stack, " + "this is bad for plugin hang analysis!"); + } else { + mBrowserCrashDumpIds.Put(aPhd.pluginId(), aCrashId); + return true; + } + } + } + + return false; +} + +mozilla::ipc::IPCResult HangMonitorParent::RecvHangEvidence( + const HangData& aHangData) { + // 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; + bool takeMinidump = false; + if (aHangData.type() == HangData::TPluginHangData) { + takeMinidump = TakeBrowserMinidump(aHangData.get_PluginHangData(), crashId); + } + + mHangMonitor->InitiateCPOWTimeout(); + + MonitorAutoLock lock(mMonitor); + + NS_DispatchToMainThread(mMainThreadTaskFactory.NewRunnableMethod( + &HangMonitorParent::SendHangNotification, aHangData, crashId, + takeMinidump)); + + 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(bool aTerminateGlobal) { + MOZ_RELEASE_ASSERT(IsOnThread()); + + if (mIPCOpen) { + Unused << SendTerminateScript(aTerminateGlobal); + } +} + +void HangMonitorParent::BeginStartingDebugger() { + MOZ_RELEASE_ASSERT(IsOnThread()); + + if (mIPCOpen) { + Unused << SendBeginStartingDebugger(); + } +} + +void HangMonitorParent::EndStartingDebugger() { + MOZ_RELEASE_ASSERT(IsOnThread()); + + if (mIPCOpen) { + Unused << SendEndStartingDebugger(); + } +} + +void HangMonitorParent::CleanupPluginHang(uint32_t aPluginId, + bool aRemoveFiles) { + MutexAutoLock lock(mBrowserCrashDumpHashLock); + nsAutoString crashId; + if (!mBrowserCrashDumpIds.Get(aPluginId, &crashId)) { + return; + } + mBrowserCrashDumpIds.Remove(aPluginId); + + if (aRemoveFiles && !crashId.IsEmpty()) { + CrashReporter::DeleteMinidumpFilesForID(crashId); + } +} + +void HangMonitorParent::UpdateMinidump(uint32_t aPluginId, + const nsString& aDumpId) { + if (aDumpId.IsEmpty()) { + return; + } + + MutexAutoLock lock(mBrowserCrashDumpHashLock); + mBrowserCrashDumpIds.Put(aPluginId, aDumpId); +} + +/* HangMonitoredProcess implementation */ + +NS_IMPL_ISUPPORTS(HangMonitoredProcess, nsIHangReport) + +NS_IMETHODIMP +HangMonitoredProcess::GetHangType(uint32_t* aHangType) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + switch (mHangData.type()) { + case HangData::TSlowScriptData: + *aHangType = SLOW_SCRIPT; + break; + case HangData::TPluginHangData: + *aHangType = PLUGIN_HANG; + break; + default: + MOZ_ASSERT_UNREACHABLE("Unexpected HangData type"); + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +NS_IMETHODIMP +HangMonitoredProcess::GetHangDuration(double* aHangDuration) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + if (mHangData.type() != HangData::TSlowScriptData) { + *aHangDuration = -1; + } else { + *aHangDuration = mHangData.get_SlowScriptData().duration(); + } + return NS_OK; +} + +NS_IMETHODIMP +HangMonitoredProcess::GetScriptBrowser(Element** aBrowser) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + if (mHangData.type() != HangData::TSlowScriptData) { + return NS_ERROR_NOT_AVAILABLE; + } + + TabId tabId = mHangData.get_SlowScriptData().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()); + if (mHangData.type() != HangData::TSlowScriptData) { + return NS_ERROR_NOT_AVAILABLE; + } + + aFileName = mHangData.get_SlowScriptData().filename(); + return NS_OK; +} + +NS_IMETHODIMP +HangMonitoredProcess::GetAddonId(nsAString& aAddonId) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + if (mHangData.type() != HangData::TSlowScriptData) { + return NS_ERROR_NOT_AVAILABLE; + } + + aAddonId = mHangData.get_SlowScriptData().addonId(); + return NS_OK; +} + +NS_IMETHODIMP +HangMonitoredProcess::GetPluginName(nsACString& aPluginName) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + if (mHangData.type() != HangData::TPluginHangData) { + return NS_ERROR_NOT_AVAILABLE; + } + + uint32_t id = mHangData.get_PluginHangData().pluginId(); + + RefPtr<nsPluginHost> host = nsPluginHost::GetInst(); + nsPluginTag* tag = host->PluginWithId(id); + if (!tag) { + return NS_ERROR_UNEXPECTED; + } + + aPluginName = tag->Name(); + return NS_OK; +} + +NS_IMETHODIMP +HangMonitoredProcess::TerminateScript() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + if (mHangData.type() != HangData::TSlowScriptData) { + return NS_ERROR_UNEXPECTED; + } + + if (!mActor) { + return NS_ERROR_UNEXPECTED; + } + + ProcessHangMonitor::Get()->Dispatch(NewNonOwningRunnableMethod<bool>( + "HangMonitorParent::TerminateScript", mActor, + &HangMonitorParent::TerminateScript, false)); + return NS_OK; +} + +NS_IMETHODIMP +HangMonitoredProcess::TerminateGlobal() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + if (mHangData.type() != HangData::TSlowScriptData) { + return NS_ERROR_UNEXPECTED; + } + + if (!mActor) { + return NS_ERROR_UNEXPECTED; + } + + ProcessHangMonitor::Get()->Dispatch(NewNonOwningRunnableMethod<bool>( + "HangMonitorParent::TerminateScript", mActor, + &HangMonitorParent::TerminateScript, true)); + return NS_OK; +} + +NS_IMETHODIMP +HangMonitoredProcess::BeginStartingDebugger() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + if (mHangData.type() != HangData::TSlowScriptData) { + return NS_ERROR_UNEXPECTED; + } + + 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 (mHangData.type() != HangData::TSlowScriptData) { + return NS_ERROR_UNEXPECTED; + } + + if (!mActor) { + return NS_ERROR_UNEXPECTED; + } + + ProcessHangMonitor::Get()->Dispatch(NewNonOwningRunnableMethod( + "HangMonitorParent::EndStartingDebugger", mActor, + &HangMonitorParent::EndStartingDebugger)); + return NS_OK; +} + +NS_IMETHODIMP +HangMonitoredProcess::TerminatePlugin() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + if (mHangData.type() != HangData::TPluginHangData) { + return NS_ERROR_UNEXPECTED; + } + + // Use the multi-process crash report generated earlier. + uint32_t id = mHangData.get_PluginHangData().pluginId(); + base::ProcessId contentPid = + mHangData.get_PluginHangData().contentProcessId(); + plugins::TerminatePlugin(id, contentPid, "HangMonitor"_ns, mDumpId); + + if (mActor) { + mActor->CleanupPluginHang(id, false); + } + return NS_OK; +} + +NS_IMETHODIMP +HangMonitoredProcess::IsReportForBrowser(nsFrameLoader* aFrameLoader, + bool* aResult) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + if (!mActor) { + *aResult = false; + return NS_OK; + } + + NS_ENSURE_STATE(aFrameLoader); + + BrowserParent* tp = BrowserParent::GetFrom(aFrameLoader); + if (!tp) { + *aResult = false; + return NS_OK; + } + + *aResult = mContentParent == tp->Manager(); + return NS_OK; +} + +NS_IMETHODIMP +HangMonitoredProcess::UserCanceled() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + if (mHangData.type() != HangData::TPluginHangData) { + return NS_OK; + } + + if (mActor) { + uint32_t id = mHangData.get_PluginHangData().pluginId(); + mActor->CleanupPluginHang(id, true); + } + 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) { + 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; + } +} + +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) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + if (!strcmp(aTopic, "xpcom-shutdown")) { + if (HangMonitorChild::sMonitor) { + MonitorAutoLock mal(*HangMonitorChild::sMonitor); + if (HangMonitorChild::sInitializing) { + mal.Wait(); + } + + if (HangMonitorChild* child = HangMonitorChild::Get()) { + child->Shutdown(); + delete child; + } + } + HangMonitorChild::sMonitor.reset(); + + 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) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + return HangMonitorChild::Get()->NotifySlowScript(aBrowserChild, aFileName, + aAddonId, aDuration); +} + +bool ProcessHangMonitor::IsDebuggerStartupComplete() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + 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; +} + +void ProcessHangMonitor::NotifyPluginHang(uint32_t aPluginId) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + return HangMonitorChild::Get()->NotifyPluginHang(aPluginId); +} + +static PProcessHangMonitorParent* CreateHangMonitorParent( + ContentParent* aContentParent, + Endpoint<PProcessHangMonitorParent>&& aEndpoint) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + ProcessHangMonitor* monitor = ProcessHangMonitor::GetOrCreate(); + auto* 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; +} + +void mozilla::CreateHangMonitorChild( + Endpoint<PProcessHangMonitorChild>&& aEndpoint) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + JSContext* cx = danger::GetJSContext(); + JS_AddInterruptCallback(cx, InterruptCallback); + + ProcessHangMonitor* monitor = ProcessHangMonitor::GetOrCreate(); + auto* child = new HangMonitorChild(monitor); + + monitor->Dispatch( + NewNonOwningRunnableMethod<Endpoint<PProcessHangMonitorChild>&&>( + "HangMonitorChild::Bind", child, &HangMonitorChild::Bind, + std::move(aEndpoint))); +} + +void ProcessHangMonitor::Dispatch(already_AddRefed<nsIRunnable> aRunnable) { + mThread->Dispatch(std::move(aRunnable), nsIEventTarget::NS_DISPATCH_NORMAL); +} + +bool ProcessHangMonitor::IsOnThread() { + bool on; + return NS_SUCCEEDED(mThread->IsOnCurrentThread(&on)) && on; +} + +/* static */ +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( + base::GetCurrentProcId(), aContentParent->OtherPid(), &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(); + delete parent; +} + +/* static */ +void ProcessHangMonitor::ClearHang() { + MOZ_ASSERT(NS_IsMainThread()); + if (HangMonitorChild* child = HangMonitorChild::Get()) { + child->ClearHang(); + } +} + +/* static */ +void ProcessHangMonitor::PaintWhileInterruptingJS( + PProcessHangMonitorParent* aParent, dom::BrowserParent* aBrowserParent, + const layers::LayersObserverEpoch& aEpoch) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + auto parent = static_cast<HangMonitorParent*>(aParent); + parent->PaintWhileInterruptingJS(aBrowserParent, aEpoch); +} + +/* static */ +void ProcessHangMonitor::ClearPaintWhileInterruptingJS( + const layers::LayersObserverEpoch& aEpoch) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + MOZ_RELEASE_ASSERT(XRE_IsContentProcess()); + + if (HangMonitorChild* child = HangMonitorChild::Get()) { + child->ClearPaintWhileInterruptingJS(aEpoch); + } +} + +/* static */ +void ProcessHangMonitor::MaybeStartPaintWhileInterruptingJS() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + 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) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + auto parent = static_cast<HangMonitorParent*>(aParent); + parent->CancelContentJSExecutionIfRunning(aBrowserParent, aNavigationType, + aCancelContentJSOptions); +} diff --git a/dom/ipc/ProcessHangMonitor.h b/dom/ipc/ProcessHangMonitor.h new file mode 100644 index 0000000000..d66d1efaed --- /dev/null +++ b/dom/ipc/ProcessHangMonitor.h @@ -0,0 +1,96 @@ +/* -*- 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 "nsStringFwd.h" + +class nsIRunnable; +class nsIBrowserChild; +class nsIThread; + +namespace mozilla { + +namespace dom { +class ContentParent; +class BrowserParent; +struct CancelContentJSOptions; +} // namespace dom + +namespace layers { +struct LayersObserverEpoch; +} // namespace layers + +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 PProcessHangMonitorParent* AddProcess( + dom::ContentParent* aContentParent); + static void RemoveProcess(PProcessHangMonitorParent* aParent); + + static void ClearHang(); + + static void PaintWhileInterruptingJS( + PProcessHangMonitorParent* aParent, dom::BrowserParent* aTab, + const layers::LayersObserverEpoch& aEpoch); + static void ClearPaintWhileInterruptingJS( + const layers::LayersObserverEpoch& aEpoch); + static void MaybeStartPaintWhileInterruptingJS(); + + static void CancelContentJSExecutionIfRunning( + PProcessHangMonitorParent* aParent, dom::BrowserParent* aTab, + nsIRemoteTab::NavigationType aNavigationType, + const dom::CancelContentJSOptions& aCancelContentJSOptions); + + enum SlowScriptAction { + Continue, + Terminate, + StartDebugger, + TerminateGlobal, + }; + SlowScriptAction NotifySlowScript(nsIBrowserChild* aBrowserChild, + const char* aFileName, + const nsString& aAddonId, + const double aDuration); + + void NotifyPluginHang(uint32_t aPluginId); + + bool IsDebuggerStartupComplete(); + + void InitiateCPOWTimeout(); + bool ShouldTimeOutCPOWs(); + + void 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/ProcessPriorityManager.cpp b/dom/ipc/ProcessPriorityManager.cpp new file mode 100644 index 0000000000..060f17968a --- /dev/null +++ b/dom/ipc/ProcessPriorityManager.cpp @@ -0,0 +1,978 @@ +/* -*- 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/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/Services.h" +#include "mozilla/StaticPrefs_dom.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 "nsTHashtable.h" +#include "nsQueryObject.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 { + +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 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 = ""_ns); + + /** + * This must be called by a ParticularProcessPriorityManager when it changes + * its priority. + */ + void NotifyProcessPriorityChanged( + ParticularProcessPriorityManager* aParticularManager, + hal::ProcessPriority aOldPriority); + + void TabActivityChanged(BrowserParent* aBrowserParent, bool aIsActive); + + 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 ObserveContentParentCreated(nsISupports* aContentParent); + void ObserveContentParentDestroyed(nsISupports* aSubject); + + nsDataHashtable<nsUint64HashKey, RefPtr<ParticularProcessPriorityManager> > + mParticularManagers; + + /** Contains the PIDs of child processes holding high-priority wakelocks */ + nsTHashtable<nsUint64HashKey> 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 nsIObserver, + public nsITimerCallback, + public nsINamed, + public nsSupportsWeakReference { + ~ParticularProcessPriorityManager(); + + public: + explicit ParticularProcessPriorityManager(ContentParent* aContentParent); + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + 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(); + + void OnRemoteBrowserFrameShown(nsISupports* aSubject); + void OnBrowserParentDestroyed(nsISupports* aSubject); + + 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 TabActivityChanged(BrowserParent* aBrowserParent, bool aIsActive); + + void ShutDown(); + + NS_IMETHOD GetName(nsACString& aName) override { + aName.AssignLiteral("ParticularProcessPriorityManager"); + return NS_OK; + } + + private: + void FireTestOnlyObserverNotification(const char* aTopic, + const nsACString& aData = ""_ns); + + void FireTestOnlyObserverNotification(const char* aTopic, + const char* aData = nullptr); + + 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 active TabId for this process. + nsTHashtable<nsUint64HashKey> mActiveBrowserParents; +}; + +/* 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() && + hal::SetProcessPrioritySupported() && + !StaticPrefs::dom_ipc_tabs_disabled(); +} + +/* 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; + } + + // If IPC tabs aren't enabled at startup, don't bother with any of this. + if (!PrefsEnabled()) { + LOG("InitProcessPriorityManager bailing due to prefs."); + + // Run StaticInit() again if the prefs change. 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"); + Preferences::RegisterCallback(PrefChangedCallback, + "dom.ipc.tabs.disabled"); + } + return; + } + + 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. + hal::SetProcessPriority(getpid(), PROCESS_PRIORITY_PARENT_PROCESS); + + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + if (os) { + os->AddObserver(this, "ipc:content-created", /* ownsWeak */ true); + 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-created")) { + ObserveContentParentCreated(aSubject); + } else if (topic.EqualsLiteral("ipc:content-shutdown")) { + ObserveContentParentDestroyed(aSubject); + } else { + MOZ_ASSERT(false); + } + + return NS_OK; +} + +already_AddRefed<ParticularProcessPriorityManager> +ProcessPriorityManagerImpl::GetParticularProcessPriorityManager( + ContentParent* aContentParent) { + uint64_t cpId = aContentParent->ChildID(); + auto entry = mParticularManagers.LookupForAdd(cpId); + RefPtr<ParticularProcessPriorityManager> pppm = + entry.OrInsert([aContentParent]() { + return new ParticularProcessPriorityManager(aContentParent); + }); + + if (!entry) { + // We created a new entry. + pppm->Init(); + FireTestOnlyObserverNotification("process-created", + nsPrintfCString("%" PRIu64, cpId)); + } + + return pppm.forget(); +} + +void ProcessPriorityManagerImpl::SetProcessPriority( + ContentParent* aContentParent, ProcessPriority aPriority) { + MOZ_ASSERT(aContentParent); + RefPtr<ParticularProcessPriorityManager> pppm = + GetParticularProcessPriorityManager(aContentParent); + if (pppm) { + pppm->SetPriorityNow(aPriority); + } +} + +void ProcessPriorityManagerImpl::ObserveContentParentCreated( + nsISupports* aContentParent) { + // Do nothing; it's sufficient to get the PPPM. But assign to nsRefPtr so we + // don't leak the already_AddRefed object. + RefPtr<ContentParent> cp = do_QueryObject(aContentParent); + RefPtr<ParticularProcessPriorityManager> pppm = + GetParticularProcessPriorityManager(cp); +} + +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.RemoveEntry(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.PutEntry(aParticularManager->ChildID()); + } else if (newPriority < PROCESS_PRIORITY_FOREGROUND_HIGH && + aOldPriority >= PROCESS_PRIORITY_FOREGROUND_HIGH) { + mHighPriorityChildIDs.RemoveEntry(aParticularManager->ChildID()); + } +} + +void ProcessPriorityManagerImpl::TabActivityChanged( + BrowserParent* aBrowserParent, bool aIsActive) { + RefPtr<ParticularProcessPriorityManager> pppm = + GetParticularProcessPriorityManager(aBrowserParent->Manager()); + if (!pppm) { + return; + } + + Telemetry::ScalarAdd( + Telemetry::ScalarID::DOM_CONTENTPROCESS_OS_PRIORITY_CHANGE_CONSIDERED, 1); + + pppm->TabActivityChanged(aBrowserParent, aIsActive); +} + +NS_IMPL_ISUPPORTS(ParticularProcessPriorityManager, nsIObserver, + 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()); + LOGP("Creating ParticularProcessPriorityManager."); +} + +void ParticularProcessPriorityManager::Init() { + RegisterWakeLockObserver(this); + + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + if (os) { + os->AddObserver(this, "remote-browser-shown", /* ownsWeak */ true); + os->AddObserver(this, "ipc:browser-destroyed", /* ownsWeak */ true); + } + + // 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."); + + // 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); + } +} + +/* 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(); + } + } +} + +NS_IMETHODIMP +ParticularProcessPriorityManager::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) { + if (!mContentParent) { + // We've been shut down. + return NS_OK; + } + + nsDependentCString topic(aTopic); + + if (topic.EqualsLiteral("remote-browser-shown")) { + OnRemoteBrowserFrameShown(aSubject); + } else if (topic.EqualsLiteral("ipc:browser-destroyed")) { + OnBrowserParentDestroyed(aSubject); + } else { + MOZ_ASSERT(false); + } + + return NS_OK; +} + +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::OnRemoteBrowserFrameShown( + nsISupports* aSubject) { + RefPtr<nsFrameLoader> fl = do_QueryObject(aSubject); + NS_ENSURE_TRUE_VOID(fl); + + BrowserParent* tp = BrowserParent::GetFrom(fl); + NS_ENSURE_TRUE_VOID(tp); + + MOZ_ASSERT(XRE_IsParentProcess()); + if (tp->Manager() != mContentParent) { + return; + } + + // Ignore notifications that aren't from a Browser + if (fl->OwnerIsMozBrowserFrame()) { + ResetPriority(); + } + + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + if (os) { + os->RemoveObserver(this, "remote-browser-shown"); + } +} + +void ParticularProcessPriorityManager::OnBrowserParentDestroyed( + nsISupports* aSubject) { + nsCOMPtr<nsIRemoteTab> remoteTab = do_QueryInterface(aSubject); + NS_ENSURE_TRUE_VOID(remoteTab); + BrowserHost* browserHost = BrowserHost::GetFrom(remoteTab.get()); + + MOZ_ASSERT(XRE_IsParentProcess()); + if (browserHost->GetContentParent() && + browserHost->GetContentParent() != mContentParent) { + return; + } + + mActiveBrowserParents.RemoveEntry(browserHost->GetTabId()); + + ResetPriority(); +} + +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 (!mActiveBrowserParents.IsEmpty() || + mContentParent->GetRemoteType() == EXTENSION_REMOTE_TYPE || + mHoldsPlayingAudioWakeLock) { + return PROCESS_PRIORITY_FOREGROUND; + } + + if (mHoldsCPUWakeLock || mHoldsHighPriorityWakeLock || + mHoldsPlayingVideoWakeLock) { + return PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE; + } + + return PROCESS_PRIORITY_BACKGROUND; +} + +void ParticularProcessPriorityManager::SetPriorityNow( + ProcessPriority aPriority) { + if (aPriority == PROCESS_PRIORITY_UNKNOWN) { + MOZ_ASSERT(false); + return; + } + + if (!ProcessPriorityManagerImpl::PrefsEnabled() || !mContentParent || + mPriority == aPriority) { + return; + } + + if (mPriority == aPriority) { + hal::SetProcessPriority(Pid(), mPriority); + return; + } + + LOGP("Changing priority from %s to %s.", ProcessPriorityToString(mPriority), + 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); + } + + hal::SetProcessPriority(Pid(), mPriority); + + if (oldPriority != mPriority) { + ProcessPriorityManagerImpl::GetSingleton()->NotifyProcessPriorityChanged( + this, oldPriority); + + Unused << mContentParent->SendNotifyProcessPriorityChanged(mPriority); + } + + FireTestOnlyObserverNotification("process-priority-set", + ProcessPriorityToString(mPriority)); +} + +void ParticularProcessPriorityManager::TabActivityChanged( + BrowserParent* aBrowserParent, bool aIsActive) { + MOZ_ASSERT(aBrowserParent); + + if (!aIsActive) { + mActiveBrowserParents.RemoveEntry(aBrowserParent->GetTabId()); + } else { + mActiveBrowserParents.PutEntry(aBrowserParent->GetTabId()); + } + + ResetPriority(); +} + +void ParticularProcessPriorityManager::ShutDown() { + MOZ_ASSERT(mContentParent); + + LOGP("shutdown for %p (mContentParent %p)", this, mContentParent); + + UnregisterWakeLockObserver(this); + + if (mResetPriorityTimer) { + mResetPriorityTimer->Cancel(); + mResetPriorityTimer = nullptr; + } + + mContentParent = nullptr; +} + +void ProcessPriorityManagerImpl::FireTestOnlyObserverNotification( + const char* aTopic, const nsACString& aData /* = ""_ns */) { + 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 /* = nullptr */) { + if (!ProcessPriorityManagerImpl::TestMode()) { + return; + } + + nsAutoCString data; + if (aData) { + data.AppendASCII(aData); + } + + FireTestOnlyObserverNotification(aTopic, data); +} + +void ParticularProcessPriorityManager::FireTestOnlyObserverNotification( + const char* aTopic, const nsACString& aData /* = ""_ns */) { + if (!ProcessPriorityManagerImpl::TestMode()) { + return; + } + + nsAutoCString data(nsPrintfCString("%" PRIu64, ChildID())); + if (!aData.IsEmpty()) { + data.Append(':'); + data.Append(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::TabActivityChanged(BrowserParent* aBrowserParent, + bool aIsActive) { + MOZ_ASSERT(aBrowserParent); + + ProcessPriorityManagerImpl* singleton = + ProcessPriorityManagerImpl::GetSingleton(); + if (!singleton) { + return; + } + + singleton->TabActivityChanged(aBrowserParent, aIsActive); +} + +} // namespace mozilla diff --git a/dom/ipc/ProcessPriorityManager.h b/dom/ipc/ProcessPriorityManager.h new file mode 100644 index 0000000000..08525cf3c3 --- /dev/null +++ b/dom/ipc/ProcessPriorityManager.h @@ -0,0 +1,84 @@ +/* -*- 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" + +namespace mozilla { +namespace dom { +class ContentParent; +class BrowserParent; +} // 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(); + + static void TabActivityChanged(dom::BrowserParent* aBrowserParent, + bool aIsActive); + + 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..a43aa7168f --- /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(Message* aMsg, 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(aMsg, aActor, variant); +} + +bool IPDLParamTraits<nsIVariant*>::Read(const Message* aMsg, + PickleIterator* aIter, + IProtocol* aActor, + RefPtr<nsIVariant>* aResult) { + IDPLVariant value; + if (!ReadIPDLParam(aMsg, aIter, 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(Message* aMsg, 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(aMsg, aActor, bag); +} + +bool IPDLParamTraits<nsIPropertyBag2*>::Read(const Message* aMsg, + PickleIterator* aIter, + IProtocol* aActor, + RefPtr<nsIPropertyBag2>* aResult) { + nsTArray<IPDLProperty> bag; + if (!ReadIPDLParam(aMsg, aIter, 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..a85f87b16f --- /dev/null +++ b/dom/ipc/PropertyBagUtils.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef IPC_PropertyBagUtils_h +#define IPC_PropertyBagUtils_h + +#include "mozilla/ipc/IPDLParamTraits.h" +#include "nsIPropertyBag2.h" +#include "nsIVariant.h" + +namespace mozilla { +namespace 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::Message* aMsg, IProtocol* aActor, nsIVariant* aParam); + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, RefPtr<nsIVariant>* aResult); +}; + +template <> +struct IPDLParamTraits<nsIPropertyBag2*> { + static void Write(IPC::Message* aMsg, IProtocol* aActor, + nsIPropertyBag2* aParam); + static bool Read(const IPC::Message* aMsg, PickleIterator* aIter, + IProtocol* aActor, RefPtr<nsIPropertyBag2>* aResult); +}; + +} // namespace ipc +} // namespace mozilla + +#endif // mozilla_ipc_URIUtils_h diff --git a/dom/ipc/RefMessageBodyService.cpp b/dom/ipc/RefMessageBodyService.cpp new file mode 100644 index 0000000000..53d3a9cecd --- /dev/null +++ b/dom/ipc/RefMessageBodyService.cpp @@ -0,0 +1,159 @@ +/* -*- 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 "nsContentUtils.h" +#include "nsDebug.h" + +namespace mozilla::dom { + +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(); + } + return sService; +} + +RefMessageBodyService::RefMessageBodyService() { + MOZ_DIAGNOSTIC_ASSERT(sService == nullptr); +} + +RefMessageBodyService::~RefMessageBodyService() { + 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 = nsContentUtils::GenerateUUIDInPlace(uuid); + if (NS_WARN_IF(aRv.Failed())) { + return nsID(); + } + + StaticMutexAutoLock lock(sRefMessageBodyServiceMutex); + GetOrCreateInternal(lock)->mMessages.Put(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.ConstIter(); !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..6b37cf3fb3 --- /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; + + 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: + RefMessageBodyService(); + ~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..a1d54d29d8 --- /dev/null +++ b/dom/ipc/ReferrerInfoUtils.cpp @@ -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/. */ + +#include "mozilla/dom/ReferrerInfoUtils.h" + +#include "ipc/IPCMessageUtilsSpecializations.h" +#include "nsSerializationHelper.h" +#include "nsString.h" + +namespace IPC { + +void ParamTraits<nsIReferrerInfo*>::Write(Message* aMsg, + nsIReferrerInfo* aParam) { + bool isNull = !aParam; + WriteParam(aMsg, 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(aMsg, infoString); +} + +bool ParamTraits<nsIReferrerInfo*>::Read(const Message* aMsg, + PickleIterator* aIter, + RefPtr<nsIReferrerInfo>* aResult) { + bool isNull; + if (!ReadParam(aMsg, aIter, &isNull)) { + return false; + } + if (isNull) { + *aResult = nullptr; + return true; + } + nsAutoCString infoString; + if (!ReadParam(aMsg, aIter, &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..803a308c7e --- /dev/null +++ b/dom/ipc/ReferrerInfoUtils.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_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(Message* aMsg, nsIReferrerInfo* aParam); + static bool Read(const Message* aMsg, PickleIterator* aIter, + 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..41a890c737 --- /dev/null +++ b/dom/ipc/RemoteBrowser.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_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 { + +namespace 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: + typedef mozilla::layers::LayersId 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 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 dom +} // namespace mozilla + +#endif // mozilla_dom_ipc_RemoteBrowser_h diff --git a/dom/ipc/RemoteType.h b/dom/ipc/RemoteType.h new file mode 100644 index 0000000000..b79c97f8c9 --- /dev/null +++ b/dom/ipc/RemoteType.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 mozilla_dom_RemoteType_h +#define mozilla_dom_RemoteType_h + +#include "nsString.h" +#include "nsReadableUtils.h" + +// These must match the similar ones in E10SUtils.jsm and ProcInfo.h. +// 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 DEFAULT_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 + +// These must start with the DEFAULT_REMOTE_TYPE above. +#define FISSION_WEB_REMOTE_TYPE "webIsolated"_ns +#define WITH_COOP_COEP_REMOTE_TYPE_PREFIX "webCOOP+COEP="_ns +#define LARGE_ALLOCATION_REMOTE_TYPE "webLargeAllocation"_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/RemoteWebProgress.cpp b/dom/ipc/RemoteWebProgress.cpp new file mode 100644 index 0000000000..0bba9a3aa9 --- /dev/null +++ b/dom/ipc/RemoteWebProgress.cpp @@ -0,0 +1,58 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "RemoteWebProgress.h" + +namespace mozilla::dom { + +NS_IMPL_ADDREF(RemoteWebProgress) +NS_IMPL_RELEASE(RemoteWebProgress) + +NS_INTERFACE_MAP_BEGIN(RemoteWebProgress) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsIWebProgress) +NS_INTERFACE_MAP_END + +NS_IMETHODIMP RemoteWebProgress::AddProgressListener( + nsIWebProgressListener* aListener, uint32_t aNotifyMask) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgress::RemoveProgressListener( + nsIWebProgressListener* aListener) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgress::GetDOMWindow(mozIDOMWindowProxy** aDOMWindow) { + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP RemoteWebProgress::GetIsTopLevel(bool* aIsTopLevel) { + NS_ENSURE_ARG_POINTER(aIsTopLevel); + *aIsTopLevel = mIsTopLevel; + return NS_OK; +} + +NS_IMETHODIMP RemoteWebProgress::GetIsLoadingDocument( + bool* aIsLoadingDocument) { + NS_ENSURE_ARG_POINTER(aIsLoadingDocument); + *aIsLoadingDocument = mIsLoadingDocument; + return NS_OK; +} + +NS_IMETHODIMP RemoteWebProgress::GetLoadType(uint32_t* aLoadType) { + NS_ENSURE_ARG_POINTER(aLoadType); + *aLoadType = mLoadType; + return NS_OK; +} + +NS_IMETHODIMP RemoteWebProgress::GetTarget(nsIEventTarget** aTarget) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP RemoteWebProgress::SetTarget(nsIEventTarget* aTarget) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +} // namespace mozilla::dom diff --git a/dom/ipc/RemoteWebProgress.h b/dom/ipc/RemoteWebProgress.h new file mode 100644 index 0000000000..118b41438d --- /dev/null +++ b/dom/ipc/RemoteWebProgress.h @@ -0,0 +1,39 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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_RemoteWebProgress_h +#define mozilla_dom_RemoteWebProgress_h + +#include "nsIWebProgress.h" +#include "nsCOMPtr.h" + +namespace mozilla { +namespace dom { + +class RemoteWebProgress final : public nsIWebProgress { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIWEBPROGRESS + + RemoteWebProgress() + : mLoadType(0), mIsLoadingDocument(false), mIsTopLevel(false) {} + + RemoteWebProgress(uint32_t aLoadType, bool aIsLoadingDocument, + bool aIsTopLevel) + : mLoadType(aLoadType), + mIsLoadingDocument(aIsLoadingDocument), + mIsTopLevel(aIsTopLevel) {} + + private: + virtual ~RemoteWebProgress() = default; + + uint32_t mLoadType; + bool mIsLoadingDocument; + bool mIsTopLevel; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_RemoteWebProgress_h diff --git a/dom/ipc/RemoteWebProgressRequest.cpp b/dom/ipc/RemoteWebProgressRequest.cpp new file mode 100644 index 0000000000..e7cf2a824c --- /dev/null +++ b/dom/ipc/RemoteWebProgressRequest.cpp @@ -0,0 +1,256 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public * + * License, v. 2.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( + nsISupports** 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::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..f583b71ec5 --- /dev/null +++ b/dom/ipc/RemoteWebProgressRequest.h @@ -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/. */ + +#ifndef mozilla_dom_RemoteWebProgressRequest_h +#define mozilla_dom_RemoteWebProgressRequest_h + +#include "nsIChannel.h" +#include "nsIClassifiedChannel.h" + +namespace mozilla { +namespace 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 dom +} // namespace mozilla + +#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..8bcdb5a438 --- /dev/null +++ b/dom/ipc/SharedMap.cpp @@ -0,0 +1,463 @@ +/* -*- 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/BlobImpl.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ContentProcessMessageManager.h" +#include "mozilla/dom/IPCBlobUtils.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/IOBuffers.h" +#include "mozilla/ScriptPreloader.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() : DOMEventTargetHelper() {} + +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::MutableHandleValue 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::MutableHandleValue 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 : IterHash(mEntries)) { + array.AppendElement(entry); + } + } + + 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: Order of evaluation of function arguments is not guaranteed, so we + // can't use entry.release() in place of entry.get() without entry->Name() + // sometimes resulting in a null dereference. + mEntries.Put(entry->Name(), entry.get()); + Unused << entry.release(); + } + + return Ok(); +} + +void SharedMap::MaybeRebuild() const { + Unused << const_cast<SharedMap*>(this)->MaybeRebuild(); +} + +WritableSharedMap::WritableSharedMap() : SharedMap() { + 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 (auto& entry : IterHash(mEntries)) { + 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 (auto& entry : IterHash(mEntries)) { + 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, aParent, *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::HandleValue 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.LookupOrAdd(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::HandleObject aGivenProto) { + return MozSharedMap_Binding::Wrap(aCx, this, aGivenProto); +} + +JSObject* WritableSharedMap::WrapObject(JSContext* aCx, + JS::HandleObject 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..8dceee70c0 --- /dev/null +++ b/dom/ipc/SharedMap.h @@ -0,0 +1,360 @@ +/* -*- 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 { +namespace 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::MutableHandleValue 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::MutableHandleValue 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::HandleObject 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::MutableHandleValue 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::HandleValue 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::HandleValue 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::HandleObject 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 dom +} // namespace mozilla + +#endif // dom_ipc_SharedMap_h diff --git a/dom/ipc/SharedMapChangeEvent.h b/dom/ipc/SharedMapChangeEvent.h new file mode 100644 index 0000000000..f191553fcb --- /dev/null +++ b/dom/ipc/SharedMapChangeEvent.h @@ -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/. */ + +#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 { +namespace dom { +namespace 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 ipc +} // namespace dom +} // namespace mozilla + +#endif // dom_ipc_SharedMapChangeEvent_h diff --git a/dom/ipc/SharedMessageBody.cpp b/dom/ipc/SharedMessageBody.cpp new file mode 100644 index 0000000000..7b01b0e815 --- /dev/null +++ b/dom/ipc/SharedMessageBody.cpp @@ -0,0 +1,297 @@ +/* -*- 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->BuildClonedMessageDataForBackgroundChild(aManager, + 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->StealFromClonedMessageDataForBackgroundChild( + 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->BorrowFromClonedMessageDataForBackgroundChild( + 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->BuildClonedMessageDataForBackgroundParent(aManager, + 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) { + 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->StealFromClonedMessageDataForBackgroundParent( + 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..e162a09928 --- /dev/null +++ b/dom/ipc/SharedStringMap.cpp @@ -0,0 +1,143 @@ +/* -*- 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/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 aKey.Compare(keys.GetBare(aEntry.mKey)); + }, + aIndex); +} + +void SharedStringMapBuilder::Add(const nsCString& aKey, + const nsString& aValue) { + mEntries.Put(aKey, {mKeyTable.Add(aKey), mValueTable.Add(aValue)}); +} + +Result<Ok, nsresult> SharedStringMapBuilder::Finalize( + loader::AutoMemMap& aMap) { + using Header = SharedStringMap::Header; + + MOZ_ASSERT(mEntries.Count() == mKeyTable.Count()); + + nsTArray<nsCString> keys(mEntries.Count()); + for (auto iter = mEntries.Iter(); !iter.Done(); iter.Next()) { + keys.AppendElement(iter.Key()); + } + 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..35a753fd55 --- /dev/null +++ b/dom/ipc/SharedStringMap.h @@ -0,0 +1,224 @@ +/* -*- 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 "nsDataHashtable.h" + +namespace mozilla { +namespace dom { +namespace 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; + + nsDataHashtable<nsCStringHashKey, Entry> mEntries; +}; + +} // namespace ipc +} // namespace dom +} // namespace mozilla + +#endif // dom_ipc_SharedStringMap_h diff --git a/dom/ipc/StringTable.h b/dom/ipc/StringTable.h new file mode 100644 index 0000000000..849d0f9866 --- /dev/null +++ b/dom/ipc/StringTable.h @@ -0,0 +1,117 @@ +/* -*- 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 "nsDataHashtable.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 { +namespace dom { +namespace 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) { + const auto& entry = mEntries.LookupForAdd(aKey).OrInsert([&]() { + Entry newEntry{mSize, aKey}; + mSize += aKey.Length() + 1; + + return newEntry; + }); + + return {entry.mOffset, aKey.Length()}; + } + + void Write(const RangedPtr<uint8_t>& aBuffer) { + auto buffer = aBuffer.ReinterpretCast<ElemType>(); + + for (auto iter = mEntries.Iter(); !iter.Done(); iter.Next()) { + auto& entry = iter.Data(); + 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; + }; + + nsDataHashtable<KeyType, Entry> mEntries; + uint32_t mSize = 0; +}; + +} // namespace ipc +} // namespace dom +} // namespace mozilla + +#endif diff --git a/dom/ipc/StructuredCloneData.cpp b/dom/ipc/StructuredCloneData.cpp new file mode 100644 index 0000000000..2ef85b5d0f --- /dev/null +++ b/dom/ipc/StructuredCloneData.cpp @@ -0,0 +1,427 @@ +/* -*- 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::AutoIPCStream; +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); + mIPCStreams = std::move(aOther.mIPCStreams); + 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->abandon(); + mBuffer->steal(&data); + mBuffer = nullptr; + mSharedData = new SharedJSAllocatedData(std::move(data)); + mInitialized = true; +} + +enum ActorFlavorEnum { + Parent = 0, + Child, +}; + +enum ManagerFlavorEnum { ContentProtocol = 0, BackgroundProtocol }; + +template <typename M> +bool BuildClonedMessageData(M* aManager, StructuredCloneData& aData, + ClonedMessageData& aClonedData) { + SerializedStructuredCloneBuffer& buffer = aClonedData.data(); + auto iter = aData.Data().Start(); + size_t size = aData.Data().Size(); + bool success; + buffer.data = aData.Data().Borrow(iter, size, &success); + if (NS_WARN_IF(!success)) { + return false; + } + if (aData.SupportsTransferring()) { + aClonedData.identifiers().AppendElements(aData.PortIdentifiers()); + } + + const nsTArray<RefPtr<BlobImpl>>& blobImpls = aData.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], aManager, + aClonedData.blobs()[i]); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + } + } + + const nsTArray<nsCOMPtr<nsIInputStream>>& inputStreams = aData.InputStreams(); + if (!inputStreams.IsEmpty()) { + if (NS_WARN_IF( + !aData.IPCStreams().SetCapacity(inputStreams.Length(), fallible))) { + return false; + } + + nsTArray<IPCStream>& streams = aClonedData.inputStreams(); + uint32_t length = inputStreams.Length(); + streams.SetCapacity(length); + for (uint32_t i = 0; i < length; ++i) { + AutoIPCStream* stream = aData.IPCStreams().AppendElement(fallible); + if (NS_WARN_IF(!stream)) { + return false; + } + + if (!stream->Serialize(inputStreams[i], aManager)) { + return false; + } + streams.AppendElement(stream->TakeValue()); + } + } + + return true; +} + +bool StructuredCloneData::BuildClonedMessageDataForParent( + ContentParent* aParent, ClonedMessageData& aClonedData) { + return BuildClonedMessageData(aParent, *this, aClonedData); +} + +bool StructuredCloneData::BuildClonedMessageDataForChild( + ContentChild* aChild, ClonedMessageData& aClonedData) { + return BuildClonedMessageData(aChild, *this, aClonedData); +} + +bool StructuredCloneData::BuildClonedMessageDataForBackgroundParent( + PBackgroundParent* aParent, ClonedMessageData& aClonedData) { + return BuildClonedMessageData(aParent, *this, aClonedData); +} + +bool StructuredCloneData::BuildClonedMessageDataForBackgroundChild( + PBackgroundChild* aChild, ClonedMessageData& aClonedData) { + return BuildClonedMessageData(aChild, *this, aClonedData); +} + +// 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> { + typedef const mozilla::dom::ClonedMessageData ClonedMessageType; + + static void ProvideBuffer(const ClonedMessageData& aClonedData, + StructuredCloneData& aData) { + const SerializedStructuredCloneBuffer& buffer = aClonedData.data(); + aData.UseExternalData(buffer.data); + } +}; + +template <> +struct MemoryTraits<CopyMemory> { + typedef const mozilla::dom::ClonedMessageData ClonedMessageType; + + static void ProvideBuffer(const ClonedMessageData& aClonedData, + StructuredCloneData& aData) { + const SerializedStructuredCloneBuffer& buffer = aClonedData.data(); + aData.CopyExternalData(buffer.data); + } +}; + +template <> +struct MemoryTraits<StealMemory> { + // note: not const! + typedef mozilla::dom::ClonedMessageData ClonedMessageType; + + 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, ActorFlavorEnum Flavor> +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::BorrowFromClonedMessageDataForParent( + const ClonedMessageData& aClonedData) { + // PContent parent is always main thread and actor constraints demand we're + // likewise on that thread. + MOZ_ASSERT(NS_IsMainThread()); + UnpackClonedMessageData<BorrowMemory, Parent>(aClonedData, *this); +} + +void StructuredCloneData::BorrowFromClonedMessageDataForChild( + const ClonedMessageData& aClonedData) { + // PContent child is always main thread and actor constraints demand we're + // likewise on that thread. + MOZ_ASSERT(NS_IsMainThread()); + UnpackClonedMessageData<BorrowMemory, Child>(aClonedData, *this); +} + +void StructuredCloneData::BorrowFromClonedMessageDataForBackgroundParent( + const ClonedMessageData& aClonedData) { + MOZ_ASSERT(IsOnBackgroundThread()); + UnpackClonedMessageData<BorrowMemory, Parent>(aClonedData, *this); +} + +void StructuredCloneData::BorrowFromClonedMessageDataForBackgroundChild( + const ClonedMessageData& aClonedData) { + // No thread assertion; BackgroundChild can happen on any thread. + UnpackClonedMessageData<BorrowMemory, Child>(aClonedData, *this); +} + +void StructuredCloneData::CopyFromClonedMessageDataForParent( + const ClonedMessageData& aClonedData) { + MOZ_ASSERT(NS_IsMainThread()); + UnpackClonedMessageData<CopyMemory, Parent>(aClonedData, *this); +} + +void StructuredCloneData::CopyFromClonedMessageDataForChild( + const ClonedMessageData& aClonedData) { + MOZ_ASSERT(NS_IsMainThread()); + UnpackClonedMessageData<CopyMemory, Child>(aClonedData, *this); +} + +void StructuredCloneData::CopyFromClonedMessageDataForBackgroundParent( + const ClonedMessageData& aClonedData) { + MOZ_ASSERT(IsOnBackgroundThread()); + UnpackClonedMessageData<CopyMemory, Parent>(aClonedData, *this); +} + +void StructuredCloneData::CopyFromClonedMessageDataForBackgroundChild( + const ClonedMessageData& aClonedData) { + UnpackClonedMessageData<CopyMemory, Child>(aClonedData, *this); +} + +void StructuredCloneData::StealFromClonedMessageDataForParent( + ClonedMessageData& aClonedData) { + MOZ_ASSERT(NS_IsMainThread()); + UnpackClonedMessageData<StealMemory, Parent>(aClonedData, *this); +} + +void StructuredCloneData::StealFromClonedMessageDataForChild( + ClonedMessageData& aClonedData) { + MOZ_ASSERT(NS_IsMainThread()); + UnpackClonedMessageData<StealMemory, Child>(aClonedData, *this); +} + +void StructuredCloneData::StealFromClonedMessageDataForBackgroundParent( + ClonedMessageData& aClonedData) { + MOZ_ASSERT(IsOnBackgroundThread()); + UnpackClonedMessageData<StealMemory, Parent>(aClonedData, *this); +} + +void StructuredCloneData::StealFromClonedMessageDataForBackgroundChild( + ClonedMessageData& aClonedData) { + UnpackClonedMessageData<StealMemory, Child>(aClonedData, *this); +} + +void StructuredCloneData::WriteIPCParams(IPC::Message* aMsg) const { + WriteParam(aMsg, Data()); +} + +bool StructuredCloneData::ReadIPCParams(const IPC::Message* aMsg, + PickleIterator* aIter) { + MOZ_ASSERT(!mInitialized); + JSStructuredCloneData data(JS::StructuredCloneScope::DifferentProcess); + if (!ReadParam(aMsg, aIter, &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..524f088159 --- /dev/null +++ b/dom/ipc/StructuredCloneData.h @@ -0,0 +1,301 @@ +/* -*- 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 PickleIterator; + +namespace mozilla { +namespace ipc { + +class AutoIPCStream; +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 uses ExtractBuffers, + * returning a fatal false if unable to extract. (And + * SerializedStructuredCloneBuffer wraps/defers to it.) 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; + + // Actor-varying methods to convert the structured clone stored in this holder + // by a previous call to Write() into ClonedMessageData IPC representation. + // (Blobs are represented in IPC by IPCBlob actors, so we need the parent to + // be able to create them.) + bool BuildClonedMessageDataForParent(ContentParent* aParent, + ClonedMessageData& aClonedData); + bool BuildClonedMessageDataForChild(ContentChild* aChild, + ClonedMessageData& aClonedData); + bool BuildClonedMessageDataForBackgroundParent( + mozilla::ipc::PBackgroundParent* aParent, ClonedMessageData& aClonedData); + bool BuildClonedMessageDataForBackgroundChild( + mozilla::ipc::PBackgroundChild* aChild, ClonedMessageData& aClonedData); + + // Actor-varying and memory-management-strategy-varying methods to initialize + // this holder from a ClonedMessageData representation. + void BorrowFromClonedMessageDataForParent( + const ClonedMessageData& aClonedData); + void BorrowFromClonedMessageDataForChild( + const ClonedMessageData& aClonedData); + void BorrowFromClonedMessageDataForBackgroundParent( + const ClonedMessageData& aClonedData); + void BorrowFromClonedMessageDataForBackgroundChild( + const ClonedMessageData& aClonedData); + + void CopyFromClonedMessageDataForParent(const ClonedMessageData& aClonedData); + void CopyFromClonedMessageDataForChild(const ClonedMessageData& aClonedData); + void CopyFromClonedMessageDataForBackgroundParent( + const ClonedMessageData& aClonedData); + void CopyFromClonedMessageDataForBackgroundChild( + const ClonedMessageData& aClonedData); + + // The steal variants of course take a non-const ClonedMessageData. + void StealFromClonedMessageDataForParent(ClonedMessageData& aClonedData); + void StealFromClonedMessageDataForChild(ClonedMessageData& aClonedData); + void StealFromClonedMessageDataForBackgroundParent( + ClonedMessageData& aClonedData); + void StealFromClonedMessageDataForBackgroundChild( + 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; } + + FallibleTArray<mozilla::ipc::AutoIPCStream>& IPCStreams() { + return mIPCStreams; + } + + // For IPC serialization + void WriteIPCParams(IPC::Message* aMessage) const; + bool ReadIPCParams(const IPC::Message* aMessage, PickleIterator* aIter); + + protected: + already_AddRefed<SharedJSAllocatedData> TakeSharedData(); + + private: + JSStructuredCloneData mExternalData; + RefPtr<SharedJSAllocatedData> mSharedData; + + // This array is needed because AutoIPCStream DTOR must be executed after the + // sending of the data via IPC. This will be fixed by bug 1353475. + FallibleTArray<mozilla::ipc::AutoIPCStream> mIPCStreams; + 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..2a1c8d9123 --- /dev/null +++ b/dom/ipc/TabContext.cpp @@ -0,0 +1,154 @@ +/* -*- 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), + mJSPluginID(-1), + mShowFocusRings(UIStateChangeType_NoChange), + mMaxTouchPoints(0) {} + +bool TabContext::IsJSPlugin() const { return mJSPluginID >= 0; } + +int32_t TabContext::JSPluginId() const { return mJSPluginID; } + +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; +} + +const nsAString& TabContext::PresentationURL() const { + return mPresentationURL; +} + +UIStateChangeType TabContext::ShowFocusRings() const { return mShowFocusRings; } + +bool TabContext::SetTabContext(uint64_t aChromeOuterWindowID, + UIStateChangeType aShowFocusRings, + const nsAString& aPresentationURL, + uint32_t aMaxTouchPoints) { + NS_ENSURE_FALSE(mInitialized, false); + + mInitialized = true; + mChromeOuterWindowID = aChromeOuterWindowID; + mPresentationURL = aPresentationURL; + mShowFocusRings = aShowFocusRings; + mMaxTouchPoints = aMaxTouchPoints; + return true; +} + +bool TabContext::SetTabContextForJSPluginFrame(int32_t aJSPluginID) { + NS_ENSURE_FALSE(mInitialized, false); + + mInitialized = true; + mJSPluginID = aJSPluginID; + return true; +} + +IPCTabContext TabContext::AsIPCTabContext() const { + if (IsJSPlugin()) { + return IPCTabContext(JSPluginFrameIPCTabContext(mJSPluginID)); + } + + return IPCTabContext(FrameIPCTabContext(mChromeOuterWindowID, + mPresentationURL, mShowFocusRings, + mMaxTouchPoints)); +} + +MaybeInvalidTabContext::MaybeInvalidTabContext(const IPCTabContext& aParams) + : mInvalidReason(nullptr) { + uint64_t chromeOuterWindowID = 0; + int32_t jsPluginId = -1; + nsAutoString presentationURL; + UIStateChangeType showFocusRings = UIStateChangeType_NoChange; + uint32_t maxTouchPoints = 0; + + switch (aParams.type()) { + case IPCTabContext::TPopupIPCTabContext: { + const PopupIPCTabContext& ipcContext = aParams.get_PopupIPCTabContext(); + + chromeOuterWindowID = ipcContext.chromeOuterWindowID(); + break; + } + case IPCTabContext::TJSPluginFrameIPCTabContext: { + const JSPluginFrameIPCTabContext& ipcContext = + aParams.get_JSPluginFrameIPCTabContext(); + + jsPluginId = ipcContext.jsPluginId(); + break; + } + case IPCTabContext::TFrameIPCTabContext: { + const FrameIPCTabContext& ipcContext = aParams.get_FrameIPCTabContext(); + + chromeOuterWindowID = ipcContext.chromeOuterWindowID(); + presentationURL = ipcContext.presentationURL(); + showFocusRings = ipcContext.showFocusRings(); + maxTouchPoints = ipcContext.maxTouchPoints(); + break; + } + default: { + MOZ_CRASH(); + } + } + + bool rv; + if (jsPluginId >= 0) { + rv = mTabContext.SetTabContextForJSPluginFrame(jsPluginId); + } else { + rv = mTabContext.SetTabContext(chromeOuterWindowID, showFocusRings, + presentationURL, maxTouchPoints); + } + if (!rv) { + 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..a7ec447321 --- /dev/null +++ b/dom/ipc/TabContext.h @@ -0,0 +1,217 @@ +/* -*- 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 { +namespace 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; + + bool IsJSPlugin() const; + int32_t JSPluginId() const; + + uint64_t ChromeOuterWindowID() const; + + /** + * Returns the presentation URL associated with the tab if this tab is + * created for presented content + */ + const nsAString& PresentationURL() const; + + UIStateChangeType ShowFocusRings() 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, + UIStateChangeType aShowFocusRings, + const nsAString& aPresentationURL, + 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); + + /** + * Set this TabContext to be for a JS plugin. aPluginID is the id of the JS + * plugin + * (@see nsFakePlugin::mId). + * As with the other protected mutator methods, this lets you modify a + * TabContext once. + * (@see TabContext::SetTabContext above for more details). + */ + bool SetTabContextForJSPluginFrame(int32_t aJSPluginID); + + 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; + + int32_t mJSPluginID; + + /** + * The requested presentation URL. + */ + nsString mPresentationURL; + + /** + * Keyboard indicator state (focus rings). + */ + UIStateChangeType mShowFocusRings; + + /** + * 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, + UIStateChangeType aShowFocusRings, + const nsAString& aPresentationURL, + uint32_t aMaxTouchPoints) { + return TabContext::SetTabContext(aChromeOuterWindowID, aShowFocusRings, + aPresentationURL, aMaxTouchPoints); + } + + bool SetTabContextForJSPluginFrame(uint32_t aJSPluginID) { + return TabContext::SetTabContextForJSPluginFrame(aJSPluginID); + } +}; + +/** + * 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 dom +} // namespace mozilla + +#endif diff --git a/dom/ipc/TabMessageTypes.h b/dom/ipc/TabMessageTypes.h new file mode 100644 index 0000000000..8678a79859 --- /dev/null +++ b/dom/ipc/TabMessageTypes.h @@ -0,0 +1,29 @@ +/* -*- 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 { +class Event; + +struct RemoteDOMEvent { + // Make sure to set the owner after deserializing. + RefPtr<Event> mEvent; +}; + +enum class EmbedderElementEventType { + NoEvent, + LoadEvent, + ErrorEvent, + EndGuard_, +}; + +} // namespace mozilla::dom + +#endif // TABMESSAGE_TYPES_H diff --git a/dom/ipc/TabMessageUtils.cpp b/dom/ipc/TabMessageUtils.cpp new file mode 100644 index 0000000000..12b219a1ec --- /dev/null +++ b/dom/ipc/TabMessageUtils.cpp @@ -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/. */ + +#include "mozilla/EventDispatcher.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/TabMessageUtils.h" +#include "nsCOMPtr.h" + +namespace mozilla::dom { + +bool ReadRemoteEvent(const IPC::Message* aMsg, PickleIterator* aIter, + RemoteDOMEvent* aResult) { + aResult->mEvent = nullptr; + nsString type; + NS_ENSURE_TRUE(ReadParam(aMsg, aIter, &type), false); + + aResult->mEvent = + EventDispatcher::CreateEvent(nullptr, nullptr, nullptr, type); + + return aResult->mEvent->Deserialize(aMsg, aIter); +} + +} // namespace mozilla::dom diff --git a/dom/ipc/TabMessageUtils.h b/dom/ipc/TabMessageUtils.h new file mode 100644 index 0000000000..6eca1dec1f --- /dev/null +++ b/dom/ipc/TabMessageUtils.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 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 mozilla::dom { + +bool ReadRemoteEvent(const IPC::Message* aMsg, PickleIterator* aIter, + mozilla::dom::RemoteDOMEvent* aResult); + +} // namespace mozilla::dom + +namespace IPC { + +template <> +struct ParamTraits<mozilla::dom::RemoteDOMEvent> { + typedef mozilla::dom::RemoteDOMEvent paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + aParam.mEvent->Serialize(aMsg, true); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return mozilla::dom::ReadRemoteEvent(aMsg, aIter, aResult); + } + + static void Log(const paramType& aParam, std::wstring* aLog) {} +}; + +template <> +struct ParamTraits<nsSizeMode> + : public ContiguousEnumSerializer<nsSizeMode, nsSizeMode_Normal, + nsSizeMode_Invalid> {}; + +template <> +struct ParamTraits<UIStateChangeType> + : public ContiguousEnumSerializer<UIStateChangeType, + UIStateChangeType_NoChange, + UIStateChangeType_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(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mVisibleRect); + WriteParam(aMsg, aParam.mScaleX); + WriteParam(aMsg, aParam.mScaleY); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + return ReadParam(aMsg, aIter, &aResult->mVisibleRect) && + ReadParam(aMsg, aIter, &aResult->mScaleX) && + ReadParam(aMsg, aIter, &aResult->mScaleY); + } +}; + +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::ScrollAxis> { + typedef mozilla::ScrollAxis paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mWhereToScroll); + WriteParam(aMsg, aParam.mWhenToScroll); + WriteParam(aMsg, aParam.mOnlyIfPerceivedScrollableDirection); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + if (!ReadParam(aMsg, aIter, &aResult->mWhereToScroll)) { + return false; + } + if (!ReadParam(aMsg, aIter, &aResult->mWhenToScroll)) { + return false; + } + + // We can't set mOnlyIfPerceivedScrollableDirection directly since it's + // a bitfield. + bool value; + if (!ReadParam(aMsg, aIter, &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..31e24e7df7 --- /dev/null +++ b/dom/ipc/URLClassifierChild.h @@ -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/. */ + +#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 { +namespace 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 dom +} // namespace mozilla + +#endif // mozilla_dom_URLClassifierChild_h diff --git a/dom/ipc/URLClassifierParent.cpp b/dom/ipc/URLClassifierParent.cpp new file mode 100644 index 0000000000..1a0e0aa420 --- /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, nullptr, 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..9f21b581b2 --- /dev/null +++ b/dom/ipc/URLClassifierParent.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_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" +#include "mozilla/dom/PContent.h" + +namespace mozilla { +namespace 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 dom +} // namespace mozilla + +#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.cpp b/dom/ipc/VsyncChild.cpp new file mode 100644 index 0000000000..3b186e126e --- /dev/null +++ b/dom/ipc/VsyncChild.cpp @@ -0,0 +1,75 @@ +/* -*- 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 "VsyncChild.h" + +#include "mozilla/SchedulerGroup.h" +#include "mozilla/VsyncDispatcher.h" +#include "nsThreadUtils.h" + +namespace mozilla::dom { + +VsyncChild::VsyncChild() + : mIsShutdown(false), mVsyncRate(TimeDuration::Forever()) { + MOZ_ASSERT(NS_IsMainThread()); +} + +VsyncChild::~VsyncChild() { MOZ_ASSERT(NS_IsMainThread()); } + +void VsyncChild::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 VsyncChild::RemoveChildRefreshTimer(VsyncObserver* aVsyncObserver) { + MOZ_ASSERT(NS_IsMainThread()); + if (mIsShutdown) { + return; + } + + if (mObservers.RemoveElement(aVsyncObserver) && mObservers.IsEmpty()) { + Unused << PVsyncChild::SendUnobserve(); + } +} + +void VsyncChild::ActorDestroy(ActorDestroyReason aActorDestroyReason) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mIsShutdown); + mIsShutdown = true; + + if (!mObservers.IsEmpty()) { + Unused << PVsyncChild::SendUnobserve(); + } + mObservers.Clear(); +} + +mozilla::ipc::IPCResult VsyncChild::RecvNotify(const VsyncEvent& aVsync, + const float& aVsyncRate) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mIsShutdown); + + SchedulerGroup::MarkVsyncRan(); + + mVsyncRate = TimeDuration::FromMilliseconds(aVsyncRate); + + for (VsyncObserver* observer : mObservers.ForwardRange()) { + observer->NotifyVsync(aVsync); + } + return IPC_OK(); +} + +TimeDuration VsyncChild::GetVsyncRate() { return mVsyncRate; } + +} // namespace mozilla::dom diff --git a/dom/ipc/VsyncChild.h b/dom/ipc/VsyncChild.h new file mode 100644 index 0000000000..6230302437 --- /dev/null +++ b/dom/ipc/VsyncChild.h @@ -0,0 +1,53 @@ +/* -*- 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" +#include "mozilla/RefPtr.h" +#include "nsISupportsImpl.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 VsyncChild final : public PVsyncChild { + NS_INLINE_DECL_REFCOUNTING(VsyncChild) + + friend class PVsyncChild; + + public: + VsyncChild(); + + void AddChildRefreshTimer(VsyncObserver* aVsyncObserver); + void RemoveChildRefreshTimer(VsyncObserver* aVsyncObserver); + + TimeDuration GetVsyncRate(); + + private: + virtual ~VsyncChild(); + + mozilla::ipc::IPCResult RecvNotify(const VsyncEvent& aVsync, + const float& aVsyncRate); + virtual 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..a640e0ff34 --- /dev/null +++ b/dom/ipc/VsyncParent.cpp @@ -0,0 +1,107 @@ +/* -*- 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 "BackgroundParent.h" +#include "BackgroundParentImpl.h" +#include "gfxPlatform.h" +#include "mozilla/Unused.h" +#include "nsThreadUtils.h" +#include "VsyncSource.h" + +namespace mozilla::dom { + +VsyncParent::VsyncParent() + : mObservingVsync(false), + mDestroyed(false), + mInitialThread(NS_GetCurrentThread()) {} + +void VsyncParent::UpdateVsyncSource( + const RefPtr<gfx::VsyncSource>& aVsyncSource) { + mVsyncSource = aVsyncSource; + if (!mVsyncSource) { + mVsyncSource = gfxPlatform::GetPlatform()->GetHardwareVsync(); + } + + if (mObservingVsync && mVsyncDispatcher) { + mVsyncDispatcher->RemoveChildRefreshTimer(this); + } + mVsyncDispatcher = mVsyncSource->GetRefreshTimerVsyncDispatcher(); + if (mObservingVsync) { + mVsyncDispatcher->AddChildRefreshTimer(this); + } +} + +bool VsyncParent::NotifyVsync(const VsyncEvent& aVsync) { + if (IsOnInitialThread()) { + DispatchVsyncEvent(aVsync); + return true; + } + + // 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(mInitialThread->Dispatch(vsyncEvent, NS_DISPATCH_NORMAL)); + return true; +} + +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 = mVsyncSource->GetGlobalDisplay().GetVsyncRate(); + Unused << SendNotify(aVsync, vsyncRate.ToMilliseconds()); + } +} + +mozilla::ipc::IPCResult VsyncParent::RecvObserve() { + AssertIsOnInitialThread(); + if (!mObservingVsync) { + if (mVsyncDispatcher) { + mVsyncDispatcher->AddChildRefreshTimer(this); + } + mObservingVsync = true; + return IPC_OK(); + } + return IPC_FAIL_NO_REASON(this); +} + +mozilla::ipc::IPCResult VsyncParent::RecvUnobserve() { + AssertIsOnInitialThread(); + if (mObservingVsync) { + if (mVsyncDispatcher) { + mVsyncDispatcher->RemoveChildRefreshTimer(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->RemoveChildRefreshTimer(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..af452fd508 --- /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" +#include "VsyncSource.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; + + public: + VsyncParent(); + void UpdateVsyncSource(const RefPtr<gfx::VsyncSource>& aVsyncSource); + + private: + virtual ~VsyncParent() = default; + + virtual bool 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<gfx::VsyncSource> mVsyncSource; + RefPtr<RefreshTimerVsyncDispatcher> mVsyncDispatcher; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_ipc_VsyncParent_h diff --git a/dom/ipc/WindowGlobalActor.cpp b/dom/ipc/WindowGlobalActor.cpp new file mode 100644 index 0000000000..64085dbe5d --- /dev/null +++ b/dom/ipc/WindowGlobalActor.cpp @@ -0,0 +1,169 @@ +/* -*- 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 "mozJSComponentLoader.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/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); + + 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.mEmbedderPolicy = InheritedPolicy(aBrowsingContext); + fields.mAutoplayPermission = nsIPermissionManager::UNKNOWN_ACTION; + return init; +} + +WindowGlobalInit WindowGlobalActor::AboutBlankInitializer( + dom::BrowsingContext* aBrowsingContext, nsIPrincipal* aPrincipal) { + WindowGlobalInit init = + BaseInitializer(aBrowsingContext, nsContentUtils::GenerateWindowId(), + nsContentUtils::GenerateWindowId()); + + init.principal() = aPrincipal; + Unused << NS_NewURI(getter_AddRefs(init.documentURI()), "about:blank"); + + return init; +} + +WindowGlobalInit WindowGlobalActor::WindowInitializer( + nsGlobalWindowInner* aWindow) { + WindowGlobalInit init = + BaseInitializer(aWindow->GetBrowsingContext(), aWindow->WindowID(), + aWindow->GetOuterWindow()->WindowID()); + + init.principal() = aWindow->GetPrincipal(); + init.documentURI() = aWindow->GetDocumentURI(); + + Document* doc = aWindow->GetDocument(); + + 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(); + + auto& fields = init.context().mFields; + fields.mCookieBehavior = Some(doc->CookieJarSettings()->GetCookieBehavior()); + fields.mIsOnContentBlockingAllowList = + doc->CookieJarSettings()->GetIsOnContentBlockingAllowList(); + fields.mIsThirdPartyWindow = doc->HasThirdPartyChannel(); + fields.mIsThirdPartyTrackingResourceWindow = + nsContentUtils::IsThirdPartyTrackingResourceWindow(aWindow); + fields.mIsSecureContext = aWindow->IsSecureContext(); + + // Initialze permission fields + fields.mAutoplayPermission = + AutoplayPolicy::GetSiteAutoplayPermission(init.principal()); + fields.mPopupPermission = PopupBlocker::GetPopupPermission(init.principal()); + + // Initialize top level permission fields + if (aWindow->GetBrowsingContext()->IsTop()) { + fields.mShortcutsPermission = + nsGlobalWindowInner::GetShortcutsPermission(init.principal()); + } + + auto policy = doc->GetEmbedderPolicy(); + if (policy.isSome()) { + fields.mEmbedderPolicy = *policy; + } + + // Init Mixed Content Fields + nsCOMPtr<nsIURI> innerDocURI = NS_GetInnermostURI(doc->GetDocumentURI()); + if (innerDocURI) { + fields.mIsSecure = innerDocURI->SchemeIs("https"); + } + nsCOMPtr<nsIChannel> mixedChannel; + aWindow->GetDocShell()->GetMixedContentChannel(getter_AddRefs(mixedChannel)); + // A non null mixedContent channel on the docshell indicates, + // that the user has overriden mixed content to allow mixed + // content loads to happen. + if (mixedChannel && (mixedChannel == doc->GetChannel())) { + fields.mAllowMixedContent = true; + } + + nsCOMPtr<nsITransportSecurityInfo> securityInfo; + if (nsCOMPtr<nsIChannel> channel = doc->GetChannel()) { + nsCOMPtr<nsILoadInfo> loadInfo(channel->LoadInfo()); + fields.mIsOriginalFrameSource = loadInfo->GetOriginalFrameSrcLoad(); + + nsCOMPtr<nsISupports> securityInfoSupports; + channel->GetSecurityInfo(getter_AddRefs(securityInfoSupports)); + securityInfo = do_QueryInterface(securityInfoSupports); + } + init.securityInfo() = securityInfo; + + fields.mIsLocalIP = 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..cb157abcfa --- /dev/null +++ b/dom/ipc/WindowGlobalChild.cpp @@ -0,0 +1,700 @@ +/* -*- 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/WindowGlobalActorsBinding.h" +#include "mozilla/dom/WindowGlobalParent.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 "nsContentUtils.h" +#include "nsDocShell.h" +#include "nsFocusManager.h" +#include "nsFrameLoaderOwner.h" +#include "nsGlobalWindowInner.h" +#include "nsFrameLoaderOwner.h" +#include "nsNetUtil.h" +#include "nsQueryObject.h" +#include "nsSerializationHelper.h" +#include "nsFrameLoader.h" + +#include "mozilla/dom/JSWindowActorBinding.h" +#include "mozilla/dom/JSWindowActorChild.h" +#include "mozilla/dom/JSActorService.h" +#include "nsIHttpChannelInternal.h" +#include "nsIURIMutator.h" + +using namespace mozilla::ipc; +using namespace mozilla::dom::ipc; + +namespace mozilla::dom { + +typedef nsRefPtrHashtable<nsUint64HashKey, WindowGlobalChild> WGCByIdMap; +static StaticAutoPtr<WGCByIdMap> gWindowGlobalChildById; + +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"); + } + +#ifdef MOZ_GECKO_PROFILER + // 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()->Id(), InnerWindowId(), + aDocumentURI->GetSpecOrDefault(), + embedderInnerWindowID); +#endif +} + +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, /* aInProcess */ true); + } else { + dom::WindowContext::FieldValues fields = aInit.context().mFields; + windowContext = + new dom::WindowContext(browsingContext, aInit.context().mInnerWindowId, + aInit.context().mOuterWindowId, + /* aInProcess */ true, std::move(fields)); + } + + RefPtr<WindowGlobalChild> windowChild = new WindowGlobalChild( + windowContext, aInit.principal(), aInit.documentURI()); + return windowChild.forget(); +} + +void WindowGlobalChild::Init() { + mWindowContext->Init(); + + // Register this WindowGlobal in the gWindowGlobalParentsById map. + if (!gWindowGlobalChildById) { + gWindowGlobalChildById = new WGCByIdMap(); + ClearOnShutdown(&gWindowGlobalChildById); + } + auto entry = gWindowGlobalChildById->LookupForAdd(InnerWindowId()); + MOZ_RELEASE_ASSERT(!entry, "Duplicate WindowGlobalChild entry for ID!"); + entry.OrInsert([&] { return this; }); +} + +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? + SetDocumentURI(aDocument->GetDocumentURI()); + SetDocumentPrincipal(aDocument->NodePrincipal()); + + nsCOMPtr<nsITransportSecurityInfo> securityInfo; + if (nsCOMPtr<nsIChannel> channel = aDocument->GetChannel()) { + nsCOMPtr<nsISupports> securityInfoSupports; + channel->GetSecurityInfo(getter_AddRefs(securityInfoSupports)); + securityInfo = do_QueryInterface(securityInfoSupports); + } + 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()); + auto policy = aDocument->GetEmbedderPolicy(); + if (policy.isSome()) { + txn.SetEmbedderPolicy(policy.ref()); + } + + if (nsCOMPtr<nsIChannel> channel = aDocument->GetChannel()) { + nsCOMPtr<nsILoadInfo> loadInfo(channel->LoadInfo()); + txn.SetIsOriginalFrameSource(loadInfo->GetOriginalFrameSrcLoad()); + } else { + txn.SetIsOriginalFrameSource(false); + } + + // Init Mixed Content Fields + nsCOMPtr<nsIURI> innerDocURI = + NS_GetInnermostURI(aDocument->GetDocumentURI()); + if (innerDocURI) { + txn.SetIsSecure(innerDocURI->SchemeIs("https")); + } + nsCOMPtr<nsIChannel> mixedChannel; + mWindowGlobal->GetDocShell()->GetMixedContentChannel( + getter_AddRefs(mixedChannel)); + // A non null mixedContent channel on the docshell indicates, + // that the user has overriden mixed content to allow mixed + // content loads to happen. + if (mixedChannel && (mixedChannel == aDocument->GetChannel())) { + txn.SetAllowMixedContent(true); + } + + MOZ_DIAGNOSTIC_ASSERT(mDocumentPrincipal->GetIsLocalIpAddress() == + mWindowContext->IsLocalIP()); + + MOZ_ALWAYS_SUCCEEDS(txn.Commit(mWindowContext)); +} + +/* static */ +already_AddRefed<WindowGlobalChild> WindowGlobalChild::GetByInnerWindowId( + uint64_t aInnerWindowId) { + if (!gWindowGlobalChildById) { + return nullptr; + } + return gWindowGlobalChildById->Get(aInnerWindowId); +} + +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(); + + // 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())); + + // Immediately resolve the promise, acknowledging the request. + aResolve(true); + + if (!aLayersId.IsValid()) { + return IPC_FAIL(this, "Received an invalid LayersId"); + } + + // 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(); + } + + // Immediately tear down the actor if we don't have a valid FrameContext. + if (NS_WARN_IF(aFrameContext.IsNullOrDiscarded())) { + BrowserBridgeChild::Send__delete__(bridge); + return IPC_OK(); + } + + RefPtr<Element> embedderElt = frameContext->GetEmbedderElement(); + if (NS_WARN_IF(!embedderElt)) { + BrowserBridgeChild::Send__delete__(bridge); + return IPC_OK(); + } + + if (NS_WARN_IF(embedderElt->GetOwnerGlobal() != GetWindowGlobal())) { + BrowserBridgeChild::Send__delete__(bridge); + 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())) { + BrowserBridgeChild::Send__delete__(bridge); + return IPC_OK(); + } + + 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::RecvGetSecurityInfo( + GetSecurityInfoResolver&& aResolve) { + Maybe<nsCString> result; + + if (nsCOMPtr<Document> doc = mWindowGlobal->GetDoc()) { + nsCOMPtr<nsISupports> secInfo; + nsresult rv = NS_OK; + + // First check if there's a failed channel, in case of a certificate + // error. + if (nsIChannel* failedChannel = doc->GetFailedChannel()) { + rv = failedChannel->GetSecurityInfo(getter_AddRefs(secInfo)); + } else { + // When there's no failed channel we should have a regular + // security info on the document. In some cases there's no + // security info at all, i.e. on HTTP sites. + secInfo = doc->GetSecurityInfo(); + } + + if (NS_SUCCEEDED(rv) && secInfo) { + nsCOMPtr<nsISerializable> secInfoSer = do_QueryInterface(secInfo); + result.emplace(); + NS_SerializeToString(secInfoSer, result.ref()); + } + } + + aResolve(result); + return IPC_OK(); +} + +mozilla::ipc::IPCResult +WindowGlobalChild::RecvSaveStorageAccessPermissionGranted() { + nsCOMPtr<nsPIDOMWindowInner> inner = GetWindowGlobal(); + if (inner) { + inner->SaveStorageAccessPermissionGranted(); + } + + nsCOMPtr<nsPIDOMWindowOuter> outer = + nsPIDOMWindowOuter::GetFromCurrentInner(inner); + if (outer) { + nsGlobalWindowOuter::Cast(outer)->SetStorageAccessPermissionGranted(true); + } + + 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(); +} + +IPCResult WindowGlobalChild::RecvRawMessage( + const JSActorMessageMeta& aMeta, const Maybe<ClonedMessageData>& aData, + const Maybe<ClonedMessageData>& aStack) { + Maybe<StructuredCloneData> data; + if (aData) { + data.emplace(); + data->BorrowFromClonedMessageDataForChild(*aData); + } + Maybe<StructuredCloneData> stack; + if (aStack) { + stack.emplace(); + stack->BorrowFromClonedMessageDataForChild(*aStack); + } + ReceiveRawMessage(aMeta, std::move(data), std::move(stack)); + return IPC_OK(); +} + +void WindowGlobalChild::SetDocumentURI(nsIURI* aDocumentURI) { +#ifdef MOZ_GECKO_PROFILER + // 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()->Id(), InnerWindowId(), + aDocumentURI->GetSpecOrDefault(), + embedderInnerWindowID); +#endif + mDocumentURI = aDocumentURI; + SendUpdateDocumentURI(aDocumentURI); +} + +void WindowGlobalChild::SetDocumentPrincipal( + nsIPrincipal* aNewDocumentPrincipal) { + MOZ_ASSERT(mDocumentPrincipal->Equals(aNewDocumentPrincipal)); + mDocumentPrincipal = aNewDocumentPrincipal; + SendUpdateDocumentPrincipal(aNewDocumentPrincipal); +} + +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<JSActor> WindowGlobalChild::InitJSActor( + JS::HandleObject 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"); + + gWindowGlobalChildById->Remove(InnerWindowId()); + +#ifdef MOZ_GECKO_PROFILER + profiler_unregister_page(InnerWindowId()); +#endif + + // 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()); +} + +WindowGlobalChild::~WindowGlobalChild() { + MOZ_ASSERT(!gWindowGlobalChildById || + !gWindowGlobalChildById->Contains(InnerWindowId())); +} + +JSObject* WindowGlobalChild::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return WindowGlobalChild_Binding::Wrap(aCx, this, aGivenProto); +} + +nsISupports* WindowGlobalChild::GetParentObject() { + return xpc::NativeGlobal(xpc::PrivilegedJunkScope()); +} + +void WindowGlobalChild::MaybeSendUpdateDocumentWouldPreloadResources() { + if (!mDocumentWouldPreloadResources) { + mDocumentWouldPreloadResources = true; + SendUpdateDocumentWouldPreloadResources(); + } +} + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WindowGlobalChild, mWindowGlobal, + mContainerFeaturePolicy) + +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..91f5829081 --- /dev/null +++ b/dom/ipc/WindowGlobalChild.h @@ -0,0 +1,195 @@ +/* -*- 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/dom/PWindowGlobalChild.h" +#include "nsRefPtrHashtable.h" +#include "nsWrapperCache.h" +#include "mozilla/dom/WindowGlobalActor.h" + +class nsGlobalWindowInner; +class nsDocShell; + +namespace mozilla { +namespace 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 { + friend class PWindowGlobalChild; + + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_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* 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); + + // 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(); + + nsISupports* GetParentObject(); + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + void MaybeSendUpdateDocumentWouldPreloadResources(); + + dom::FeaturePolicy* GetContainerFeaturePolicy() const { + return mContainerFeaturePolicy; + } + + protected: + const nsACString& GetRemoteType() override; + + already_AddRefed<JSActor> InitJSActor(JS::HandleObject 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 RecvGetSecurityInfo( + GetSecurityInfoResolver&& aResolve); + + mozilla::ipc::IPCResult RecvSaveStorageAccessPermissionGranted(); + + mozilla::ipc::IPCResult RecvAddBlockedFrameNodeByClassifier( + const MaybeDiscardedBrowsingContext& aNode); + + mozilla::ipc::IPCResult RecvResetScalingZoom(); + + mozilla::ipc::IPCResult RecvSetContainerFeaturePolicy( + dom::FeaturePolicy* aContainerFeaturePolicy); + + 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; + bool mDocumentWouldPreloadResources = false; +}; + +} // namespace dom +} // namespace mozilla + +#endif // !defined(mozilla_dom_WindowGlobalChild_h) diff --git a/dom/ipc/WindowGlobalParent.cpp b/dom/ipc/WindowGlobalParent.cpp new file mode 100644 index 0000000000..ae32c88402 --- /dev/null +++ b/dom/ipc/WindowGlobalParent.cpp @@ -0,0 +1,1322 @@ +/* -*- 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/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/MediaController.h" +#include "mozilla/dom/RemoteWebProgress.h" +#include "mozilla/dom/WindowGlobalChild.h" +#include "mozilla/dom/ChromeUtils.h" +#include "mozilla/dom/ipc/IdType.h" +#include "mozilla/dom/ipc/StructuredCloneData.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/ServoCSSParser.h" +#include "mozilla/ServoStyleSet.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Variant.h" +#include "mozJSComponentLoader.h" +#include "nsContentUtils.h" +#include "nsDocShell.h" +#include "nsDocShellLoadState.h" +#include "nsError.h" +#include "nsFrameLoader.h" +#include "nsFrameLoaderOwner.h" +#include "nsGlobalWindowInner.h" +#include "nsQueryObject.h" +#include "nsFrameLoaderOwner.h" +#include "nsNetUtil.h" +#include "nsSandboxFlags.h" +#include "nsSerializationHelper.h" +#include "nsIBrowser.h" +#include "nsIPromptCollection.h" +#include "nsITimer.h" +#include "nsITransportSecurityInfo.h" +#include "nsISharePicker.h" +#include "mozilla/Telemetry.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" + +using namespace mozilla::ipc; +using namespace mozilla::dom::ipc; + +extern mozilla::LazyLogModule gUseCountersLog; + +namespace mozilla::dom { + +WindowGlobalParent::WindowGlobalParent( + CanonicalBrowsingContext* aBrowsingContext, uint64_t aInnerWindowId, + uint64_t aOuterWindowId, bool aInProcess, FieldValues&& aInit) + : WindowContext(aBrowsingContext, aInnerWindowId, aOuterWindowId, + aInProcess, std::move(aInit)), + mIsInitialDocument(false), + 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, bool aInProcess) { + 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, aInProcess, + std::move(fields)); + wgp->mDocumentPrincipal = aInit.principal(); + wgp->mDocumentURI = aInit.documentURI(); + 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"); + + 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.GetOrInsert(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::Telemetry::Accumulate( + mozilla::Telemetry::HistogramID:: + FX_NUMBER_OF_UNIQUE_SITE_ORIGINS_PER_DOCUMENT, + 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(); + } + 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(); + } + 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(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?) + mDocumentURI = aURI; + return IPC_OK(); +} + +IPCResult WindowGlobalParent::RecvUpdateDocumentPrincipal( + nsIPrincipal* aNewDocumentPrincipal) { + 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; + return IPC_OK(); +} +mozilla::ipc::IPCResult WindowGlobalParent::RecvUpdateDocumentTitle( + const nsString& aTitle) { + if (mDocumentTitle == aTitle) { + return IPC_OK(); + } + + mDocumentTitle = 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(); + } + + (new AsyncEventDispatcher(frameElement, u"pagetitlechanged"_ns, + CanBubble::eYes, ChromeOnlyDispatch::eYes)) + ->RunDOMEventWhenSafe(); + + 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->BorrowFromClonedMessageDataForParent(*aData); + } + Maybe<StructuredCloneData> stack; + if (aStack) { + stack.emplace(); + stack->BorrowFromClonedMessageDataForParent(*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) { + MOZ_ASSERT(NS_IsMainThread()); + DebugOnly<bool> isCookiesBlocked = + aEvent == nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER || + aEvent == nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER || + (aEvent == nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN && + StaticPrefs::network_cookie_rejectForeignWithExceptions_enabled()); + 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); + + // Notify the OnContentBlockingEvent if necessary. + if (event) { + if (!GetBrowsingContext()->GetWebProgress()) { + return; + } + + nsCOMPtr<nsIWebProgress> webProgress = + new RemoteWebProgress(0, false, BrowsingContext()->IsTopContent()); + GetBrowsingContext()->Top()->GetWebProgress()->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<JSActor> WindowGlobalParent::InitJSActor( + JS::HandleObject 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() { + return CanSend() && BrowsingContext()->GetCurrentWindowGlobal() == this; +} + +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) override { + mResolver(NS_OK); + } + + virtual void RejectedCallback(JSContext* aCx, + JS::Handle<JS::Value> aValue) 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(); +} + +mozilla::ipc::IPCResult +WindowGlobalParent::RecvUpdateDocumentWouldPreloadResources() { + TopWindowContext()->mDocumentTreeWouldPreloadResources = true; + return IPC_OK(); +} + +mozilla::ipc::IPCResult WindowGlobalParent::RecvSubmitLoadEventPreloadTelemetry( + TimeStamp aNavigationStart, TimeStamp aLoadEventStart, + TimeStamp aLoadEventEnd) { + if (!IsTop()) { + return IPC_FAIL(this, "submit preload telemetry on non-toplevel document"); + } + + if (mDocumentTreeWouldPreloadResources) { + Telemetry::AccumulateTimeDelta( + Telemetry::TIME_TO_LOAD_EVENT_START_PRELOAD_MS, aNavigationStart, + aLoadEventStart); + Telemetry::AccumulateTimeDelta(Telemetry::TIME_TO_LOAD_EVENT_END_PRELOAD_MS, + aNavigationStart, aLoadEventEnd); + } else { + Telemetry::AccumulateTimeDelta( + Telemetry::TIME_TO_LOAD_EVENT_START_NO_PRELOAD_MS, aNavigationStart, + aLoadEventStart); + Telemetry::AccumulateTimeDelta( + Telemetry::TIME_TO_LOAD_EVENT_END_NO_PRELOAD_MS, aNavigationStart, + aLoadEventEnd); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult +WindowGlobalParent::RecvSubmitTimeToFirstInteractionPreloadTelemetry( + uint32_t aMillis) { + if (!IsTop()) { + return IPC_FAIL(this, "submit preload telemetry on non-toplevel document"); + } + + if (mDocumentTreeWouldPreloadResources) { + Telemetry::Accumulate(Telemetry::TIME_TO_FIRST_INTERACTION_PRELOAD_MS, + aMillis); + } else { + Telemetry::Accumulate(Telemetry::TIME_TO_FIRST_INTERACTION_NO_PRELOAD_MS, + aMillis); + } + + return IPC_OK(); +} + +mozilla::ipc::IPCResult +WindowGlobalParent::RecvSubmitLoadInputEventResponsePreloadTelemetry( + uint32_t aMillis) { + if (!IsTop()) { + return IPC_FAIL(this, "submit preload telemetry on non-toplevel document"); + } + + if (mDocumentTreeWouldPreloadResources) { + Telemetry::Accumulate(Telemetry::LOAD_INPUT_EVENT_RESPONSE_PRELOAD_MS, + aMillis); + } else { + Telemetry::Accumulate(Telemetry::LOAD_INPUT_EVENT_RESPONSE_NO_PRELOAD_MS, + aMillis); + } + + return IPC_OK(); +} + +namespace { + +class CheckPermitUnloadRequest final : public PromiseNativeHandler, + public nsITimerCallback { + public: + CheckPermitUnloadRequest(WindowGlobalParent* aWGP, bool aHasInProcessBlocker, + nsIContentViewer::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 = nsIContentViewer::eDontPromptAndUnload; + } + if (action != nsIContentViewer::ePrompt) { + SendReply(action == nsIContentViewer::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) override { + MOZ_ASSERT(mState == State::PROMPTING); + + SendReply(JS::ToBoolean(aValue)); + } + + void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) 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; + + nsIContentViewer::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, + nsIContentViewer::PermitUnloadAction(aAction), + [promise](bool aAllow) { promise->MaybeResolve(aAllow); }); + request->Run(/* aIgnoreProcess */ nullptr, aTimeout); + + return promise.forget(); +} + +already_AddRefed<mozilla::dom::Promise> WindowGlobalParent::DrawSnapshot( + const DOMRect* aRect, double aScale, const nsACString& aBackgroundColor, + 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::None; + if (!aRect) { + // If no explicit Rect was passed, we want the currently visible viewport. + flags = gfx::CrossProcessPaintFlags::DrawView; + } + + 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); + }); +} + +already_AddRefed<Promise> WindowGlobalParent::GetSecurityInfo( + ErrorResult& aRv) { + RefPtr<BrowserParent> browserParent = GetBrowserParent(); + if (NS_WARN_IF(!browserParent)) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + nsIGlobalObject* global = GetParentObject(); + RefPtr<Promise> promise = Promise::Create(global, aRv); + if (aRv.Failed()) { + return nullptr; + } + + SendGetSecurityInfo( + [promise](Maybe<nsCString>&& aResult) { + if (aResult) { + nsCOMPtr<nsISupports> infoObj; + nsresult rv = + NS_DeserializeObject(aResult.value(), getter_AddRefs(infoObj)); + if (NS_WARN_IF(NS_FAILED(rv))) { + promise->MaybeReject(NS_ERROR_FAILURE); + } + nsCOMPtr<nsITransportSecurityInfo> info = do_QueryInterface(infoObj); + if (!info) { + promise->MaybeReject(NS_ERROR_FAILURE); + } + promise->MaybeResolve(info); + } else { + promise->MaybeResolveWithUndefined(); + } + }, + [promise](ResponseRejectReason&& aReason) { + promise->MaybeReject(NS_ERROR_FAILURE); + }); + + return promise.forget(); +} + +/** + * 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())); + + Telemetry::Accumulate(Telemetry::TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED, 1); + + for (int32_t c = 0; c < eUseCounter_Count; ++c) { + auto uc = static_cast<UseCounter>(c); + if (!mPageUseCounters->mUseCounters[uc]) { + continue; + } + + auto id = static_cast<Telemetry::HistogramID>( + Telemetry::HistogramFirstUseCounter + uc * 2 + 1); + MOZ_LOG(gUseCountersLog, LogLevel::Debug, + (" > %s\n", Telemetry::GetHistogramName(id))); + Telemetry::Accumulate(id, 1); + } + } else { + MOZ_LOG(gUseCountersLog, LogLevel::Debug, + (" > no page use counter data was received")); + } + + mSentPageUseCounters = true; + mPageUseCounters = nullptr; +} + +void WindowGlobalParent::ActorDestroy(ActorDestroyReason aWhy) { + 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); + } + } + + // Note that our WindowContext has become discarded. + WindowContext::Discard(); + + ContentParent* cp = nullptr; + if (!IsInProcess()) { + cp = static_cast<ContentParent*>(Manager()->Manager()); + } + + RefPtr<WindowGlobalParent> self(this); + Group()->EachOtherParent(cp, [&](ContentParent* otherContent) { + // Keep the WindowContext alive until other processes have acknowledged it + // has been discarded. + auto resolve = [self](bool) {}; + auto reject = [self](mozilla::ipc::ResponseRejectReason) {}; + otherContent->SendDiscardWindowContext(InnerWindowId(), resolve, reject); + }); + + // Report content blocking log when destroyed. + // There shouldn't have any content blocking log when a documnet is loaded in + // the parent process(See NotifyContentBlockingeEvent), 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()->ReportOrigins(); + } + } + } + } + + // 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); + } +} + +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)) == + aStateFlags, + "Invalid flags specified!"); + + if ((mSecurityState & aStateFlags) == aStateFlags) { + return; + } + + mSecurityState |= aStateFlags; + + if (GetBrowsingContext()->GetCurrentWindowGlobal() == this) { + GetBrowsingContext()->UpdateSecurityState(); + } +} + +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..77a5b09ab6 --- /dev/null +++ b/dom/ipc/WindowGlobalParent.h @@ -0,0 +1,351 @@ +/* -*- 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/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 "nsDataHashtable.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; + +/** + * 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() { + return CanonicalBrowsingContext::Cast(WindowContext::GetBrowsingContext()); + } + + // 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); + + // 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; } + + // 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; } + + nsIPrincipal* GetContentBlockingAllowListPrincipal() const { + return mDocContentBlockingAllowListPrincipal; + } + + Maybe<ClientInfo> GetClientInfo() { return mClientInfo; } + + uint64_t ContentParentId(); + + int32_t OsPid(); + + bool IsCurrentGlobal(); + + bool IsProcessRoot(); + + uint32_t ContentBlockingEvents(); + + void GetContentBlockingLog(nsAString& aLog); + + bool IsInitialDocument() { return mIsInitialDocument; } + + already_AddRefed<mozilla::dom::Promise> PermitUnload( + PermitUnloadAction aAction, uint32_t aTimeout, mozilla::ErrorResult& aRv); + + already_AddRefed<mozilla::dom::Promise> DrawSnapshot( + const DOMRect* aRect, double aScale, const nsACString& aBackgroundColor, + mozilla::ErrorResult& aRv); + + already_AddRefed<Promise> GetSecurityInfo(ErrorResult& aRv); + + static already_AddRefed<WindowGlobalParent> CreateDisconnected( + const WindowGlobalInit& aInit, bool aInProcess = false); + + // 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 = Nothing()); + + 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; + + protected: + already_AddRefed<JSActor> InitJSActor(JS::HandleObject 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(nsIURI* aURI); + mozilla::ipc::IPCResult RecvUpdateDocumentPrincipal( + nsIPrincipal* aNewDocumentPrincipal); + 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) { + mIsInitialDocument = 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 RecvUpdateDocumentWouldPreloadResources(); + mozilla::ipc::IPCResult RecvSubmitLoadEventPreloadTelemetry( + TimeStamp aNavigationStart, TimeStamp aLoadEventStart, + TimeStamp aLoadEventEnd); + mozilla::ipc::IPCResult RecvSubmitTimeToFirstInteractionPreloadTelemetry( + uint32_t aMillis); + mozilla::ipc::IPCResult RecvSubmitLoadInputEventResponsePreloadTelemetry( + uint32_t aMillis); + + 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); + + private: + WindowGlobalParent(CanonicalBrowsingContext* aBrowsingContext, + uint64_t aInnerWindowId, uint64_t aOuterWindowId, + bool aInProcess, FieldValues&& aInit); + + ~WindowGlobalParent(); + + bool ShouldTrackSiteOriginTelemetry(); + void FinishAccumulatingPageUseCounters(); + + // NOTE: This document principal doesn't reflect possible |document.domain| + // mutations which may have been made in the actual document. + nsCOMPtr<nsIPrincipal> mDocumentPrincipal; + // The principal to use for the content blocking allow list. + nsCOMPtr<nsIPrincipal> mDocContentBlockingAllowListPrincipal; + nsCOMPtr<nsIURI> mDocumentURI; + nsString mDocumentTitle; + + 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(); + + nsDataHashtable<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; +}; + +} // 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..933443b775 --- /dev/null +++ b/dom/ipc/WindowGlobalTypes.ipdlh @@ -0,0 +1,38 @@ +/* -*- 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"; +using refcounted 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. + nsIPrincipal principal; + nsIURI documentURI; + + bool blockAllMixedContent; + bool upgradeInsecureRequests; + uint32_t sandboxFlags; + CookieJarSettingsArgs cookieJarSettings; + uint32_t httpsOnlyStatus; + nsITransportSecurityInfo securityInfo; +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/ipc/fuzztest/content_parent_ipc_libfuzz.cpp b/dom/ipc/fuzztest/content_parent_ipc_libfuzz.cpp new file mode 100644 index 0000000000..a506705702 --- /dev/null +++ b/dom/ipc/fuzztest/content_parent_ipc_libfuzz.cpp @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" + +#include "FuzzingInterface.h" +#include "ProtocolFuzzer.h" + +#include "mozilla/RefPtr.h" +#include "mozilla/devtools/PHeapSnapshotTempFileHelper.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/gfx/gfxVars.h" + +int FuzzingInitContentParentIPC(int* argc, char*** argv) { return 0; } + +static int RunContentParentIPCFuzzing(const uint8_t* data, size_t size) { + static mozilla::dom::ContentParent* p = + mozilla::ipc::ProtocolFuzzerHelper::CreateContentParent( + DEFAULT_REMOTE_TYPE); + + static nsTArray<nsCString> ignored = mozilla::ipc::LoadIPCMessageBlacklist( + getenv("MOZ_IPC_MESSAGE_FUZZ_BLACKLIST")); + + mozilla::ipc::FuzzProtocol(p, data, size, ignored); + + return 0; +} + +MOZ_FUZZING_INTERFACE_RAW(FuzzingInitContentParentIPC, + RunContentParentIPCFuzzing, ContentParentIPC); diff --git a/dom/ipc/fuzztest/moz.build b/dom/ipc/fuzztest/moz.build new file mode 100644 index 0000000000..831c9fd9a9 --- /dev/null +++ b/dom/ipc/fuzztest/moz.build @@ -0,0 +1,20 @@ +# -*- 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/. + +Library("FuzzingContentParentIPC") + +LOCAL_INCLUDES += [ + "/dom/base", +] + +SOURCES += ["content_parent_ipc_libfuzz.cpp"] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul-gtest" + +# Add libFuzzer configuration directives +include("/tools/fuzzing/libfuzzer-config.mozbuild") diff --git a/dom/ipc/jar.mn b/dom/ipc/jar.mn new file mode 100644 index 0000000000..8cc8826703 --- /dev/null +++ b/dom/ipc/jar.mn @@ -0,0 +1,9 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.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/test-ipc.xhtml (test.xhtml) + content/global/remote-test-ipc.js (remote-test.js) + 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..6af20323c3 --- /dev/null +++ b/dom/ipc/jsactor/JSActor.cpp @@ -0,0 +1,479 @@ +/* -*- 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/ClonedErrorHolder.h" +#include "mozilla/dom/ClonedErrorHolderBinding.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 "js/Promise.h" +#include "xpcprivate.h" +#include "nsFrameMessageManager.h" +#include "nsICrashReporter.h" + +namespace mozilla::dom { + +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_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 (auto& query : tmp->mPendingQueries) { + CycleCollectionNoteChild(cb, query.GetData().mPromise.get(), + "Pending Query Promise"); + } +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(JSActor) + +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. + nsDataHashtable<nsUint64HashKey, PendingQuery> pendingQueries; + mPendingQueries.SwapElements(pendingQueries); + for (auto& entry : pendingQueries) { + nsPrintfCString message( + "Actor '%s' destroyed before query '%s' was resolved", mName.get(), + NS_LossyConvertUTF16toASCII(entry.GetData().mMessageName).get()); + entry.GetData().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); +} + +/* static */ +bool JSActor::AllowMessage(const JSActorMessageMeta& aMetadata, + size_t aDataLength) { + // A message includes more than structured clone data, so subtract + // 20KB to make it more likely that a message within this bound won't + // result in an overly large IPC message. + static const size_t kMaxMessageSize = + IPC::Channel::kMaximumMessageSize - 20 * 1024; + if (aDataLength < kMaxMessageSize) { + return true; + } + + return false; +} + +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, ErrorResult& aRv) { + 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; + } + + 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) { + 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; + + mPendingQueries.Put(meta.queryId(), + PendingQuery{promise, meta.messageName()}); + + SendRawMessage(meta, std::move(data), CaptureJSStack(aCx), aRv); + 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); + 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); + + // 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.GetAndRemove(aMetadata.queryId()); + if (NS_WARN_IF(!query)) { + aRv.ThrowUnknownError("Received reply for non-pending query"); + return; + } + + 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) { + 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 (RefPtr<ClonedErrorHolder> ceh = + ClonedErrorHolder::Create(aCx, error, IgnoreErrors())) { + JS::RootedObject obj(aCx); + // Note: We can't use `ToJSValue` here because ClonedErrorHolder isn't + // wrapper cached. + if (ceh->WrapObject(aCx, nullptr, &obj)) { + value.setObject(*obj); + } else { + 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) { + 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); + } 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); + + 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..ee2e1da601 --- /dev/null +++ b/dom/ipc/jsactor/JSActor.h @@ -0,0 +1,178 @@ +/* -*- 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 "nsDataHashtable.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_SCRIPT_HOLDER_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, 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; + + // Check if a message is so large that IPC will probably crash if we try to + // send it. If it is too large, record telemetry about the message. + static bool AllowMessage(const JSActorMessageMeta& aMetadata, + size_t aDataLength); + + // 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) override; + + void ResolvedCallback(JSContext* aCx, + JS::Handle<JS::Value> aValue) 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; + nsDataHashtable<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..fda2cf7efc --- /dev/null +++ b/dom/ipc/jsactor/JSActorManager.cpp @@ -0,0 +1,225 @@ +/* -*- 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/JSActorService.h" +#include "mozilla/dom/PWindowGlobal.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/ScopeExit.h" +#include "mozJSComponentLoader.h" +#include "jsapi.h" +#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; + } + + bool isParent = nativeActor->GetSide() == mozilla::ipc::ParentSide; + auto& side = isParent ? protocol->Parent() : protocol->Child(); + + // We're about to construct the actor, so make sure we're in the JSM realm + // while importing etc. + JSAutoRealm ar(aCx, xpc::PrivilegedJunkScope()); + + // Load the module using mozJSComponentLoader. + RefPtr<mozJSComponentLoader> loader = mozJSComponentLoader::Get(); + MOZ_ASSERT(loader); + + // If a module URI was provided, use it to construct an instance of the actor. + JS::RootedObject actorObj(aCx); + if (side.mModuleURI) { + JS::RootedObject global(aCx); + JS::RootedObject exports(aCx); + aRv = loader->Import(aCx, side.mModuleURI.ref(), &global, &exports); + if (aRv.Failed()) { + return nullptr; + } + MOZ_ASSERT(exports, "null exports!"); + + // Load the specific property from our module. + JS::RootedValue ctor(aCx); + nsAutoCString ctorName(aName); + ctorName.Append(isParent ? "Parent"_ns : "Child"_ns); + 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.Put(aName, RefPtr{actor}); + return actor.forget(); +} + +#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); + if (error.Failed()) { + CHILD_DIAGNOSTIC_ASSERT(false, "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 (auto& entry : mJSActors) { + entry.GetData()->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. + nsRefPtrHashtable<nsCStringHashKey, JSActor> actors; + mJSActors.SwapElements(actors); + for (auto& entry : actors) { + CrashReporter::AutoAnnotateCrashReport autoActorName( + CrashReporter::Annotation::JSActorName, entry.GetData()->Name()); + entry.GetData()->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..80f4893b06 --- /dev/null +++ b/dom/ipc/jsactor/JSActorManager.h @@ -0,0 +1,94 @@ +/* -*- 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 it's 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); + + /** + * 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::HandleObject 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/JSActorService.cpp b/dom/ipc/jsactor/JSActorService.cpp new file mode 100644 index 0000000000..93b80b87c7 --- /dev/null +++ b/dom/ipc/jsactor/JSActorService.cpp @@ -0,0 +1,306 @@ +/* -*- 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()); + + auto entry = mWindowActorDescriptors.LookupForAdd(aName); + if (entry) { + aRv.ThrowNotSupportedError(nsPrintfCString( + "'%s' actor is already registered.", PromiseFlatCString(aName).get())); + return; + } + + // Insert a new entry for the protocol. + RefPtr<JSWindowActorProtocol> proto = + JSWindowActorProtocol::FromWebIDLOptions(aName, aOptions, aRv); + if (NS_WARN_IF(aRv.Failed())) { + entry.OrRemove(); + return; + } + + entry.OrInsert([&] { return proto; }); + + // 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 (auto& bp : cp->ManagedPBrowserParent()) { + for (auto& wgp : bp.GetKey()->ManagedPWindowGlobalParent()) { + managers.AppendElement( + static_cast<WindowGlobalParent*>(wgp.GetKey())); + } + } + } + + for (auto& wgp : + InProcessParent::Singleton()->ManagedPWindowGlobalParent()) { + managers.AppendElement(static_cast<WindowGlobalParent*>(wgp.GetKey())); + } + for (auto& wgc : + InProcessChild::Singleton()->ManagedPWindowGlobalChild()) { + managers.AppendElement(static_cast<WindowGlobalChild*>(wgc.GetKey())); + } + } else { + for (auto& bc : ContentChild::GetSingleton()->ManagedPBrowserChild()) { + for (auto& wgc : bc.GetKey()->ManagedPWindowGlobalChild()) { + managers.AppendElement(static_cast<WindowGlobalChild*>(wgc.GetKey())); + } + } + } + + 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.Put(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.Put(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 (auto iter = mWindowActorDescriptors.ConstIter(); !iter.Done(); + iter.Next()) { + aInfos.AppendElement(iter.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 (auto iter = mWindowActorDescriptors.Iter(); !iter.Done(); iter.Next()) { + iter.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()); + + auto entry = mProcessActorDescriptors.LookupForAdd(aName); + if (entry) { + aRv.ThrowNotSupportedError(nsPrintfCString( + "'%s' actor is already registered.", PromiseFlatCString(aName).get())); + return; + } + + // Insert a new entry for the protocol. + RefPtr<JSProcessActorProtocol> proto = + JSProcessActorProtocol::FromWebIDLOptions(aName, aOptions, aRv); + if (NS_WARN_IF(aRv.Failed())) { + entry.OrRemove(); + return; + } + + entry.OrInsert([&] { return proto; }); + + // 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 (auto iter = mProcessActorDescriptors.ConstIter(); !iter.Done(); + iter.Next()) { + aInfos.AppendElement(iter.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..610d0fc996 --- /dev/null +++ b/dom/ipc/jsactor/JSActorService.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_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 clsas for both `JSWindowActorProtocol` and `JSProcessActorProtocol` + * which can be used by generic code. + */ +class JSActorProtocol : public nsISupports { + public: + struct Sided { + Maybe<nsCString> mModuleURI; + }; + + virtual const Sided& Parent() const = 0; + virtual const Sided& Child() const = 0; +}; + +} // 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..be0d49743c --- /dev/null +++ b/dom/ipc/jsactor/JSProcessActorChild.cpp @@ -0,0 +1,105 @@ +/* -*- 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_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(JSProcessActorChild, JSActor) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +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; + } + + size_t length = 0; + if (aData) { + length += aData->DataLength(); + } + if (aStack) { + length += aStack->DataLength(); + } + if (NS_WARN_IF(!AllowMessage(aMeta, length))) { + aRv.ThrowDataCloneError( + nsPrintfCString("JSProcessActorChild serialization error: data too " + "large, in actor '%s'", + PromiseFlatCString(aMeta.actorName()).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. + 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->BuildClonedMessageDataForChild(contentChild, *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->BuildClonedMessageDataForChild(contentChild, *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..fc7df73751 --- /dev/null +++ b/dom/ipc/jsactor/JSProcessActorChild.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_JSProcessActorChild_h +#define mozilla_dom_JSProcessActorChild_h + +#include "mozilla/dom/JSActor.h" +#include "nsIDOMProcessChild.h" + +namespace mozilla { +namespace dom { + +// Placeholder implementation. +class JSProcessActorChild final : public JSActor { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_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 dom +} // namespace mozilla + +#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..4b7cc17105 --- /dev/null +++ b/dom/ipc/jsactor/JSProcessActorParent.cpp @@ -0,0 +1,109 @@ +/* -*- 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_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(JSProcessActorParent, JSActor) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +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; + } + + size_t length = 0; + if (aData) { + length += aData->DataLength(); + } + if (aStack) { + length += aStack->DataLength(); + } + if (NS_WARN_IF(!AllowMessage(aMeta, length))) { + aRv.ThrowDataError(nsPrintfCString( + "Actor '%s' cannot send message '%s': message too long.", + 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->BuildClonedMessageDataForParent(contentParent, *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->BuildClonedMessageDataForParent(contentParent, *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..239efbeb59 --- /dev/null +++ b/dom/ipc/jsactor/JSProcessActorParent.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_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_SCRIPT_HOLDER_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..58d0051210 --- /dev/null +++ b/dom/ipc/jsactor/JSProcessActorProtocol.cpp @@ -0,0 +1,169 @@ +/* -*- 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" + +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()); + + // Content processes aren't the parent process, so this flag is irrelevant and + // not propagated. + proto->mIncludeParent = false; + proto->mRemoteTypes = aInfo.remoteTypes().Clone(); + proto->mChild.mModuleURI = aInfo.url(); + proto->mChild.mObservers = aInfo.observers().Clone(); + + return proto.forget(); +} + +JSProcessActorInfo JSProcessActorProtocol::ToIPC() { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + + JSProcessActorInfo info; + info.name() = mName; + info.remoteTypes() = mRemoteTypes.Clone(); + info.url() = mChild.mModuleURI; + info.observers() = mChild.mObservers.Clone(); + 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); + proto->mIncludeParent = aOptions.mIncludeParent; + + if (aOptions.mRemoteTypes.WasPassed()) { + MOZ_ASSERT(aOptions.mRemoteTypes.Value().Length()); + proto->mRemoteTypes = aOptions.mRemoteTypes.Value(); + } + + if (aOptions.mParent.WasPassed()) { + proto->mParent.mModuleURI.emplace(aOptions.mParent.Value().mModuleURI); + } + if (aOptions.mChild.WasPassed()) { + proto->mChild.mModuleURI.emplace(aOptions.mChild.Value().mModuleURI); + } + + if (!aOptions.mChild.WasPassed() && !aOptions.mParent.WasPassed()) { + aRv.ThrowNotSupportedError( + "No point registering an actor with neither child nor parent " + "specifications."); + return nullptr; + } + + if (aOptions.mChild.WasPassed() && + aOptions.mChild.Value().mObservers.WasPassed()) { + proto->mChild.mObservers = aOptions.mChild.Value().mObservers.Value(); + } + + 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..2230283dcf --- /dev/null +++ b/dom/ipc/jsactor/JSProcessActorProtocol.h @@ -0,0 +1,76 @@ +/* -*- 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; + +/** + * 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; + + 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..2b533e296d --- /dev/null +++ b/dom/ipc/jsactor/JSWindowActorChild.cpp @@ -0,0 +1,155 @@ +/* -*- 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; } + +void JSWindowActorChild::Init(const nsACString& aName, + WindowGlobalChild* aManager) { + MOZ_ASSERT(!mManager, "Cannot Init() a JSWindowActorChild twice!"); + SetName(aName); + mManager = aManager; + + InvokeCallback(CallbackFunction::ActorCreated); +} + +void JSWindowActorChild::SendRawMessage( + const JSActorMessageMeta& aMeta, Maybe<ipc::StructuredCloneData>&& aData, + Maybe<ipc::StructuredCloneData>&& aStack, ErrorResult& aRv) { + if (NS_WARN_IF(!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; + } + + size_t length = 0; + if (aData) { + length += aData->DataLength(); + } + if (aStack) { + length += aStack->DataLength(); + } + + if (NS_WARN_IF(!AllowMessage(aMeta, length))) { + aRv.ThrowDataCloneError( + nsPrintfCString("JSWindowActorChild serialization error: data too " + "large, in actor '%s'", + PromiseFlatCString(aMeta.actorName()).get())); + return; + } + + // Cross-process case - send data over WindowGlobalChild to other side. + ContentChild* cc = ContentChild::GetSingleton(); + Maybe<ClonedMessageData> msgData; + if (aData) { + msgData.emplace(); + if (NS_WARN_IF(!aData->BuildClonedMessageDataForChild(cc, *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->BuildClonedMessageDataForChild(cc, *stackData)) { + stackData.reset(); + } + } + + if (NS_WARN_IF(!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_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(JSWindowActorChild, JSActor) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +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..73521c376a --- /dev/null +++ b/dom/ipc/jsactor/JSWindowActorChild.h @@ -0,0 +1,84 @@ +/* -*- 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 { +namespace dom { + +class JSWindowActorChild final : public JSActor { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_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; + 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 dom +} // namespace mozilla + +#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..66db461ae4 --- /dev/null +++ b/dom/ipc/jsactor/JSWindowActorParent.cpp @@ -0,0 +1,121 @@ +/* -*- 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; } + +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; + } + + size_t length = 0; + if (aData) { + length += aData->DataLength(); + } + if (aStack) { + length += aStack->DataLength(); + } + + if (NS_WARN_IF(!AllowMessage(aMeta, length))) { + aRv.ThrowDataCloneError( + nsPrintfCString("JSWindowActorParent serialization error: data too " + "large, in actor '%s'", + PromiseFlatCString(aMeta.actorName()).get())); + return; + } + + // Cross-process case - send data over WindowGlobalParent to other side. + RefPtr<BrowserParent> browserParent = mManager->GetBrowserParent(); + ContentParent* cp = browserParent->Manager(); + + Maybe<ClonedMessageData> msgData; + if (aData) { + msgData.emplace(); + if (NS_WARN_IF(!aData->BuildClonedMessageDataForParent(cp, *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->BuildClonedMessageDataForParent(cp, *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_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(JSWindowActorParent, JSActor) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +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..56d6fd3f7d --- /dev/null +++ b/dom/ipc/jsactor/JSWindowActorParent.h @@ -0,0 +1,68 @@ +/* -*- 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 { +namespace dom { + +class JSWindowActorParent final : public JSActor { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_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; + 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 dom +} // namespace mozilla + +#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..4d18b02116 --- /dev/null +++ b/dom/ipc/jsactor/JSWindowActorProtocol.cpp @@ -0,0 +1,388 @@ +/* -*- 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 "nsContentUtils.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, mURIMatcher) + +/* static */ already_AddRefed<JSWindowActorProtocol> +JSWindowActorProtocol::FromIPC(const JSWindowActorInfo& aInfo) { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess()); + + RefPtr<JSWindowActorProtocol> proto = new JSWindowActorProtocol(aInfo.name()); + // 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->mRemoteTypes = aInfo.remoteTypes().Clone(); + proto->mMessageManagerGroups = aInfo.messageManagerGroups().Clone(); + proto->mChild.mModuleURI = aInfo.url(); + + 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()); + } + } + + proto->mChild.mObservers = aInfo.observers().Clone(); + return proto.forget(); +} + +JSWindowActorInfo JSWindowActorProtocol::ToIPC() { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + + JSWindowActorInfo info; + info.name() = mName; + info.allFrames() = mAllFrames; + info.matches() = mMatches.Clone(); + info.remoteTypes() = mRemoteTypes.Clone(); + info.messageManagerGroups() = mMessageManagerGroups.Clone(); + info.url() = mChild.mModuleURI; + + 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()); + } + } + + info.observers() = mChild.mObservers.Clone(); + 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); + 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.mRemoteTypes.WasPassed()) { + MOZ_ASSERT(aOptions.mRemoteTypes.Value().Length()); + proto->mRemoteTypes = aOptions.mRemoteTypes.Value(); + } + + if (aOptions.mMessageManagerGroups.WasPassed()) { + proto->mMessageManagerGroups = aOptions.mMessageManagerGroups.Value(); + } + + if (aOptions.mParent.WasPassed()) { + proto->mParent.mModuleURI.emplace(aOptions.mParent.Value().mModuleURI); + } + if (aOptions.mChild.WasPassed()) { + proto->mChild.mModuleURI.emplace(aOptions.mChild.Value().mModuleURI); + } + + if (!aOptions.mChild.WasPassed() && !aOptions.mParent.WasPassed()) { + aRv.ThrowNotSupportedError( + "No point registering an actor with neither child nor parent " + "specifications."); + return nullptr; + } + + // 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()); + } + } + } + + if (aOptions.mChild.WasPassed() && + aOptions.mChild.Value().mObservers.WasPassed()) { + proto->mChild.mObservers = aOptions.mChild.Value().mObservers.Value(); + } + + 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; + } + + // 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 our event listener & call it. + JS::Rooted<JSObject*> global(jsapi.cx(), + 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", + /* 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::MatchPatternSet* JSWindowActorProtocol::GetURIMatcher() { + // If we've already created the pattern set, return it. + if (mURIMatcher || mMatches.IsEmpty()) { + return mURIMatcher; + } + + // Constructing the MatchPatternSet requires a JS environment to be run in. + // We can construct it here in the JSM scope, as we will be keeping it around. + AutoJSAPI jsapi; + MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope())); + GlobalObject global(jsapi.cx(), xpc::PrivilegedJunkScope()); + + nsTArray<OwningStringOrMatchPattern> patterns; + patterns.SetCapacity(mMatches.Length()); + for (nsString& s : mMatches) { + auto entry = patterns.AppendElement(); + entry->SetAsString() = s; + } + + MatchPatternOptions matchPatternOptions; + // Make MatchPattern's mSchemes create properly. + matchPatternOptions.mRestrictSchemes = false; + mURIMatcher = extensions::MatchPatternSet::Constructor( + global, patterns, matchPatternOptions, IgnoreErrors()); + 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::MatchPatternSet* 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..993e6b4e6e --- /dev/null +++ b/dom/ipc/jsactor/JSWindowActorProtocol.h @@ -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/. */ + +#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; + +/** + * 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; + }; + + 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::MatchPatternSet* 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; + + ParentSide mParent; + ChildSide mChild; + + RefPtr<extensions::MatchPatternSet> 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..caa1bfc88e --- /dev/null +++ b/dom/ipc/moz.build @@ -0,0 +1,247 @@ +# -*- 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"] + +XPIDL_SOURCES += [ + "nsIDOMProcessChild.idl", + "nsIDOMProcessParent.idl", + "nsIHangReport.idl", +] + +XPIDL_MODULE = "dom" + +EXTRA_JS_MODULES += [ + "ManifestMessagesChild.jsm", +] + +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", + "CoalescedWheelData.h", + "ContentChild.h", + "ContentParent.h", + "ContentProcess.h", + "ContentProcessManager.h", + "CSPMessageUtils.h", + "DocShellMessageUtils.h", + "EffectsInfo.h", + "FilePickerParent.h", + "InProcessChild.h", + "InProcessParent.h", + "MaybeDiscarded.h", + "MemoryReportRequest.h", + "NativeThreadId.h", + "PermissionMessageUtils.h", + "ProcessActor.h", + "PropertyBagUtils.h", + "ReferrerInfoUtils.h", + "RefMessageBodyService.h", + "RemoteBrowser.h", + "RemoteType.h", + "RemoteWebProgress.h", + "RemoteWebProgressRequest.h", + "SharedMessageBody.h", + "TabContext.h", + "TabMessageTypes.h", + "TabMessageUtils.h", + "URLClassifierChild.h", + "URLClassifierParent.h", + "UserActivationIPCUtils.h", + "VsyncChild.h", + "VsyncParent.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", + "CoalescedMouseData.cpp", + "CoalescedWheelData.cpp", + "ColorPickerParent.cpp", + "ContentParent.cpp", + "ContentProcess.cpp", + "ContentProcessManager.cpp", + "CSPMessageUtils.cpp", + "DocShellMessageUtils.cpp", + "FilePickerParent.cpp", + "InProcessImpl.cpp", + "MemMapSnapshot.cpp", + "MemoryReportRequest.cpp", + "MMPrinter.cpp", + "PermissionMessageUtils.cpp", + "PreallocatedProcessManager.cpp", + "ProcessActor.cpp", + "ProcessPriorityManager.cpp", + "PropertyBagUtils.cpp", + "ReferrerInfoUtils.cpp", + "RefMessageBodyService.cpp", + "RemoteBrowser.cpp", + "RemoteWebProgress.cpp", + "RemoteWebProgressRequest.cpp", + "SharedMap.cpp", + "SharedMessageBody.cpp", + "SharedStringMap.cpp", + "StructuredCloneData.cpp", + "TabContext.cpp", + "TabMessageUtils.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", + "VsyncChild.cpp", + "VsyncParent.cpp", +] + +PREPROCESSED_IPDL_SOURCES += [ + "PBrowser.ipdl", + "PBrowserBridge.ipdl", + "PContent.ipdl", +] + +IPDL_SOURCES += [ + "DOMTypes.ipdlh", + "MemoryReportTypes.ipdlh", + "PColorPicker.ipdl", + "PContentPermission.ipdlh", + "PContentPermissionRequest.ipdl", + "PCycleCollectWithLogs.ipdl", + "PFilePicker.ipdl", + "PInProcess.ipdl", + "PLoginReputation.ipdl", + "PPluginWidget.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/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["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.ini", + "tests/JSProcessActor/browser.ini", + "tests/JSWindowActor/browser.ini", +] + +MOCHITEST_CHROME_MANIFESTS += ["tests/chrome.ini"] +MOCHITEST_MANIFESTS += ["tests/mochitest.ini"] +XPCSHELL_TESTS_MANIFESTS += ["tests/xpcshell.ini"] + +CXXFLAGS += CONFIG["TK_CFLAGS"] + +if CONFIG["CC_TYPE"] in ("clang", "gcc"): + CXXFLAGS += ["-Wno-error=shadow"] + +if CONFIG["FUZZING"] and CONFIG["FUZZING_INTERFACES"]: + TEST_DIRS += ["fuzztest"] + +# 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..5c6e04fee9 --- /dev/null +++ b/dom/ipc/nsIDOMProcessChild.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 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); + + /** 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..a96f8eb3c0 --- /dev/null +++ b/dom/ipc/nsIDOMProcessParent.idl @@ -0,0 +1,57 @@ +/* -*- 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); + + /** 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..cf3ecdf984 --- /dev/null +++ b/dom/ipc/nsIHangReport.idl @@ -0,0 +1,76 @@ +/* -*- 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 +{ + const unsigned long SLOW_SCRIPT = 1; + const unsigned long PLUGIN_HANG = 2; + + // The type of hang being reported: SLOW_SCRIPT or PLUGIN_HANG. + readonly attribute unsigned long hangType; + + // For SLOW_SCRIPT reports, these fields contain information about the + // slow script. + // Only valid for SLOW_SCRIPT reports. + readonly attribute Element scriptBrowser; + readonly attribute ACString scriptFileName; + // Duration of the hang so far. + readonly attribute double hangDuration; + readonly attribute AString addonId; + + // For PLUGIN_HANGs, this field contains information about the plugin. + // Only valid for PLUGIN_HANG reports. + readonly attribute ACString pluginName; + + // 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. + // Only valid for SLOW_SCRIPT reports. + void terminateScript(); + + // Terminate all scripts on the global that triggered the slow script + // warning. + // Only valid for SLOW_SCRIPT reports. + void terminateGlobal(); + + // Terminate the plugin if it is still hung. + // Only valid for PLUGIN_HANG reports. + void terminatePlugin(); + + // Ask the content process to start up the slow script debugger. + // Only valid for SLOW_SCRIPT reports. + 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. + // Only valid for SLOW_SCRIPT reports. + void endStartingDebugger(); + + // Inquire whether the report is for a content process loaded by the given + // frameloader. + bool isReportForBrowser(in FrameLoader aFrameLoader); +}; diff --git a/dom/ipc/remote-test.js b/dom/ipc/remote-test.js new file mode 100644 index 0000000000..395aef8e47 --- /dev/null +++ b/dom/ipc/remote-test.js @@ -0,0 +1,55 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* eslint-env mozilla/frame-script */ + +dump("Loading remote script!\n"); +dump(content + "\n"); + +var cpm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(); +cpm.addMessageListener("cpm-async", function(m) { + cpm.sendSyncMessage("ppm-sync"); + dump(content.document.documentElement); + cpm.sendAsyncMessage("ppm-async"); +}); + +var dshell = content.docShell.sameTypeRootTreeItem.QueryInterface( + Ci.nsIDocShell +); + +addEventListener( + "click", + function(e) { + dump(e.target + "\n"); + if ( + ChromeUtils.getClassName(e.target) === "HTMLAnchorElement" && + dshell == docShell + ) { + var retval = docShell.messageManager.sendSyncMessage("linkclick", { + href: e.target.href, + }); + dump(uneval(retval[0]) + "\n"); + // Test here also that both retvals are the same + sendAsyncMessage( + "linkclick-reply-object", + uneval(retval[0]) == uneval(retval[1]) ? retval[0] : "" + ); + } + }, + true +); + +addMessageListener("chrome-message", function(m) { + dump(uneval(m.json) + "\n"); + sendAsyncMessage("chrome-message-reply", m.json); +}); + +addMessageListener("speed-test-start", function(m) { + // eslint-disable-next-line no-empty + while (sendSyncMessage("speed-test")[0].message != "done") {} +}); + +addMessageListener("async-echo", function(m) { + sendAsyncMessage(m.name); +}); diff --git a/dom/ipc/test.xhtml b/dom/ipc/test.xhtml new file mode 100644 index 0000000000..6f6cea9c7b --- /dev/null +++ b/dom/ipc/test.xhtml @@ -0,0 +1,259 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + width="800" height="800" orient="vertical" onload="initRemoteFrameScript();"> + <script> + + function dumpClientRect(r) { + dump(r.left + "," + r.top + "," + r.right + "," + + r.bottom + "," + r.width + "," + r.height + "\n"); + } + + function handleMozAfterPaint(e) { + return; + dump(e.type + "\n") + var rects = e.clientRects; + var i; + dump("\tclientRects:\n"); + for (i = 0; i < rects.length; ++i) { + var r = rects.item(i); + dump("\t\t"); + dumpClientRect(rects.item(i)); + } + + dump("\tboundingClientRect\n\t\t"); + dumpClientRect(e.boundingClientRect); + + var paintRequests = e.paintRequests; + dump("\tpaintRequests\n"); + for (i = 0; i < paintRequests.length; ++i) { + var pr = paintRequests.item(i); + dump("\t\t"); + dumpClientRect(pr.clientRect); + if (pr.reason) + dump("\t\t" + pr.reason + "\n"); + } + } + + function handleMozScrolledAreaChanged(e) { + return; + dump(e.type + "\n"); + dump("\t" + e.x + "," + e.y + "," + e.width + "," + e.height + "\n"); + } + + function restart() { + var y = document.getElementById('page'); + var p = y.parentNode; + p.removeChild(y); + p.appendChild(y); + + var fl = y.frameLoader; + fl.activateFrameEvent("MozAfterPaint", true); + fl.activateFrameEvent("MozScrolledAreaChanged", true); + y.addEventListener("MozAfterPaint", handleMozAfterPaint, true); + y.addEventListener("MozScrolledAreaChanged", handleMozScrolledAreaChanged, true); + } + + function loadURL(url) { + document.getElementById('page').setAttribute('src', url); + } + + function randomClick() { + // First focus the remote frame, then dispatch click. This way remote frame gets focus before + // mouse event. + document.getElementById('page').focus(); + var frameLoader = document.getElementById('page').frameLoader; + var x = parseInt(Math.random() * 100); + var y = parseInt(Math.random() * 100); + frameLoader.sendCrossProcessMouseEvent("mousedown", x, y, 0, 1, 0); + frameLoader.sendCrossProcessMouseEvent("mouseup", x, y, 0, 1, 0); + } + + function openWindow() { + window.open('chrome://global/content/test-ipc.xhtml', '_blank', 'chrome,resizable,width=800,height=800'); + } + + function closeWindow() { + window.close(); + } + + function initRemoteFrameScript() { + // 1. Test that loading a script works, and that accessing process level mm and + // global mm works. + var ppm = Components.classes["@mozilla.org/parentprocessmessagemanager;1"] + .getService(); + var gm = Components.classes["@mozilla.org/globalmessagemanager;1"] + .getService(); + var cpm = Components.classes["@mozilla.org/childprocessmessagemanager;1"] + .getService(); + + if (ppm.childCount != 2) { + alert("Should have two child processes!"); + } + var childprocessmm = ppm.getChildAt(1); // 0 is the in-process child process mm + + childprocessmm.addMessageListener("ppm-sync", + function(m) { + if (m.target != childprocessmm) alert("Wrong target!"); + document.getElementById("messageLog").value += "[SYNC1 PPM]"; + } + ); + + ppm.addMessageListener("ppm-sync", + function(m) { + // Check that global process message manager gets the per-process mm as target. + if (m.target != childprocessmm) alert("Wrong target!"); + document.getElementById("messageLog").value += "[SYNC2 PPM]"; + } + ); + childprocessmm.addMessageListener("ppm-async", + function(m) { + if (m.target != childprocessmm) alert("Wrong target!"); + document.getElementById("messageLog").value += "[ASYNC1 PPM]"; + } + ); + ppm.addMessageListener("ppm-async", + function(m) { + // Check that global process message manager gets the per-process mm as target. + if (m.target != childprocessmm) alert("Wrong target!"); + document.getElementById("messageLog").value += "[ASYNC2 PPM]"; + } + ); + messageManager.loadFrameScript("chrome://global/content/remote-test-ipc.js", true); + ppm.sendAsyncMessage("cpm-async"); + + // 2. Test that adding message listener works, and that receiving a sync message works. + messageManager.addMessageListener("linkclick", + function(m) { + // This checks that json sending works in sync messages. + document.getElementById("messageLog").value = m.name + ": " + m.json.href; + return { message: "linkclick-received" }; + }); + + // 3. Test that returning multiple json results works. + messageManager.addMessageListener("linkclick", + function(m) { + return { message: "linkclick-received" }; + }); + + // 4. Test that receiving an async message works. + // Test also that sending async message to content works. + // Test also that { receiveMessage: function(m) {} } works. + messageManager.addMessageListener("linkclick-reply-object", + { foobarObjectVar: true, + receiveMessage: function(m) { + var s = (m.json.message == "linkclick-received") && + (this.foobarObjectVar) ? "PASS" : "FAIL"; + messageManager.broadcastAsyncMessage("chrome-message", { ok : s } ); + } + } + ); + + // 5. Final test to check that everything went ok. + messageManager.addMessageListener("chrome-message-reply", + function(m) { + // Check that 'this' and .target values are handled correctly + if (m.target == document.getElementById("page") && + this == messageManager) { + // Check that the message properties are enumerable. + var hasName = false; + var hasSync = false; + var hasJSON = false; + for (i in m) { + if (i == "name") { + hasName = true; + } else if (i == "sync") { + hasSync = true; + } else if (i == "json") { + hasJSON = true; + } + } + if (hasName && hasSync && hasJSON) { + document.getElementById("messageLog").value += ", " + m.json.ok; + } + } + }); + } + + var speedTestStartTime = 0; + var speedTestCount = 0; + function messageSpeed() { + speedTestCount = 0; + messageManager.addMessageListener("speed-test", speedHandler); + messageManager.broadcastAsyncMessage("speed-test-start"); + } + + function speedHandler() { + if (!speedTestCount) { + speedTestStartTime = new Date().getTime(); + } + if (++speedTestCount == 1000) { + setTimeout("alert('" + speedTestCount + " in " + (new Date().getTime() - speedTestStartTime) + "ms')", 0); + return { message: "done" }; + } + return { message: "continue" }; + } + + var addRemoveTestCount = 0; + + function echoListener() { + if (++addRemoveTestCount == 1) { + alert("Expected echo message"); + messageManager.removeMessageListener("async-echo", echoListener); + messageManager.broadcastAsyncMessage("async-echo"); + return; + } + alert("Unexpected echo message"); + } + + function listenerAddRemove() { + addRemoveTestCount = 0; + messageManager.addMessageListener("async-echo", echoListener); + messageManager.broadcastAsyncMessage("async-echo"); + } + + var MozAfterPaintCount = 0; + function enableMozAfterPaint() { + messageManager.addMessageListener("MozAfterPaint", + function(m) { + document.getElementById("messageLog").value = m.name + "[" + (++MozAfterPaintCount) + "]"; + }); + messageManager.loadFrameScript("data:,addEventListener('MozAfterPaint', function(e) { sendAsyncMessage('MozAfterPaint'); },true);", false); + } + + Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + </script> + + <toolbar id="controls"> + <toolbarbutton label="Back"/> + <toolbarbutton label="Forward"/> + <html:input onchange="loadURL(this.value)" style="-moz-box-flex: 1" id="URL"/> + </toolbar> + <toolbar> + <toolbarbutton onclick="restart()" label="Recover"/> + <toolbarbutton onclick="randomClick()" label="random click"/> + <toolbarbutton onclick="messageSpeed()" label="test message handling speed"/> + <toolbarbutton onclick="listenerAddRemove()" label="test listener add/remove"/> + <toolbarbutton onclick="enableMozAfterPaint()" label="MozAfterPaint"/> + <toolbarbutton onclick="openWindow()" label="open new window"/> + <toolbarbutton onclick="closeWindow()" label="close this window"/> + </toolbar> + <toolbar><label value="Load a script (URL) to content process:"/> + <html:input style="-moz-box-flex: 1" id="script"/><button + label="send" oncommand="document.getElementById('page') + .frameLoader.messageManager + .loadFrameScript(this.previousSibling.value, false);"/> + </toolbar> + <toolbar> + <label value="Eval script in chrome context"/> + <html:input style="-moz-box-flex: 1"/><button label="run" oncommand="eval(this.previousSibling.value); // eslint-disable-line no-eval"/> + </toolbar> + + <browser type="content" src="http://www.google.com/" flex="1" id="page" remote="true"/> + <label id="messageLog" value="" crop="center"/> +</window> diff --git a/dom/ipc/tests/.eslintrc.js b/dom/ipc/tests/.eslintrc.js new file mode 100644 index 0000000000..4e9d6a74a8 --- /dev/null +++ b/dom/ipc/tests/.eslintrc.js @@ -0,0 +1,9 @@ +"use strict"; + +module.exports = { + extends: [ + "plugin:mozilla/browser-test", + "plugin:mozilla/mochitest-test", + "plugin:mozilla/xpcshell-test", + ], +}; diff --git a/dom/ipc/tests/JSProcessActor/browser.ini b/dom/ipc/tests/JSProcessActor/browser.ini new file mode 100644 index 0000000000..c3d8645a33 --- /dev/null +++ b/dom/ipc/tests/JSProcessActor/browser.ini @@ -0,0 +1,11 @@ +[DEFAULT] +support-files = + head.js + +[browser_getActor.js] +[browser_getActor_filter.js] +[browser_observer_notification.js] +[browser_registerProcessActor.js] +[browser_sendAsyncMessage.js] +[browser_sendQuery.js] + diff --git a/dom/ipc/tests/JSProcessActor/browser_getActor.js b/dom/ipc/tests/JSProcessActor/browser_getActor.js new file mode 100644 index 0000000000..e972eaaac9 --- /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..3d5c620d16 --- /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..dfe92ad240 --- /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..e57f762744 --- /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() { + SimpleTest.doesThrow( + () => + ChromeUtils.registerContentActor( + "TestProcessActor", + processActorOptions + ), + "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..f81d4ddbee --- /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..69fe881367 --- /dev/null +++ b/dom/ipc/tests/JSProcessActor/browser_sendQuery.js @@ -0,0 +1,82 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +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) { + 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.jsm:33:31\n" + + asyncStack, + "Error should have the correct stack" + ); + }, +}); + +declTest("sendQuery Exception", { + async test(browser) { + 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.jsm:36:22\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/head.js b/dom/ipc/tests/JSProcessActor/head.js new file mode 100644 index 0000000000..86f35f4c57 --- /dev/null +++ b/dom/ipc/tests/JSProcessActor/head.js @@ -0,0 +1,66 @@ +/* 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 = { + parent: { + moduleURI: "resource://testing-common/TestProcessActorParent.jsm", + }, + child: { + moduleURI: "resource://testing-common/TestProcessActorChild.jsm", + observers: ["test-js-content-actor-child-observer"], + }, +}; + +function promiseNotification(aNotification) { + const { Services } = ChromeUtils.import( + "resource://gre/modules/Services.jsm" + ); + 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) { + let { url = "about:blank", includeParent = false, remoteTypes, test } = cfg; + + // Build the actor options object which will be used to register & unregister + // our process actor. + let actorOptions = { + parent: Object.assign({}, processActorOptions.parent), + child: Object.assign({}, processActorOptions.child), + }; + actorOptions.includeParent = includeParent; + if (remoteTypes !== undefined) { + actorOptions.remoteTypes = remoteTypes; + } + + // 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)); + }); + } 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.ini b/dom/ipc/tests/JSWindowActor/browser.ini new file mode 100644 index 0000000000..fe07d9a97d --- /dev/null +++ b/dom/ipc/tests/JSWindowActor/browser.ini @@ -0,0 +1,19 @@ +[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] +[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] diff --git a/dom/ipc/tests/JSWindowActor/browser_contentWindow.js b/dom/ipc/tests/JSWindowActor/browser_contentWindow.js new file mode 100644 index 0000000000..cfdc5b114f --- /dev/null +++ b/dom/ipc/tests/JSWindowActor/browser_contentWindow.js @@ -0,0 +1,47 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +declTest("contentWindow null when inner window inactive", { + matches: [TEST_URL + "*"], + url: TEST_URL + "?1", + + async test(browser) { + { + let parent = browser.browsingContext.currentWindowGlobal; + let actorParent = parent.getActor("TestWindow"); + + await actorParent.sendQuery("storeActor"); + } + + { + let url = TEST_URL + "?2"; + let loaded = BrowserTestUtils.browserLoaded(browser, false, url); + await BrowserTestUtils.loadURI(browser, url); + await loaded; + } + + let parent = browser.browsingContext.currentWindowGlobal; + let actorParent = parent.getActor("TestWindow"); + + let result = await actorParent.sendQuery("checkActor"); + if (SpecialPowers.useRemoteSubframes) { + is( + result.status, + "error", + "Should get an error when bfcache is disabled for Fission" + ); + is( + result.errorType, + "InvalidStateError", + "Should get an InvalidStateError without bfcache" + ); + } else { + 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..a1ef293cc0 --- /dev/null +++ b/dom/ipc/tests/JSWindowActor/browser_crash_report.js @@ -0,0 +1,112 @@ +/* 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..25feb47179 --- /dev/null +++ b/dom/ipc/tests/JSWindowActor/browser_destroy_callbacks.js @@ -0,0 +1,194 @@ +/* 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..1874a0a174 --- /dev/null +++ b/dom/ipc/tests/JSWindowActor/browser_event_listener.js @@ -0,0 +1,43 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +declTest("test event triggering actor creation", { + async test(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>`; + }); + + // Wait for the observer notification. + let observePromise = new Promise(resolve => { + const TOPIC = "test-js-window-actor-parent-event"; + Services.obs.addObserver(function obs(subject, topic, data) { + is(topic, TOPIC, "topic matches"); + + Services.obs.removeObserver(obs, TOPIC); + resolve({ subject, data }); + }, TOPIC); + }); + + // Click on the select to show the dropdown. + await BrowserTestUtils.synthesizeMouseAtCenter("#testSelect", {}, 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" + ); + }, +}); diff --git a/dom/ipc/tests/JSWindowActor/browser_getActor.js b/dom/ipc/tests/JSWindowActor/browser_getActor.js new file mode 100644 index 0000000000..53205fc7d9 --- /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..7ee938dddb --- /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..ae8c40f781 --- /dev/null +++ b/dom/ipc/tests/JSWindowActor/browser_observer_notification.js @@ -0,0 +1,111 @@ +/* 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-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", { + 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", { + 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", { + 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..95e1a0c422 --- /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..838fa653b4 --- /dev/null +++ b/dom/ipc/tests/JSWindowActor/browser_registerWindowActor.js @@ -0,0 +1,12 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +declTest("double register", { + async test() { + SimpleTest.doesThrow( + () => ChromeUtils.registerWindowActor("TestWindow", windowActorOptions), + "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..077732c45e --- /dev/null +++ b/dom/ipc/tests/JSWindowActor/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; + 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; + }); + }, +}); diff --git a/dom/ipc/tests/JSWindowActor/browser_sendQuery.js b/dom/ipc/tests/JSWindowActor/browser_sendQuery.js new file mode 100644 index 0000000000..149744a2fc --- /dev/null +++ b/dom/ipc/tests/JSWindowActor/browser_sendQuery.js @@ -0,0 +1,117 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +const ERROR_LINE_NUMBER = 41; +const EXCEPTION_LINE_NUMBER = ERROR_LINE_NUMBER + 3; + +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) { + 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.jsm:${ERROR_LINE_NUMBER}:31\n` + + asyncStack, + "Error should have the correct stack" + ); + }, +}); + +declTest("sendQuery Exception", { + async test(browser) { + 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.jsm:${EXCEPTION_LINE_NUMBER}:22\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/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..a1a98f66e0 --- /dev/null +++ b/dom/ipc/tests/JSWindowActor/head.js @@ -0,0 +1,71 @@ +/* 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 = { + parent: { + moduleURI: "resource://testing-common/TestWindowParent.jsm", + }, + child: { + moduleURI: "resource://testing-common/TestWindowChild.jsm", + + events: { + mozshowdropdown: {}, + }, + + observers: ["test-js-window-actor-child-observer", "audio-playback"], + }, +}; + +function declTest(name, cfg) { + let { + url = "about:blank", + allFrames = false, + includeChrome = false, + matches, + remoteTypes, + messageManagerGroups, + test, + } = cfg; + + // Build the actor options object which will be used to register & unregister + // our window actor. + let actorOptions = { + parent: Object.assign({}, windowActorOptions.parent), + child: Object.assign({}, windowActorOptions.child), + }; + 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)); + }); + } 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..cf50371c8a --- /dev/null +++ b/dom/ipc/tests/blob_verify.sjs @@ -0,0 +1,20 @@ +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 = []; + 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.ini b/dom/ipc/tests/browser.ini new file mode 100644 index 0000000000..aa60226764 --- /dev/null +++ b/dom/ipc/tests/browser.ini @@ -0,0 +1,22 @@ +[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] +skip-if = os != "win" # The Process Priority Manager is only enabled for Windows so far. Bug 1522879. +[browser_crash_oopiframe.js] +skip-if = !fission || !crashreporter || verify +[browser_domainPolicy.js] +[browser_memory_distribution_telemetry.js] +skip-if = true || !e10s # This is an e10s only probe, but the test is currently broken. See Bug 1449991 +[browser_cancel_content_js.js] +skip-if = !e10s +[browser_bug1646088.js] +support-files = file_dummy.html +skip-if = !e10s diff --git a/dom/ipc/tests/browser_CrashService_crash.js b/dom/ipc/tests/browser_CrashService_crash.js new file mode 100644 index 0000000000..9504d68ae7 --- /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.jsm). + +/* 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.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..b74b2b84cd --- /dev/null +++ b/dom/ipc/tests/browser_ProcessPriorityManager.js @@ -0,0 +1,520 @@ +/* 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; + +/** + * 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.priorityMap = new WeakMap(); + this.priorityMap.set( + this.tabbrowser.selectedBrowser, + PROCESS_PRIORITY_FOREGROUND + ); + this.noChangeBrowsers = new WeakMap(); + 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.window = null; + } + + /** + * Returns a Promise that resolves when a particular <browser> + * has its content process reach a particular priority. Will + * eventually time out if that priority is never reached. + * + * @param browser (<browser>) + * The <browser> that we expect to change priority. + * @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(browser, expectedPriority) { + return TestUtils.waitForCondition(() => { + let currentPriority = this.priorityMap.get(browser); + if (currentPriority == expectedPriority) { + Assert.ok( + true, + `Browser at ${browser.currentURI.spec} reached expected ` + + `priority: ${currentPriority}` + ); + return true; + } + return false; + }, `Waiting for browser at ${browser.currentURI.spec} to reach priority ` + expectedPriority); + } + + /** + * Returns a Promise that resolves after a duration of + * WAIT_FOR_CHANGE_TIME_MS. During that time, if the passed browser + * changes priority, a test failure will be registered. + * + * @param browser (<browser>) + * The <browser> that we expect to change priority. + * @return Promise + * @resolves undefined + * Once the WAIT_FOR_CHANGE_TIME_MS duration has passed. + */ + async ensureNoPriorityChange(browser) { + this.noChangeBrowsers.set(browser, null); + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(resolve => setTimeout(resolve, WAIT_FOR_CHANGE_TIME_MS)); + let priority = this.noChangeBrowsers.get(browser); + Assert.equal( + priority, + null, + `Should have seen no process priority change for a browser at ${browser.currentURI.spec}` + ); + this.noChangeBrowsers.delete(browser); + } + + /** + * Makes sure that a particular foreground browser has been + * registered in the priority map. This is needed because browsers are + * only registered when their priorities change - and if a browser's + * priority never changes during a test, then they wouldn't be registered. + * + * The passed browser must be a foreground browser, since it's assumed that + * the associated content process is running with foreground priority. + * + * @param browser (browser) + * A _foreground_ browser. + */ + ensureForegroundRegistered(browser) { + if (!this.priorityMap.has(browser)) { + this.priorityMap.set(browser, PROCESS_PRIORITY_FOREGROUND); + } + } + + /** + * Synchronously returns the priority of a particular browser's + * content process. + * + * @param browser (browser) + * The browser to get the content process priority for. + * @return String + * The priority that the browser's content process is at. + */ + currentPriority(browser) { + return this.priorityMap.get(browser); + } + + /** + * 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); + for (let browser of this.tabbrowser.browsers) { + if (browser.frameLoader.childID == childID) { + info( + `Browser at: ${browser.currentURI.spec} transitioning to ${priority}` + ); + if (this.noChangeBrowsers.has(browser)) { + this.noChangeBrowsers.set(browser, priority); + } + this.priorityMap.set(browser, priority); + } + } + } +} + +let gTabPriorityWatcher; + +add_task(async function setup() { + // 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." + ); + + gTabPriorityWatcher.ensureForegroundRegistered(fromBrowser); + + let fromPromise; + if ( + gTabPriorityWatcher.currentPriority(fromBrowser) == fromTabExpectedPriority + ) { + fromPromise = gTabPriorityWatcher.ensureNoPriorityChange(fromBrowser); + } else { + fromPromise = gTabPriorityWatcher.waitForPriorityChange( + fromBrowser, + fromTabExpectedPriority + ); + } + + let toPromise; + if ( + gTabPriorityWatcher.currentPriority(toBrowser) == + PROCESS_PRIORITY_FOREGROUND + ) { + toPromise = gTabPriorityWatcher.ensureNoPriorityChange(toBrowser); + } else { + toPromise = gTabPriorityWatcher.waitForPriorityChange( + 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. + */ +add_task(async function test_normal_background_tab() { + let originalTab = gBrowser.selectedTab; + + await BrowserTestUtils.withNewTab("http://example.com", 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, + }); + }); +}); + +/** + * 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("http://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. + await SpecialPowers.spawn(browser, [], async () => { + let video = content.document.createElement("video"); + video.src = "http://mochi.test:8888/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(); + }); + + 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. + await 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("http://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 = "http://mochi.test:8888/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("http://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, + }); + }); +}); diff --git a/dom/ipc/tests/browser_bug1646088.js b/dom/ipc/tests/browser_bug1646088.js new file mode 100644 index 0000000000..4466c67129 --- /dev/null +++ b/dom/ipc/tests/browser_bug1646088.js @@ -0,0 +1,70 @@ +const { PromiseUtils } = ChromeUtils.import( + "resource://gre/modules/PromiseUtils.jsm" +); + +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 = PromiseUtils.defer(); + let finishSwitch = PromiseUtils.defer(); + 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. We do this from the content process to make sure the frontend + // has no chance to trigger an eager process switch. + info("Beginning process switch into file URI process"); + let browserLoaded = BrowserTestUtils.browserLoaded(browser); + await SpecialPowers.spawn(browser, [uriString], uri => { + content.location = uri; + }); + 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 = PromiseUtils.defer(); + 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_cancel_content_js.js b/dom/ipc/tests/browser_cancel_content_js.js new file mode 100644 index 0000000000..d319de87e4 --- /dev/null +++ b/dom/ipc/tests/browser_cancel_content_js.js @@ -0,0 +1,68 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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, cancelContentJSPref, shouldCancel) { + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.ipc.cancel_content_js_when_navigating", cancelContentJSPref], + ["dom.max_script_run_time", 20], + ], + }); + 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} with cancel content JS ${ + cancelContentJSPref ? "enabled" : "disabled" + }` + ); + const nextPageLoaded = BrowserTestUtils.waitForContentEvent( + tab.linkedBrowser, + "DOMTitleChanged" + ); + BrowserTestUtils.loadURI(gBrowser, nextPage); + + const result = await Promise.race([ + nextPageLoaded, + loopEnded.then(() => "timeout"), + ]); + + const timedOut = result === "timeout"; + if (shouldCancel) { + ok(timedOut === false, "expected next page to be loaded"); + } else { + ok(timedOut === true, "expected timeout"); + } + + BrowserTestUtils.removeTab(tab); +} + +add_task(async () => test_navigation(NEXT_PAGE, true, true)); +add_task(async () => test_navigation(NEXT_PAGE, false, false)); +add_task(async () => test_navigation(JS_URI, true, false)); +add_task(async () => test_navigation(JS_URI, false, false)); diff --git a/dom/ipc/tests/browser_crash_oopiframe.js b/dom/ipc/tests/browser_crash_oopiframe.js new file mode 100644 index 0000000000..2cf133e5e3 --- /dev/null +++ b/dom/ipc/tests/browser_crash_oopiframe.js @@ -0,0 +1,167 @@ +"use strict"; + +/** + * 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 browser, rootBC, iframeBC; + + for (let count = 0; count < numTabs; count++) { + let tab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + url: "about:blank", + }); + + browser = tab.linkedBrowser; + rootBC = browser.browsingContext; + + // 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(browser, [], 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; + }); + } + + 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"); + ok(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" + ); + } + + // 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.querySelectorAll(".notification-button"); + is( + buttons.length, + 2, + "Notification " + count + " should have only two buttons." + ); + } + + // Press the ignore button on the visible notification. + let notificationBox = gBrowser.getNotificationBox(gBrowser.selectedBrowser); + let notification = notificationBox.currentNotification; + notification.dismiss(); + + for (let count = 1; count <= numTabs; count++) { + let nb = gBrowser.getNotificationBox(gBrowser.browsers[count]); + + await TestUtils.waitForCondition( + () => !nb.currentNotification, + "notification closed" + ); + + ok( + !nb.currentNotification, + "notification " + count + " closed when dismiss button is pressed" + ); + } + + 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() { + // 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); +}); diff --git a/dom/ipc/tests/browser_domainPolicy.js b/dom/ipc/tests/browser_domainPolicy.js new file mode 100644 index 0000000000..cec360f2e1 --- /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_task(async function setup() { + 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_memory_distribution_telemetry.js b/dom/ipc/tests/browser_memory_distribution_telemetry.js new file mode 100644 index 0000000000..bec51fa1cd --- /dev/null +++ b/dom/ipc/tests/browser_memory_distribution_telemetry.js @@ -0,0 +1,92 @@ +"use strict"; + +var session = ChromeUtils.import( + "resource://gre/modules/TelemetrySession.jsm", + null +); + +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"); + }); + + session.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]; + ok( + fewTabsSnapshot.sum > 0, + "Zero difference between all the content processes is unlikely, what happened?" + ); + ok( + 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/chrome.ini b/dom/ipc/tests/chrome.ini new file mode 100644 index 0000000000..206f0a1501 --- /dev/null +++ b/dom/ipc/tests/chrome.ini @@ -0,0 +1,11 @@ +[DEFAULT] +skip-if = os == 'android' +support-files = + process_error.xhtml + +[test_process_error.xhtml] +skip-if = !crashreporter + + +[test_process_error_oom.xhtml] +skip-if = !crashreporter 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_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..1a481c4163 --- /dev/null +++ b/dom/ipc/tests/file_dummy.html @@ -0,0 +1,4 @@ +<!doctype html> +<body> + <h1>This is a dummy file</h1> +</body> diff --git a/dom/ipc/tests/mochitest.ini b/dom/ipc/tests/mochitest.ini new file mode 100644 index 0000000000..263a1cfb1f --- /dev/null +++ b/dom/ipc/tests/mochitest.ini @@ -0,0 +1,12 @@ +[DEFAULT] + +[test_temporaryfile_stream.html] +skip-if = !e10s || toolkit == 'android' || (os == "win" && processor == "aarch64") # Bug 1525959, aarch64 due to 1531150 +support-files = + blob_verify.sjs + !/dom/canvas/test/captureStream_common.js +[test_Preallocated.html] +skip-if = !e10s || toolkit == 'android' || tsan # Bug 1525959. tsan: Bug 1683730 +[test_window_open_discarded_bc.html] +skip-if = toolkit == 'android' +[test_bcg_processes.html] diff --git a/dom/ipc/tests/process_error.xhtml b/dom/ipc/tests/process_error.xhtml new file mode 100644 index 0000000000..7f0a5e20dd --- /dev/null +++ b/dom/ipc/tests/process_error.xhtml @@ -0,0 +1,63 @@ +<?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 {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); + const {BrowserTestUtils} = ChromeUtils.import("resource://testing-common/BrowserTestUtils.jsm"); + + 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"); + + // Let's check whether we have correctly reported OOM. + var isLikelyOOM = subject.getPropertyAsBool('isLikelyOOM'); + is(isLikelyOOM, crashType == 'CRASH_OOM', 'isLikelyOOM is correct'); + } + + 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..5d2b2bd3a6 --- /dev/null +++ b/dom/ipc/tests/test_Preallocated.html @@ -0,0 +1,52 @@ +<!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"; +/* eslint-env mozilla/frame-script */ + +SimpleTest.waitForExplicitFinish(); + +function expectProcessCreated() { + return new Promise(resolve => { + function parentExpectProcessCreated() { + const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); + 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..8109b59a42 --- /dev/null +++ b/dom/ipc/tests/test_bcg_processes.html @@ -0,0 +1,46 @@ +<!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"; +/* eslint-env mozilla/frame-script */ + +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..e3498320ee --- /dev/null +++ b/dom/ipc/tests/test_blob_sliced_from_child_process.js @@ -0,0 +1,140 @@ +"use strict"; +/* eslint-env mozilla/frame-script */ + +const { ExtensionTestUtils } = ChromeUtils.import( + "resource://testing-common/ExtensionXPCShellUtils.jsm" +); + +ExtensionTestUtils.init(this); + +function childFrameScript() { + "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 ExtensionTestUtils.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..4f3b10d7a2 --- /dev/null +++ b/dom/ipc/tests/test_blob_sliced_from_parent_process.js @@ -0,0 +1,167 @@ +"use strict"; +/* eslint-env mozilla/frame-script */ + +const { ExtensionTestUtils } = ChromeUtils.import( + "resource://testing-common/ExtensionXPCShellUtils.jsm" +); + +ExtensionTestUtils.init(this); + +function childFrameScript() { + 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 ExtensionTestUtils.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_bug1086684.js b/dom/ipc/tests/test_bug1086684.js new file mode 100644 index 0000000000..c5d4836d98 --- /dev/null +++ b/dom/ipc/tests/test_bug1086684.js @@ -0,0 +1,99 @@ +"use strict"; +/* eslint-env mozilla/frame-script */ + +const { AddonTestUtils } = ChromeUtils.import( + "resource://testing-common/AddonTestUtils.jsm" +); +const { ExtensionTestUtils } = ChromeUtils.import( + "resource://testing-common/ExtensionXPCShellUtils.jsm" +); + +AddonTestUtils.init(this); +ExtensionTestUtils.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 = AddonTestUtils.createHttpServer({ hosts: ["example.com"] }); +server.registerPathHandler(childFramePath, (request, response) => { + response.write(childFrameContents); +}); + +function childFrameScript() { + "use strict"; + + let { MockFilePicker } = ChromeUtils.import( + "resource://testing-common/MockFilePicker.jsm" + ); + + 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() { + let page = await ExtensionTestUtils.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(); +}); diff --git a/dom/ipc/tests/test_child_docshell.js b/dom/ipc/tests/test_child_docshell.js new file mode 100644 index 0000000000..3871c35496 --- /dev/null +++ b/dom/ipc/tests/test_child_docshell.js @@ -0,0 +1,93 @@ +"use strict"; +/* eslint-env mozilla/frame-script */ + +const { ExtensionTestUtils } = ChromeUtils.import( + "resource://testing-common/ExtensionXPCShellUtils.jsm" +); + +ExtensionTestUtils.init(this); + +add_task(async function test() { + let page = await ExtensionTestUtils.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() { + const { Services } = ChromeUtils.import( + "resource://gre/modules/Services.jsm" + ); + + 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_process_error_oom.xhtml b/dom/ipc/tests/test_process_error_oom.xhtml new file mode 100644 index 0000000000..03de3b07eb --- /dev/null +++ b/dom/ipc/tests/test_process_error_oom.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?crashType=CRASH_OOM', '_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..112093e44c --- /dev/null +++ b/dom/ipc/tests/test_sharedMap.js @@ -0,0 +1,382 @@ +"use strict"; + +const { AppConstants } = ChromeUtils.import( + "resource://gre/modules/AppConstants.jsm" +); +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { ExtensionUtils } = ChromeUtils.import( + "resource://gre/modules/ExtensionUtils.jsm" +); +const { ExtensionTestUtils } = ChromeUtils.import( + "resource://testing-common/ExtensionXPCShellUtils.jsm" +); + +const PROCESS_COUNT_PREF = "dom.ipc.processCount"; + +const remote = AppConstants.platform !== "android"; + +ExtensionTestUtils.init(this); + +let contentPage; + +async function readBlob(key, sharedData = Services.cpmm.sharedData) { + 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(undefined, getContents); + checkMap(contents, expected); + } +} + +async function loadContentPage() { + let page = await ExtensionTestUtils.loadContentPage("about:blank", { + remote, + }); + registerCleanupFunction(() => page.close()); + + page.addFrameScriptHelper(` + var {ExtensionUtils} = ChromeUtils.import("resource://gre/modules/ExtensionUtils.jsm"); + Cu.importGlobalProperties(["FileReader"]); + `); + return page; +} + +add_task(async function setup() { + // 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..f101264eeb --- /dev/null +++ b/dom/ipc/tests/test_window_open_discarded_bc.html @@ -0,0 +1,37 @@ +<!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(() => !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.ini b/dom/ipc/tests/xpcshell.ini new file mode 100644 index 0000000000..a93537c9b6 --- /dev/null +++ b/dom/ipc/tests/xpcshell.ini @@ -0,0 +1,10 @@ +[test_sharedMap.js] +skip-if = os == "android" && processor == "x86_64" +[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" |