1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/modules/wake_lock/wake_lock.h"
6
7 #include "third_party/blink/public/mojom/feature_policy/feature_policy_feature.mojom-blink.h"
8 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
9 #include "third_party/blink/renderer/core/dom/document.h"
10 #include "third_party/blink/renderer/core/dom/dom_exception.h"
11 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
12 #include "third_party/blink/renderer/core/frame/local_frame.h"
13 #include "third_party/blink/renderer/core/frame/web_feature.h"
14 #include "third_party/blink/renderer/core/page/page.h"
15 #include "third_party/blink/renderer/modules/permissions/permission_utils.h"
16 #include "third_party/blink/renderer/modules/wake_lock/wake_lock_manager.h"
17 #include "third_party/blink/renderer/modules/wake_lock/wake_lock_type.h"
18 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
19 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
20 #include "third_party/blink/renderer/platform/scheduler/public/frame_or_worker_scheduler.h"
21 #include "third_party/blink/renderer/platform/wtf/functional.h"
22 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
23
24 namespace blink {
25
26 using mojom::blink::PermissionService;
27 using mojom::blink::PermissionStatus;
28
WakeLock(Document & document)29 WakeLock::WakeLock(Document& document)
30 : ExecutionContextLifecycleObserver(&document),
31 PageVisibilityObserver(document.GetPage()),
32 managers_{
33 MakeGarbageCollected<WakeLockManager>(document.ToExecutionContext(),
34 WakeLockType::kScreen),
35 MakeGarbageCollected<WakeLockManager>(document.ToExecutionContext(),
36 WakeLockType::kSystem)} {
37 document.GetScheduler()->RegisterStickyFeature(
38 SchedulingPolicy::Feature::kWakeLock,
39 {SchedulingPolicy::RecordMetricsForBackForwardCache()});
40 }
41
WakeLock(DedicatedWorkerGlobalScope & worker_scope)42 WakeLock::WakeLock(DedicatedWorkerGlobalScope& worker_scope)
43 : ExecutionContextLifecycleObserver(&worker_scope),
44 PageVisibilityObserver(nullptr),
45 managers_{MakeGarbageCollected<WakeLockManager>(&worker_scope,
46 WakeLockType::kScreen),
47 MakeGarbageCollected<WakeLockManager>(&worker_scope,
48 WakeLockType::kSystem)} {}
49
request(ScriptState * script_state,const String & type,ExceptionState & exception_state)50 ScriptPromise WakeLock::request(ScriptState* script_state,
51 const String& type,
52 ExceptionState& exception_state) {
53 // https://w3c.github.io/wake-lock/#the-request-method
54 auto* context = ExecutionContext::From(script_state);
55 DCHECK(context->IsDocument() || context->IsDedicatedWorkerGlobalScope());
56
57 if (type == "screen" &&
58 !RuntimeEnabledFeatures::ScreenWakeLockEnabled(context)) {
59 exception_state.ThrowTypeError(
60 "The provided value 'screen' is not a valid enum value of type "
61 "WakeLockType.");
62 return ScriptPromise();
63 }
64
65 if (type == "system" && !RuntimeEnabledFeatures::SystemWakeLockEnabled()) {
66 exception_state.ThrowTypeError(
67 "The provided value 'system' is not a valid enum value of type "
68 "WakeLockType.");
69 return ScriptPromise();
70 }
71
72 // 2.1 If type is 'screen' and the document is not allowed to use the
73 // policy-controlled feature named "screen-wake-lock", reject promise with
74 // a "NotAllowedError" DOMException and return promise.
75 // [N.B. Per https://github.com/w3c/webappsec-feature-policy/issues/207 there
76 // is no official support for workers in the Feature Policy spec, but we can
77 // perform FP checks in workers in Blink]
78 // 2.2. If the user agent denies the wake lock of this type for document,
79 // reject promise with a "NotAllowedError" DOMException and return
80 // promise.
81 if (type == "screen" &&
82 !context->IsFeatureEnabled(
83 mojom::blink::FeaturePolicyFeature::kScreenWakeLock,
84 ReportOptions::kReportOnFailure)) {
85 exception_state.ThrowDOMException(
86 DOMExceptionCode::kNotAllowedError,
87 "Access to Screen Wake Lock features is disallowed by feature policy");
88 return ScriptPromise();
89 }
90
91 // TODO: Check feature policy enabling for System Wake Lock
92
93 if (context->IsDedicatedWorkerGlobalScope()) {
94 // 3. If the current global object is the DedicatedWorkerGlobalScope object:
95 // 3.1. If the current global object's owner set is empty, reject promise
96 // with a "NotAllowedError" DOMException and return promise.
97 // 3.2. If type is "screen", reject promise with a "NotAllowedError"
98 // DOMException, and return promise.
99 if (type == "screen") {
100 exception_state.ThrowDOMException(
101 DOMExceptionCode::kNotAllowedError,
102 "Screen locks cannot be requested from workers");
103 return ScriptPromise();
104 }
105 } else if (context->IsDocument()) {
106 // 2. Let document be the responsible document of the current settings
107 // object.
108 auto* document = Document::From(context);
109
110 // 4. Otherwise, if the current global object is the Window object:
111 // 4.1. If the document's browsing context is null, reject promise with a
112 // "NotAllowedError" DOMException and return promise.
113 if (!document) {
114 exception_state.ThrowDOMException(
115 DOMExceptionCode::kNotAllowedError,
116 "The document has no associated browsing context");
117 return ScriptPromise();
118 }
119
120 // 4.2. If document is not fully active, reject promise with a
121 // "NotAllowedError" DOMException, and return promise.
122 if (!document->IsActive()) {
123 exception_state.ThrowDOMException(DOMExceptionCode::kNotAllowedError,
124 "The document is not active");
125 return ScriptPromise();
126 }
127 // 4.3. If type is "screen" and the Document of the top-level browsing
128 // context is hidden, reject promise with a "NotAllowedError"
129 // DOMException, and return promise.
130 if (type == "screen" &&
131 !(document->GetPage() && document->GetPage()->IsPageVisible())) {
132 exception_state.ThrowDOMException(DOMExceptionCode::kNotAllowedError,
133 "The requesting page is not visible");
134 return ScriptPromise();
135 }
136 }
137
138 // 1. Let promise be a new promise.
139 auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
140 ScriptPromise promise = resolver->Promise();
141
142 WakeLockType wake_lock_type = ToWakeLockType(type);
143
144 switch (wake_lock_type) {
145 case WakeLockType::kScreen:
146 UseCounter::Count(context, WebFeature::kWakeLockAcquireScreenLock);
147 break;
148 case WakeLockType::kSystem:
149 UseCounter::Count(context, WebFeature::kWakeLockAcquireSystemLock);
150 break;
151 default:
152 NOTREACHED();
153 break;
154 }
155
156 // 5. Run the following steps in parallel, but abort when type is "screen" and
157 // document is hidden:
158 DoRequest(wake_lock_type, resolver);
159
160 // 7. Return promise.
161 return promise;
162 }
163
DoRequest(WakeLockType type,ScriptPromiseResolver * resolver)164 void WakeLock::DoRequest(WakeLockType type, ScriptPromiseResolver* resolver) {
165 // https://w3c.github.io/wake-lock/#the-request-method
166 // 5.1. Let state be the result of awaiting obtain permission steps with type:
167 ObtainPermission(
168 type, WTF::Bind(&WakeLock::DidReceivePermissionResponse,
169 WrapPersistent(this), type, WrapPersistent(resolver)));
170 }
171
DidReceivePermissionResponse(WakeLockType type,ScriptPromiseResolver * resolver,PermissionStatus status)172 void WakeLock::DidReceivePermissionResponse(WakeLockType type,
173 ScriptPromiseResolver* resolver,
174 PermissionStatus status) {
175 // https://w3c.github.io/wake-lock/#the-request-method
176 DCHECK(status == PermissionStatus::GRANTED ||
177 status == PermissionStatus::DENIED);
178 DCHECK(resolver);
179 // 5.1.1. If state is "denied", then reject promise with a "NotAllowedError"
180 // DOMException, and abort these steps.
181 if (status != PermissionStatus::GRANTED) {
182 resolver->Reject(MakeGarbageCollected<DOMException>(
183 DOMExceptionCode::kNotAllowedError,
184 "Wake Lock permission request denied"));
185 return;
186 }
187 // 6. If aborted, run these steps:
188 // 6.1. Reject promise with a "NotAllowedError" DOMException.
189 if (type == WakeLockType::kScreen &&
190 !(GetPage() && GetPage()->IsPageVisible())) {
191 resolver->Reject(MakeGarbageCollected<DOMException>(
192 DOMExceptionCode::kNotAllowedError,
193 "The requesting page is not visible"));
194 return;
195 }
196 // 5.3. Let success be the result of awaiting acquire a wake lock with lock
197 // and type:
198 // 5.3.1. If success is false then reject promise with a "NotAllowedError"
199 // DOMException, and abort these steps.
200 WakeLockManager* manager = managers_[static_cast<size_t>(type)];
201 DCHECK(manager);
202 manager->AcquireWakeLock(resolver);
203 }
204
ContextDestroyed()205 void WakeLock::ContextDestroyed() {
206 // https://w3c.github.io/wake-lock/#handling-document-loss-of-full-activity
207 // 1. Let document be the responsible document of the current settings object.
208 // 2. Let screenRecord be the platform wake lock's state record associated
209 // with document and wake lock type "screen".
210 // 3. For each lock in screenRecord.[[ActiveLocks]]:
211 // 3.1. Run release a wake lock with lock and "screen".
212 // 4. Let systemRecord be the platform wake lock's state record associated
213 // with document and wake lock type "system".
214 // 5. For each lock in systemRecord.[[ActiveLocks]]:
215 // 5.1. Run release a wake lock with lock and "system".
216 for (WakeLockManager* manager : managers_) {
217 if (manager)
218 manager->ClearWakeLocks();
219 }
220 }
221
PageVisibilityChanged()222 void WakeLock::PageVisibilityChanged() {
223 // https://w3c.github.io/wake-lock/#handling-document-loss-of-visibility
224 // 1. Let document be the Document of the top-level browsing context.
225 // 2. If document's visibility state is "visible", abort these steps.
226 if (GetPage() && GetPage()->IsPageVisible())
227 return;
228 // 3. Let screenRecord be the platform wake lock's state record associated
229 // with wake lock type "screen".
230 // 4. For each lock in screenRecord.[[ActiveLocks]]:
231 // 4.1. Run release a wake lock with lock and "screen".
232 WakeLockManager* manager =
233 managers_[static_cast<size_t>(WakeLockType::kScreen)];
234 if (manager)
235 manager->ClearWakeLocks();
236 }
237
ObtainPermission(WakeLockType type,base::OnceCallback<void (PermissionStatus)> callback)238 void WakeLock::ObtainPermission(
239 WakeLockType type,
240 base::OnceCallback<void(PermissionStatus)> callback) {
241 // https://w3c.github.io/wake-lock/#dfn-obtain-permission
242 // Note we actually implement a simplified version of the "obtain permission"
243 // algorithm that essentially just calls the "request permission to use"
244 // algorithm from the Permissions spec (i.e. we bypass all the steps covering
245 // calling the "query a permission" algorithm and handling its result).
246 // * Right now, we can do that because there is no way for Chromium's
247 // permission system to get to the "prompt" state given how
248 // WakeLockPermissionContext is currently implemented.
249 // * Even if WakeLockPermissionContext changes in the future, this Blink
250 // implementation is unlikely to change because
251 // WakeLockPermissionContext::RequestPermission() will take its
252 // |user_gesture| argument into account to actually implement a slightly
253 // altered version of "request permission to use", the behavior of which
254 // will match the definition of "obtain permission" in the Wake Lock spec.
255 DCHECK(type == WakeLockType::kScreen || type == WakeLockType::kSystem);
256 static_assert(
257 static_cast<mojom::blink::WakeLockType>(WakeLockType::kScreen) ==
258 mojom::blink::WakeLockType::kScreen,
259 "WakeLockType and mojom::blink::WakeLockType must have identical values");
260 static_assert(
261 static_cast<mojom::blink::WakeLockType>(WakeLockType::kSystem) ==
262 mojom::blink::WakeLockType::kSystem,
263 "WakeLockType and mojom::blink::WakeLockType must have identical values");
264
265 auto* local_frame = GetExecutionContext()->IsDocument()
266 ? Document::From(GetExecutionContext())->GetFrame()
267 : nullptr;
268 GetPermissionService()->RequestPermission(
269 CreateWakeLockPermissionDescriptor(
270 static_cast<mojom::blink::WakeLockType>(type)),
271 LocalFrame::HasTransientUserActivation(local_frame), std::move(callback));
272 }
273
GetPermissionService()274 PermissionService* WakeLock::GetPermissionService() {
275 if (!permission_service_) {
276 ConnectToPermissionService(
277 GetExecutionContext(),
278 permission_service_.BindNewPipeAndPassReceiver());
279 }
280 return permission_service_.get();
281 }
282
Trace(Visitor * visitor)283 void WakeLock::Trace(Visitor* visitor) {
284 for (const WakeLockManager* manager : managers_)
285 visitor->Trace(manager);
286 PageVisibilityObserver::Trace(visitor);
287 ExecutionContextLifecycleObserver::Trace(visitor);
288 ScriptWrappable::Trace(visitor);
289 }
290
291 } // namespace blink
292