| // 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/execution_context/navigator_base.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; |
| |
| // static |
| const char WakeLock::kSupplementName[] = "WakeLock"; |
| |
| // static |
| WakeLock* WakeLock::wakeLock(NavigatorBase& navigator) { |
| WakeLock* supplement = Supplement<NavigatorBase>::From<WakeLock>(navigator); |
| if (!supplement && navigator.GetExecutionContext()) { |
| supplement = MakeGarbageCollected<WakeLock>(navigator); |
| ProvideTo(navigator, supplement); |
| } |
| return supplement; |
| } |
| |
| WakeLock::WakeLock(NavigatorBase& navigator) |
| : Supplement<NavigatorBase>(navigator), |
| ExecutionContextLifecycleObserver(navigator.GetExecutionContext()), |
| PageVisibilityObserver(navigator.DomWindow() |
| ? navigator.DomWindow()->GetFrame()->GetPage() |
| : nullptr), |
| permission_service_(navigator.GetExecutionContext()), |
| managers_{ |
| MakeGarbageCollected<WakeLockManager>(navigator.GetExecutionContext(), |
| WakeLockType::kScreen), |
| MakeGarbageCollected<WakeLockManager>(navigator.GetExecutionContext(), |
| 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 permissions 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::kWakeLock))); |
| } |
| return permission_service_.get(); |
| } |
| |
| void WakeLock::Trace(Visitor* visitor) const { |
| for (const WakeLockManager* manager : managers_) |
| visitor->Trace(manager); |
| visitor->Trace(permission_service_); |
| Supplement<NavigatorBase>::Trace(visitor); |
| PageVisibilityObserver::Trace(visitor); |
| ExecutionContextLifecycleObserver::Trace(visitor); |
| ScriptWrappable::Trace(visitor); |
| } |
| |
| } // namespace blink |