summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/modules/wake_lock/wake_lock.cc
blob: 8b3983f11ace46c2019e0a0e8597c9f46690e1aa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "third_party/blink/renderer/modules/wake_lock/wake_lock.h"

#include "third_party/blink/public/mojom/feature_policy/feature_policy_feature.mojom-blink.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/web_feature.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/modules/permissions/permission_utils.h"
#include "third_party/blink/renderer/modules/wake_lock/wake_lock_manager.h"
#include "third_party/blink/renderer/modules/wake_lock/wake_lock_type.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"

namespace blink {

using mojom::blink::PermissionService;
using mojom::blink::PermissionStatus;

WakeLock::WakeLock(LocalDOMWindow& window)
    : ExecutionContextLifecycleObserver(&window),
      PageVisibilityObserver(window.GetFrame()->GetPage()),
      permission_service_(&window),
      managers_{
          MakeGarbageCollected<WakeLockManager>(&window, WakeLockType::kScreen),
          MakeGarbageCollected<WakeLockManager>(&window,
                                                WakeLockType::kSystem)} {}

WakeLock::WakeLock(DedicatedWorkerGlobalScope& worker_scope)
    : ExecutionContextLifecycleObserver(&worker_scope),
      PageVisibilityObserver(nullptr),
      permission_service_(&worker_scope),
      managers_{MakeGarbageCollected<WakeLockManager>(&worker_scope,
                                                      WakeLockType::kScreen),
                MakeGarbageCollected<WakeLockManager>(&worker_scope,
                                                      WakeLockType::kSystem)} {}

ScriptPromise WakeLock::request(ScriptState* script_state,
                                const String& type,
                                ExceptionState& exception_state) {
  // 4.1. If the document's browsing context is null, reject promise with a
  //      "NotAllowedError" DOMException and return promise.
  if (!script_state->ContextIsValid()) {
    exception_state.ThrowDOMException(
        DOMExceptionCode::kNotAllowedError,
        "The document has no associated browsing context");
    return ScriptPromise();
  }

  // https://w3c.github.io/screen-wake-lock/#the-request-method
  auto* context = ExecutionContext::From(script_state);
  DCHECK(context->IsWindow() || context->IsDedicatedWorkerGlobalScope());

  if (type == "screen" &&
      !RuntimeEnabledFeatures::ScreenWakeLockEnabled(context)) {
    exception_state.ThrowTypeError(
        "The provided value 'screen' is not a valid enum value of type "
        "WakeLockType.");
    return ScriptPromise();
  }

  if (type == "system" && !RuntimeEnabledFeatures::SystemWakeLockEnabled()) {
    exception_state.ThrowTypeError(
        "The provided value 'system' is not a valid enum value of type "
        "WakeLockType.");
    return ScriptPromise();
  }

  // 2.1 If type is 'screen' and the document is not allowed to use the
  //     policy-controlled feature named "screen-wake-lock", reject promise with
  //     a "NotAllowedError" DOMException and return promise.
  // [N.B. Per https://github.com/w3c/webappsec-feature-policy/issues/207 there
  // is no official support for workers in the Feature Policy spec, but we can
  // perform FP checks in workers in Blink]
  // 2.2. If the user agent denies the wake lock of this type for document,
  //      reject promise with a "NotAllowedError" DOMException and return
  //      promise.
  if (type == "screen" &&
      !context->IsFeatureEnabled(
          mojom::blink::FeaturePolicyFeature::kScreenWakeLock,
          ReportOptions::kReportOnFailure)) {
    exception_state.ThrowDOMException(
        DOMExceptionCode::kNotAllowedError,
        "Access to Screen Wake Lock features is disallowed by feature policy");
    return ScriptPromise();
  }

  // TODO: Check feature policy enabling for System Wake Lock

  if (context->IsDedicatedWorkerGlobalScope()) {
    // 3. If the current global object is the DedicatedWorkerGlobalScope object:
    // 3.1. If the current global object's owner set is empty, reject promise
    //      with a "NotAllowedError" DOMException and return promise.
    // 3.2. If type is "screen", reject promise with a "NotAllowedError"
    //      DOMException, and return promise.
    if (type == "screen") {
      exception_state.ThrowDOMException(
          DOMExceptionCode::kNotAllowedError,
          "Screen locks cannot be requested from workers");
      return ScriptPromise();
    }
  } else if (auto* window = DynamicTo<LocalDOMWindow>(context)) {
    // 2. Let document be the responsible document of the current settings
    // object.

    // 4. Otherwise, if the current global object is the Window object:
    // 4.2. If document is not fully active, reject promise with a
    //      "NotAllowedError" DOMException, and return promise.
    if (!window->document()->IsActive()) {
      exception_state.ThrowDOMException(DOMExceptionCode::kNotAllowedError,
                                        "The document is not active");
      return ScriptPromise();
    }
    // 4.3. If type is "screen" and the Document of the top-level browsing
    //      context is hidden, reject promise with a "NotAllowedError"
    //      DOMException, and return promise.
    if (type == "screen" && !window->GetFrame()->GetPage()->IsPageVisible()) {
      exception_state.ThrowDOMException(DOMExceptionCode::kNotAllowedError,
                                        "The requesting page is not visible");
      return ScriptPromise();
    }
  }

  // 1. Let promise be a new promise.
  auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
  ScriptPromise promise = resolver->Promise();

  WakeLockType wake_lock_type = ToWakeLockType(type);

  switch (wake_lock_type) {
    case WakeLockType::kScreen:
      UseCounter::Count(context, WebFeature::kWakeLockAcquireScreenLock);
      break;
    case WakeLockType::kSystem:
      UseCounter::Count(context, WebFeature::kWakeLockAcquireSystemLock);
      break;
    default:
      NOTREACHED();
      break;
  }

  // 5. Run the following steps in parallel, but abort when type is "screen" and
  // document is hidden:
  DoRequest(wake_lock_type, resolver);

  // 7. Return promise.
  return promise;
}

void WakeLock::DoRequest(WakeLockType type, ScriptPromiseResolver* resolver) {
  // https://w3c.github.io/screen-wake-lock/#the-request-method
  // 5.1. Let state be the result of awaiting obtain permission steps with type:
  ObtainPermission(
      type, WTF::Bind(&WakeLock::DidReceivePermissionResponse,
                      WrapPersistent(this), type, WrapPersistent(resolver)));
}

void WakeLock::DidReceivePermissionResponse(WakeLockType type,
                                            ScriptPromiseResolver* resolver,
                                            PermissionStatus status) {
  // https://w3c.github.io/screen-wake-lock/#the-request-method
  DCHECK(status == PermissionStatus::GRANTED ||
         status == PermissionStatus::DENIED);
  DCHECK(resolver);
  // 5.1.1. If state is "denied", then reject promise with a "NotAllowedError"
  //        DOMException, and abort these steps.
  if (status != PermissionStatus::GRANTED) {
    resolver->Reject(MakeGarbageCollected<DOMException>(
        DOMExceptionCode::kNotAllowedError,
        "Wake Lock permission request denied"));
    return;
  }
  // 6. If aborted, run these steps:
  // 6.1. Reject promise with a "NotAllowedError" DOMException.
  if (type == WakeLockType::kScreen &&
      !(GetPage() && GetPage()->IsPageVisible())) {
    resolver->Reject(MakeGarbageCollected<DOMException>(
        DOMExceptionCode::kNotAllowedError,
        "The requesting page is not visible"));
    return;
  }
  // 5.3. Let success be the result of awaiting acquire a wake lock with lock
  // and type:
  // 5.3.1. If success is false then reject promise with a "NotAllowedError"
  //        DOMException, and abort these steps.
  WakeLockManager* manager = managers_[static_cast<size_t>(type)];
  DCHECK(manager);
  manager->AcquireWakeLock(resolver);
}

void WakeLock::ContextDestroyed() {
  // https://w3c.github.io/screen-wake-lock/#handling-document-loss-of-full-activity
  // 1. Let document be the responsible document of the current settings object.
  // 2. Let screenRecord be the platform wake lock's state record associated
  // with document and wake lock type "screen".
  // 3. For each lock in screenRecord.[[ActiveLocks]]:
  // 3.1. Run release a wake lock with lock and "screen".
  // 4. Let systemRecord be the platform wake lock's state record associated
  // with document and wake lock type "system".
  // 5. For each lock in systemRecord.[[ActiveLocks]]:
  // 5.1. Run release a wake lock with lock and "system".
  for (WakeLockManager* manager : managers_) {
    if (manager)
      manager->ClearWakeLocks();
  }
}

void WakeLock::PageVisibilityChanged() {
  // https://w3c.github.io/screen-wake-lock/#handling-document-loss-of-visibility
  // 1. Let document be the Document of the top-level browsing context.
  // 2. If document's visibility state is "visible", abort these steps.
  if (GetPage() && GetPage()->IsPageVisible())
    return;
  // 3. Let screenRecord be the platform wake lock's state record associated
  // with wake lock type "screen".
  // 4. For each lock in screenRecord.[[ActiveLocks]]:
  // 4.1. Run release a wake lock with lock and "screen".
  WakeLockManager* manager =
      managers_[static_cast<size_t>(WakeLockType::kScreen)];
  if (manager)
    manager->ClearWakeLocks();
}

void WakeLock::ObtainPermission(
    WakeLockType type,
    base::OnceCallback<void(PermissionStatus)> callback) {
  // https://w3c.github.io/screen-wake-lock/#dfn-obtain-permission
  // Note we actually implement a simplified version of the "obtain permission"
  // algorithm that essentially just calls the "request permission to use"
  // algorithm from the Permissions spec (i.e. we bypass all the steps covering
  // calling the "query a permission" algorithm and handling its result).
  // * Right now, we can do that because there is no way for Chromium's
  //   permission system to get to the "prompt" state given how
  //   WakeLockPermissionContext is currently implemented.
  // * Even if WakeLockPermissionContext changes in the future, this Blink
  //   implementation is unlikely to change because
  //   WakeLockPermissionContext::RequestPermission() will take its
  //   |user_gesture| argument into account to actually implement a slightly
  //   altered version of "request permission to use", the behavior of which
  //   will match the definition of "obtain permission" in the Wake Lock spec.
  mojom::blink::PermissionName permission_name;
  switch (type) {
    case WakeLockType::kScreen:
      permission_name = mojom::blink::PermissionName::SCREEN_WAKE_LOCK;
      break;
    case WakeLockType::kSystem:
      permission_name = mojom::blink::PermissionName::SYSTEM_WAKE_LOCK;
      break;
  }

  auto* window = DynamicTo<LocalDOMWindow>(GetExecutionContext());
  auto* local_frame = window ? window->GetFrame() : nullptr;
  GetPermissionService()->RequestPermission(
      CreatePermissionDescriptor(permission_name),
      LocalFrame::HasTransientUserActivation(local_frame), std::move(callback));
}

PermissionService* WakeLock::GetPermissionService() {
  if (!permission_service_.is_bound()) {
    ConnectToPermissionService(
        GetExecutionContext(),
        permission_service_.BindNewPipeAndPassReceiver(
            GetExecutionContext()->GetTaskRunner(TaskType::kMiscPlatformAPI)));
  }
  return permission_service_.get();
}

void WakeLock::Trace(Visitor* visitor) const {
  for (const WakeLockManager* manager : managers_)
    visitor->Trace(manager);
  visitor->Trace(permission_service_);
  PageVisibilityObserver::Trace(visitor);
  ExecutionContextLifecycleObserver::Trace(visitor);
  ScriptWrappable::Trace(visitor);
}

}  // namespace blink