blob: 4b77850a1dd2ad06eceef1ab830b2e6f7d3f5af9 [file] [log] [blame]
// 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