blob: df328f17801c5abb0611f5f0ff58020d483f2962 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// 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/permissions_policy/permissions_policy_feature.mojom-blink.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_throw_dom_exception.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"
namespace blink {
using mojom::blink::PermissionService;
// 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(),
V8WakeLockType::Enum::kScreen),
MakeGarbageCollected<WakeLockManager>(
navigator.GetExecutionContext(),
V8WakeLockType::Enum::kSystem)} {}
ScriptPromiseTyped<WakeLockSentinel> WakeLock::request(
ScriptState* script_state,
V8WakeLockType type,
ExceptionState& exception_state) {
// https://w3c.github.io/screen-wake-lock/#the-request-method
// 4. 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 ScriptPromiseTyped<WakeLockSentinel>();
}
auto* context = ExecutionContext::From(script_state);
DCHECK(context->IsWindow() || context->IsDedicatedWorkerGlobalScope());
if (type == V8WakeLockType::Enum::kSystem &&
!RuntimeEnabledFeatures::SystemWakeLockEnabled()) {
exception_state.ThrowTypeError(
"The provided value 'system' is not a valid enum value of type "
"WakeLockType.");
return ScriptPromiseTyped<WakeLockSentinel>();
}
// 2. If document is not allowed to use the policy-controlled feature named
// "screen-wake-lock", return a promise rejected with a "NotAllowedError"
// DOMException.
// TODO: Check permissions policy enabling for System Wake Lock
// [N.B. Per https://github.com/w3c/webappsec-permissions-policy/issues/207
// there is no official support for workers in the Permissions Policy spec,
// but we can perform FP checks in workers in Blink]
if (type == V8WakeLockType::Enum::kScreen &&
!context->IsFeatureEnabled(
mojom::blink::PermissionsPolicyFeature::kScreenWakeLock,
ReportOptions::kReportOnFailure)) {
exception_state.ThrowDOMException(DOMExceptionCode::kNotAllowedError,
"Access to Screen Wake Lock features is "
"disallowed by permissions policy");
return ScriptPromiseTyped<WakeLockSentinel>();
}
if (context->IsDedicatedWorkerGlobalScope()) {
// N.B. The following steps were removed from the spec when System Wake Lock
// was spun off into a separate specification.
// 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 == V8WakeLockType::Enum::kScreen) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotAllowedError,
"Screen locks cannot be requested from workers");
return ScriptPromiseTyped<WakeLockSentinel>();
}
} else if (auto* window = DynamicTo<LocalDOMWindow>(context)) {
// 1. Let document be this's relevant settings object's associated
// Document.
// 5. If document is not fully active, return a promise rejected with with a
// "NotAllowedError" DOMException.
if (!window->document()->IsActive()) {
exception_state.ThrowDOMException(DOMExceptionCode::kNotAllowedError,
"The document is not active");
return ScriptPromiseTyped<WakeLockSentinel>();
}
// 6. If the steps to determine the visibility state return hidden, return a
// promise rejected with "NotAllowedError" DOMException.
if (type == V8WakeLockType::Enum::kScreen &&
!window->GetFrame()->GetPage()->IsPageVisible()) {
exception_state.ThrowDOMException(DOMExceptionCode::kNotAllowedError,
"The requesting page is not visible");
return ScriptPromiseTyped<WakeLockSentinel>();
}
// Measure calls without sticky activation as proposed in
// https://github.com/w3c/screen-wake-lock/pull/351.
if (type == V8WakeLockType::Enum::kScreen &&
!window->GetFrame()->HasStickyUserActivation()) {
UseCounter::Count(
context,
WebFeature::kWakeLockAcquireScreenLockWithoutStickyActivation);
}
}
// 7. Let promise be a new promise.
auto* resolver =
MakeGarbageCollected<ScriptPromiseResolverTyped<WakeLockSentinel>>(
script_state, exception_state.GetContext());
auto promise = resolver->Promise();
switch (type.AsEnum()) {
case V8WakeLockType::Enum::kScreen:
UseCounter::Count(context, WebFeature::kWakeLockAcquireScreenLock);
break;
case V8WakeLockType::Enum::kSystem:
UseCounter::Count(context, WebFeature::kWakeLockAcquireSystemLock);
break;
}
// 8. Run the following steps in parallel:
DoRequest(type.AsEnum(), resolver);
// 9. Return promise.
return promise;
}
void WakeLock::DoRequest(
V8WakeLockType::Enum type,
ScriptPromiseResolverTyped<WakeLockSentinel>* resolver) {
// https://w3c.github.io/screen-wake-lock/#the-request-method
// 8.1. Let state be the result of requesting permission to use
// "screen-wake-lock".
mojom::blink::PermissionName permission_name;
switch (type) {
case V8WakeLockType::Enum::kScreen:
permission_name = mojom::blink::PermissionName::SCREEN_WAKE_LOCK;
break;
case V8WakeLockType::Enum::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),
resolver->WrapCallbackInScriptScope(
WTF::BindOnce(&WakeLock::DidReceivePermissionResponse,
WrapPersistent(this), type)));
}
void WakeLock::DidReceivePermissionResponse(
V8WakeLockType::Enum type,
ScriptPromiseResolverTyped<WakeLockSentinel>* resolver,
mojom::blink::PermissionStatus status) {
// https://w3c.github.io/screen-wake-lock/#the-request-method
DCHECK(status == mojom::blink::PermissionStatus::GRANTED ||
status == mojom::blink::PermissionStatus::DENIED);
// 8.2. If state is "denied", then:
// 8.2.1. Queue a global task on the screen wake lock task source given
// document's relevant global object to reject promise with a
// "NotAllowedError" DOMException.
// 8.2.2. Abort these steps.
if (status != mojom::blink::PermissionStatus::GRANTED) {
resolver->Reject(V8ThrowDOMException::CreateOrDie(
resolver->GetScriptState()->GetIsolate(),
DOMExceptionCode::kNotAllowedError,
"Wake Lock permission request denied"));
return;
}
// 8.3. Queue a global task on the screen wake lock task source given
// document's relevant global object to run these steps:
if (type == V8WakeLockType::Enum::kScreen &&
!(GetPage() && GetPage()->IsPageVisible())) {
// 8.3.1. If the steps to determine the visibility state return hidden,
// then:
// 8.3.1.1. Reject promise with a "NotAllowedError" DOMException.
// 8.3.1.2. Abort these steps.
resolver->Reject(V8ThrowDOMException::CreateOrDie(
resolver->GetScriptState()->GetIsolate(),
DOMExceptionCode::kNotAllowedError,
"The requesting page is not visible"));
return;
}
// Steps 8.3.2 to 8.3.5 are described in AcquireWakeLock() and related
// functions.
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. For each lock in document.[[ActiveLocks]]["screen"]:
// 1.1. Run release a wake lock with document, lock, and "screen".
// N.B. The following steps were removed from the spec when System Wake Lock
// was spun off into a separate specification.
// 2. For each lock in document.[[ActiveLocks]]["system"]:
// 2.1. Run release a wake lock with document, 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
if (GetPage() && GetPage()->IsPageVisible())
return;
// 1. For each lock in document.[[ActiveLocks]]["screen"]:
// 1.1. Run release a wake lock with document, lock, and "screen".
WakeLockManager* manager =
managers_[static_cast<size_t>(V8WakeLockType::Enum::kScreen)];
if (manager)
manager->ClearWakeLocks();
}
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 Member<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