BroadcastChannel.cpp (16656B)
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/. */ 6 7 #include "BroadcastChannel.h" 8 9 #include "BroadcastChannelChild.h" 10 #include "mozilla/StorageAccess.h" 11 #include "mozilla/dom/BroadcastChannelBinding.h" 12 #include "mozilla/dom/Document.h" 13 #include "mozilla/dom/File.h" 14 #include "mozilla/dom/MessageEvent.h" 15 #include "mozilla/dom/MessageEventBinding.h" 16 #include "mozilla/dom/Navigator.h" 17 #include "mozilla/dom/RefMessageBodyService.h" 18 #include "mozilla/dom/RootedDictionary.h" 19 #include "mozilla/dom/SharedMessageBody.h" 20 #include "mozilla/dom/StructuredCloneHolder.h" 21 #include "mozilla/dom/WorkerRef.h" 22 #include "mozilla/dom/WorkerRunnable.h" 23 #include "mozilla/dom/WorkerScope.h" 24 #include "mozilla/dom/ipc/StructuredCloneData.h" 25 #include "mozilla/ipc/BackgroundChild.h" 26 #include "mozilla/ipc/BackgroundUtils.h" 27 #include "mozilla/ipc/PBackgroundChild.h" 28 #include "nsGlobalWindowInner.h" 29 #include "nsICookieJarSettings.h" 30 31 #ifdef XP_WIN 32 # undef PostMessage 33 #endif 34 35 namespace mozilla { 36 37 using namespace ipc; 38 39 namespace dom { 40 41 using namespace ipc; 42 43 namespace { 44 45 class CloseRunnable final : public DiscardableRunnable { 46 public: 47 explicit CloseRunnable(BroadcastChannel* aBC) 48 : DiscardableRunnable("BroadcastChannel CloseRunnable"), mBC(aBC) { 49 MOZ_ASSERT(mBC); 50 } 51 52 NS_IMETHOD Run() override { 53 mBC->Shutdown(); 54 return NS_OK; 55 } 56 57 private: 58 ~CloseRunnable() = default; 59 60 RefPtr<BroadcastChannel> mBC; 61 }; 62 63 class TeardownRunnable { 64 protected: 65 explicit TeardownRunnable(BroadcastChannelChild* aActor) : mActor(aActor) { 66 MOZ_ASSERT(mActor); 67 } 68 69 void RunInternal() { 70 MOZ_ASSERT(mActor); 71 if (!mActor->IsActorDestroyed()) { 72 mActor->SendClose(); 73 } 74 } 75 76 protected: 77 virtual ~TeardownRunnable() = default; 78 79 private: 80 RefPtr<BroadcastChannelChild> mActor; 81 }; 82 83 class TeardownRunnableOnMainThread final : public Runnable, 84 public TeardownRunnable { 85 public: 86 explicit TeardownRunnableOnMainThread(BroadcastChannelChild* aActor) 87 : Runnable("TeardownRunnableOnMainThread"), TeardownRunnable(aActor) {} 88 89 NS_IMETHOD Run() override { 90 RunInternal(); 91 return NS_OK; 92 } 93 }; 94 95 class TeardownRunnableOnWorker final : public WorkerControlRunnable, 96 public TeardownRunnable { 97 public: 98 TeardownRunnableOnWorker(WorkerPrivate* aWorkerPrivate, 99 BroadcastChannelChild* aActor) 100 : WorkerControlRunnable("TeardownRunnableOnWorker"), 101 TeardownRunnable(aActor) {} 102 103 bool WorkerRun(JSContext*, WorkerPrivate*) override { 104 RunInternal(); 105 return true; 106 } 107 108 bool PreDispatch(WorkerPrivate* aWorkerPrivate) override { return true; } 109 110 void PostDispatch(WorkerPrivate* aWorkerPrivate, 111 bool aDispatchResult) override {} 112 113 bool PreRun(WorkerPrivate* aWorkerPrivate) override { return true; } 114 115 void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, 116 bool aRunResult) override {} 117 }; 118 119 } // namespace 120 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); 131 } 132 133 BroadcastChannel::~BroadcastChannel() { 134 Shutdown(); 135 MOZ_ASSERT(!mWorkerRef); 136 } 137 138 JSObject* BroadcastChannel::WrapObject(JSContext* aCx, 139 JS::Handle<JSObject*> aGivenProto) { 140 return BroadcastChannel_Binding::Wrap(aCx, this, aGivenProto); 141 } 142 143 /* static */ 144 already_AddRefed<BroadcastChannel> 145 BroadcastChannel::UnpartitionedTestingChannel(const GlobalObject& aGlobal, 146 const nsAString& aChannel, 147 ErrorResult& aRv) { 148 // This function must not be used outside of automation. 149 if (!xpc::IsInAutomation()) { 150 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 151 return nullptr; 152 } 153 154 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); 155 if (NS_WARN_IF(!global)) { 156 aRv.Throw(NS_ERROR_FAILURE); 157 return nullptr; 158 } 159 160 nsID portUUID = {}; 161 aRv = nsID::GenerateUUIDInPlace(portUUID); 162 if (aRv.Failed()) { 163 return nullptr; 164 } 165 166 RefPtr<BroadcastChannel> bc = 167 new BroadcastChannel(global, aChannel, portUUID); 168 169 nsCOMPtr<nsIPrincipal> unpartitionedPrincipal; 170 171 if (NS_IsMainThread()) { 172 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global); 173 if (NS_WARN_IF(!window)) { 174 aRv.Throw(NS_ERROR_FAILURE); 175 return nullptr; 176 } 177 178 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(global); 179 if (NS_WARN_IF(!sop)) { 180 aRv.Throw(NS_ERROR_FAILURE); 181 return nullptr; 182 } 183 184 // We are *intentionally* getting the unpartitioned principal here for 185 // testing purposes, but the correct thing to do normally is to get the 186 // storage key/principal. Passerby, do not imitate this code. 187 unpartitionedPrincipal = sop->GetPrincipal(); 188 if (!unpartitionedPrincipal) { 189 aRv.Throw(NS_ERROR_UNEXPECTED); 190 return nullptr; 191 } 192 } else { 193 JSContext* cx = aGlobal.Context(); 194 195 WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx); 196 MOZ_ASSERT(workerPrivate); 197 198 RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create( 199 workerPrivate, "BroadcastChannel", [bc]() { bc->Shutdown(); }); 200 // We are already shutting down the worker. Let's return a non-active 201 // object. 202 if (NS_WARN_IF(!workerRef)) { 203 aRv.Throw(NS_ERROR_FAILURE); 204 return nullptr; 205 } 206 207 // We are *intentionally* getting the unpartitioned principal here for 208 // testing purposes, but the correct thing to do normally is to get the 209 // storage key/principal. Passerby, do not imitate this code. 210 unpartitionedPrincipal = workerPrivate->GetPrincipal(); 211 212 bc->mWorkerRef = workerRef; 213 } 214 215 // Register this component to PBackground. 216 PBackgroundChild* actorChild = BackgroundChild::GetOrCreateForCurrentThread(); 217 if (NS_WARN_IF(!actorChild)) { 218 // Firefox is probably shutting down. Let's return a 'generic' error. 219 aRv.Throw(NS_ERROR_FAILURE); 220 return nullptr; 221 } 222 223 nsAutoCString origin; 224 aRv = unpartitionedPrincipal->GetOrigin(origin); 225 if (NS_WARN_IF(aRv.Failed())) { 226 return nullptr; 227 } 228 229 nsString originForEvents; 230 aRv = nsContentUtils::GetWebExposedOriginSerialization(unpartitionedPrincipal, 231 originForEvents); 232 if (NS_WARN_IF(aRv.Failed())) { 233 return nullptr; 234 } 235 236 PrincipalInfo unpartitionedPrincipalInfo; 237 aRv = PrincipalToPrincipalInfo(unpartitionedPrincipal, 238 &unpartitionedPrincipalInfo); 239 if (NS_WARN_IF(aRv.Failed())) { 240 return nullptr; 241 } 242 243 PBroadcastChannelChild* actor = actorChild->SendPBroadcastChannelConstructor( 244 unpartitionedPrincipalInfo, origin, nsString(aChannel)); 245 if (!actor) { 246 // The PBackground actor is shutting down, return a 'generic' error. 247 aRv.Throw(NS_ERROR_FAILURE); 248 return nullptr; 249 } 250 251 bc->mActor = static_cast<BroadcastChannelChild*>(actor); 252 bc->mActor->SetParent(bc); 253 bc->mOriginForEvents = std::move(originForEvents); 254 255 return bc.forget(); 256 } 257 258 /* static */ 259 already_AddRefed<BroadcastChannel> BroadcastChannel::Constructor( 260 const GlobalObject& aGlobal, const nsAString& aChannel, ErrorResult& aRv) { 261 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); 262 if (NS_WARN_IF(!global)) { 263 aRv.Throw(NS_ERROR_FAILURE); 264 return nullptr; 265 } 266 267 nsID portUUID = {}; 268 aRv = nsID::GenerateUUIDInPlace(portUUID); 269 if (aRv.Failed()) { 270 return nullptr; 271 } 272 273 RefPtr<BroadcastChannel> bc = 274 new BroadcastChannel(global, aChannel, portUUID); 275 276 nsCOMPtr<nsIPrincipal> storagePrincipal; 277 278 StorageAccess storageAccess; 279 280 nsCOMPtr<nsICookieJarSettings> cjs; 281 if (NS_IsMainThread()) { 282 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global); 283 if (NS_WARN_IF(!window)) { 284 aRv.Throw(NS_ERROR_FAILURE); 285 return nullptr; 286 } 287 288 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(global); 289 if (NS_WARN_IF(!sop)) { 290 aRv.Throw(NS_ERROR_FAILURE); 291 return nullptr; 292 } 293 294 storagePrincipal = sop->GetEffectiveStoragePrincipal(); 295 if (!storagePrincipal) { 296 aRv.Throw(NS_ERROR_UNEXPECTED); 297 return nullptr; 298 } 299 storageAccess = StorageAllowedForWindow(window); 300 301 Document* doc = window->GetExtantDoc(); 302 if (doc) { 303 cjs = doc->CookieJarSettings(); 304 } 305 } else { 306 JSContext* cx = aGlobal.Context(); 307 308 WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx); 309 MOZ_ASSERT(workerPrivate); 310 311 RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create( 312 workerPrivate, "BroadcastChannel", [bc]() { bc->Shutdown(); }); 313 // We are already shutting down the worker. Let's return a non-active 314 // object. 315 if (NS_WARN_IF(!workerRef)) { 316 aRv.Throw(NS_ERROR_FAILURE); 317 return nullptr; 318 } 319 320 storageAccess = workerPrivate->StorageAccess(); 321 322 storagePrincipal = workerPrivate->GetEffectiveStoragePrincipal(); 323 324 bc->mWorkerRef = workerRef; 325 326 cjs = workerPrivate->CookieJarSettings(); 327 } 328 329 // We want to allow opaque origins. 330 if (!storagePrincipal->GetIsNullPrincipal() && 331 (storageAccess == StorageAccess::eDeny || 332 (ShouldPartitionStorage(storageAccess) && 333 !StoragePartitioningEnabled(storageAccess, cjs)))) { 334 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); 335 return nullptr; 336 } 337 338 // Register this component to PBackground. 339 PBackgroundChild* actorChild = BackgroundChild::GetOrCreateForCurrentThread(); 340 if (NS_WARN_IF(!actorChild)) { 341 // Firefox is probably shutting down. Let's return a 'generic' error. 342 aRv.Throw(NS_ERROR_FAILURE); 343 return nullptr; 344 } 345 346 nsAutoCString origin; 347 aRv = storagePrincipal->GetOrigin(origin); 348 if (NS_WARN_IF(aRv.Failed())) { 349 return nullptr; 350 } 351 352 nsString originForEvents; 353 aRv = nsContentUtils::GetWebExposedOriginSerialization(storagePrincipal, 354 originForEvents); 355 if (NS_WARN_IF(aRv.Failed())) { 356 return nullptr; 357 } 358 359 PrincipalInfo storagePrincipalInfo; 360 aRv = PrincipalToPrincipalInfo(storagePrincipal, &storagePrincipalInfo); 361 if (NS_WARN_IF(aRv.Failed())) { 362 return nullptr; 363 } 364 365 PBroadcastChannelChild* actor = actorChild->SendPBroadcastChannelConstructor( 366 storagePrincipalInfo, origin, nsString(aChannel)); 367 if (!actor) { 368 // The PBackground actor is shutting down, return a 'generic' error. 369 aRv.Throw(NS_ERROR_FAILURE); 370 return nullptr; 371 } 372 373 bc->mActor = static_cast<BroadcastChannelChild*>(actor); 374 bc->mActor->SetParent(bc); 375 bc->mOriginForEvents = std::move(originForEvents); 376 377 return bc.forget(); 378 } 379 380 void BroadcastChannel::PostMessage(JSContext* aCx, 381 JS::Handle<JS::Value> aMessage, 382 ErrorResult& aRv) { 383 nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal(); 384 if (!global || !global->IsEligibleForMessaging()) { 385 return; 386 } 387 388 if (mState != StateActive) { 389 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 390 return; 391 } 392 393 Maybe<nsID> agentClusterId = global->GetAgentClusterId(); 394 395 RefPtr<SharedMessageBody> data = new SharedMessageBody( 396 StructuredCloneHolder::TransferringNotSupported, agentClusterId); 397 398 data->Write(aCx, aMessage, JS::UndefinedHandleValue, mPortUUID, 399 mRefMessageBodyService, aRv); 400 if (NS_WARN_IF(aRv.Failed())) { 401 return; 402 } 403 404 RemoveDocFromBFCache(); 405 406 MessageData message; 407 SharedMessageBody::FromSharedToMessageChild(mActor->Manager(), data, message); 408 mActor->SendPostMessage(message); 409 } 410 411 void BroadcastChannel::Close() { 412 if (mState != StateActive) { 413 return; 414 } 415 416 // We cannot call Shutdown() immediatelly because we could have some 417 // postMessage runnable already dispatched. Instead, we change the state to 418 // StateClosed and we shutdown the actor asynchrounsly. 419 420 mState = StateClosed; 421 RefPtr<CloseRunnable> runnable = new CloseRunnable(this); 422 423 if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) { 424 NS_WARNING("Failed to dispatch to the current thread!"); 425 } 426 } 427 428 void BroadcastChannel::Shutdown() { 429 mState = StateClosed; 430 431 // The DTOR of this WorkerRef will release the worker for us. 432 mWorkerRef = nullptr; 433 434 if (mActor) { 435 mActor->SetParent(nullptr); 436 437 if (NS_IsMainThread()) { 438 RefPtr<TeardownRunnableOnMainThread> runnable = 439 new TeardownRunnableOnMainThread(mActor); 440 NS_DispatchToCurrentThread(runnable); 441 } else { 442 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); 443 MOZ_ASSERT(workerPrivate); 444 445 RefPtr<TeardownRunnableOnWorker> runnable = 446 new TeardownRunnableOnWorker(workerPrivate, mActor); 447 runnable->Dispatch(workerPrivate); 448 } 449 450 mActor = nullptr; 451 } 452 453 IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onmessage); 454 } 455 456 void BroadcastChannel::RemoveDocFromBFCache() { 457 if (!NS_IsMainThread()) { 458 return; 459 } 460 461 if (nsPIDOMWindowInner* window = GetOwnerWindow()) { 462 window->RemoveFromBFCacheSync(); 463 } 464 } 465 466 void BroadcastChannel::DisconnectFromOwner() { 467 Shutdown(); 468 DOMEventTargetHelper::DisconnectFromOwner(); 469 } 470 471 void BroadcastChannel::MessageReceived(const MessageData& aData) { 472 if (NS_FAILED(CheckCurrentGlobalCorrectness())) { 473 RemoveDocFromBFCache(); 474 return; 475 } 476 477 nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal(); 478 if (!global || !global->IsEligibleForMessaging()) { 479 return; 480 } 481 482 // Let's ignore messages after a close/shutdown. 483 if (mState != StateActive) { 484 return; 485 } 486 487 nsCOMPtr<nsIGlobalObject> globalObject; 488 489 if (NS_IsMainThread()) { 490 globalObject = GetParentObject(); 491 } else { 492 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); 493 MOZ_ASSERT(workerPrivate); 494 globalObject = workerPrivate->GlobalScope(); 495 } 496 497 AutoJSAPI jsapi; 498 if (!globalObject || !jsapi.Init(globalObject)) { 499 NS_WARNING("Failed to initialize AutoJSAPI object."); 500 return; 501 } 502 503 JSContext* cx = jsapi.cx(); 504 505 RefPtr<SharedMessageBody> data = SharedMessageBody::FromMessageToSharedChild( 506 aData, StructuredCloneHolder::TransferringNotSupported); 507 if (NS_WARN_IF(!data)) { 508 DispatchError(cx); 509 return; 510 } 511 512 IgnoredErrorResult rv; 513 JS::Rooted<JS::Value> value(cx); 514 515 data->Read(cx, &value, mRefMessageBodyService, 516 SharedMessageBody::ReadMethod::KeepRefMessageBody, rv); 517 if (NS_WARN_IF(rv.Failed())) { 518 JS_ClearPendingException(cx); 519 DispatchError(cx); 520 return; 521 } 522 523 RemoveDocFromBFCache(); 524 525 RootedDictionary<MessageEventInit> init(cx); 526 init.mBubbles = false; 527 init.mCancelable = false; 528 init.mOrigin = mOriginForEvents; 529 init.mData = value; 530 531 RefPtr<MessageEvent> event = 532 MessageEvent::Constructor(this, u"message"_ns, init); 533 534 event->SetTrusted(true); 535 536 DispatchEvent(*event); 537 } 538 539 void BroadcastChannel::MessageDelivered(const nsID& aMessageID, 540 uint32_t aOtherBCs) { 541 mRefMessageBodyService->SetMaxCount(aMessageID, aOtherBCs); 542 } 543 544 void BroadcastChannel::DispatchError(JSContext* aCx) { 545 RootedDictionary<MessageEventInit> init(aCx); 546 init.mBubbles = false; 547 init.mCancelable = false; 548 init.mOrigin = mOriginForEvents; 549 550 RefPtr<Event> event = 551 MessageEvent::Constructor(this, u"messageerror"_ns, init); 552 event->SetTrusted(true); 553 554 DispatchEvent(*event); 555 } 556 557 NS_IMPL_CYCLE_COLLECTION_CLASS(BroadcastChannel) 558 559 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(BroadcastChannel, 560 DOMEventTargetHelper) 561 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 562 563 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(BroadcastChannel, 564 DOMEventTargetHelper) 565 tmp->Shutdown(); 566 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 567 568 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BroadcastChannel) 569 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) 570 571 NS_IMPL_ADDREF_INHERITED(BroadcastChannel, DOMEventTargetHelper) 572 NS_IMPL_RELEASE_INHERITED(BroadcastChannel, DOMEventTargetHelper) 573 574 } // namespace dom 575 } // namespace mozilla