tor-browser

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

WakeLockJS.cpp (8267B)


      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 "WakeLockJS.h"
      8 
      9 #include "ErrorList.h"
     10 #include "WakeLock.h"
     11 #include "WakeLockSentinel.h"
     12 #include "mozilla/AlreadyAddRefed.h"
     13 #include "mozilla/Assertions.h"
     14 #include "mozilla/Hal.h"
     15 #include "mozilla/Logging.h"
     16 #include "mozilla/StaticPrefs_dom.h"
     17 #include "mozilla/dom/Document.h"
     18 #include "mozilla/dom/Event.h"
     19 #include "mozilla/dom/EventTarget.h"
     20 #include "mozilla/dom/FeaturePolicyUtils.h"
     21 #include "mozilla/dom/Navigator.h"
     22 #include "mozilla/dom/Promise.h"
     23 #include "mozilla/dom/WakeLockBinding.h"
     24 #include "nsCOMPtr.h"
     25 #include "nsCRT.h"
     26 #include "nsContentPermissionHelper.h"
     27 #include "nsError.h"
     28 #include "nsIGlobalObject.h"
     29 #include "nsISupports.h"
     30 #include "nsPIDOMWindow.h"
     31 #include "nsServiceManagerUtils.h"
     32 #include "nscore.h"
     33 
     34 namespace mozilla::dom {
     35 
     36 static mozilla::LazyLogModule sLogger("ScreenWakeLock");
     37 
     38 #define MIN_BATTERY_LEVEL 0.05
     39 
     40 nsLiteralCString WakeLockJS::GetRequestErrorMessage(RequestError aRv) {
     41  switch (aRv) {
     42    case RequestError::DocInactive:
     43      return "The requesting document is inactive."_ns;
     44    case RequestError::DocHidden:
     45      return "The requesting document is hidden."_ns;
     46    case RequestError::PolicyDisallowed:
     47      return "A permissions policy does not allow screen-wake-lock for the requesting document."_ns;
     48    case RequestError::PrefDisabled:
     49      return "The pref dom.screenwakelock.enabled is disabled."_ns;
     50    case RequestError::InternalFailure:
     51      return "A browser-internal error occured."_ns;
     52    case RequestError::PermissionDenied:
     53      return "Permission to request screen-wake-lock was denied."_ns;
     54    default:
     55      MOZ_ASSERT_UNREACHABLE("Unknown error reason");
     56      return "Unknown error"_ns;
     57  }
     58 }
     59 
     60 // https://w3c.github.io/screen-wake-lock/#the-request-method steps 2-5
     61 WakeLockJS::RequestError WakeLockJS::WakeLockAllowedForDocument(
     62    Document* aDoc) {
     63  if (!aDoc) {
     64    return RequestError::InternalFailure;
     65  }
     66 
     67  // Step 2. check policy-controlled feature screen-wake-lock
     68  if (!FeaturePolicyUtils::IsFeatureAllowed(aDoc, u"screen-wake-lock"_ns)) {
     69    return RequestError::PolicyDisallowed;
     70  }
     71 
     72  // Step 3. Deny wake lock for user agent specific reasons
     73  if (!StaticPrefs::dom_screenwakelock_enabled()) {
     74    return RequestError::PrefDisabled;
     75  }
     76 
     77  // Step 4 check doc active
     78  if (!aDoc->IsActive()) {
     79    return RequestError::DocInactive;
     80  }
     81 
     82  // Step 5. check doc visible
     83  if (aDoc->Hidden()) {
     84    return RequestError::DocHidden;
     85  }
     86 
     87  return RequestError::Success;
     88 }
     89 
     90 // https://w3c.github.io/screen-wake-lock/#dfn-applicable-wake-lock
     91 static bool IsWakeLockApplicable(WakeLockType aType) {
     92  hal::BatteryInformation batteryInfo;
     93  hal::GetCurrentBatteryInformation(&batteryInfo);
     94  if (batteryInfo.level() <= MIN_BATTERY_LEVEL && !batteryInfo.charging()) {
     95    return false;
     96  }
     97 
     98  // only currently supported wake lock type
     99  return aType == WakeLockType::Screen;
    100 }
    101 
    102 // https://w3c.github.io/screen-wake-lock/#dfn-release-a-wake-lock
    103 void ReleaseWakeLock(Document* aDoc, WakeLockSentinel* aLock,
    104                     WakeLockType aType) {
    105  MOZ_ASSERT(aLock);
    106  MOZ_ASSERT(aDoc);
    107 
    108  RefPtr<WakeLockSentinel> kungFuDeathGrip = aLock;
    109  aDoc->ActiveWakeLocks(aType).Remove(aLock);
    110  aLock->NotifyLockReleased();
    111  MOZ_LOG(sLogger, LogLevel::Debug, ("Released wake lock sentinel"));
    112 }
    113 
    114 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(WakeLockJS)
    115 
    116 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WakeLockJS)
    117  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
    118 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
    119 
    120 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WakeLockJS)
    121  tmp->DetachListeners();
    122  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
    123  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
    124 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
    125 
    126 NS_IMPL_CYCLE_COLLECTING_ADDREF(WakeLockJS)
    127 NS_IMPL_CYCLE_COLLECTING_RELEASE(WakeLockJS)
    128 
    129 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WakeLockJS)
    130  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
    131  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
    132  NS_INTERFACE_MAP_ENTRY(nsIObserver)
    133  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
    134 NS_INTERFACE_MAP_END
    135 
    136 WakeLockJS::WakeLockJS(nsPIDOMWindowInner* aWindow) : mWindow(aWindow) {
    137  AttachListeners();
    138 }
    139 
    140 WakeLockJS::~WakeLockJS() { DetachListeners(); }
    141 
    142 nsISupports* WakeLockJS::GetParentObject() const { return mWindow; }
    143 
    144 JSObject* WakeLockJS::WrapObject(JSContext* aCx,
    145                                 JS::Handle<JSObject*> aGivenProto) {
    146  return WakeLock_Binding::Wrap(aCx, this, aGivenProto);
    147 }
    148 
    149 // https://w3c.github.io/screen-wake-lock/#the-request-method Step 7.3
    150 Result<already_AddRefed<WakeLockSentinel>, WakeLockJS::RequestError>
    151 WakeLockJS::Obtain(WakeLockType aType, Document* aDoc) {
    152  // Step 7.3.1. check visibility again
    153  // Out of spec, but also check everything else
    154  RequestError rv = WakeLockAllowedForDocument(aDoc);
    155  if (rv != RequestError::Success) {
    156    return Err(rv);
    157  }
    158  // Step 7.3.3. let lock be a new WakeLockSentinel
    159  RefPtr<WakeLockSentinel> lock =
    160      MakeRefPtr<WakeLockSentinel>(mWindow->AsGlobal(), aType);
    161  // Step 7.3.2. acquire a wake lock
    162  if (IsWakeLockApplicable(aType)) {
    163    lock->AcquireActualLock();
    164  }
    165 
    166  // Steps 7.3.4. append lock to locks
    167  aDoc->ActiveWakeLocks(aType).Insert(lock);
    168 
    169  return lock.forget();
    170 }
    171 
    172 // https://w3c.github.io/screen-wake-lock/#the-request-method
    173 already_AddRefed<Promise> WakeLockJS::Request(WakeLockType aType,
    174                                              ErrorResult& aRv) {
    175  MOZ_LOG(sLogger, LogLevel::Debug, ("Received request for wake lock"));
    176  nsCOMPtr<nsIGlobalObject> global = mWindow->AsGlobal();
    177 
    178  RefPtr<Promise> promise = Promise::Create(global, aRv);
    179  NS_ENSURE_FALSE(aRv.Failed(), nullptr);
    180 
    181  // Steps 1-5
    182  nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
    183  RequestError rv = WakeLockAllowedForDocument(doc);
    184  if (rv != RequestError::Success) {
    185    promise->MaybeRejectWithNotAllowedError(GetRequestErrorMessage(rv));
    186    return promise.forget();
    187  }
    188 
    189  // For now, we don't check the permission as we always grant the lock
    190  // Step 7.3. Queue a task
    191  NS_DispatchToMainThread(NS_NewRunnableFunction(
    192      "ObtainWakeLock",
    193      [aType, promise, doc, self = RefPtr<WakeLockJS>(this)]() {
    194        auto lockOrErr = self->Obtain(aType, doc);
    195        if (lockOrErr.isOk()) {
    196          RefPtr<WakeLockSentinel> lock = lockOrErr.unwrap();
    197          promise->MaybeResolve(lock);
    198          MOZ_LOG(sLogger, LogLevel::Debug,
    199                  ("Resolved promise with wake lock sentinel"));
    200        } else {
    201          promise->MaybeRejectWithNotAllowedError(
    202              GetRequestErrorMessage(lockOrErr.unwrapErr()));
    203        }
    204      }));
    205 
    206  return promise.forget();
    207 }
    208 
    209 void WakeLockJS::AttachListeners() {
    210  hal::RegisterBatteryObserver(this);
    211 
    212  nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
    213  MOZ_ASSERT(prefBranch);
    214  DebugOnly<nsresult> rv =
    215      prefBranch->AddObserver("dom.screenwakelock.enabled", this, true);
    216  MOZ_ASSERT(NS_SUCCEEDED(rv));
    217 }
    218 
    219 void WakeLockJS::DetachListeners() {
    220  hal::UnregisterBatteryObserver(this);
    221 
    222  if (nsCOMPtr<nsIPrefBranch> prefBranch =
    223          do_GetService(NS_PREFSERVICE_CONTRACTID)) {
    224    prefBranch->RemoveObserver("dom.screenwakelock.enabled", this);
    225  }
    226 }
    227 
    228 NS_IMETHODIMP WakeLockJS::Observe(nsISupports* aSubject, const char* aTopic,
    229                                  const char16_t* aData) {
    230  if (nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
    231    if (!StaticPrefs::dom_screenwakelock_enabled()) {
    232      nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
    233      MOZ_ASSERT(doc);
    234      doc->UnlockAllWakeLocks(WakeLockType::Screen);
    235    }
    236  }
    237  return NS_OK;
    238 }
    239 
    240 void WakeLockJS::Notify(const hal::BatteryInformation& aBatteryInfo) {
    241  if (aBatteryInfo.level() > MIN_BATTERY_LEVEL || aBatteryInfo.charging()) {
    242    return;
    243  }
    244  nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
    245  MOZ_ASSERT(doc);
    246  doc->UnlockAllWakeLocks(WakeLockType::Screen);
    247 }
    248 
    249 }  // namespace mozilla::dom