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/SharedMessageBody.h"
18 #include "mozilla/dom/WorkerPrivate.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"
26 #include "nsContentUtils.h"
28 #include "nsIBFCacheEntry.h"
29 #include "nsICookieJarSettings.h"
30 #include "mozilla/dom/Document.h"
46 class CloseRunnable final
: public DiscardableRunnable
{
48 explicit CloseRunnable(BroadcastChannel
* aBC
)
49 : DiscardableRunnable("BroadcastChannel CloseRunnable"), mBC(aBC
) {
53 NS_IMETHOD
Run() override
{
59 ~CloseRunnable() = default;
61 RefPtr
<BroadcastChannel
> mBC
;
64 class TeardownRunnable
{
66 explicit TeardownRunnable(BroadcastChannelChild
* aActor
) : mActor(aActor
) {
72 if (!mActor
->IsActorDestroyed()) {
78 virtual ~TeardownRunnable() = default;
81 RefPtr
<BroadcastChannelChild
> mActor
;
84 class TeardownRunnableOnMainThread final
: public Runnable
,
85 public TeardownRunnable
{
87 explicit TeardownRunnableOnMainThread(BroadcastChannelChild
* aActor
)
88 : Runnable("TeardownRunnableOnMainThread"), TeardownRunnable(aActor
) {}
90 NS_IMETHOD
Run() override
{
96 class TeardownRunnableOnWorker final
: public WorkerControlRunnable
,
97 public TeardownRunnable
{
99 TeardownRunnableOnWorker(WorkerPrivate
* aWorkerPrivate
,
100 BroadcastChannelChild
* aActor
)
101 : WorkerControlRunnable(aWorkerPrivate
, WorkerThreadUnchangedBusyCount
),
102 TeardownRunnable(aActor
) {}
104 bool WorkerRun(JSContext
*, WorkerPrivate
*) override
{
109 bool PreDispatch(WorkerPrivate
* aWorkerPrivate
) override
{ return true; }
111 void PostDispatch(WorkerPrivate
* aWorkerPrivate
,
112 bool aDispatchResult
) override
{}
114 bool PreRun(WorkerPrivate
* aWorkerPrivate
) override
{ return true; }
116 void PostRun(JSContext
* aCx
, WorkerPrivate
* aWorkerPrivate
,
117 bool aRunResult
) override
{}
122 BroadcastChannel::BroadcastChannel(nsIGlobalObject
* aGlobal
,
123 const nsAString
& aChannel
,
124 const nsID
& aPortUUID
)
125 : DOMEventTargetHelper(aGlobal
),
126 mRefMessageBodyService(RefMessageBodyService::GetOrCreate()),
129 mPortUUID(aPortUUID
) {
131 KeepAliveIfHasListenersFor(u
"message"_ns
);
134 BroadcastChannel::~BroadcastChannel() {
136 MOZ_ASSERT(!mWorkerRef
);
139 JSObject
* BroadcastChannel::WrapObject(JSContext
* aCx
,
140 JS::Handle
<JSObject
*> aGivenProto
) {
141 return BroadcastChannel_Binding::Wrap(aCx
, this, aGivenProto
);
145 already_AddRefed
<BroadcastChannel
> BroadcastChannel::Constructor(
146 const GlobalObject
& aGlobal
, const nsAString
& aChannel
, ErrorResult
& aRv
) {
147 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
148 if (NS_WARN_IF(!global
)) {
149 aRv
.Throw(NS_ERROR_FAILURE
);
154 aRv
= nsContentUtils::GenerateUUIDInPlace(portUUID
);
159 RefPtr
<BroadcastChannel
> bc
=
160 new BroadcastChannel(global
, aChannel
, portUUID
);
162 nsAutoCString origin
;
163 nsAutoString originNoSuffix
;
164 PrincipalInfo storagePrincipalInfo
;
166 StorageAccess storageAccess
;
168 nsCOMPtr
<nsICookieJarSettings
> cjs
;
169 if (NS_IsMainThread()) {
170 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryInterface(global
);
171 if (NS_WARN_IF(!window
)) {
172 aRv
.Throw(NS_ERROR_FAILURE
);
176 nsCOMPtr
<nsIGlobalObject
> incumbent
= mozilla::dom::GetIncumbentGlobal();
179 aRv
.Throw(NS_ERROR_FAILURE
);
183 nsCOMPtr
<nsIScriptObjectPrincipal
> sop
= do_QueryInterface(incumbent
);
184 if (NS_WARN_IF(!sop
)) {
185 aRv
.Throw(NS_ERROR_FAILURE
);
189 nsIPrincipal
* storagePrincipal
= sop
->GetEffectiveStoragePrincipal();
190 if (!storagePrincipal
) {
191 aRv
.Throw(NS_ERROR_UNEXPECTED
);
195 aRv
= storagePrincipal
->GetOrigin(origin
);
196 if (NS_WARN_IF(aRv
.Failed())) {
200 nsAutoCString originNoSuffix8
;
201 aRv
= storagePrincipal
->GetOriginNoSuffix(originNoSuffix8
);
202 if (NS_WARN_IF(aRv
.Failed())) {
205 CopyUTF8toUTF16(originNoSuffix8
, originNoSuffix
);
207 aRv
= PrincipalToPrincipalInfo(storagePrincipal
, &storagePrincipalInfo
);
208 if (NS_WARN_IF(aRv
.Failed())) {
212 storageAccess
= StorageAllowedForWindow(window
);
214 Document
* doc
= window
->GetExtantDoc();
216 cjs
= doc
->CookieJarSettings();
219 JSContext
* cx
= aGlobal
.Context();
221 WorkerPrivate
* workerPrivate
= GetWorkerPrivateFromContext(cx
);
222 MOZ_ASSERT(workerPrivate
);
224 RefPtr
<StrongWorkerRef
> workerRef
= StrongWorkerRef::Create(
225 workerPrivate
, "BroadcastChannel", [bc
]() { bc
->Shutdown(); });
226 // We are already shutting down the worker. Let's return a non-active
228 if (NS_WARN_IF(!workerRef
)) {
229 aRv
.Throw(NS_ERROR_FAILURE
);
233 storageAccess
= workerPrivate
->StorageAccess();
234 storagePrincipalInfo
= workerPrivate
->GetEffectiveStoragePrincipalInfo();
235 origin
= workerPrivate
->EffectiveStoragePrincipalOrigin();
237 originNoSuffix
= workerPrivate
->GetLocationInfo().mOrigin
;
239 bc
->mWorkerRef
= workerRef
;
241 cjs
= workerPrivate
->CookieJarSettings();
244 // We want to allow opaque origins.
245 if (storagePrincipalInfo
.type() != PrincipalInfo::TNullPrincipalInfo
&&
246 (storageAccess
== StorageAccess::eDeny
||
247 (ShouldPartitionStorage(storageAccess
) &&
248 !StoragePartitioningEnabled(storageAccess
, cjs
)))) {
249 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
253 // Register this component to PBackground.
254 PBackgroundChild
* actorChild
= BackgroundChild::GetOrCreateForCurrentThread();
255 if (NS_WARN_IF(!actorChild
)) {
256 // Firefox is probably shutting down. Let's return a 'generic' error.
257 aRv
.Throw(NS_ERROR_FAILURE
);
261 PBroadcastChannelChild
* actor
= actorChild
->SendPBroadcastChannelConstructor(
262 storagePrincipalInfo
, origin
, nsString(aChannel
));
264 bc
->mActor
= static_cast<BroadcastChannelChild
*>(actor
);
265 MOZ_ASSERT(bc
->mActor
);
267 bc
->mActor
->SetParent(bc
);
268 bc
->mOriginNoSuffix
= originNoSuffix
;
273 void BroadcastChannel::PostMessage(JSContext
* aCx
,
274 JS::Handle
<JS::Value
> aMessage
,
276 if (mState
!= StateActive
) {
277 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
281 Maybe
<nsID
> agentClusterId
;
282 nsCOMPtr
<nsIGlobalObject
> global
= GetOwnerGlobal();
285 agentClusterId
= global
->GetAgentClusterId();
288 RefPtr
<SharedMessageBody
> data
= new SharedMessageBody(
289 StructuredCloneHolder::TransferringNotSupported
, agentClusterId
);
291 data
->Write(aCx
, aMessage
, JS::UndefinedHandleValue
, mPortUUID
,
292 mRefMessageBodyService
, aRv
);
293 if (NS_WARN_IF(aRv
.Failed())) {
297 RemoveDocFromBFCache();
300 SharedMessageBody::FromSharedToMessageChild(mActor
->Manager(), data
, message
);
301 mActor
->SendPostMessage(message
);
304 void BroadcastChannel::Close() {
305 if (mState
!= StateActive
) {
309 // We cannot call Shutdown() immediatelly because we could have some
310 // postMessage runnable already dispatched. Instead, we change the state to
311 // StateClosed and we shutdown the actor asynchrounsly.
313 mState
= StateClosed
;
314 RefPtr
<CloseRunnable
> runnable
= new CloseRunnable(this);
316 if (NS_FAILED(NS_DispatchToCurrentThread(runnable
))) {
317 NS_WARNING("Failed to dispatch to the current thread!");
321 void BroadcastChannel::Shutdown() {
322 mState
= StateClosed
;
324 // The DTOR of this WorkerRef will release the worker for us.
325 mWorkerRef
= nullptr;
328 mActor
->SetParent(nullptr);
330 if (NS_IsMainThread()) {
331 RefPtr
<TeardownRunnableOnMainThread
> runnable
=
332 new TeardownRunnableOnMainThread(mActor
);
333 NS_DispatchToCurrentThread(runnable
);
335 WorkerPrivate
* workerPrivate
= GetCurrentThreadWorkerPrivate();
336 MOZ_ASSERT(workerPrivate
);
338 RefPtr
<TeardownRunnableOnWorker
> runnable
=
339 new TeardownRunnableOnWorker(workerPrivate
, mActor
);
340 runnable
->Dispatch();
346 IgnoreKeepAliveIfHasListenersFor(u
"message"_ns
);
349 void BroadcastChannel::RemoveDocFromBFCache() {
350 if (!NS_IsMainThread()) {
354 if (nsPIDOMWindowInner
* window
= GetOwner()) {
355 window
->RemoveFromBFCacheSync();
359 void BroadcastChannel::DisconnectFromOwner() {
361 DOMEventTargetHelper::DisconnectFromOwner();
364 void BroadcastChannel::MessageReceived(const MessageData
& aData
) {
365 if (NS_FAILED(CheckCurrentGlobalCorrectness())) {
366 RemoveDocFromBFCache();
370 // Let's ignore messages after a close/shutdown.
371 if (mState
!= StateActive
) {
375 nsCOMPtr
<nsIGlobalObject
> globalObject
;
377 if (NS_IsMainThread()) {
378 globalObject
= GetParentObject();
380 WorkerPrivate
* workerPrivate
= GetCurrentThreadWorkerPrivate();
381 MOZ_ASSERT(workerPrivate
);
382 globalObject
= workerPrivate
->GlobalScope();
386 if (!globalObject
|| !jsapi
.Init(globalObject
)) {
387 NS_WARNING("Failed to initialize AutoJSAPI object.");
391 JSContext
* cx
= jsapi
.cx();
393 RefPtr
<SharedMessageBody
> data
= SharedMessageBody::FromMessageToSharedChild(
394 aData
, StructuredCloneHolder::TransferringNotSupported
);
395 if (NS_WARN_IF(!data
)) {
400 IgnoredErrorResult rv
;
401 JS::Rooted
<JS::Value
> value(cx
);
403 data
->Read(cx
, &value
, mRefMessageBodyService
,
404 SharedMessageBody::ReadMethod::KeepRefMessageBody
, rv
);
405 if (NS_WARN_IF(rv
.Failed())) {
406 JS_ClearPendingException(cx
);
411 RemoveDocFromBFCache();
413 RootedDictionary
<MessageEventInit
> init(cx
);
414 init
.mBubbles
= false;
415 init
.mCancelable
= false;
416 init
.mOrigin
= mOriginNoSuffix
;
419 RefPtr
<MessageEvent
> event
=
420 MessageEvent::Constructor(this, u
"message"_ns
, init
);
422 event
->SetTrusted(true);
424 DispatchEvent(*event
);
427 void BroadcastChannel::MessageDelivered(const nsID
& aMessageID
,
428 uint32_t aOtherBCs
) {
429 mRefMessageBodyService
->SetMaxCount(aMessageID
, aOtherBCs
);
432 void BroadcastChannel::DispatchError(JSContext
* aCx
) {
433 RootedDictionary
<MessageEventInit
> init(aCx
);
434 init
.mBubbles
= false;
435 init
.mCancelable
= false;
436 init
.mOrigin
= mOriginNoSuffix
;
438 RefPtr
<Event
> event
=
439 MessageEvent::Constructor(this, u
"messageerror"_ns
, init
);
440 event
->SetTrusted(true);
442 DispatchEvent(*event
);
445 NS_IMPL_CYCLE_COLLECTION_CLASS(BroadcastChannel
)
447 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(BroadcastChannel
,
448 DOMEventTargetHelper
)
449 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
451 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(BroadcastChannel
,
452 DOMEventTargetHelper
)
454 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
456 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BroadcastChannel
)
457 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper
)
459 NS_IMPL_ADDREF_INHERITED(BroadcastChannel
, DOMEventTargetHelper
)
460 NS_IMPL_RELEASE_INHERITED(BroadcastChannel
, DOMEventTargetHelper
)
463 } // namespace mozilla