tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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