blob: 594effa4c1e67bd2e4f918c231a639b0bd0ced8a [file] [log] [blame]
// Copyright 2019 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/idle/idle_detector.h"
#include <utility>
#include "base/time/time.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "third_party/blink/public/mojom/feature_policy/feature_policy.mojom-blink.h"
#include "third_party/blink/public/mojom/idle/idle_manager.mojom-blink.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_idle_options.h"
#include "third_party/blink/renderer/core/dom/abort_signal.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/security_context.h"
#include "third_party/blink/renderer/modules/idle/idle_manager.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
namespace blink {
namespace {
using mojom::blink::IdleManagerError;
const char kAbortMessage[] = "Idle detection aborted.";
const char kFeaturePolicyBlocked[] =
"Access to the feature \"idle-detection\" is disallowed by permissions "
"policy.";
constexpr base::TimeDelta kMinimumThreshold = base::TimeDelta::FromSeconds(60);
} // namespace
IdleDetector* IdleDetector::Create(ScriptState* script_state) {
return MakeGarbageCollected<IdleDetector>(
ExecutionContext::From(script_state));
}
IdleDetector::IdleDetector(ExecutionContext* context)
: ExecutionContextClient(context), receiver_(this, context) {}
IdleDetector::~IdleDetector() = default;
const AtomicString& IdleDetector::InterfaceName() const {
return event_target_names::kIdleDetector;
}
ExecutionContext* IdleDetector::GetExecutionContext() const {
return ExecutionContextClient::GetExecutionContext();
}
bool IdleDetector::HasPendingActivity() const {
// This object should be considered active as long as there are registered
// event listeners.
return GetExecutionContext() && HasEventListeners();
}
String IdleDetector::userState() const {
if (!state_)
return String();
switch (state_->user) {
case mojom::blink::UserIdleState::kActive:
return "active";
case mojom::blink::UserIdleState::kIdle:
return "idle";
}
}
String IdleDetector::screenState() const {
if (!state_)
return String();
switch (state_->screen) {
case mojom::blink::ScreenIdleState::kLocked:
return "locked";
case mojom::blink::ScreenIdleState::kUnlocked:
return "unlocked";
}
}
// static
ScriptPromise IdleDetector::requestPermission(ScriptState* script_state,
ExceptionState& exception_state) {
if (!script_state->ContextIsValid())
return ScriptPromise();
auto* context = ExecutionContext::From(script_state);
return IdleManager::From(context)->RequestPermission(script_state,
exception_state);
}
ScriptPromise IdleDetector::start(ScriptState* script_state,
const IdleOptions* options,
ExceptionState& exception_state) {
ExecutionContext* context = ExecutionContext::From(script_state);
DCHECK(context->IsContextThread());
if (!context->IsFeatureEnabled(
mojom::blink::FeaturePolicyFeature::kIdleDetection,
ReportOptions::kReportOnFailure)) {
exception_state.ThrowSecurityError(kFeaturePolicyBlocked);
return ScriptPromise();
}
if (receiver_.is_bound()) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"Idle detector is already started.");
return ScriptPromise();
}
if (options->hasThreshold()) {
auto threshold = base::TimeDelta::FromMilliseconds(options->threshold());
if (threshold < kMinimumThreshold) {
exception_state.ThrowTypeError("Minimum threshold is 1 minute.");
return ScriptPromise();
}
threshold_ = threshold;
}
if (options->hasSignal()) {
signal_ = options->signal();
signal_->AddAlgorithm(WTF::Bind(&IdleDetector::Abort,
WrapWeakPersistent(this),
WrapWeakPersistent(signal_.Get())));
}
if (signal_ && signal_->aborted()) {
exception_state.ThrowDOMException(DOMExceptionCode::kAbortError,
kAbortMessage);
return ScriptPromise();
}
// See https://bit.ly/2S0zRAS for task types.
scoped_refptr<base::SingleThreadTaskRunner> task_runner =
context->GetTaskRunner(TaskType::kMiscPlatformAPI);
mojo::PendingRemote<mojom::blink::IdleMonitor> remote;
receiver_.Bind(remote.InitWithNewPipeAndPassReceiver(), task_runner);
receiver_.set_disconnect_handler(WTF::Bind(
&IdleDetector::OnMonitorDisconnected, WrapWeakPersistent(this)));
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
IdleManager::From(context)->AddMonitor(
threshold_, std::move(remote),
WTF::Bind(&IdleDetector::OnAddMonitor, WrapWeakPersistent(this),
WrapPersistent(resolver)));
return promise;
}
void IdleDetector::Abort(AbortSignal* signal) {
// There is no RemoveAlgorithm() method on AbortSignal so compare the signal
// bound to this callback to the one last passed to start().
if (signal_ != signal)
return;
if (resolver_) {
resolver_->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kAbortError, kAbortMessage));
resolver_ = nullptr;
}
receiver_.reset();
}
void IdleDetector::OnMonitorDisconnected() {
if (resolver_) {
resolver_->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNotSupportedError, "Idle detection not available."));
resolver_ = nullptr;
}
receiver_.reset();
}
void IdleDetector::OnAddMonitor(ScriptPromiseResolver* resolver,
IdleManagerError error,
mojom::blink::IdleStatePtr state) {
switch (error) {
case IdleManagerError::kPermissionDisabled:
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNotAllowedError,
"Idle detection permission denied"));
break;
case IdleManagerError::kSuccess:
DCHECK(state);
resolver->Resolve();
Update(std::move(state));
break;
}
resolver_ = nullptr;
}
void IdleDetector::Update(mojom::blink::IdleStatePtr state) {
DCHECK(receiver_.is_bound());
if (!GetExecutionContext() || GetExecutionContext()->IsContextDestroyed())
return;
if (state_ && state->Equals(*state_))
return;
state_ = std::move(state);
DispatchEvent(*Event::Create(event_type_names::kChange));
}
void IdleDetector::Trace(Visitor* visitor) const {
visitor->Trace(signal_);
visitor->Trace(resolver_);
visitor->Trace(receiver_);
EventTargetWithInlineData::Trace(visitor);
ExecutionContextClient::Trace(visitor);
ActiveScriptWrappable::Trace(visitor);
}
} // namespace blink