Skip to content

Commit

Permalink
sensors: Add virtual sensor commands to browser_protocol.pdl and CDP …
Browse files Browse the repository at this point in the history
…implementation

Design doc:
https://docs.google.com/document/d/1JS2Wnyc9GiS_u1Ry3w4KsCyd51g8fm4RDEUcanZgzbM/edit?usp=sharing

This adds a few types and 3 new commands to the Emulation domain:
* getOverriddenSensorInformation(type: SensorType) -> { requestedSamplingFrequency: number }
* setSensorOverrideEnabled(enabled: boolean, type: SensorType, optional metadata: SensorMetadata)
* setSensorOverrideReadings(type: SensorType, reading: SensorReading)

Together, they allow us to implement the create/get/update/remove
virtual sensor WebDriver commands from the Automation section of the
Generic Sensor spec (the ChromeDriver bits will come later).

Most of the code has already been implemented elsewhere in content and
services. The CDP bits involve exposing the virtual sensor operations
from WebContentsSensorProviderProxy via an RAII object called
ScopedVirtualSensorForDevTools: since the entry point to the virtual
sensor operations is always CDP, this object is responsible for
automatically creating and removing virtual sensors while WCSPP takes
care of creating only one ScopedVirtualSensorForDevTools per sensor
type. This way, each EmulationHandler instance has its own set of
virtual sensors that are cleared when it is disabled, and other handlers
that exist at the same time are unable to create, interact with or
remove these virtual sensors.

[The increase in binary size for Fuchsia cannot be avoided, it all comes
mostly from the generated code for the new data in browser_protocol.pdl and
the new code in content::protocol::EmulationHandler]

Bug: 1278377
Fuchsia-Binary-Size: Size increase is unavoidable.
Change-Id: Ie891bea3f46ff90192ed860f8ffe5361c8485df7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4770864
Commit-Queue: Raphael Kubo Da Costa <raphael.kubo.da.costa@intel.com>
Reviewed-by: Matt Reynolds <mattreynolds@chromium.org>
Auto-Submit: Raphael Kubo Da Costa <raphael.kubo.da.costa@intel.com>
Reviewed-by: Andrey Kosyakov <caseq@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1211361}
  • Loading branch information
