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
, nsIDOMEventListener
)
131 NS_INTERFACE_MAP_ENTRY(nsIDocumentActivity
)
132 NS_INTERFACE_MAP_ENTRY(nsIObserver
)
133 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference
)
136 WakeLockJS::WakeLockJS(nsPIDOMWindowInner
* aWindow
) : mWindow(aWindow
) {
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();
155 return Err(RequestError::InternalFailure
);
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
,
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);
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"));
202 promise
->MaybeRejectWithNotAllowedError(
203 GetRequestErrorMessage(lockOrErr
.unwrapErr()));
207 return promise
.forget();
210 void WakeLockJS::AttachListeners() {
211 nsCOMPtr
<Document
> doc
= mWindow
->GetExtantDoc();
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() {
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();
249 doc
->UnlockAllWakeLocks(WakeLockType::Screen
);
255 void WakeLockJS::NotifyOwnerDocumentActivityChanged() {
256 nsCOMPtr
<Document
> doc
= mWindow
->GetExtantDoc();
258 if (!doc
->IsActive()) {
259 doc
->UnlockAllWakeLocks(WakeLockType::Screen
);
263 NS_IMETHODIMP
WakeLockJS::HandleEvent(Event
* aEvent
) {
265 aEvent
->GetType(type
);
267 if (type
.EqualsLiteral("visibilitychange")) {
268 nsCOMPtr
<Document
> doc
= do_QueryInterface(aEvent
->GetTarget());
269 NS_ENSURE_STATE(doc
);
272 doc
->UnlockAllWakeLocks(WakeLockType::Screen
);
279 void WakeLockJS::Notify(const hal::BatteryInformation
& aBatteryInfo
) {
280 if (aBatteryInfo
.level() > MIN_BATTERY_LEVEL
|| aBatteryInfo
.charging()) {
283 nsCOMPtr
<Document
> doc
= mWindow
->GetExtantDoc();
285 doc
->UnlockAllWakeLocks(WakeLockType::Screen
);
288 } // namespace mozilla::dom