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