Backed out changeset 06f41c22f3a6 (bug 1888460) for causing linux xpcshell failures...
[gecko.git] / dom / broadcastchannel / BroadcastChannel.cpp
blobc89231badf7a9034ea4e06204939dd42cb38980a
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"
30 #ifdef XP_WIN
31 # undef PostMessage
32 #endif
34 namespace mozilla {
36 using namespace ipc;
38 namespace dom {
40 using namespace ipc;
42 namespace {
44 class CloseRunnable final : public DiscardableRunnable {
45 public:
46 explicit CloseRunnable(BroadcastChannel* aBC)
47 : DiscardableRunnable("BroadcastChannel CloseRunnable"), mBC(aBC) {
48 MOZ_ASSERT(mBC);
51 NS_IMETHOD Run() override {
52 mBC->Shutdown();
53 return NS_OK;
56 private:
57 ~CloseRunnable() = default;
59 RefPtr<BroadcastChannel> mBC;
62 class TeardownRunnable {
63 protected:
64 explicit TeardownRunnable(BroadcastChannelChild* aActor) : mActor(aActor) {
65 MOZ_ASSERT(mActor);
68 void RunInternal() {
69 MOZ_ASSERT(mActor);
70 if (!mActor->IsActorDestroyed()) {
71 mActor->SendClose();
75 protected:
76 virtual ~TeardownRunnable() = default;
78 private:
79 RefPtr<BroadcastChannelChild> mActor;
82 class TeardownRunnableOnMainThread final : public Runnable,
83 public TeardownRunnable {
84 public:
85 explicit TeardownRunnableOnMainThread(BroadcastChannelChild* aActor)
86 : Runnable("TeardownRunnableOnMainThread"), TeardownRunnable(aActor) {}
88 NS_IMETHOD Run() override {
89 RunInternal();
90 return NS_OK;
94 class TeardownRunnableOnWorker final : public WorkerControlRunnable,
95 public TeardownRunnable {
96 public:
97 TeardownRunnableOnWorker(WorkerPrivate* aWorkerPrivate,
98 BroadcastChannelChild* aActor)
99 : WorkerControlRunnable(aWorkerPrivate, "TeardownRunnableOnWorker",
100 WorkerThread),
101 TeardownRunnable(aActor) {}
103 bool WorkerRun(JSContext*, WorkerPrivate*) override {
104 RunInternal();
105 return true;
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 {}
119 } // namespace
121 BroadcastChannel::BroadcastChannel(nsIGlobalObject* aGlobal,
122 const nsAString& aChannel,
123 const nsID& aPortUUID)
124 : DOMEventTargetHelper(aGlobal),
125 mRefMessageBodyService(RefMessageBodyService::GetOrCreate()),
126 mChannel(aChannel),
127 mState(StateActive),
128 mPortUUID(aPortUUID) {
129 MOZ_ASSERT(aGlobal);
130 KeepAliveIfHasListenersFor(nsGkAtoms::onmessage);
133 BroadcastChannel::~BroadcastChannel() {
134 Shutdown();
135 MOZ_ASSERT(!mWorkerRef);
138 JSObject* BroadcastChannel::WrapObject(JSContext* aCx,
139 JS::Handle<JSObject*> aGivenProto) {
140 return BroadcastChannel_Binding::Wrap(aCx, this, aGivenProto);
143 /* static */
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);
149 return nullptr;
152 nsID portUUID = {};
153 aRv = nsID::GenerateUUIDInPlace(portUUID);
154 if (aRv.Failed()) {
155 return nullptr;
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);
170 return nullptr;
173 nsCOMPtr<nsIGlobalObject> incumbent = mozilla::dom::GetIncumbentGlobal();
175 if (!incumbent) {
176 aRv.Throw(NS_ERROR_FAILURE);
177 return nullptr;
180 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(incumbent);
181 if (NS_WARN_IF(!sop)) {
182 aRv.Throw(NS_ERROR_FAILURE);
183 return nullptr;
186 storagePrincipal = sop->GetEffectiveStoragePrincipal();
187 if (!storagePrincipal) {
188 aRv.Throw(NS_ERROR_UNEXPECTED);
189 return nullptr;
191 storageAccess = StorageAllowedForWindow(window);
193 Document* doc = window->GetExtantDoc();
194 if (doc) {
195 cjs = doc->CookieJarSettings();
197 } else {
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
206 // object.
207 if (NS_WARN_IF(!workerRef)) {
208 aRv.Throw(NS_ERROR_FAILURE);
209 return nullptr;
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);
227 return nullptr;
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);
235 return nullptr;
238 nsAutoCString origin;
239 aRv = storagePrincipal->GetOrigin(origin);
240 if (NS_WARN_IF(aRv.Failed())) {
241 return nullptr;
244 nsString originForEvents;
245 aRv = nsContentUtils::GetWebExposedOriginSerialization(storagePrincipal,
246 originForEvents);
247 if (NS_WARN_IF(aRv.Failed())) {
248 return nullptr;
251 PrincipalInfo storagePrincipalInfo;
252 aRv = PrincipalToPrincipalInfo(storagePrincipal, &storagePrincipalInfo);
253 if (NS_WARN_IF(aRv.Failed())) {
254 return nullptr;
257 PBroadcastChannelChild* actor = actorChild->SendPBroadcastChannelConstructor(
258 storagePrincipalInfo, origin, nsString(aChannel));
259 if (!actor) {
260 // The PBackground actor is shutting down, return a 'generic' error.
261 aRv.Throw(NS_ERROR_FAILURE);
262 return nullptr;
265 bc->mActor = static_cast<BroadcastChannelChild*>(actor);
266 bc->mActor->SetParent(bc);
267 bc->mOriginForEvents = std::move(originForEvents);
269 return bc.forget();
272 void BroadcastChannel::PostMessage(JSContext* aCx,
273 JS::Handle<JS::Value> aMessage,
274 ErrorResult& aRv) {
275 if (mState != StateActive) {
276 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
277 return;
280 Maybe<nsID> agentClusterId;
281 nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
282 MOZ_ASSERT(global);
283 if (global) {
284 agentClusterId = global->GetAgentClusterId();
287 if (!global->IsEligibleForMessaging()) {
288 return;
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())) {
297 return;
300 RemoveDocFromBFCache();
302 MessageData message;
303 SharedMessageBody::FromSharedToMessageChild(mActor->Manager(), data, message);
304 mActor->SendPostMessage(message);
307 void BroadcastChannel::Close() {
308 if (mState != StateActive) {
309 return;
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;
330 if (mActor) {
331 mActor->SetParent(nullptr);
333 if (NS_IsMainThread()) {
334 RefPtr<TeardownRunnableOnMainThread> runnable =
335 new TeardownRunnableOnMainThread(mActor);
336 NS_DispatchToCurrentThread(runnable);
337 } else {
338 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
339 MOZ_ASSERT(workerPrivate);
341 RefPtr<TeardownRunnableOnWorker> runnable =
342 new TeardownRunnableOnWorker(workerPrivate, mActor);
343 runnable->Dispatch();
346 mActor = nullptr;
349 IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onmessage);
352 void BroadcastChannel::RemoveDocFromBFCache() {
353 if (!NS_IsMainThread()) {
354 return;
357 if (nsPIDOMWindowInner* window = GetOwner()) {
358 window->RemoveFromBFCacheSync();
362 void BroadcastChannel::DisconnectFromOwner() {
363 Shutdown();
364 DOMEventTargetHelper::DisconnectFromOwner();
367 void BroadcastChannel::MessageReceived(const MessageData& aData) {
368 if (NS_FAILED(CheckCurrentGlobalCorrectness())) {
369 RemoveDocFromBFCache();
370 return;
373 // Let's ignore messages after a close/shutdown.
374 if (mState != StateActive) {
375 return;
378 nsCOMPtr<nsIGlobalObject> globalObject;
380 if (NS_IsMainThread()) {
381 globalObject = GetParentObject();
382 } else {
383 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
384 MOZ_ASSERT(workerPrivate);
385 globalObject = workerPrivate->GlobalScope();
388 AutoJSAPI jsapi;
389 if (!globalObject || !jsapi.Init(globalObject)) {
390 NS_WARNING("Failed to initialize AutoJSAPI object.");
391 return;
394 JSContext* cx = jsapi.cx();
396 RefPtr<SharedMessageBody> data = SharedMessageBody::FromMessageToSharedChild(
397 aData, StructuredCloneHolder::TransferringNotSupported);
398 if (NS_WARN_IF(!data)) {
399 DispatchError(cx);
400 return;
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);
410 DispatchError(cx);
411 return;
414 RemoveDocFromBFCache();
416 RootedDictionary<MessageEventInit> init(cx);
417 init.mBubbles = false;
418 init.mCancelable = false;
419 init.mOrigin = mOriginForEvents;
420 init.mData = value;
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)
456 tmp->Shutdown();
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)
465 } // namespace dom
466 } // namespace mozilla