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/. */
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"
23 #include "nsIGlobalObject.h"
24 #include "nsISupports.h"
25 #include "nsPIDOMWindow.h"
26 #include "nsContentPermissionHelper.h"
27 #include "nsServiceManagerUtils.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
) {
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
;
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(
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
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()) {
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
) {
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
, nsIObserver
)
131 NS_INTERFACE_MAP_ENTRY(nsIObserver
)
132 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference
)
135 WakeLockJS::WakeLockJS(nsPIDOMWindowInner
* aWindow
) : mWindow(aWindow
) {
139 WakeLockJS::~WakeLockJS() { DetachListeners(); }
141 nsISupports
* WakeLockJS::GetParentObject() const { return mWindow
; }
143 JSObject
* WakeLockJS::WrapObject(JSContext
* aCx
,
144 JS::Handle
<JSObject
*> aGivenProto
) {
145 return WakeLock_Binding::Wrap(aCx
, this, aGivenProto
);
148 // https://w3c.github.io/screen-wake-lock/#the-request-method Step 7.3
149 Result
<already_AddRefed
<WakeLockSentinel
>, WakeLockJS::RequestError
>
150 WakeLockJS::Obtain(WakeLockType aType
, Document
* aDoc
) {
151 // Step 7.3.1. check visibility again
152 // Out of spec, but also check everything else
153 RequestError rv
= WakeLockAllowedForDocument(aDoc
);
154 if (rv
!= RequestError::Success
) {
157 // Step 7.3.3. let lock be a new WakeLockSentinel
158 RefPtr
<WakeLockSentinel
> lock
=
159 MakeRefPtr
<WakeLockSentinel
>(mWindow
->AsGlobal(), aType
);
160 // Step 7.3.2. acquire a wake lock
161 if (IsWakeLockApplicable(aType
)) {
162 lock
->AcquireActualLock();
165 // Steps 7.3.4. append lock to locks
166 aDoc
->ActiveWakeLocks(aType
).Insert(lock
);
168 return lock
.forget();
171 // https://w3c.github.io/screen-wake-lock/#the-request-method
172 already_AddRefed
<Promise
> WakeLockJS::Request(WakeLockType aType
,
174 MOZ_LOG(sLogger
, LogLevel::Debug
, ("Received request for wake lock"));
175 nsCOMPtr
<nsIGlobalObject
> global
= mWindow
->AsGlobal();
177 RefPtr
<Promise
> promise
= Promise::Create(global
, aRv
);
178 NS_ENSURE_FALSE(aRv
.Failed(), nullptr);
181 nsCOMPtr
<Document
> doc
= mWindow
->GetExtantDoc();
182 RequestError rv
= WakeLockAllowedForDocument(doc
);
183 if (rv
!= RequestError::Success
) {
184 promise
->MaybeRejectWithNotAllowedError(GetRequestErrorMessage(rv
));
185 return promise
.forget();
188 // For now, we don't check the permission as we always grant the lock
189 // Step 7.3. Queue a task
190 NS_DispatchToMainThread(NS_NewRunnableFunction(
192 [aType
, promise
, doc
, self
= RefPtr
<WakeLockJS
>(this)]() {
193 auto lockOrErr
= self
->Obtain(aType
, doc
);
194 if (lockOrErr
.isOk()) {
195 RefPtr
<WakeLockSentinel
> lock
= lockOrErr
.unwrap();
196 promise
->MaybeResolve(lock
);
197 MOZ_LOG(sLogger
, LogLevel::Debug
,
198 ("Resolved promise with wake lock sentinel"));
200 promise
->MaybeRejectWithNotAllowedError(
201 GetRequestErrorMessage(lockOrErr
.unwrapErr()));
205 return promise
.forget();
208 void WakeLockJS::AttachListeners() {
209 hal::RegisterBatteryObserver(this);
211 nsCOMPtr
<nsIPrefBranch
> prefBranch
= do_GetService(NS_PREFSERVICE_CONTRACTID
);
212 MOZ_ASSERT(prefBranch
);
213 DebugOnly
<nsresult
> rv
=
214 prefBranch
->AddObserver("dom.screenwakelock.enabled", this, true);
215 MOZ_ASSERT(NS_SUCCEEDED(rv
));
218 void WakeLockJS::DetachListeners() {
219 hal::UnregisterBatteryObserver(this);
221 if (nsCOMPtr
<nsIPrefBranch
> prefBranch
=
222 do_GetService(NS_PREFSERVICE_CONTRACTID
)) {
223 prefBranch
->RemoveObserver("dom.screenwakelock.enabled", this);
227 NS_IMETHODIMP
WakeLockJS::Observe(nsISupports
* aSubject
, const char* aTopic
,
228 const char16_t
* aData
) {
229 if (nsCRT::strcmp(aTopic
, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID
) == 0) {
230 if (!StaticPrefs::dom_screenwakelock_enabled()) {
231 nsCOMPtr
<Document
> doc
= mWindow
->GetExtantDoc();
233 doc
->UnlockAllWakeLocks(WakeLockType::Screen
);
239 void WakeLockJS::Notify(const hal::BatteryInformation
& aBatteryInfo
) {
240 if (aBatteryInfo
.level() > MIN_BATTERY_LEVEL
|| aBatteryInfo
.charging()) {
243 nsCOMPtr
<Document
> doc
= mWindow
->GetExtantDoc();
245 doc
->UnlockAllWakeLocks(WakeLockType::Screen
);
248 } // namespace mozilla::dom