blob: 06f0d2c5234c44ef1bdd2e46161d6ef32379728e [file] [log] [blame]
// Copyright 2014 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 <memory>
#include <string>
#include <utility>
#include "base/command_line.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/platform_thread.h"
#include "build/build_config.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/shell/browser/shell_javascript_dialog_manager.h"
#include "device/sensors/data_fetcher_shared_memory.h"
#include "device/sensors/device_sensor_service.h"
#include "device/sensors/public/cpp/device_motion_hardware_buffer.h"
#include "device/sensors/public/cpp/device_orientation_hardware_buffer.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "mojo/public/cpp/system/buffer.h"
#include "services/device/public/cpp/generic_sensor/platform_sensor_configuration.h"
#include "services/device/public/cpp/generic_sensor/sensor_reading.h"
#include "services/device/public/interfaces/constants.mojom.h"
#include "services/device/public/interfaces/sensor.mojom.h"
#include "services/device/public/interfaces/sensor_provider.mojom.h"
#include "services/service_manager/public/cpp/service_context.h"
namespace content {
namespace {
class FakeDataFetcher : public device::DataFetcherSharedMemory {
public:
FakeDataFetcher() : sensor_data_available_(true) {}
~FakeDataFetcher() override {}
void SetOrientationStartedCallback(
base::Closure orientation_started_callback) {
orientation_started_callback_ = orientation_started_callback;
}
void SetOrientationStoppedCallback(
base::Closure orientation_stopped_callback) {
orientation_stopped_callback_ = orientation_stopped_callback;
}
void SetOrientationAbsoluteStartedCallback(
base::Closure orientation_absolute_started_callback) {
orientation_absolute_started_callback_ =
orientation_absolute_started_callback;
}
void SetOrientationAbsoluteStoppedCallback(
base::Closure orientation_absolute_stopped_callback) {
orientation_absolute_stopped_callback_ =
orientation_absolute_stopped_callback;
}
bool Start(device::ConsumerType consumer_type, void* buffer) override {
EXPECT_TRUE(buffer);
switch (consumer_type) {
case device::CONSUMER_TYPE_ORIENTATION: {
device::DeviceOrientationHardwareBuffer* orientation_buffer =
static_cast<device::DeviceOrientationHardwareBuffer*>(buffer);
if (sensor_data_available_)
UpdateOrientation(orientation_buffer);
SetOrientationBufferReady(orientation_buffer);
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
orientation_started_callback_);
} break;
case device::CONSUMER_TYPE_ORIENTATION_ABSOLUTE: {
device::DeviceOrientationHardwareBuffer* orientation_buffer =
static_cast<device::DeviceOrientationHardwareBuffer*>(buffer);
if (sensor_data_available_)
UpdateOrientationAbsolute(orientation_buffer);
SetOrientationBufferReady(orientation_buffer);
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
orientation_absolute_started_callback_);
} break;
default:
return false;
}
return true;
}
bool Stop(device::ConsumerType consumer_type) override {
switch (consumer_type) {
case device::CONSUMER_TYPE_ORIENTATION:
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
orientation_stopped_callback_);
break;
case device::CONSUMER_TYPE_ORIENTATION_ABSOLUTE:
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
orientation_absolute_stopped_callback_);
break;
default:
return false;
}
return true;
}
void Fetch(unsigned consumer_bitmask) override {
FAIL() << "fetch should not be called";
}
FetcherType GetType() const override { return FETCHER_TYPE_DEFAULT; }
void SetSensorDataAvailable(bool available) {
sensor_data_available_ = available;
}
void SetOrientationBufferReady(
device::DeviceOrientationHardwareBuffer* buffer) {
buffer->seqlock.WriteBegin();
buffer->data.all_available_sensors_are_active = true;
buffer->seqlock.WriteEnd();
}
void UpdateOrientation(device::DeviceOrientationHardwareBuffer* buffer) {
buffer->seqlock.WriteBegin();
buffer->data.alpha = 1;
buffer->data.has_alpha = true;
buffer->data.beta = 2;
buffer->data.has_beta = true;
buffer->data.gamma = 3;
buffer->data.has_gamma = true;
buffer->data.all_available_sensors_are_active = true;
buffer->seqlock.WriteEnd();
}
void UpdateOrientationAbsolute(
device::DeviceOrientationHardwareBuffer* buffer) {
buffer->seqlock.WriteBegin();
buffer->data.alpha = 4;
buffer->data.has_alpha = true;
buffer->data.beta = 5;
buffer->data.has_beta = true;
buffer->data.gamma = 6;
buffer->data.has_gamma = true;
buffer->data.absolute = true;
buffer->data.all_available_sensors_are_active = true;
buffer->seqlock.WriteEnd();
}
// The below callbacks should be run on the UI thread.
base::Closure orientation_started_callback_;
base::Closure orientation_absolute_started_callback_;
base::Closure orientation_stopped_callback_;
base::Closure orientation_absolute_stopped_callback_;
bool sensor_data_available_;
private:
DISALLOW_COPY_AND_ASSIGN(FakeDataFetcher);
};
class FakeSensor : public device::mojom::Sensor {
public:
FakeSensor(device::mojom::SensorType sensor_type)
: sensor_type_(sensor_type) {
shared_buffer_handle_ = mojo::SharedBufferHandle::Create(
sizeof(device::SensorReadingSharedBuffer) *
static_cast<uint64_t>(device::mojom::SensorType::LAST));
}
~FakeSensor() override = default;
// device::mojom::Sensor:
void AddConfiguration(
const device::PlatformSensorConfiguration& configuration,
AddConfigurationCallback callback) override {
std::move(callback).Run(true);
SensorReadingChanged();
}
// device::mojom::Sensor:
void GetDefaultConfiguration(
GetDefaultConfigurationCallback callback) override {
std::move(callback).Run(GetDefaultConfiguration());
}
// device::mojom::Sensor:
void RemoveConfiguration(
const device::PlatformSensorConfiguration& configuration) override {}
// device::mojom::Sensor:
void Suspend() override {}
void Resume() override {}
void ConfigureReadingChangeNotifications(bool enabled) override {
reading_notification_enabled_ = enabled;
}
device::PlatformSensorConfiguration GetDefaultConfiguration() {
return device::PlatformSensorConfiguration(60 /* frequency */);
}
device::mojom::ReportingMode GetReportingMode() {
return device::mojom::ReportingMode::ON_CHANGE;
}
double GetMaximumSupportedFrequency() { return 60.0; }
double GetMinimumSupportedFrequency() { return 1.0; }
device::mojom::SensorClientRequest GetClient() {
return mojo::MakeRequest(&client_);
}
mojo::ScopedSharedBufferHandle GetSharedBufferHandle() {
return shared_buffer_handle_->Clone(
mojo::SharedBufferHandle::AccessMode::READ_ONLY);
}
uint64_t GetBufferOffset() {
return device::SensorReadingSharedBuffer::GetOffset(sensor_type_);
}
void set_reading(device::SensorReading reading) { reading_ = reading; }
void SensorReadingChanged() {
if (!shared_buffer_handle_.is_valid())
return;
mojo::ScopedSharedBufferMapping shared_buffer =
shared_buffer_handle_->MapAtOffset(
device::mojom::SensorInitParams::kReadBufferSizeForTests,
GetBufferOffset());
device::SensorReadingSharedBuffer* buffer =
static_cast<device::SensorReadingSharedBuffer*>(shared_buffer.get());
auto& seqlock = buffer->seqlock.value();
seqlock.WriteBegin();
buffer->reading = reading_;
seqlock.WriteEnd();
if (client_ && reading_notification_enabled_)
client_->SensorReadingChanged();
}
private:
device::mojom::SensorType sensor_type_;
bool reading_notification_enabled_ = true;
mojo::ScopedSharedBufferHandle shared_buffer_handle_;
device::mojom::SensorClientPtr client_;
device::SensorReading reading_;
DISALLOW_COPY_AND_ASSIGN(FakeSensor);
};
class FakeSensorProvider : public device::mojom::SensorProvider {
public:
FakeSensorProvider() : binding_(this) {}
~FakeSensorProvider() override = default;
void Bind(const std::string& interface_name,
mojo::ScopedMessagePipeHandle handle,
const service_manager::BindSourceInfo& source_info) {
DCHECK(!binding_.is_bound());
binding_.Bind(device::mojom::SensorProviderRequest(std::move(handle)));
}
void set_accelerometer_is_available(bool accelerometer_is_available) {
accelerometer_is_available_ = accelerometer_is_available;
}
void set_linear_acceleration_sensor_is_available(
bool linear_acceleration_sensor_is_available) {
linear_acceleration_sensor_is_available_ =
linear_acceleration_sensor_is_available;
}
void set_gyroscope_is_available(bool gyroscope_is_available) {
gyroscope_is_available_ = gyroscope_is_available;
}
// device::mojom::sensorProvider:
void GetSensor(device::mojom::SensorType type,
device::mojom::SensorRequest sensor_request,
GetSensorCallback callback) override {
std::unique_ptr<FakeSensor> sensor;
device::SensorReading reading;
reading.raw.timestamp =
(base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
switch (type) {
case device::mojom::SensorType::ACCELEROMETER:
if (accelerometer_is_available_) {
sensor = base::MakeUnique<FakeSensor>(
device::mojom::SensorType::ACCELEROMETER);
reading.accel.x = 4;
reading.accel.y = 5;
reading.accel.z = 6;
}
break;
case device::mojom::SensorType::LINEAR_ACCELERATION:
if (linear_acceleration_sensor_is_available_) {
sensor = base::MakeUnique<FakeSensor>(
device::mojom::SensorType::LINEAR_ACCELERATION);
reading.accel.x = 1;
reading.accel.y = 2;
reading.accel.z = 3;
}
break;
case device::mojom::SensorType::GYROSCOPE:
if (gyroscope_is_available_) {
sensor = base::MakeUnique<FakeSensor>(
device::mojom::SensorType::GYROSCOPE);
reading.gyro.x = 7;
reading.gyro.y = 8;
reading.gyro.z = 9;
}
break;
default:
NOTIMPLEMENTED();
}
if (sensor) {
sensor->set_reading(reading);
auto init_params = device::mojom::SensorInitParams::New();
init_params->memory = sensor->GetSharedBufferHandle();
init_params->buffer_offset = sensor->GetBufferOffset();
init_params->default_configuration = sensor->GetDefaultConfiguration();
init_params->maximum_frequency = sensor->GetMaximumSupportedFrequency();
init_params->minimum_frequency = sensor->GetMinimumSupportedFrequency();
std::move(callback).Run(std::move(init_params), sensor->GetClient());
mojo::MakeStrongBinding(std::move(sensor), std::move(sensor_request));
} else {
std::move(callback).Run(nullptr, nullptr);
}
}
private:
mojo::Binding<device::mojom::SensorProvider> binding_;
bool accelerometer_is_available_ = true;
bool linear_acceleration_sensor_is_available_ = true;
bool gyroscope_is_available_ = true;
DISALLOW_COPY_AND_ASSIGN(FakeSensorProvider);
};
class DeviceSensorBrowserTest : public ContentBrowserTest {
public:
DeviceSensorBrowserTest()
: fetcher_(nullptr),
io_loop_finished_event_(
base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED) {}
void SetUpOnMainThread() override {
// Initialize the RunLoops now that the main thread has been created.
orientation_started_runloop_.reset(new base::RunLoop());
orientation_stopped_runloop_.reset(new base::RunLoop());
orientation_absolute_started_runloop_.reset(new base::RunLoop());
orientation_absolute_stopped_runloop_.reset(new base::RunLoop());
#if defined(OS_ANDROID)
// On Android, the DeviceSensorService lives on the UI thread.
SetUpFetcher();
#endif // defined(OS_ANDROID)
sensor_provider_ = base::MakeUnique<FakeSensorProvider>();
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(&DeviceSensorBrowserTest::SetUpOnIOThread,
base::Unretained(this)));
io_loop_finished_event_.Wait();
}
void SetUpFetcher() {
fetcher_ = new FakeDataFetcher();
fetcher_->SetOrientationStartedCallback(
orientation_started_runloop_->QuitClosure());
fetcher_->SetOrientationStoppedCallback(
orientation_stopped_runloop_->QuitClosure());
fetcher_->SetOrientationAbsoluteStartedCallback(
orientation_absolute_started_runloop_->QuitClosure());
fetcher_->SetOrientationAbsoluteStoppedCallback(
orientation_absolute_stopped_runloop_->QuitClosure());
device::DeviceSensorService::GetInstance()->SetDataFetcherForTesting(
fetcher_);
}
void SetUpOnIOThread() {
#if !defined(OS_ANDROID)
// On non-Android platforms, the DeviceSensorService lives on the IO thread.
SetUpFetcher();
#endif // !defined(OS_ANDROID)
// Because Device Service also runs in this process(browser process), here
// we can directly set our binder to intercept interface requests against
// it.
service_manager::ServiceContext::SetGlobalBinderForTesting(
device::mojom::kServiceName, device::mojom::SensorProvider::Name_,
base::Bind(&FakeSensorProvider::Bind,
base::Unretained(sensor_provider_.get())));
io_loop_finished_event_.Signal();
}
void DelayAndQuit(base::TimeDelta delay) {
base::PlatformThread::Sleep(delay);
base::RunLoop::QuitCurrentWhenIdleDeprecated();
}
void WaitForAlertDialogAndQuitAfterDelay(base::TimeDelta delay) {
ShellJavaScriptDialogManager* dialog_manager =
static_cast<ShellJavaScriptDialogManager*>(
shell()->GetJavaScriptDialogManager(shell()->web_contents()));
scoped_refptr<MessageLoopRunner> runner = new MessageLoopRunner();
dialog_manager->set_dialog_request_callback(
base::Bind(&DeviceSensorBrowserTest::DelayAndQuit,
base::Unretained(this), delay));
runner->Run();
}
FakeDataFetcher* fetcher_;
std::unique_ptr<FakeSensorProvider> sensor_provider_;
// NOTE: These can only be initialized once the main thread has been created
// and so must be pointers instead of plain objects.
std::unique_ptr<base::RunLoop> orientation_started_runloop_;
std::unique_ptr<base::RunLoop> orientation_stopped_runloop_;
std::unique_ptr<base::RunLoop> orientation_absolute_started_runloop_;
std::unique_ptr<base::RunLoop> orientation_absolute_stopped_runloop_;
private:
base::WaitableEvent io_loop_finished_event_;
};
IN_PROC_BROWSER_TEST_F(DeviceSensorBrowserTest, OrientationTest) {
// The test page will register an event handler for orientation events,
// expects to get an event with fake values, then removes the event
// handler and navigates to #pass.
GURL test_url = GetTestUrl("device_sensors", "device_orientation_test.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 2);
EXPECT_EQ("pass", shell()->web_contents()->GetLastCommittedURL().ref());
orientation_started_runloop_->Run();
orientation_stopped_runloop_->Run();
}
IN_PROC_BROWSER_TEST_F(DeviceSensorBrowserTest, OrientationAbsoluteTest) {
// The test page will register an event handler for absolute orientation
// events, expects to get an event with fake values, then removes the event
// handler and navigates to #pass.
GURL test_url =
GetTestUrl("device_sensors", "device_orientation_absolute_test.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 2);
EXPECT_EQ("pass", shell()->web_contents()->GetLastCommittedURL().ref());
orientation_absolute_started_runloop_->Run();
orientation_absolute_stopped_runloop_->Run();
}
IN_PROC_BROWSER_TEST_F(DeviceSensorBrowserTest, MotionTest) {
// The test page will register an event handler for motion events,
// expects to get an event with fake values, then removes the event
// handler and navigates to #pass.
GURL test_url = GetTestUrl("device_sensors", "device_motion_test.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 2);
EXPECT_EQ("pass", shell()->web_contents()->GetLastCommittedURL().ref());
}
IN_PROC_BROWSER_TEST_F(DeviceSensorBrowserTest, OrientationNullTest) {
// The test page registers an event handler for orientation events and
// expects to get an event with null values, because no sensor data can be
// provided.
fetcher_->SetSensorDataAvailable(false);
GURL test_url =
GetTestUrl("device_sensors", "device_orientation_null_test.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 2);
EXPECT_EQ("pass", shell()->web_contents()->GetLastCommittedURL().ref());
orientation_started_runloop_->Run();
orientation_stopped_runloop_->Run();
}
IN_PROC_BROWSER_TEST_F(DeviceSensorBrowserTest, OrientationAbsoluteNullTest) {
// The test page registers an event handler for absolute orientation events
// and expects to get an event with null values, because no sensor data can be
// provided.
fetcher_->SetSensorDataAvailable(false);
GURL test_url = GetTestUrl("device_sensors",
"device_orientation_absolute_null_test.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 2);
EXPECT_EQ("pass", shell()->web_contents()->GetLastCommittedURL().ref());
orientation_absolute_started_runloop_->Run();
orientation_absolute_stopped_runloop_->Run();
}
IN_PROC_BROWSER_TEST_F(DeviceSensorBrowserTest, MotionNullTest) {
// The test page registers an event handler for motion events and
// expects to get an event with null values, because no sensor data can be
// provided.
sensor_provider_->set_accelerometer_is_available(false);
sensor_provider_->set_linear_acceleration_sensor_is_available(false);
sensor_provider_->set_gyroscope_is_available(false);
GURL test_url = GetTestUrl("device_sensors", "device_motion_null_test.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 2);
EXPECT_EQ("pass", shell()->web_contents()->GetLastCommittedURL().ref());
}
IN_PROC_BROWSER_TEST_F(DeviceSensorBrowserTest,
MotionOnlySomeSensorsAreAvailableTest) {
// The test page registers an event handler for motion events and
// expects to get an event with only the gyroscope and linear acceleration
// sensor values, because no accelerometer values can be provided.
sensor_provider_->set_accelerometer_is_available(false);
GURL test_url =
GetTestUrl("device_sensors",
"device_motion_only_some_sensors_are_available_test.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 2);
EXPECT_EQ("pass", shell()->web_contents()->GetLastCommittedURL().ref());
}
IN_PROC_BROWSER_TEST_F(DeviceSensorBrowserTest, NullTestWithAlert) {
// The test page registers an event handlers for motion/orientation events and
// expects to get events with null values. The test raises a modal alert
// dialog with a delay to test that the one-off null-events still propagate to
// window after the alert is dismissed and the callbacks are invoked which
// eventually navigate to #pass.
fetcher_->SetSensorDataAvailable(false);
sensor_provider_->set_accelerometer_is_available(false);
sensor_provider_->set_linear_acceleration_sensor_is_available(false);
sensor_provider_->set_gyroscope_is_available(false);
TestNavigationObserver same_tab_observer(shell()->web_contents(), 2);
GURL test_url =
GetTestUrl("device_sensors", "device_sensors_null_test_with_alert.html");
shell()->LoadURL(test_url);
// TODO(timvolodine): investigate if it is possible to test this without
// delay, crbug.com/360044.
WaitForAlertDialogAndQuitAfterDelay(base::TimeDelta::FromMilliseconds(500));
orientation_started_runloop_->Run();
orientation_stopped_runloop_->Run();
same_tab_observer.Wait();
EXPECT_EQ("pass", shell()->web_contents()->GetLastCommittedURL().ref());
}
} // namespace
} // namespace content