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