/* -*- 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 "mozilla/ClearOnShutdown.h" #include "mozilla/ipc/InProcessParent.h" #include "mozilla/dom/BrowserBridgeParent.h" #include "mozilla/dom/CanonicalBrowsingContext.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/BrowserHost.h" #include "mozilla/dom/BrowserParent.h" #include "mozilla/dom/WindowGlobalActorsBinding.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/ServoCSSParser.h" #include "mozilla/ServoStyleSet.h" #include "mozJSComponentLoader.h" #include "nsContentUtils.h" #include "nsDocShell.h" #include "nsError.h" #include "nsFrameLoader.h" #include "nsFrameLoaderOwner.h" #include "nsGlobalWindowInner.h" #include "nsQueryObject.h" #include "nsFrameLoaderOwner.h" #include "nsSerializationHelper.h" #include "nsITransportSecurityInfo.h" #include "nsISharePicker.h" #include "mozilla/dom/DOMException.h" #include "mozilla/dom/DOMExceptionBinding.h" #include "mozilla/dom/JSWindowActorBinding.h" #include "mozilla/dom/JSWindowActorParent.h" #include "mozilla/dom/JSWindowActorService.h" using namespace mozilla::ipc; using namespace mozilla::dom::ipc; namespace mozilla { namespace dom { typedef nsRefPtrHashtable WGPByIdMap; static StaticAutoPtr gWindowGlobalParentsById; WindowGlobalParent::WindowGlobalParent(const WindowGlobalInit& aInit, bool aInProcess) : mDocumentPrincipal(aInit.principal()), mDocumentURI(aInit.documentURI()), mInnerWindowId(aInit.innerWindowId()), mOuterWindowId(aInit.outerWindowId()), mInProcess(aInProcess), mIsInitialDocument(false), mHasBeforeUnload(false) { MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess(), "Parent process only"); MOZ_RELEASE_ASSERT(mDocumentPrincipal, "Must have a valid principal"); // NOTE: mBrowsingContext initialized in Init() MOZ_RELEASE_ASSERT(aInit.browsingContext(), "Must be made in BrowsingContext"); } void WindowGlobalParent::Init(const WindowGlobalInit& aInit) { MOZ_ASSERT(Manager(), "Should have a manager!"); // Register this WindowGlobal in the gWindowGlobalParentsById map. if (!gWindowGlobalParentsById) { gWindowGlobalParentsById = new WGPByIdMap(); ClearOnShutdown(&gWindowGlobalParentsById); } auto entry = gWindowGlobalParentsById->LookupForAdd(mInnerWindowId); MOZ_RELEASE_ASSERT(!entry, "Duplicate WindowGlobalParent entry for ID!"); entry.OrInsert([&] { return this; }); // Determine which content process the window global is coming from. dom::ContentParentId processId(0); if (!mInProcess) { ContentParent* cp = static_cast(Manager()->Manager()); processId = cp->ChildID(); // Ensure the content process has permissions for this principal. cp->TransmitPermissionsForPrincipal(mDocumentPrincipal); } mBrowsingContext = CanonicalBrowsingContext::Cast(aInit.browsingContext()); MOZ_ASSERT(mBrowsingContext); MOZ_DIAGNOSTIC_ASSERT( !mBrowsingContext->GetParent() || mBrowsingContext->GetEmbedderInnerWindowId(), "When creating a non-root WindowGlobalParent, the WindowGlobalParent " "for our embedder should've already been created."); // Attach ourself to the browsing context. mBrowsingContext->RegisterWindowGlobal(this); // If there is no current window global, assume we're about to become it // optimistically. if (!mBrowsingContext->GetCurrentWindowGlobal()) { mBrowsingContext->SetCurrentWindowGlobal(this); } // Ensure we have a document URI if (!mDocumentURI) { NS_NewURI(getter_AddRefs(mDocumentURI), "about:blank"); } nsCOMPtr obs = services::GetObserverService(); if (obs) { obs->NotifyObservers(this, "window-global-created", nullptr); } } /* static */ already_AddRefed WindowGlobalParent::GetByInnerWindowId( uint64_t aInnerWindowId) { if (!gWindowGlobalParentsById) { return nullptr; } return gWindowGlobalParentsById->Get(aInnerWindowId); } already_AddRefed WindowGlobalParent::GetChildActor() { if (!CanSend()) { return nullptr; } IProtocol* otherSide = InProcessParent::ChildActorFor(this); return do_AddRef(static_cast(otherSide)); } already_AddRefed WindowGlobalParent::GetBrowserParent() { if (IsInProcess() || !CanSend()) { return nullptr; } return do_AddRef(static_cast(Manager())); } already_AddRefed WindowGlobalParent::GetRootFrameLoader() { dom::BrowsingContext* top = BrowsingContext()->Top(); RefPtr frameLoaderOwner = do_QueryObject(top->GetEmbedderElement()); if (frameLoaderOwner) { return frameLoaderOwner->GetFrameLoader(); } return nullptr; } uint64_t WindowGlobalParent::ContentParentId() { RefPtr browserParent = GetBrowserParent(); return browserParent ? browserParent->Manager()->ChildID() : 0; } int32_t WindowGlobalParent::OsPid() { RefPtr 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 embedder = BrowsingContext()->GetEmbedderWindowGlobal(); if (NS_WARN_IF(!embedder)) { return false; } return ContentParentId() != embedder->ContentParentId(); } mozilla::ipc::IPCResult WindowGlobalParent::RecvLoadURI( dom::BrowsingContext* aTargetBC, nsDocShellLoadState* aLoadState, bool aSetNavigating) { if (!aTargetBC || aTargetBC->IsDiscarded()) { MOZ_LOG( BrowsingContext::GetLog(), LogLevel::Debug, ("ParentIPC: Trying to send a message with dead or detached context")); return IPC_OK(); } // FIXME: For cross-process loads, we should double check CanAccess() for the // source browsing context in the parent process. if (aTargetBC->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. aTargetBC->LoadURI(nullptr, aLoadState, aSetNavigating); return IPC_OK(); } mozilla::ipc::IPCResult WindowGlobalParent::RecvInternalLoad( dom::BrowsingContext* aTargetBC, nsDocShellLoadState* aLoadState) { if (!aTargetBC || aTargetBC->IsDiscarded()) { MOZ_LOG( BrowsingContext::GetLog(), LogLevel::Debug, ("ParentIPC: Trying to send a message with dead or detached context")); return IPC_OK(); } // FIXME: For cross-process loads, we should double check CanAccess() for the // source browsing context in the parent process. if (aTargetBC->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. aTargetBC->InternalLoad(mBrowsingContext, aLoadState, nullptr, nullptr); 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::RecvSetHasBeforeUnload(bool aHasBeforeUnload) { mHasBeforeUnload = aHasBeforeUnload; return IPC_OK(); } IPCResult WindowGlobalParent::RecvBecomeCurrentWindowGlobal() { mBrowsingContext->SetCurrentWindowGlobal(this); return IPC_OK(); } IPCResult WindowGlobalParent::RecvDestroy() { if (CanSend()) { RefPtr browserParent = GetBrowserParent(); if (!browserParent || !browserParent->IsDestroyed()) { // Make a copy so that we can avoid potential iterator invalidation when // calling the user-provided Destroy() methods. nsTArray> windowActors(mWindowActors.Count()); for (auto iter = mWindowActors.Iter(); !iter.Done(); iter.Next()) { windowActors.AppendElement(iter.UserData()); } for (auto& windowActor : windowActors) { windowActor->StartDestroy(); } Unused << Send__delete__(this); } } return IPC_OK(); } IPCResult WindowGlobalParent::RecvRawMessage( const JSWindowActorMessageMeta& aMeta, const ClonedMessageData& aData, const ClonedMessageData& aStack) { StructuredCloneData data; data.BorrowFromClonedMessageDataForParent(aData); StructuredCloneData stack; stack.BorrowFromClonedMessageDataForParent(aStack); ReceiveRawMessage(aMeta, std::move(data), std::move(stack)); return IPC_OK(); } void WindowGlobalParent::ReceiveRawMessage( const JSWindowActorMessageMeta& aMeta, StructuredCloneData&& aData, StructuredCloneData&& aStack) { RefPtr actor = GetActor(aMeta.actorName(), IgnoreErrors()); if (actor) { actor->ReceiveRawMessage(aMeta, std::move(aData), std::move(aStack)); } } const nsAString& WindowGlobalParent::GetRemoteType() { if (RefPtr browserParent = GetBrowserParent()) { return browserParent->Manager()->GetRemoteType(); } return VoidString(); } already_AddRefed WindowGlobalParent::GetActor( const nsAString& aName, ErrorResult& aRv) { if (!CanSend()) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return nullptr; } // Check if this actor has already been created, and return it if it has. if (mWindowActors.Contains(aName)) { return do_AddRef(mWindowActors.GetWeak(aName)); } // Otherwise, we want to create a new instance of this actor. JS::RootedObject obj(RootingCx()); ConstructActor(aName, &obj, aRv); if (aRv.Failed()) { return nullptr; } // Unwrap our actor to a JSWindowActorParent object. RefPtr actor; if (NS_FAILED(UNWRAP_OBJECT(JSWindowActorParent, &obj, actor))) { return nullptr; } MOZ_RELEASE_ASSERT(!actor->GetManager(), "mManager was already initialized once!"); actor->Init(aName, this); mWindowActors.Put(aName, actor); return actor.forget(); } bool WindowGlobalParent::IsCurrentGlobal() { return CanSend() && mBrowsingContext->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 aValue) override { mResolver(NS_OK); } virtual void RejectedCallback(JSContext* aCx, JS::Handle aValue) override { if (NS_WARN_IF(!aValue.isObject())) { mResolver(NS_ERROR_FAILURE); return; } // nsresult is stored as Exception internally in Promise JS::Rooted obj(aCx, &aValue.toObject()); RefPtr 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::RecvShare( IPCWebShareData&& aData, WindowGlobalParent::ShareResolver&& aResolver) { // Widget Layer handoff... nsCOMPtr sharePicker = do_GetService("@mozilla.org/sharepicker;1"); if (!sharePicker) { aResolver(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return IPC_OK(); } // Initialize the ShareWidget RefPtr parent = GetBrowserParent(); nsCOMPtr 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; 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 handler = new ShareHandler(std::move(aResolver)); promise->AppendNativeHandler(handler); return IPC_OK(); } already_AddRefed WindowGlobalParent::DrawSnapshot( const DOMRect* aRect, double aScale, const nsAString& aBackgroundColor, mozilla::ErrorResult& aRv) { nsIGlobalObject* global = GetParentObject(); RefPtr 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; } if (!gfx::CrossProcessPaint::Start(this, aRect, (float)aScale, color, gfx::CrossProcessPaintFlags::None, promise)) { aRv = NS_ERROR_FAILURE; return nullptr; } return promise.forget(); } void WindowGlobalParent::DrawSnapshotInternal(gfx::CrossProcessPaint* aPaint, const Maybe& aRect, float aScale, nscolor aBackgroundColor, uint32_t aFlags) { auto promise = SendDrawSnapshot(aRect, aScale, aBackgroundColor, aFlags); RefPtr paint(aPaint); RefPtr 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 WindowGlobalParent::GetSecurityInfo( ErrorResult& aRv) { RefPtr browserParent = GetBrowserParent(); if (NS_WARN_IF(!browserParent)) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } nsIGlobalObject* global = GetParentObject(); RefPtr promise = Promise::Create(global, aRv); if (aRv.Failed()) { return nullptr; } SendGetSecurityInfo( [promise](Maybe&& aResult) { if (aResult) { nsCOMPtr infoObj; nsresult rv = NS_DeserializeObject(aResult.value(), getter_AddRefs(infoObj)); if (NS_WARN_IF(NS_FAILED(rv))) { promise->MaybeReject(NS_ERROR_FAILURE); } nsCOMPtr 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(); } void WindowGlobalParent::ActorDestroy(ActorDestroyReason aWhy) { gWindowGlobalParentsById->Remove(mInnerWindowId); mBrowsingContext->UnregisterWindowGlobal(this); // Destroy our JSWindowActors, and reject any pending queries. nsRefPtrHashtable windowActors; mWindowActors.SwapElements(windowActors); for (auto iter = windowActors.Iter(); !iter.Done(); iter.Next()) { iter.Data()->RejectPendingQueries(); iter.Data()->AfterDestroy(); } windowActors.Clear(); nsCOMPtr obs = services::GetObserverService(); if (obs) { obs->NotifyObservers(this, "window-global-destroyed", nullptr); } } WindowGlobalParent::~WindowGlobalParent() { MOZ_ASSERT(!gWindowGlobalParentsById || !gWindowGlobalParentsById->Contains(mInnerWindowId)); MOZ_ASSERT(!mWindowActors.Count()); } JSObject* WindowGlobalParent::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return WindowGlobalParent_Binding::Wrap(aCx, this, aGivenProto); } nsIGlobalObject* WindowGlobalParent::GetParentObject() { return xpc::NativeGlobal(xpc::PrivilegedJunkScope()); } NS_IMPL_CYCLE_COLLECTION_INHERITED(WindowGlobalParent, WindowGlobalActor, mBrowsingContext, mWindowActors) NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(WindowGlobalParent, WindowGlobalActor) NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WindowGlobalParent) NS_INTERFACE_MAP_END_INHERITING(WindowGlobalActor) NS_IMPL_ADDREF_INHERITED(WindowGlobalParent, WindowGlobalActor) NS_IMPL_RELEASE_INHERITED(WindowGlobalParent, WindowGlobalActor) } // namespace dom } // namespace mozilla