tor-browser

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

LockManager.cpp (8615B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
      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 "mozilla/dom/LockManager.h"
      8 
      9 #include "mozilla/BasePrincipal.h"
     10 #include "mozilla/ErrorResult.h"
     11 #include "mozilla/dom/AutoEntryScript.h"
     12 #include "mozilla/dom/LockManagerBinding.h"
     13 #include "mozilla/dom/Promise.h"
     14 #include "mozilla/dom/WorkerCommon.h"
     15 #include "mozilla/dom/locks/LockManagerChild.h"
     16 #include "mozilla/dom/locks/LockRequestChild.h"
     17 #include "mozilla/dom/locks/PLockManager.h"
     18 #include "mozilla/ipc/BackgroundChild.h"
     19 #include "mozilla/ipc/BackgroundUtils.h"
     20 #include "mozilla/ipc/PBackgroundChild.h"
     21 
     22 namespace mozilla::dom {
     23 
     24 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(LockManager, mOwner)
     25 NS_IMPL_CYCLE_COLLECTING_ADDREF(LockManager)
     26 NS_IMPL_CYCLE_COLLECTING_RELEASE(LockManager)
     27 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LockManager)
     28  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
     29  NS_INTERFACE_MAP_ENTRY(nsISupports)
     30 NS_INTERFACE_MAP_END
     31 
     32 JSObject* LockManager::WrapObject(JSContext* aCx,
     33                                  JS::Handle<JSObject*> aGivenProto) {
     34  return LockManager_Binding::Wrap(aCx, this, aGivenProto);
     35 }
     36 
     37 LockManager::LockManager(nsIGlobalObject* aGlobal) : mOwner(aGlobal) {
     38  Maybe<nsID> clientID;
     39  nsCOMPtr<nsIPrincipal> principal;
     40 
     41  if (XRE_IsParentProcess() && aGlobal->PrincipalOrNull() &&
     42      aGlobal->PrincipalOrNull()->IsSystemPrincipal()) {
     43    clientID = Nothing();
     44    principal = aGlobal->PrincipalOrNull();
     45  } else {
     46    Maybe<ClientInfo> clientInfo = aGlobal->GetClientInfo();
     47    if (!clientInfo) {
     48      // Pass the nonworking object and let request()/query() throw.
     49      return;
     50    }
     51 
     52    principal = clientInfo->GetPrincipal().unwrapOr(nullptr);
     53    if (!principal) {
     54      return;
     55    }
     56 
     57    if (!principal->GetIsContentPrincipal()) {
     58      // Same, the methods will throw instead of the constructor.
     59      return;
     60    }
     61 
     62    clientID = Some(clientInfo->Id());
     63  }
     64 
     65  mozilla::ipc::PBackgroundChild* backgroundActor =
     66      mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
     67  mActor = new locks::LockManagerChild(aGlobal);
     68 
     69  if (!backgroundActor->SendPLockManagerConstructor(
     70          mActor, WrapNotNull(principal), clientID)) {
     71    // Failed to construct the actor. Pass the nonworking object and let the
     72    // methods throw.
     73    mActor = nullptr;
     74    return;
     75  }
     76 }
     77 
     78 already_AddRefed<LockManager> LockManager::Create(nsIGlobalObject& aGlobal) {
     79  RefPtr<LockManager> manager = new LockManager(&aGlobal);
     80 
     81  if (!NS_IsMainThread()) {
     82    // Grabbing WorkerRef may fail and that will cause the methods throw later.
     83    manager->mWorkerRef =
     84        WeakWorkerRef::Create(GetCurrentThreadWorkerPrivate(), [manager]() {
     85          // Others may grab a strong reference and block immediate destruction.
     86          // Shutdown early as we don't have to wait for them.
     87          manager->Shutdown();
     88          manager->mWorkerRef = nullptr;
     89        });
     90    // Do not handle the WeakWorkerRef creation fail here.
     91    // Suppose WorkerNavigator::Invalidate() should call LockManager::Shutdown()
     92    // before set WorkerNavigator::mLocks as nullptr.
     93  }
     94 
     95  return manager.forget();
     96 }
     97 
     98 static bool ValidateRequestArguments(const nsAString& name,
     99                                     const LockOptions& options,
    100                                     ErrorResult& aRv) {
    101  if (name.Length() > 0 && name.First() == u'-') {
    102    aRv.ThrowNotSupportedError("Names starting with `-` are reserved");
    103    return false;
    104  }
    105  if (options.mSteal) {
    106    if (options.mIfAvailable) {
    107      aRv.ThrowNotSupportedError(
    108          "`steal` and `ifAvailable` cannot be used together");
    109      return false;
    110    }
    111    if (options.mMode != LockMode::Exclusive) {
    112      aRv.ThrowNotSupportedError(
    113          "`steal` is only supported for exclusive lock requests");
    114      return false;
    115    }
    116  }
    117  if (options.mSignal.WasPassed()) {
    118    if (options.mSteal) {
    119      aRv.ThrowNotSupportedError(
    120          "`steal` and `signal` cannot be used together");
    121      return false;
    122    }
    123    if (options.mIfAvailable) {
    124      aRv.ThrowNotSupportedError(
    125          "`ifAvailable` and `signal` cannot be used together");
    126      return false;
    127    }
    128    if (options.mSignal.Value().Aborted()) {
    129      AutoJSAPI jsapi;
    130      if (!jsapi.Init(options.mSignal.Value().GetParentObject())) {
    131        aRv.ThrowNotSupportedError("Signal's realm isn't active anymore.");
    132        return false;
    133      }
    134 
    135      JSContext* cx = jsapi.cx();
    136      JS::Rooted<JS::Value> reason(cx);
    137      options.mSignal.Value().GetReason(cx, &reason);
    138      aRv.MightThrowJSException();
    139      aRv.ThrowJSException(cx, reason);
    140      return false;
    141    }
    142  }
    143  return true;
    144 }
    145 
    146 already_AddRefed<Promise> LockManager::Request(const nsAString& aName,
    147                                               LockGrantedCallback& aCallback,
    148                                               ErrorResult& aRv) {
    149  return Request(aName, LockOptions(), aCallback, aRv);
    150 };
    151 already_AddRefed<Promise> LockManager::Request(const nsAString& aName,
    152                                               const LockOptions& aOptions,
    153                                               LockGrantedCallback& aCallback,
    154                                               ErrorResult& aRv) {
    155  if (!mOwner->PrincipalOrNull() ||
    156      !mOwner->PrincipalOrNull()->IsSystemPrincipal()) {
    157    if (!mOwner->GetClientInfo()) {
    158      // We do have nsPIDOMWindowInner::IsFullyActive for this kind of check,
    159      // but this should be sufficient here as unloaded iframe is the only
    160      // non-fully-active case that Web Locks should worry about (since it does
    161      // not enter bfcache).
    162      aRv.ThrowInvalidStateError(
    163          "The document of the lock manager is not fully active");
    164      return nullptr;
    165    }
    166  }
    167 
    168  const StorageAccess access = mOwner->GetStorageAccess();
    169  bool allowed =
    170      access > StorageAccess::eDeny ||
    171      (StaticPrefs::
    172           privacy_partition_always_partition_third_party_non_cookie_storage() &&
    173       ShouldPartitionStorage(access));
    174  if (!allowed) {
    175    // Step 4: If origin is an opaque origin, then return a promise rejected
    176    // with a "SecurityError" DOMException.
    177    // But per https://w3c.github.io/web-locks/#lock-managers this really means
    178    // whether it has storage access.
    179    aRv.ThrowSecurityError("request() is not allowed in this context");
    180    return nullptr;
    181  }
    182 
    183  if (!mActor) {
    184    aRv.ThrowNotSupportedError(
    185        "Web Locks API is not enabled for this kind of document");
    186    return nullptr;
    187  }
    188 
    189  if (!NS_IsMainThread() && !mWorkerRef) {
    190    aRv.ThrowInvalidStateError("request() is not allowed at this point");
    191    return nullptr;
    192  }
    193 
    194  if (!ValidateRequestArguments(aName, aOptions, aRv)) {
    195    return nullptr;
    196  }
    197 
    198  RefPtr<Promise> promise = Promise::Create(mOwner, aRv);
    199  if (aRv.Failed()) {
    200    return nullptr;
    201  }
    202 
    203  mActor->RequestLock({nsString(aName), promise, &aCallback}, aOptions);
    204  return promise.forget();
    205 };
    206 
    207 already_AddRefed<Promise> LockManager::Query(ErrorResult& aRv) {
    208  if (!mOwner->PrincipalOrNull() ||
    209      !mOwner->PrincipalOrNull()->IsSystemPrincipal()) {
    210    if (!mOwner->GetClientInfo()) {
    211      aRv.ThrowInvalidStateError(
    212          "The document of the lock manager is not fully active");
    213      return nullptr;
    214    }
    215  }
    216 
    217  if (mOwner->GetStorageAccess() <= StorageAccess::eDeny) {
    218    aRv.ThrowSecurityError("query() is not allowed in this context");
    219    return nullptr;
    220  }
    221 
    222  if (!mActor) {
    223    aRv.ThrowNotSupportedError(
    224        "Web Locks API is not enabled for this kind of document");
    225    return nullptr;
    226  }
    227 
    228  if (!NS_IsMainThread() && !mWorkerRef) {
    229    aRv.ThrowInvalidStateError("query() is not allowed at this point");
    230    return nullptr;
    231  }
    232 
    233  RefPtr<Promise> promise = Promise::Create(mOwner, aRv);
    234  if (aRv.Failed()) {
    235    return nullptr;
    236  }
    237 
    238  mActor->SendQuery()->Then(
    239      GetCurrentSerialEventTarget(), __func__,
    240      [promise](locks::LockManagerChild::QueryPromise::ResolveOrRejectValue&&
    241                    aResult) {
    242        if (aResult.IsResolve()) {
    243          promise->MaybeResolve(aResult.ResolveValue());
    244        } else {
    245          promise->MaybeRejectWithUnknownError("Query failed");
    246        }
    247      });
    248  return promise.forget();
    249 };
    250 
    251 void LockManager::Shutdown() {
    252  if (mActor) {
    253    locks::PLockManagerChild::Send__delete__(mActor);
    254    mActor = nullptr;
    255  }
    256 }
    257 
    258 }  // namespace mozilla::dom