blob: 23b242fa9378cea6d3a6c71aa046f0b545017513 [file] [log] [blame]
// Copyright 2016 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/sensor/sensor.h"
#include <utility>
#include "services/device/public/cpp/generic_sensor/sensor_traits.h"
#include "services/device/public/mojom/sensor.mojom-blink.h"
#include "third_party/blink/public/mojom/feature_policy/feature_policy.mojom-blink.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/timing/dom_window_performance.h"
#include "third_party/blink/renderer/core/timing/window_performance.h"
#include "third_party/blink/renderer/modules/sensor/sensor_error_event.h"
#include "third_party/blink/renderer/modules/sensor/sensor_provider_proxy.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/web_test_support.h"
namespace blink {
namespace {
const double kWaitingIntervalThreshold = 0.01;
bool AreFeaturesEnabled(
ExecutionContext* context,
const Vector<mojom::blink::FeaturePolicyFeature>& features) {
return std::all_of(features.begin(), features.end(),
[context](mojom::blink::FeaturePolicyFeature feature) {
return context->IsFeatureEnabled(
feature, ReportOptions::kReportOnFailure);
});
}
} // namespace
Sensor::Sensor(ExecutionContext* execution_context,
const SensorOptions* sensor_options,
ExceptionState& exception_state,
device::mojom::blink::SensorType type,
const Vector<mojom::blink::FeaturePolicyFeature>& features)
: ExecutionContextLifecycleObserver(execution_context),
frequency_(0.0),
type_(type),
state_(SensorState::kIdle),
last_reported_timestamp_(0.0) {
// [SecureContext] in idl.
DCHECK(execution_context->IsSecureContext());
DCHECK(!features.IsEmpty());
if (!AreFeaturesEnabled(execution_context, features)) {
exception_state.ThrowSecurityError(
"Access to sensor features is disallowed by permissions policy");
return;
}
// Check the given frequency value.
if (sensor_options->hasFrequency()) {
frequency_ = sensor_options->frequency();
const double max_allowed_frequency =
device::GetSensorMaxAllowedFrequency(type_);
if (frequency_ > max_allowed_frequency) {
frequency_ = max_allowed_frequency;
String message = String::Format(
"Maximum allowed frequency value for this sensor type is %.0f Hz.",
max_allowed_frequency);
auto* console_message = MakeGarbageCollected<ConsoleMessage>(
mojom::ConsoleMessageSource::kJavaScript,
mojom::ConsoleMessageLevel::kInfo, std::move(message));
execution_context->AddConsoleMessage(console_message);
}
}
}
Sensor::Sensor(ExecutionContext* execution_context,
const SpatialSensorOptions* options,
ExceptionState& exception_state,
device::mojom::blink::SensorType sensor_type,
const Vector<mojom::blink::FeaturePolicyFeature>& features)
: Sensor(execution_context,
static_cast<const SensorOptions*>(options),
exception_state,
sensor_type,
features) {
use_screen_coords_ = (options->referenceFrame() == "screen");
}
Sensor::~Sensor() = default;
void Sensor::start() {
if (state_ != SensorState::kIdle)
return;
state_ = SensorState::kActivating;
Activate();
}
void Sensor::stop() {
if (state_ == SensorState::kIdle)
return;
Deactivate();
state_ = SensorState::kIdle;
}
// Getters
bool Sensor::activated() const {
return state_ == SensorState::kActivated;
}
bool Sensor::hasReading() const {
if (!activated())
return false;
DCHECK(sensor_proxy_);
return sensor_proxy_->GetReading().timestamp() != 0.0;
}
base::Optional<DOMHighResTimeStamp> Sensor::timestamp(
ScriptState* script_state) const {
if (!hasReading()) {
return base::nullopt;
}
LocalDOMWindow* window = LocalDOMWindow::From(script_state);
if (!window) {
return base::nullopt;
}
WindowPerformance* performance = DOMWindowPerformance::performance(*window);
DCHECK(performance);
DCHECK(sensor_proxy_);
return performance->MonotonicTimeToDOMHighResTimeStamp(
base::TimeTicks() +
base::TimeDelta::FromSecondsD(sensor_proxy_->GetReading().timestamp()));
}
void Sensor::Trace(Visitor* visitor) const {
visitor->Trace(sensor_proxy_);
ActiveScriptWrappable::Trace(visitor);
ExecutionContextLifecycleObserver::Trace(visitor);
EventTargetWithInlineData::Trace(visitor);
}
bool Sensor::HasPendingActivity() const {
if (state_ == SensorState::kIdle)
return false;
return GetExecutionContext() && HasEventListeners();
}
auto Sensor::CreateSensorConfig() -> SensorConfigurationPtr {
auto result = SensorConfiguration::New();
double default_frequency = sensor_proxy_->GetDefaultFrequency();
double minimum_frequency = sensor_proxy_->GetFrequencyLimits().first;
double maximum_frequency = sensor_proxy_->GetFrequencyLimits().second;
if (frequency_ == 0.0) // i.e. was never set.
frequency_ = default_frequency;
if (frequency_ > maximum_frequency)
frequency_ = maximum_frequency;
if (frequency_ < minimum_frequency)
frequency_ = minimum_frequency;
result->frequency = frequency_;
return result;
}
void Sensor::InitSensorProxyIfNeeded() {
if (sensor_proxy_)
return;
LocalDOMWindow* window = To<LocalDOMWindow>(GetExecutionContext());
auto* provider = SensorProviderProxy::From(window);
sensor_proxy_ = provider->GetSensorProxy(type_);
if (!sensor_proxy_) {
sensor_proxy_ =
provider->CreateSensorProxy(type_, window->GetFrame()->GetPage());
}
}
void Sensor::ContextDestroyed() {
if (!IsIdleOrErrored())
Deactivate();
if (sensor_proxy_)
sensor_proxy_->Detach();
}
void Sensor::OnSensorInitialized() {
if (state_ != SensorState::kActivating)
return;
RequestAddConfiguration();
}
void Sensor::OnSensorReadingChanged() {
if (state_ != SensorState::kActivated)
return;
// Return if reading update is already scheduled or the cached
// reading is up-to-date.
if (pending_reading_notification_.IsActive())
return;
double elapsedTime =
sensor_proxy_->GetReading().timestamp() - last_reported_timestamp_;
DCHECK_GT(elapsedTime, 0.0);
DCHECK_GT(configuration_->frequency, 0.0);
double waitingTime = 1 / configuration_->frequency - elapsedTime;
// Negative or zero 'waitingTime' means that polling period has elapsed.
// We also avoid scheduling if the elapsed time is slightly behind the
// polling period.
auto sensor_reading_changed =
WTF::Bind(&Sensor::NotifyReading, WrapWeakPersistent(this));
if (waitingTime < kWaitingIntervalThreshold) {
// Invoke JS callbacks in a different callchain to obviate
// possible modifications of SensorProxy::observers_ container
// while it is being iterated through.
pending_reading_notification_ = PostCancellableTask(
*GetExecutionContext()->GetTaskRunner(TaskType::kSensor), FROM_HERE,
std::move(sensor_reading_changed));
} else {
pending_reading_notification_ = PostDelayedCancellableTask(
*GetExecutionContext()->GetTaskRunner(TaskType::kSensor), FROM_HERE,
std::move(sensor_reading_changed),
base::TimeDelta::FromSecondsD(waitingTime));
}
}
void Sensor::OnSensorError(DOMExceptionCode code,
const String& sanitized_message,
const String& unsanitized_message) {
HandleError(code, sanitized_message, unsanitized_message);
}
void Sensor::OnAddConfigurationRequestCompleted(bool result) {
if (state_ != SensorState::kActivating)
return;
if (!result) {
HandleError(DOMExceptionCode::kNotReadableError,
"start() call has failed.");
return;
}
if (!GetExecutionContext())
return;
pending_activated_notification_ = PostCancellableTask(
*GetExecutionContext()->GetTaskRunner(TaskType::kSensor), FROM_HERE,
WTF::Bind(&Sensor::NotifyActivated, WrapWeakPersistent(this)));
}
void Sensor::Activate() {
DCHECK_EQ(state_, SensorState::kActivating);
InitSensorProxyIfNeeded();
DCHECK(sensor_proxy_);
if (sensor_proxy_->IsInitialized())
RequestAddConfiguration();
else
sensor_proxy_->Initialize();
sensor_proxy_->AddObserver(this);
}
void Sensor::Deactivate() {
DCHECK_NE(state_, SensorState::kIdle);
// state_ is not set to kIdle here as on error it should
// transition to the kIdle state in the same call chain
// the error event is dispatched, i.e. inside NotifyError().
pending_reading_notification_.Cancel();
pending_activated_notification_.Cancel();
pending_error_notification_.Cancel();
if (!sensor_proxy_)
return;
if (sensor_proxy_->IsInitialized()) {
DCHECK(configuration_);
sensor_proxy_->RemoveConfiguration(configuration_->Clone());
last_reported_timestamp_ = 0.0;
}
sensor_proxy_->RemoveObserver(this);
}
void Sensor::RequestAddConfiguration() {
if (!configuration_) {
configuration_ = CreateSensorConfig();
DCHECK(configuration_);
DCHECK_GE(configuration_->frequency,
sensor_proxy_->GetFrequencyLimits().first);
DCHECK_LE(configuration_->frequency,
sensor_proxy_->GetFrequencyLimits().second);
}
DCHECK(sensor_proxy_);
sensor_proxy_->AddConfiguration(
configuration_->Clone(),
WTF::Bind(&Sensor::OnAddConfigurationRequestCompleted,
WrapWeakPersistent(this)));
}
void Sensor::HandleError(DOMExceptionCode code,
const String& sanitized_message,
const String& unsanitized_message) {
if (!GetExecutionContext()) {
// Deactivate() is already called from Sensor::ContextDestroyed().
return;
}
if (IsIdleOrErrored())
return;
Deactivate();
auto* error = MakeGarbageCollected<DOMException>(code, sanitized_message,
unsanitized_message);
pending_error_notification_ = PostCancellableTask(
*GetExecutionContext()->GetTaskRunner(TaskType::kSensor), FROM_HERE,
WTF::Bind(&Sensor::NotifyError, WrapWeakPersistent(this),
WrapPersistent(error)));
}
void Sensor::NotifyReading() {
DCHECK_EQ(state_, SensorState::kActivated);
last_reported_timestamp_ = sensor_proxy_->GetReading().timestamp();
DispatchEvent(*Event::Create(event_type_names::kReading));
}
void Sensor::NotifyActivated() {
DCHECK_EQ(state_, SensorState::kActivating);
state_ = SensorState::kActivated;
// Explicitly call the Sensor implementation of hasReading(). Subclasses may
// override the method and introduce additional requirements, but in this case
// we are really only interested in whether there is data in the shared
// buffer, so that we can then process it possibly for the first time in
// OnSensorReadingChanged().
if (Sensor::hasReading()) {
// If reading has already arrived, process the reading values (a subclass
// may do some filtering, for example) and then send an initial "reading"
// event right away.
DCHECK(!pending_reading_notification_.IsActive());
pending_reading_notification_ = PostCancellableTask(
*GetExecutionContext()->GetTaskRunner(TaskType::kSensor), FROM_HERE,
WTF::Bind(&Sensor::OnSensorReadingChanged, WrapWeakPersistent(this)));
}
DispatchEvent(*Event::Create(event_type_names::kActivate));
}
void Sensor::NotifyError(DOMException* error) {
DCHECK_NE(state_, SensorState::kIdle);
state_ = SensorState::kIdle;
DispatchEvent(*SensorErrorEvent::Create(event_type_names::kError, error));
}
bool Sensor::IsIdleOrErrored() const {
return (state_ == SensorState::kIdle) ||
pending_error_notification_.IsActive();
}
const device::SensorReading& Sensor::GetReading() const {
DCHECK(sensor_proxy_);
return sensor_proxy_->GetReading(use_screen_coords_);
}
} // namespace blink