no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / dom / power / WakeLockJS.cpp
blob50e4a716c0ee9f3280a9f6bde1c58022bb99b160
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/. */
7 #include "ErrorList.h"
8 #include "mozilla/AlreadyAddRefed.h"
9 #include "mozilla/Assertions.h"
10 #include "mozilla/Logging.h"
11 #include "mozilla/dom/Document.h"
12 #include "mozilla/dom/Event.h"
13 #include "mozilla/dom/EventTarget.h"
14 #include "mozilla/dom/FeaturePolicyUtils.h"
15 #include "mozilla/dom/Navigator.h"
16 #include "mozilla/dom/Promise.h"
17 #include "mozilla/dom/WakeLockBinding.h"
18 #include "mozilla/Hal.h"
19 #include "mozilla/StaticPrefs_dom.h"
20 #include "nsCOMPtr.h"
21 #include "nsCRT.h"
22 #include "nsError.h"
23 #include "nsIGlobalObject.h"
24 #include "nsISupports.h"
25 #include "nsPIDOMWindow.h"
26 #include "nsContentPermissionHelper.h"
27 #include "nsServiceManagerUtils.h"
28 #include "nscore.h"
29 #include "WakeLock.h"
30 #include "WakeLockJS.h"
31 #include "WakeLockSentinel.h"
33 namespace mozilla::dom {
35 static mozilla::LazyLogModule sLogger("ScreenWakeLock");
37 #define MIN_BATTERY_LEVEL 0.05
39 nsLiteralCString WakeLockJS::GetRequestErrorMessage(RequestError aRv) {
40 switch (aRv) {
41 case RequestError::DocInactive:
42 return "The requesting document is inactive."_ns;
43 case RequestError::DocHidden:
44 return "The requesting document is hidden."_ns;
45 case RequestError::PolicyDisallowed:
46 return "A permissions policy does not allow screen-wake-lock for the requesting document."_ns;
47 case RequestError::PrefDisabled:
48 return "The pref dom.screenwakelock.enabled is disabled."_ns;
49 case RequestError::InternalFailure:
50 return "A browser-internal error occured."_ns;
51 case RequestError::PermissionDenied:
52 return "Permission to request screen-wake-lock was denied."_ns;
53 default:
54 MOZ_ASSERT_UNREACHABLE("Unknown error reason");
55 return "Unknown error"_ns;
59 // https://w3c.github.io/screen-wake-lock/#the-request-method steps 2-5
60 WakeLockJS::RequestError WakeLockJS::WakeLockAllowedForDocument(
61 Document* aDoc) {
62 if (!aDoc) {
63 return RequestError::InternalFailure;
66 // Step 2. check policy-controlled feature screen-wake-lock
67 if (!FeaturePolicyUtils::IsFeatureAllowed(aDoc, u"screen-wake-lock"_ns)) {
68 return RequestError::PolicyDisallowed;
71 // Step 3. Deny wake lock for user agent specific reasons
72 if (!StaticPrefs::dom_screenwakelock_enabled()) {
73 return RequestError::PrefDisabled;
76 // Step 4 check doc active
77 if (!aDoc->IsActive()) {
78 return RequestError::DocInactive;
81 // Step 5. check doc visible
82 if (aDoc->Hidden()) {
83 return RequestError::DocHidden;
86 return RequestError::Success;
89 // https://w3c.github.io/screen-wake-lock/#dfn-applicable-wake-lock
90 static bool IsWakeLockApplicable(WakeLockType aType) {
91 hal::BatteryInformation batteryInfo;
92 hal::GetCurrentBatteryInformation(&batteryInfo);
93 if (batteryInfo.level() <= MIN_BATTERY_LEVEL && !batteryInfo.charging()) {
94 return false;
97 // only currently supported wake lock type
98 return aType == WakeLockType::Screen;
101 // https://w3c.github.io/screen-wake-lock/#dfn-release-a-wake-lock
102 void ReleaseWakeLock(Document* aDoc, WakeLockSentinel* aLock,
103 WakeLockType aType) {
104 MOZ_ASSERT(aLock);
105 MOZ_ASSERT(aDoc);
107 RefPtr<WakeLockSentinel> kungFuDeathGrip = aLock;
108 aDoc->ActiveWakeLocks(aType).Remove(aLock);
109 aLock->NotifyLockReleased();
110 MOZ_LOG(sLogger, LogLevel::Debug, ("Released wake lock sentinel"));
113 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(WakeLockJS)
115 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WakeLockJS)
116 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
117 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
119 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WakeLockJS)
120 tmp->DetachListeners();
121 NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
122 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
123 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
125 NS_IMPL_CYCLE_COLLECTING_ADDREF(WakeLockJS)
126 NS_IMPL_CYCLE_COLLECTING_RELEASE(WakeLockJS)
128 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WakeLockJS)
129 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
130 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventListener)
131 NS_INTERFACE_MAP_ENTRY(nsIDocumentActivity)
132 NS_INTERFACE_MAP_ENTRY(nsIObserver)
133 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
134 NS_INTERFACE_MAP_END
136 WakeLockJS::WakeLockJS(nsPIDOMWindowInner* aWindow) : mWindow(aWindow) {
137 AttachListeners();
140 WakeLockJS::~WakeLockJS() { DetachListeners(); }
142 nsISupports* WakeLockJS::GetParentObject() const { return mWindow; }
144 JSObject* WakeLockJS::WrapObject(JSContext* aCx,
145 JS::Handle<JSObject*> aGivenProto) {
146 return WakeLock_Binding::Wrap(aCx, this, aGivenProto);
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) {
152 // Step 7.3.1. check visibility again
153 nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
154 if (!doc) {
155 return Err(RequestError::InternalFailure);
157 if (doc->Hidden()) {
158 return Err(RequestError::DocHidden);
160 // Step 7.3.3. let lock be a new WakeLockSentinel
161 RefPtr<WakeLockSentinel> lock =
162 MakeRefPtr<WakeLockSentinel>(mWindow->AsGlobal(), aType);
163 // Step 7.3.2. acquire a wake lock
164 if (IsWakeLockApplicable(aType)) {
165 lock->AcquireActualLock();
168 // Steps 7.3.4. append lock to locks
169 doc->ActiveWakeLocks(aType).Insert(lock);
171 return lock.forget();
174 // https://w3c.github.io/screen-wake-lock/#the-request-method
175 already_AddRefed<Promise> WakeLockJS::Request(WakeLockType aType,
176 ErrorResult& aRv) {
177 MOZ_LOG(sLogger, LogLevel::Debug, ("Received request for wake lock"));
178 nsCOMPtr<nsIGlobalObject> global = mWindow->AsGlobal();
180 RefPtr<Promise> promise = Promise::Create(global, aRv);
181 NS_ENSURE_FALSE(aRv.Failed(), nullptr);
183 // Steps 1-5
184 nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
185 RequestError rv = WakeLockAllowedForDocument(doc);
186 if (rv != RequestError::Success) {
187 promise->MaybeRejectWithNotAllowedError(GetRequestErrorMessage(rv));
188 return promise.forget();
191 // For now, we don't check the permission as we always grant the lock
192 // Step 7.3. Queue a task
193 NS_DispatchToMainThread(NS_NewRunnableFunction(
194 "ObtainWakeLock", [aType, promise, self = RefPtr<WakeLockJS>(this)]() {
195 auto lockOrErr = self->Obtain(aType);
196 if (lockOrErr.isOk()) {
197 RefPtr<WakeLockSentinel> lock = lockOrErr.unwrap();
198 promise->MaybeResolve(lock);
199 MOZ_LOG(sLogger, LogLevel::Debug,
200 ("Resolved promise with wake lock sentinel"));
201 } else {
202 promise->MaybeRejectWithNotAllowedError(
203 GetRequestErrorMessage(lockOrErr.unwrapErr()));
205 }));
207 return promise.forget();
210 void WakeLockJS::AttachListeners() {
211 nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
212 MOZ_ASSERT(doc);
213 DebugOnly<nsresult> rv =
214 doc->AddSystemEventListener(u"visibilitychange"_ns, this, true, false);
215 MOZ_ASSERT(NS_SUCCEEDED(rv));
216 doc->RegisterActivityObserver(ToSupports(this));
218 hal::RegisterBatteryObserver(this);
220 nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID);
221 MOZ_ASSERT(prefBranch);
222 rv = prefBranch->AddObserver("dom.screenwakelock.enabled", this, true);
223 MOZ_ASSERT(NS_SUCCEEDED(rv));
226 void WakeLockJS::DetachListeners() {
227 if (mWindow) {
228 if (nsCOMPtr<Document> doc = mWindow->GetExtantDoc()) {
229 doc->RemoveSystemEventListener(u"visibilitychange"_ns, this, true);
231 doc->UnregisterActivityObserver(ToSupports(this));
235 hal::UnregisterBatteryObserver(this);
237 if (nsCOMPtr<nsIPrefBranch> prefBranch =
238 do_GetService(NS_PREFSERVICE_CONTRACTID)) {
239 prefBranch->RemoveObserver("dom.screenwakelock.enabled", this);
243 NS_IMETHODIMP WakeLockJS::Observe(nsISupports* aSubject, const char* aTopic,
244 const char16_t* aData) {
245 if (nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
246 if (!StaticPrefs::dom_screenwakelock_enabled()) {
247 nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
248 MOZ_ASSERT(doc);
249 doc->UnlockAllWakeLocks(WakeLockType::Screen);
252 return NS_OK;
255 void WakeLockJS::NotifyOwnerDocumentActivityChanged() {
256 nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
257 MOZ_ASSERT(doc);
258 if (!doc->IsActive()) {
259 doc->UnlockAllWakeLocks(WakeLockType::Screen);
263 NS_IMETHODIMP WakeLockJS::HandleEvent(Event* aEvent) {
264 nsAutoString type;
265 aEvent->GetType(type);
267 if (type.EqualsLiteral("visibilitychange")) {
268 nsCOMPtr<Document> doc = do_QueryInterface(aEvent->GetTarget());
269 NS_ENSURE_STATE(doc);
271 if (doc->Hidden()) {
272 doc->UnlockAllWakeLocks(WakeLockType::Screen);
276 return NS_OK;
279 void WakeLockJS::Notify(const hal::BatteryInformation& aBatteryInfo) {
280 if (aBatteryInfo.level() > MIN_BATTERY_LEVEL || aBatteryInfo.charging()) {
281 return;
283 nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
284 MOZ_ASSERT(doc);
285 doc->UnlockAllWakeLocks(WakeLockType::Screen);
288 } // namespace mozilla::dom