| // 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_frame.h" |
| #include "third_party/blink/renderer/core/frame/web_feature.h" |
| #include "third_party/blink/renderer/modules/permissions/permission_utils.h" |
| #include "third_party/blink/renderer/modules/wake_lock/wake_lock_state_record.h" |
| #include "third_party/blink/renderer/modules/wake_lock/wake_lock_type.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(Document& document) |
| : ContextLifecycleObserver(&document), |
| PageVisibilityObserver(document.GetPage()), |
| state_records_{ |
| MakeGarbageCollected<WakeLockStateRecord>(&document, |
| WakeLockType::kScreen), |
| MakeGarbageCollected<WakeLockStateRecord>(&document, |
| WakeLockType::kSystem)} {} |
| |
| WakeLock::WakeLock(DedicatedWorkerGlobalScope& worker_scope) |
| : ContextLifecycleObserver(&worker_scope), |
| PageVisibilityObserver(nullptr), |
| state_records_{ |
| MakeGarbageCollected<WakeLockStateRecord>(&worker_scope, |
| WakeLockType::kScreen), |
| MakeGarbageCollected<WakeLockStateRecord>(&worker_scope, |
| WakeLockType::kSystem)} {} |
| |
| ScriptPromise WakeLock::request(ScriptState* script_state, const String& type) { |
| // https://w3c.github.io/wake-lock/#request-static-method |
| auto* context = ExecutionContext::From(script_state); |
| DCHECK(context->IsDocument() || context->IsDedicatedWorkerGlobalScope()); |
| |
| // 2.1. If document is not allowed to use the policy-controlled feature named |
| // "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 (!context->GetSecurityContext().IsFeatureEnabled( |
| mojom::FeaturePolicyFeature::kWakeLock, |
| ReportOptions::kReportOnFailure)) { |
| return ScriptPromise::RejectWithDOMException( |
| script_state, |
| MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kNotAllowedError, |
| "Access to WakeLock features is disallowed by feature policy")); |
| } |
| |
| 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") { |
| return ScriptPromise::RejectWithDOMException( |
| script_state, MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kNotAllowedError, |
| "Screen locks cannot be requested from workers")); |
| } |
| } else if (context->IsDocument()) { |
| // 2. Let document be the responsible document of the current settings |
| // object. |
| auto* document = To<Document>(context); |
| |
| // 4. Otherwise, if the current global object is the Window object: |
| // 4.1. If the document's browsing context is null, reject promise with a |
| // "NotAllowedError" DOMException and return promise. |
| if (!document) { |
| return ScriptPromise::RejectWithDOMException( |
| script_state, MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kNotAllowedError, |
| "The document has no associated browsing context")); |
| } |
| |
| // 4.2. If document is not fully active, reject promise with a |
| // "NotAllowedError" DOMException, and return promise. |
| if (!document->IsActive()) { |
| return ScriptPromise::RejectWithDOMException( |
| script_state, |
| MakeGarbageCollected<DOMException>(DOMExceptionCode::kNotAllowedError, |
| "The document is not active")); |
| } |
| // 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" && |
| !(document->GetPage() && document->GetPage()->IsPageVisible())) { |
| return ScriptPromise::RejectWithDOMException( |
| script_state, MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kNotAllowedError, |
| "The requesting page is not visible")); |
| } |
| } |
| |
| 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; |
| } |
| |
| // 6. Run the following steps in parallel, but abort when options' signal |
| // member is present and its aborted flag is set: |
| // 6.1. Let state be the result of awaiting obtain permission steps with |
| // type: |
| DoRequest(wake_lock_type, resolver); |
| |
| // 7. Return promise. |
| return promise; |
| } |
| |
| void WakeLock::DoRequest(WakeLockType type, ScriptPromiseResolver* resolver) { |
| ObtainPermission( |
| type, WTF::Bind(&WakeLock::DidReceivePermissionResponse, |
| WrapPersistent(this), type, WrapPersistent(resolver))); |
| } |
| |
| void WakeLock::DidReceivePermissionResponse(WakeLockType type, |
| ScriptPromiseResolver* resolver, |
| PermissionStatus status) { |
| // https://w3c.github.io/wake-lock/#request-static-method |
| DCHECK(status == PermissionStatus::GRANTED || |
| status == PermissionStatus::DENIED); |
| DCHECK(resolver); |
| // 6.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; |
| } |
| // https://github.com/w3c/wake-lock/issues/222: the page can become hidden |
| // between request() and WakeLockStateRecord::AcquireWakeLock(), in which case |
| // we need to abort early. |
| if (type == WakeLockType::kScreen && |
| !(GetPage() && GetPage()->IsPageVisible())) { |
| resolver->Reject(MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kNotAllowedError, |
| "The requesting page is not visible")); |
| return; |
| } |
| // 6.2. Let success be the result of awaiting acquire a wake lock with promise |
| // and type: |
| // 6.2.1. If success is false then reject promise with a "NotAllowedError" |
| // DOMException, and abort these steps. |
| WakeLockStateRecord* state_record = state_records_[static_cast<size_t>(type)]; |
| DCHECK(state_record); |
| state_record->AcquireWakeLock(resolver); |
| } |
| |
| void WakeLock::ContextDestroyed(ExecutionContext*) { |
| // https://w3c.github.io/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 lockPromise in screenRecord.[[WakeLockStateRecord]]: |
| // 3.1. Run release a wake lock with lockPromise and "screen". |
| // 4. Let systemRecord be the platform wake lock's state record associated |
| // with document and wake lock type "system". |
| // 5. For each lockPromise in systemRecord.[[WakeLockStateRecord]]: |
| // 5.1. Run release a wake lock with lockPromise and "system". |
| for (WakeLockStateRecord* state_record : state_records_) { |
| if (state_record) |
| state_record->ClearWakeLocks(); |
| } |
| } |
| |
| void WakeLock::PageVisibilityChanged() { |
| // https://w3c.github.io/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 lockPromise in screenRecord.[[WakeLockStateRecord]]: |
| // 4.1. Run release a wake lock with lockPromise and "screen". |
| WakeLockStateRecord* state_record = |
| state_records_[static_cast<size_t>(WakeLockType::kScreen)]; |
| if (state_record) |
| state_record->ClearWakeLocks(); |
| } |
| |
| void WakeLock::ObtainPermission( |
| WakeLockType type, |
| base::OnceCallback<void(PermissionStatus)> callback) { |
| // https://w3c.github.io/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. |
| DCHECK(type == WakeLockType::kScreen || type == WakeLockType::kSystem); |
| static_assert( |
| static_cast<mojom::blink::WakeLockType>(WakeLockType::kScreen) == |
| mojom::blink::WakeLockType::kScreen, |
| "WakeLockType and mojom::blink::WakeLockType must have identical values"); |
| static_assert( |
| static_cast<mojom::blink::WakeLockType>(WakeLockType::kSystem) == |
| mojom::blink::WakeLockType::kSystem, |
| "WakeLockType and mojom::blink::WakeLockType must have identical values"); |
| |
| auto* local_frame = GetExecutionContext()->IsDocument() |
| ? To<Document>(GetExecutionContext())->GetFrame() |
| : nullptr; |
| GetPermissionService()->RequestPermission( |
| CreateWakeLockPermissionDescriptor( |
| static_cast<mojom::blink::WakeLockType>(type)), |
| LocalFrame::HasTransientUserActivation(local_frame), std::move(callback)); |
| } |
| |
| PermissionService* WakeLock::GetPermissionService() { |
| if (!permission_service_) { |
| ConnectToPermissionService( |
| GetExecutionContext(), |
| permission_service_.BindNewPipeAndPassReceiver()); |
| } |
| return permission_service_.get(); |
| } |
| |
| void WakeLock::Trace(Visitor* visitor) { |
| for (WakeLockStateRecord* state_record : state_records_) |
| visitor->Trace(state_record); |
| PageVisibilityObserver::Trace(visitor); |
| ContextLifecycleObserver::Trace(visitor); |
| ScriptWrappable::Trace(visitor); |
| } |
| |
| } // namespace blink |