/* -*- 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 "BroadcastChannel.h" #include "BroadcastChannelChild.h" #include "mozilla/dom/BroadcastChannelBinding.h" #include "mozilla/dom/Navigator.h" #include "mozilla/dom/File.h" #include "mozilla/dom/MessageEvent.h" #include "mozilla/dom/MessageEventBinding.h" #include "mozilla/dom/StructuredCloneHolder.h" #include "mozilla/dom/ipc/StructuredCloneData.h" #include "mozilla/dom/RefMessageBodyService.h" #include "mozilla/dom/RootedDictionary.h" #include "mozilla/dom/SharedMessageBody.h" #include "mozilla/dom/WorkerScope.h" #include "mozilla/dom/WorkerRef.h" #include "mozilla/dom/WorkerRunnable.h" #include "mozilla/ipc/BackgroundChild.h" #include "mozilla/ipc/BackgroundUtils.h" #include "mozilla/ipc/PBackgroundChild.h" #include "mozilla/StorageAccess.h" #include "nsICookieJarSettings.h" #include "mozilla/dom/Document.h" #ifdef XP_WIN # undef PostMessage #endif namespace mozilla { using namespace ipc; namespace dom { using namespace ipc; namespace { class CloseRunnable final : public DiscardableRunnable { public: explicit CloseRunnable(BroadcastChannel* aBC) : DiscardableRunnable("BroadcastChannel CloseRunnable"), mBC(aBC) { MOZ_ASSERT(mBC); } NS_IMETHOD Run() override { mBC->Shutdown(); return NS_OK; } private: ~CloseRunnable() = default; RefPtr mBC; }; class TeardownRunnable { protected: explicit TeardownRunnable(BroadcastChannelChild* aActor) : mActor(aActor) { MOZ_ASSERT(mActor); } void RunInternal() { MOZ_ASSERT(mActor); if (!mActor->IsActorDestroyed()) { mActor->SendClose(); } } protected: virtual ~TeardownRunnable() = default; private: RefPtr mActor; }; class TeardownRunnableOnMainThread final : public Runnable, public TeardownRunnable { public: explicit TeardownRunnableOnMainThread(BroadcastChannelChild* aActor) : Runnable("TeardownRunnableOnMainThread"), TeardownRunnable(aActor) {} NS_IMETHOD Run() override { RunInternal(); return NS_OK; } }; class TeardownRunnableOnWorker final : public WorkerControlRunnable, public TeardownRunnable { public: TeardownRunnableOnWorker(WorkerPrivate* aWorkerPrivate, BroadcastChannelChild* aActor) : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), TeardownRunnable(aActor) {} bool WorkerRun(JSContext*, WorkerPrivate*) override { RunInternal(); return true; } bool PreDispatch(WorkerPrivate* aWorkerPrivate) override { return true; } void PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override {} bool PreRun(WorkerPrivate* aWorkerPrivate) override { return true; } void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult) override {} }; } // namespace BroadcastChannel::BroadcastChannel(nsIGlobalObject* aGlobal, const nsAString& aChannel, const nsID& aPortUUID) : DOMEventTargetHelper(aGlobal), mRefMessageBodyService(RefMessageBodyService::GetOrCreate()), mChannel(aChannel), mState(StateActive), mPortUUID(aPortUUID) { MOZ_ASSERT(aGlobal); KeepAliveIfHasListenersFor(nsGkAtoms::onmessage); } BroadcastChannel::~BroadcastChannel() { Shutdown(); MOZ_ASSERT(!mWorkerRef); } JSObject* BroadcastChannel::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return BroadcastChannel_Binding::Wrap(aCx, this, aGivenProto); } /* static */ already_AddRefed BroadcastChannel::Constructor( const GlobalObject& aGlobal, const nsAString& aChannel, ErrorResult& aRv) { nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); if (NS_WARN_IF(!global)) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } nsID portUUID = {}; aRv = nsID::GenerateUUIDInPlace(portUUID); if (aRv.Failed()) { return nullptr; } RefPtr bc = new BroadcastChannel(global, aChannel, portUUID); nsCOMPtr storagePrincipal; StorageAccess storageAccess; nsCOMPtr cjs; if (NS_IsMainThread()) { nsCOMPtr window = do_QueryInterface(global); if (NS_WARN_IF(!window)) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } nsCOMPtr incumbent = mozilla::dom::GetIncumbentGlobal(); if (!incumbent) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } nsCOMPtr sop = do_QueryInterface(incumbent); if (NS_WARN_IF(!sop)) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } storagePrincipal = sop->GetEffectiveStoragePrincipal(); if (!storagePrincipal) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } storageAccess = StorageAllowedForWindow(window); Document* doc = window->GetExtantDoc(); if (doc) { cjs = doc->CookieJarSettings(); } } else { JSContext* cx = aGlobal.Context(); WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx); MOZ_ASSERT(workerPrivate); RefPtr workerRef = StrongWorkerRef::Create( workerPrivate, "BroadcastChannel", [bc]() { bc->Shutdown(); }); // We are already shutting down the worker. Let's return a non-active // object. if (NS_WARN_IF(!workerRef)) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } storageAccess = workerPrivate->StorageAccess(); storagePrincipal = workerPrivate->GetEffectiveStoragePrincipal(); bc->mWorkerRef = workerRef; cjs = workerPrivate->CookieJarSettings(); } // We want to allow opaque origins. if (!storagePrincipal->GetIsNullPrincipal() && (storageAccess == StorageAccess::eDeny || (ShouldPartitionStorage(storageAccess) && !StoragePartitioningEnabled(storageAccess, cjs)))) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return nullptr; } // Register this component to PBackground. PBackgroundChild* actorChild = BackgroundChild::GetOrCreateForCurrentThread(); if (NS_WARN_IF(!actorChild)) { // Firefox is probably shutting down. Let's return a 'generic' error. aRv.Throw(NS_ERROR_FAILURE); return nullptr; } nsAutoCString origin; aRv = storagePrincipal->GetOrigin(origin); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } nsString originForEvents; aRv = nsContentUtils::GetUTFOrigin(storagePrincipal, originForEvents); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } PrincipalInfo storagePrincipalInfo; aRv = PrincipalToPrincipalInfo(storagePrincipal, &storagePrincipalInfo); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } PBroadcastChannelChild* actor = actorChild->SendPBroadcastChannelConstructor( storagePrincipalInfo, origin, nsString(aChannel)); bc->mActor = static_cast(actor); MOZ_ASSERT(bc->mActor); bc->mActor->SetParent(bc); bc->mOriginForEvents = std::move(originForEvents); return bc.forget(); } void BroadcastChannel::PostMessage(JSContext* aCx, JS::Handle aMessage, ErrorResult& aRv) { if (mState != StateActive) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } Maybe agentClusterId; nsCOMPtr global = GetOwnerGlobal(); MOZ_ASSERT(global); if (global) { agentClusterId = global->GetAgentClusterId(); } RefPtr data = new SharedMessageBody( StructuredCloneHolder::TransferringNotSupported, agentClusterId); data->Write(aCx, aMessage, JS::UndefinedHandleValue, mPortUUID, mRefMessageBodyService, aRv); if (NS_WARN_IF(aRv.Failed())) { return; } RemoveDocFromBFCache(); MessageData message; SharedMessageBody::FromSharedToMessageChild(mActor->Manager(), data, message); mActor->SendPostMessage(message); } void BroadcastChannel::Close() { if (mState != StateActive) { return; } // We cannot call Shutdown() immediatelly because we could have some // postMessage runnable already dispatched. Instead, we change the state to // StateClosed and we shutdown the actor asynchrounsly. mState = StateClosed; RefPtr runnable = new CloseRunnable(this); if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) { NS_WARNING("Failed to dispatch to the current thread!"); } } void BroadcastChannel::Shutdown() { mState = StateClosed; // The DTOR of this WorkerRef will release the worker for us. mWorkerRef = nullptr; if (mActor) { mActor->SetParent(nullptr); if (NS_IsMainThread()) { RefPtr runnable = new TeardownRunnableOnMainThread(mActor); NS_DispatchToCurrentThread(runnable); } else { WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); MOZ_ASSERT(workerPrivate); RefPtr runnable = new TeardownRunnableOnWorker(workerPrivate, mActor); runnable->Dispatch(); } mActor = nullptr; } IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onmessage); } void BroadcastChannel::RemoveDocFromBFCache() { if (!NS_IsMainThread()) { return; } if (nsPIDOMWindowInner* window = GetOwner()) { window->RemoveFromBFCacheSync(); } } void BroadcastChannel::DisconnectFromOwner() { Shutdown(); DOMEventTargetHelper::DisconnectFromOwner(); } void BroadcastChannel::MessageReceived(const MessageData& aData) { if (NS_FAILED(CheckCurrentGlobalCorrectness())) { RemoveDocFromBFCache(); return; } // Let's ignore messages after a close/shutdown. if (mState != StateActive) { return; } nsCOMPtr globalObject; if (NS_IsMainThread()) { globalObject = GetParentObject(); } else { WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); MOZ_ASSERT(workerPrivate); globalObject = workerPrivate->GlobalScope(); } AutoJSAPI jsapi; if (!globalObject || !jsapi.Init(globalObject)) { NS_WARNING("Failed to initialize AutoJSAPI object."); return; } JSContext* cx = jsapi.cx(); RefPtr data = SharedMessageBody::FromMessageToSharedChild( aData, StructuredCloneHolder::TransferringNotSupported); if (NS_WARN_IF(!data)) { DispatchError(cx); return; } IgnoredErrorResult rv; JS::Rooted value(cx); data->Read(cx, &value, mRefMessageBodyService, SharedMessageBody::ReadMethod::KeepRefMessageBody, rv); if (NS_WARN_IF(rv.Failed())) { JS_ClearPendingException(cx); DispatchError(cx); return; } RemoveDocFromBFCache(); RootedDictionary init(cx); init.mBubbles = false; init.mCancelable = false; init.mOrigin = mOriginForEvents; init.mData = value; RefPtr event = MessageEvent::Constructor(this, u"message"_ns, init); event->SetTrusted(true); DispatchEvent(*event); } void BroadcastChannel::MessageDelivered(const nsID& aMessageID, uint32_t aOtherBCs) { mRefMessageBodyService->SetMaxCount(aMessageID, aOtherBCs); } void BroadcastChannel::DispatchError(JSContext* aCx) { RootedDictionary init(aCx); init.mBubbles = false; init.mCancelable = false; init.mOrigin = mOriginForEvents; RefPtr event = MessageEvent::Constructor(this, u"messageerror"_ns, init); event->SetTrusted(true); DispatchEvent(*event); } NS_IMPL_CYCLE_COLLECTION_CLASS(BroadcastChannel) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(BroadcastChannel, DOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(BroadcastChannel, DOMEventTargetHelper) tmp->Shutdown(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BroadcastChannel) NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) NS_IMPL_ADDREF_INHERITED(BroadcastChannel, DOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(BroadcastChannel, DOMEventTargetHelper) } // namespace dom } // namespace mozilla