1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "BroadcastChannel.h"
8 #include "BroadcastChannelChild.h"
9 #include "mozilla/dom/BroadcastChannelBinding.h"
10 #include "mozilla/dom/Navigator.h"
11 #include "mozilla/dom/File.h"
12 #include "mozilla/dom/MessageEvent.h"
13 #include "mozilla/dom/MessageEventBinding.h"
14 #include "mozilla/dom/StructuredCloneHolder.h"
15 #include "mozilla/dom/ipc/StructuredCloneData.h"
16 #include "mozilla/dom/RefMessageBodyService.h"
17 #include "mozilla/dom/RootedDictionary.h"
18 #include "mozilla/dom/SharedMessageBody.h"
19 #include "mozilla/dom/WorkerScope.h"
20 #include "mozilla/dom/WorkerRef.h"
21 #include "mozilla/dom/WorkerRunnable.h"
22 #include "mozilla/ipc/BackgroundChild.h"
23 #include "mozilla/ipc/BackgroundUtils.h"
24 #include "mozilla/ipc/PBackgroundChild.h"
25 #include "mozilla/StorageAccess.h"
27 #include "nsICookieJarSettings.h"
28 #include "mozilla/dom/Document.h"
44 class CloseRunnable final
: public DiscardableRunnable
{
46 explicit CloseRunnable(BroadcastChannel
* aBC
)
47 : DiscardableRunnable("BroadcastChannel CloseRunnable"), mBC(aBC
) {
51 NS_IMETHOD
Run() override
{
57 ~CloseRunnable() = default;
59 RefPtr
<BroadcastChannel
> mBC
;
62 class TeardownRunnable
{
64 explicit TeardownRunnable(BroadcastChannelChild
* aActor
) : mActor(aActor
) {
70 if (!mActor
->IsActorDestroyed()) {
76 virtual ~TeardownRunnable() = default;
79 RefPtr
<BroadcastChannelChild
> mActor
;
82 class TeardownRunnableOnMainThread final
: public Runnable
,
83 public TeardownRunnable
{
85 explicit TeardownRunnableOnMainThread(BroadcastChannelChild
* aActor
)
86 : Runnable("TeardownRunnableOnMainThread"), TeardownRunnable(aActor
) {}
88 NS_IMETHOD
Run() override
{
94 class TeardownRunnableOnWorker final
: public WorkerControlRunnable
,
95 public TeardownRunnable
{
97 TeardownRunnableOnWorker(WorkerPrivate
* aWorkerPrivate
,
98 BroadcastChannelChild
* aActor
)
99 : WorkerControlRunnable(aWorkerPrivate
, "TeardownRunnableOnWorker",
101 TeardownRunnable(aActor
) {}
103 bool WorkerRun(JSContext
*, WorkerPrivate
*) override
{
108 bool PreDispatch(WorkerPrivate
* aWorkerPrivate
) override
{ return true; }
110 void PostDispatch(WorkerPrivate
* aWorkerPrivate
,
111 bool aDispatchResult
) override
{}
113 bool PreRun(WorkerPrivate
* aWorkerPrivate
) override
{ return true; }
115 void PostRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
,
116 bool aRunResult
) override
{}
121 BroadcastChannel::BroadcastChannel(nsIGlobalObject
* aGlobal
,
122 const nsAString
& aChannel
,
123 const nsID
& aPortUUID
)
124 : DOMEventTargetHelper(aGlobal
),
125 mRefMessageBodyService(RefMessageBodyService::GetOrCreate()),
128 mPortUUID(aPortUUID
) {
130 KeepAliveIfHasListenersFor(nsGkAtoms::onmessage
);
133 BroadcastChannel::~BroadcastChannel() {
135 MOZ_ASSERT(!mWorkerRef
);
138 JSObject
* BroadcastChannel::WrapObject(JSContext
* aCx
,
139 JS::Handle
<JSObject
*> aGivenProto
) {
140 return BroadcastChannel_Binding::Wrap(aCx
, this, aGivenProto
);
144 already_AddRefed
<BroadcastChannel
> BroadcastChannel::Constructor(
145 const GlobalObject
& aGlobal
, const nsAString
& aChannel
, ErrorResult
& aRv
) {
146 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
147 if (NS_WARN_IF(!global
)) {
148 aRv
.Throw(NS_ERROR_FAILURE
);
153 aRv
= nsID::GenerateUUIDInPlace(portUUID
);
158 RefPtr
<BroadcastChannel
> bc
=
159 new BroadcastChannel(global
, aChannel
, portUUID
);
161 nsCOMPtr
<nsIPrincipal
> storagePrincipal
;
163 StorageAccess storageAccess
;
165 nsCOMPtr
<nsICookieJarSettings
> cjs
;
166 if (NS_IsMainThread()) {
167 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryInterface(global
);
168 if (NS_WARN_IF(!window
)) {
169 aRv
.Throw(NS_ERROR_FAILURE
);
173 nsCOMPtr
<nsIGlobalObject
> incumbent
= mozilla::dom::GetIncumbentGlobal();
176 aRv
.Throw(NS_ERROR_FAILURE
);
180 nsCOMPtr
<nsIScriptObjectPrincipal
> sop
= do_QueryInterface(incumbent
);
181 if (NS_WARN_IF(!sop
)) {
182 aRv
.Throw(NS_ERROR_FAILURE
);
186 storagePrincipal
= sop
->GetEffectiveStoragePrincipal();
187 if (!storagePrincipal
) {
188 aRv
.Throw(NS_ERROR_UNEXPECTED
);
191 storageAccess
= StorageAllowedForWindow(window
);
193 Document
* doc
= window
->GetExtantDoc();
195 cjs
= doc
->CookieJarSettings();
198 JSContext
* cx
= aGlobal
.Context();
200 WorkerPrivate
* workerPrivate
= GetWorkerPrivateFromContext(cx
);
201 MOZ_ASSERT(workerPrivate
);
203 RefPtr
<StrongWorkerRef
> workerRef
= StrongWorkerRef::Create(
204 workerPrivate
, "BroadcastChannel", [bc
]() { bc
->Shutdown(); });
205 // We are already shutting down the worker. Let's return a non-active
207 if (NS_WARN_IF(!workerRef
)) {
208 aRv
.Throw(NS_ERROR_FAILURE
);
212 storageAccess
= workerPrivate
->StorageAccess();
214 storagePrincipal
= workerPrivate
->GetEffectiveStoragePrincipal();
216 bc
->mWorkerRef
= workerRef
;
218 cjs
= workerPrivate
->CookieJarSettings();
221 // We want to allow opaque origins.
222 if (!storagePrincipal
->GetIsNullPrincipal() &&
223 (storageAccess
== StorageAccess::eDeny
||
224 (ShouldPartitionStorage(storageAccess
) &&
225 !StoragePartitioningEnabled(storageAccess
, cjs
)))) {
226 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
230 // Register this component to PBackground.
231 PBackgroundChild
* actorChild
= BackgroundChild::GetOrCreateForCurrentThread();
232 if (NS_WARN_IF(!actorChild
)) {
233 // Firefox is probably shutting down. Let's return a 'generic' error.
234 aRv
.Throw(NS_ERROR_FAILURE
);
238 nsAutoCString origin
;
239 aRv
= storagePrincipal
->GetOrigin(origin
);
240 if (NS_WARN_IF(aRv
.Failed())) {
244 nsString originForEvents
;
245 aRv
= nsContentUtils::GetWebExposedOriginSerialization(storagePrincipal
,
247 if (NS_WARN_IF(aRv
.Failed())) {
251 PrincipalInfo storagePrincipalInfo
;
252 aRv
= PrincipalToPrincipalInfo(storagePrincipal
, &storagePrincipalInfo
);
253 if (NS_WARN_IF(aRv
.Failed())) {
257 PBroadcastChannelChild
* actor
= actorChild
->SendPBroadcastChannelConstructor(
258 storagePrincipalInfo
, origin
, nsString(aChannel
));
260 // The PBackground actor is shutting down, return a 'generic' error.
261 aRv
.Throw(NS_ERROR_FAILURE
);
265 bc
->mActor
= static_cast<BroadcastChannelChild
*>(actor
);
266 bc
->mActor
->SetParent(bc
);
267 bc
->mOriginForEvents
= std::move(originForEvents
);
272 void BroadcastChannel::PostMessage(JSContext
* aCx
,
273 JS::Handle
<JS::Value
> aMessage
,
275 if (mState
!= StateActive
) {
276 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
280 Maybe
<nsID
> agentClusterId
;
281 nsCOMPtr
<nsIGlobalObject
> global
= GetOwnerGlobal();
284 agentClusterId
= global
->GetAgentClusterId();
287 if (!global
->IsEligibleForMessaging()) {
291 RefPtr
<SharedMessageBody
> data
= new SharedMessageBody(
292 StructuredCloneHolder::TransferringNotSupported
, agentClusterId
);
294 data
->Write(aCx
, aMessage
, JS::UndefinedHandleValue
, mPortUUID
,
295 mRefMessageBodyService
, aRv
);
296 if (NS_WARN_IF(aRv
.Failed())) {
300 RemoveDocFromBFCache();
303 SharedMessageBody::FromSharedToMessageChild(mActor
->Manager(), data
, message
);
304 mActor
->SendPostMessage(message
);
307 void BroadcastChannel::Close() {
308 if (mState
!= StateActive
) {
312 // We cannot call Shutdown() immediatelly because we could have some
313 // postMessage runnable already dispatched. Instead, we change the state to
314 // StateClosed and we shutdown the actor asynchrounsly.
316 mState
= StateClosed
;
317 RefPtr
<CloseRunnable
> runnable
= new CloseRunnable(this);
319 if (NS_FAILED(NS_DispatchToCurrentThread(runnable
))) {
320 NS_WARNING("Failed to dispatch to the current thread!");
324 void BroadcastChannel::Shutdown() {
325 mState
= StateClosed
;
327 // The DTOR of this WorkerRef will release the worker for us.
328 mWorkerRef
= nullptr;
331 mActor
->SetParent(nullptr);
333 if (NS_IsMainThread()) {
334 RefPtr
<TeardownRunnableOnMainThread
> runnable
=
335 new TeardownRunnableOnMainThread(mActor
);
336 NS_DispatchToCurrentThread(runnable
);
338 WorkerPrivate
* workerPrivate
= GetCurrentThreadWorkerPrivate();
339 MOZ_ASSERT(workerPrivate
);
341 RefPtr
<TeardownRunnableOnWorker
> runnable
=
342 new TeardownRunnableOnWorker(workerPrivate
, mActor
);
343 runnable
->Dispatch();
349 IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onmessage
);
352 void BroadcastChannel::RemoveDocFromBFCache() {
353 if (!NS_IsMainThread()) {
357 if (nsPIDOMWindowInner
* window
= GetOwner()) {
358 window
->RemoveFromBFCacheSync();
362 void BroadcastChannel::DisconnectFromOwner() {
364 DOMEventTargetHelper::DisconnectFromOwner();
367 void BroadcastChannel::MessageReceived(const MessageData
& aData
) {
368 if (NS_FAILED(CheckCurrentGlobalCorrectness())) {
369 RemoveDocFromBFCache();
373 // Let's ignore messages after a close/shutdown.
374 if (mState
!= StateActive
) {
378 nsCOMPtr
<nsIGlobalObject
> globalObject
;
380 if (NS_IsMainThread()) {
381 globalObject
= GetParentObject();
383 WorkerPrivate
* workerPrivate
= GetCurrentThreadWorkerPrivate();
384 MOZ_ASSERT(workerPrivate
);
385 globalObject
= workerPrivate
->GlobalScope();
389 if (!globalObject
|| !jsapi
.Init(globalObject
)) {
390 NS_WARNING("Failed to initialize AutoJSAPI object.");
394 JSContext
* cx
= jsapi
.cx();
396 RefPtr
<SharedMessageBody
> data
= SharedMessageBody::FromMessageToSharedChild(
397 aData
, StructuredCloneHolder::TransferringNotSupported
);
398 if (NS_WARN_IF(!data
)) {
403 IgnoredErrorResult rv
;
404 JS::Rooted
<JS::Value
> value(cx
);
406 data
->Read(cx
, &value
, mRefMessageBodyService
,
407 SharedMessageBody::ReadMethod::KeepRefMessageBody
, rv
);
408 if (NS_WARN_IF(rv
.Failed())) {
409 JS_ClearPendingException(cx
);
414 RemoveDocFromBFCache();
416 RootedDictionary
<MessageEventInit
> init(cx
);
417 init
.mBubbles
= false;
418 init
.mCancelable
= false;
419 init
.mOrigin
= mOriginForEvents
;
422 RefPtr
<MessageEvent
> event
=
423 MessageEvent::Constructor(this, u
"message"_ns
, init
);
425 event
->SetTrusted(true);
427 DispatchEvent(*event
);
430 void BroadcastChannel::MessageDelivered(const nsID
& aMessageID
,
431 uint32_t aOtherBCs
) {
432 mRefMessageBodyService
->SetMaxCount(aMessageID
, aOtherBCs
);
435 void BroadcastChannel::DispatchError(JSContext
* aCx
) {
436 RootedDictionary
<MessageEventInit
> init(aCx
);
437 init
.mBubbles
= false;
438 init
.mCancelable
= false;
439 init
.mOrigin
= mOriginForEvents
;
441 RefPtr
<Event
> event
=
442 MessageEvent::Constructor(this, u
"messageerror"_ns
, init
);
443 event
->SetTrusted(true);
445 DispatchEvent(*event
);
448 NS_IMPL_CYCLE_COLLECTION_CLASS(BroadcastChannel
)
450 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(BroadcastChannel
,
451 DOMEventTargetHelper
)
452 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
454 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(BroadcastChannel
,
455 DOMEventTargetHelper
)
457 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
459 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BroadcastChannel
)
460 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
462 NS_IMPL_ADDREF_INHERITED(BroadcastChannel
, DOMEventTargetHelper
)
463 NS_IMPL_RELEASE_INHERITED(BroadcastChannel
, DOMEventTargetHelper
)
466 } // namespace mozilla