rakuco authored and Chromium LUCI CQ committed Oct 18, 2023
1 parent cc7a117 commit 7a99b78
Show file tree
Hide file tree
Showing 26 changed files with 1,132 additions and 35 deletions.
256 changes: 256 additions & 0 deletions content/browser/devtools/protocol/emulation_handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@
#include <utility>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/types/expected.h"
#include "build/build_config.h"
#include "components/download/public/common/download_url_parameters.h"
#include "content/browser/devtools/devtools_agent_host_impl.h"
#include "content/browser/generic_sensor/web_contents_sensor_provider_proxy.h"
#include "content/browser/idle/idle_manager_impl.h"
#include "content/browser/renderer_host/input/touch_emulator.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
Expand All @@ -21,9 +24,11 @@
#include "content/public/common/content_client.h"
#include "content/public/common/url_constants.h"
#include "net/http/http_util.h"
#include "services/device/public/cpp/generic_sensor/sensor_reading.h"
#include "services/device/public/cpp/geolocation/geoposition.h"
#include "services/device/public/mojom/geolocation_context.mojom.h"
#include "services/device/public/mojom/geoposition.mojom.h"
#include "services/device/public/mojom/sensor.mojom-shared.h"
#include "services/network/public/cpp/client_hints.h"
#include "ui/display/mojom/screen_orientation.mojom.h"
#include "ui/events/gesture_detection/gesture_provider_config_helper.h"
Expand All @@ -35,6 +40,10 @@ namespace {

constexpr char kCommandIsOnlyAvailableAtTopTarget[] =
"Command can only be executed on top-level targets";
constexpr char kSensorIsAlreadyOverridden[] =
"The specified sensor type is already overridden";
constexpr char kSensorIsNotOverridden[] =
"This sensor type is not being overridden with a virtual sensor";

display::mojom::ScreenOrientation WebScreenOrientationTypeFromString(
const std::string& type) {
Expand Down Expand Up @@ -105,6 +114,9 @@ void EmulationHandler::SetRenderer(int process_host_id,
RenderFrameHostImpl* frame_host) {
if (host_ == frame_host)
return;
if (!frame_host) {
sensor_overrides_.clear();
}
host_ = frame_host;
if (touch_emulation_enabled_)
UpdateTouchEventEmulationState();
Expand All @@ -131,9 +143,253 @@ Response EmulationHandler::Disable() {
prefers_color_scheme_ = "";
prefers_reduced_motion_ = "";
prefers_reduced_transparency_ = "";
sensor_overrides_.clear();
return Response::Success();
}

namespace {

Response ConvertSensorType(const Emulation::SensorType& type,
device::mojom::SensorType* out_type) {
if (type == Emulation::SensorTypeEnum::AbsoluteOrientation) {
*out_type = device::mojom::SensorType::ABSOLUTE_ORIENTATION_QUATERNION;
} else if (type == Emulation::SensorTypeEnum::Accelerometer) {
*out_type = device::mojom::SensorType::ACCELEROMETER;
} else if (type == Emulation::SensorTypeEnum::AmbientLight) {
*out_type = device::mojom::SensorType::AMBIENT_LIGHT;
} else if (type == Emulation::SensorTypeEnum::Gravity) {
*out_type = device::mojom::SensorType::GRAVITY;
} else if (type == Emulation::SensorTypeEnum::Gyroscope) {
*out_type = device::mojom::SensorType::GYROSCOPE;
} else if (type == Emulation::SensorTypeEnum::LinearAcceleration) {
*out_type = device::mojom::SensorType::LINEAR_ACCELERATION;
} else if (type == Emulation::SensorTypeEnum::Magnetometer) {
*out_type = device::mojom::SensorType::MAGNETOMETER;
} else if (type == Emulation::SensorTypeEnum::RelativeOrientation) {
*out_type = device::mojom::SensorType::RELATIVE_ORIENTATION_QUATERNION;
} else {
return Response::InvalidParams("Invalid sensor type: " + type);
}

return Response::Success();
}

Response ConvertSensorReading(device::mojom::SensorType type,
Emulation::SensorReading* const reading,
device::SensorReading* out_reading) {
switch (type) {
case device::mojom::SensorType::AMBIENT_LIGHT: {
if (!reading->HasSingle()) {
return Response::InvalidParams(
"This sensor type requires a 'single' parameter");
}
auto* single_value = reading->GetSingle(nullptr);
out_reading->als.value = single_value->GetValue();
break;
}
case device::mojom::SensorType::ACCELEROMETER:
case device::mojom::SensorType::GRAVITY:
case device::mojom::SensorType::GYROSCOPE:
case device::mojom::SensorType::LINEAR_ACCELERATION:
case device::mojom::SensorType::MAGNETOMETER: {
if (!reading->HasXyz()) {
return Response::InvalidParams(
"This sensor type requires an 'xyz' parameter");
}
auto* xyz = reading->GetXyz(nullptr);
out_reading->accel.x = xyz->GetX();
out_reading->accel.y = xyz->GetY();
out_reading->accel.z = xyz->GetZ();
break;
}
case device::mojom::SensorType::ABSOLUTE_ORIENTATION_QUATERNION:
case device::mojom::SensorType::RELATIVE_ORIENTATION_QUATERNION: {
if (!reading->HasQuaternion()) {
return Response::InvalidParams(
"This sensor type requires a 'quaternion' parameter");
}
auto* quaternion = reading->GetQuaternion(nullptr);
out_reading->orientation_quat.x = quaternion->GetX();
out_reading->orientation_quat.y = quaternion->GetY();
out_reading->orientation_quat.z = quaternion->GetZ();
out_reading->orientation_quat.w = quaternion->GetW();
break;
}
case device::mojom::SensorType::ABSOLUTE_ORIENTATION_EULER_ANGLES:
case device::mojom::SensorType::PRESSURE:
case device::mojom::SensorType::PROXIMITY:
case device::mojom::SensorType::RELATIVE_ORIENTATION_EULER_ANGLES:
return Response::InvalidParams("Unsupported sensor type");
}
out_reading->raw.timestamp =
(base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
return Response::Success();
}

base::expected<device::mojom::VirtualSensorMetadataPtr, Response>
ParseSensorMetadata(Maybe<Emulation::SensorMetadata>& metadata) {
if (!metadata.has_value()) {
return device::mojom::VirtualSensorMetadata::New();
}

if (metadata->HasMinimumFrequency() && metadata->HasMaximumFrequency() &&
metadata->GetMinimumFrequency(0) > metadata->GetMaximumFrequency(0)) {
return base::unexpected(
Response::InvalidParams("The specified minimum frequency is higher "
"than the maximum frequency"));
}

auto virtual_sensor_metadata = device::mojom::VirtualSensorMetadata::New();
if (metadata->HasAvailable()) {
virtual_sensor_metadata->available = metadata->GetAvailable(true);
}
if (metadata->HasMinimumFrequency()) {
virtual_sensor_metadata->minimum_frequency =
device::mojom::NullableDouble::New(metadata->GetMinimumFrequency(0));
}
if (metadata->HasMaximumFrequency()) {
virtual_sensor_metadata->maximum_frequency =
device::mojom::NullableDouble::New(metadata->GetMaximumFrequency(0));
}
return virtual_sensor_metadata;
}

} // namespace

void EmulationHandler::GetOverriddenSensorInformation(
const Emulation::SensorType& type,
std::unique_ptr<GetOverriddenSensorInformationCallback> callback) {
if (!host_) {
callback->sendFailure(Response::InternalError());
return;
}

device::mojom::SensorType sensor_type;
if (auto response = ConvertSensorType(type, &sensor_type);
!response.IsSuccess()) {
callback->sendFailure(response);
return;
}

auto it = sensor_overrides_.find(sensor_type);
if (it == sensor_overrides_.end()) {
callback->sendFailure(Response::InvalidParams(kSensorIsNotOverridden));
return;
}

it->second->GetVirtualSensorInformation(base::BindOnce(
[](std::unique_ptr<GetOverriddenSensorInformationCallback> callback,
device::mojom::GetVirtualSensorInformationResultPtr result) {
if (result->is_error()) {
switch (result->get_error()) {
case device::mojom::GetVirtualSensorInformationError::
kSensorTypeNotOverridden:
callback->sendFailure(
Response::InvalidParams(kSensorIsNotOverridden));
return;
}
}
CHECK(result->is_info());
callback->sendSuccess(result->get_info()->sampling_frequency);
},
std::move(callback)));
}

void EmulationHandler::SetSensorOverrideEnabled(
bool enabled,
const Emulation::SensorType& type,
Maybe<Emulation::SensorMetadata> metadata,
std::unique_ptr<SetSensorOverrideEnabledCallback> callback) {
if (!host_) {
callback->sendFailure(Response::InternalError());
return;
}

device::mojom::SensorType sensor_type;
if (auto response = ConvertSensorType(type, &sensor_type);
!response.IsSuccess()) {
callback->sendFailure(response);
return;
}

if (enabled) {
auto virtual_sensor_metadata = ParseSensorMetadata(metadata);
if (!virtual_sensor_metadata.has_value()) {
callback->sendFailure(virtual_sensor_metadata.error());
return;
}

if (sensor_overrides_.contains(sensor_type)) {
callback->sendFailure(
Response::InvalidParams(kSensorIsAlreadyOverridden));
return;
}

auto virtual_sensor =
WebContentsSensorProviderProxy::GetOrCreate(GetWebContents())
->CreateVirtualSensorForDevTools(
sensor_type, std::move(virtual_sensor_metadata.value()));
if (!virtual_sensor) {
callback->sendFailure(
Response::InvalidParams(kSensorIsAlreadyOverridden));
return;
}
sensor_overrides_[sensor_type] = std::move(virtual_sensor);
} else {
sensor_overrides_.erase(sensor_type);
}
callback->sendSuccess();
}

void EmulationHandler::SetSensorOverrideReadings(
const Emulation::SensorType& type,
std::unique_ptr<Emulation::SensorReading> reading,
std::unique_ptr<SetSensorOverrideReadingsCallback> callback) {
if (!host_) {
callback->sendFailure(Response::InternalError());
return;
}

device::mojom::SensorType sensor_type;
if (auto response = ConvertSensorType(type, &sensor_type);
!response.IsSuccess()) {
callback->sendFailure(response);
return;
}

device::SensorReading device_reading;
if (auto response =
ConvertSensorReading(sensor_type, reading.get(), &device_reading);
!response.IsSuccess()) {
callback->sendFailure(response);
return;
}

auto it = sensor_overrides_.find(sensor_type);
if (it == sensor_overrides_.end()) {
callback->sendFailure(Response::InvalidParams(kSensorIsNotOverridden));
return;
}

it->second->UpdateVirtualSensor(
device_reading,
base::BindOnce(
[](std::unique_ptr<SetSensorOverrideReadingsCallback> callback,
device::mojom::UpdateVirtualSensorResult result) {
switch (result) {
case device::mojom::UpdateVirtualSensorResult::
kSensorTypeNotOverridden:
callback->sendFailure(
Response::InvalidParams(kSensorIsNotOverridden));
break;
case device::mojom::UpdateVirtualSensorResult::kSuccess:
callback->sendSuccess();
break;
}
},
std::move(callback)));
}

Response EmulationHandler::SetIdleOverride(bool is_user_active,
bool is_screen_unlocked) {
if (!host_)
Expand Down
25 changes: 25 additions & 0 deletions content/browser/devtools/protocol/emulation_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@
#ifndef CONTENT_BROWSER_DEVTOOLS_PROTOCOL_EMULATION_HANDLER_H_
#define CONTENT_BROWSER_DEVTOOLS_PROTOCOL_EMULATION_HANDLER_H_

#include <memory>

#include "base/containers/flat_map.h"
#include "content/browser/devtools/protocol/devtools_domain_handler.h"
#include "content/browser/devtools/protocol/emulation.h"
#include "content/browser/devtools/protocol/protocol.h"
#include "services/device/public/mojom/sensor.mojom-shared.h"
#include "services/device/public/mojom/sensor_provider.mojom-shared.h"
#include "third_party/blink/public/common/user_agent/user_agent_metadata.h"
#include "third_party/blink/public/common/widget/device_emulation_params.h"

Expand All @@ -23,6 +29,7 @@ namespace content {
class DevToolsAgentHostImpl;
class RenderFrameHostImpl;
class RenderWidgetHostImpl;
class ScopedVirtualSensorForDevTools;
class WebContentsImpl;

namespace protocol {
Expand All @@ -46,6 +53,19 @@ class EmulationHandler : public DevToolsDomainHandler,

Response Disable() override;

void GetOverriddenSensorInformation(
const Emulation::SensorType& type,
std::unique_ptr<GetOverriddenSensorInformationCallback>) override;
void SetSensorOverrideEnabled(
bool enabled,
const Emulation::SensorType& type,
Maybe<Emulation::SensorMetadata> metadata,
std::unique_ptr<SetSensorOverrideEnabledCallback>) override;
void SetSensorOverrideReadings(
const Emulation::SensorType& type,
std::unique_ptr<Emulation::SensorReading> reading,
std::unique_ptr<SetSensorOverrideReadingsCallback>) override;

Response SetIdleOverride(bool is_user_active,
bool is_screen_unlocked) override;
Response ClearIdleOverride() override;
Expand Down Expand Up @@ -110,6 +130,7 @@ class EmulationHandler : public DevToolsDomainHandler,

private:
WebContentsImpl* GetWebContents();

void UpdateTouchEventEmulationState();
void UpdateDeviceEmulationState();
void UpdateDeviceEmulationStateForHost(
Expand Down Expand Up @@ -137,6 +158,10 @@ class EmulationHandler : public DevToolsDomainHandler,
// "prefers-reduced-transparency" client hint header, when present.
std::string prefers_reduced_transparency_;

base::flat_map<device::mojom::SensorType,
std::unique_ptr<ScopedVirtualSensorForDevTools>>
sensor_overrides_;

RenderFrameHostImpl* host_;

base::ScopedClosureRunner capture_handle_;
Expand Down
24 changes: 23 additions & 1 deletion content/browser/devtools/protocol_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,29 @@
},
{
"domain": "Emulation",
"include": ["setIdleOverride", "clearIdleOverride", "setGeolocationOverride", "clearGeolocationOverride", "setEmitTouchEventsForMouse", "canEmulate", "setDeviceMetricsOverride", "clearDeviceMetricsOverride", "setVisibleSize", "setUserAgentOverride", "setFocusEmulationEnabled", "setEmulatedMedia"]
"include": [
"getOverriddenSensorInformation",
"setSensorOverrideEnabled",
"setSensorOverrideReadings",

"setIdleOverride",
"clearIdleOverride",
"setGeolocationOverride",
"clearGeolocationOverride",
"setEmitTouchEventsForMouse",
"canEmulate",
"setDeviceMetricsOverride",
"clearDeviceMetricsOverride",
"setVisibleSize",
"setUserAgentOverride",
"setFocusEmulationEnabled",
"setEmulatedMedia"
],
"async": [
"getOverriddenSensorInformation",
"setSensorOverrideEnabled",
"setSensorOverrideReadings"
]
},
{
"domain": "FedCm"
Expand Down

0 comments on commit 7a99b78

Please sign in to comment.