diff options
Diffstat (limited to 'dom/broadcastchannel/BroadcastChannel.cpp')
-rw-r--r-- | dom/broadcastchannel/BroadcastChannel.cpp | 457 |
1 files changed, 457 insertions, 0 deletions
diff --git a/dom/broadcastchannel/BroadcastChannel.cpp b/dom/broadcastchannel/BroadcastChannel.cpp new file mode 100644 index 0000000000..9f1a461bb6 --- /dev/null +++ b/dom/broadcastchannel/BroadcastChannel.cpp @@ -0,0 +1,457 @@ +/* -*- 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<BroadcastChannel> 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<BroadcastChannelChild> 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<JSObject*> aGivenProto) { + return BroadcastChannel_Binding::Wrap(aCx, this, aGivenProto); +} + +/* static */ +already_AddRefed<BroadcastChannel> BroadcastChannel::Constructor( + const GlobalObject& aGlobal, const nsAString& aChannel, ErrorResult& aRv) { + nsCOMPtr<nsIGlobalObject> 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<BroadcastChannel> bc = + new BroadcastChannel(global, aChannel, portUUID); + + nsCOMPtr<nsIPrincipal> storagePrincipal; + + StorageAccess storageAccess; + + nsCOMPtr<nsICookieJarSettings> cjs; + if (NS_IsMainThread()) { + nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global); + if (NS_WARN_IF(!window)) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + nsCOMPtr<nsIGlobalObject> incumbent = mozilla::dom::GetIncumbentGlobal(); + + if (!incumbent) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + nsCOMPtr<nsIScriptObjectPrincipal> 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<StrongWorkerRef> 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<BroadcastChannelChild*>(actor); + MOZ_ASSERT(bc->mActor); + + bc->mActor->SetParent(bc); + bc->mOriginForEvents = std::move(originForEvents); + + return bc.forget(); +} + +void BroadcastChannel::PostMessage(JSContext* aCx, + JS::Handle<JS::Value> aMessage, + ErrorResult& aRv) { + if (mState != StateActive) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + Maybe<nsID> agentClusterId; + nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal(); + MOZ_ASSERT(global); + if (global) { + agentClusterId = global->GetAgentClusterId(); + } + + RefPtr<SharedMessageBody> 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<CloseRunnable> 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<TeardownRunnableOnMainThread> runnable = + new TeardownRunnableOnMainThread(mActor); + NS_DispatchToCurrentThread(runnable); + } else { + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + + RefPtr<TeardownRunnableOnWorker> 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<nsIGlobalObject> 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<SharedMessageBody> data = SharedMessageBody::FromMessageToSharedChild( + aData, StructuredCloneHolder::TransferringNotSupported); + if (NS_WARN_IF(!data)) { + DispatchError(cx); + return; + } + + IgnoredErrorResult rv; + JS::Rooted<JS::Value> 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<MessageEventInit> init(cx); + init.mBubbles = false; + init.mCancelable = false; + init.mOrigin = mOriginForEvents; + init.mData = value; + + RefPtr<MessageEvent> 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<MessageEventInit> init(aCx); + init.mBubbles = false; + init.mCancelable = false; + init.mOrigin = mOriginForEvents; + + RefPtr<Event> 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 |