blob: df8fbc7813326720dc05f4ca4250382df9aa75fa [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 "base/command_line.h"
#include "base/macros.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_light_hardware_buffer.h"
#include "device/sensors/public/cpp/device_motion_hardware_buffer.h"
#include "device/sensors/public/cpp/device_orientation_hardware_buffer.h"
namespace content {
namespace {
class FakeDataFetcher : public device::DataFetcherSharedMemory {
public:
FakeDataFetcher() : sensor_data_available_(true) {}
~FakeDataFetcher() override {}
void SetMotionStartedCallback(base::Closure motion_started_callback) {
motion_started_callback_ = motion_started_callback;
}
void SetMotionStoppedCallback(base::Closure motion_stopped_callback) {
motion_stopped_callback_ = motion_stopped_callback;
}
void SetLightStartedCallback(base::Closure light_started_callback) {
light_started_callback_ = light_started_callback;
}
void SetLightStoppedCallback(base::Closure light_stopped_callback) {
light_stopped_callback_ = light_stopped_callback;
}
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;
}
bool Start(device::ConsumerType consumer_type, void* buffer) override {
EXPECT_TRUE(buffer);
switch (consumer_type) {
case device::CONSUMER_TYPE_MOTION: {
device::DeviceMotionHardwareBuffer* motion_buffer =
static_cast<device::DeviceMotionHardwareBuffer*>(buffer);
if (sensor_data_available_)
UpdateMotion(motion_buffer);
SetMotionBufferReady(motion_buffer);
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
motion_started_callback_);
} break;
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_LIGHT: {
device::DeviceLightHardwareBuffer* light_buffer =
static_cast<device::DeviceLightHardwareBuffer*>(buffer);
UpdateLight(light_buffer,
sensor_data_available_
? 100
: std::numeric_limits<double>::infinity());
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
light_started_callback_);
} break;
default:
return false;
}
return true;
}
bool Stop(device::ConsumerType consumer_type) override {
switch (consumer_type) {
case device::CONSUMER_TYPE_MOTION:
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
motion_stopped_callback_);
break;
case device::CONSUMER_TYPE_ORIENTATION:
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
orientation_stopped_callback_);
break;
case device::CONSUMER_TYPE_LIGHT:
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
light_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 SetMotionBufferReady(device::DeviceMotionHardwareBuffer* buffer) {
buffer->seqlock.WriteBegin();
buffer->data.allAvailableSensorsAreActive = true;
buffer->seqlock.WriteEnd();
}
void SetOrientationBufferReady(
device::DeviceOrientationHardwareBuffer* buffer) {
buffer->seqlock.WriteBegin();
buffer->data.allAvailableSensorsAreActive = true;
buffer->seqlock.WriteEnd();
}
void UpdateMotion(device::DeviceMotionHardwareBuffer* buffer) {
buffer->seqlock.WriteBegin();
buffer->data.accelerationX = 1;
buffer->data.hasAccelerationX = true;
buffer->data.accelerationY = 2;
buffer->data.hasAccelerationY = true;
buffer->data.accelerationZ = 3;
buffer->data.hasAccelerationZ = true;
buffer->data.accelerationIncludingGravityX = 4;
buffer->data.hasAccelerationIncludingGravityX = true;
buffer->data.accelerationIncludingGravityY = 5;
buffer->data.hasAccelerationIncludingGravityY = true;
buffer->data.accelerationIncludingGravityZ = 6;
buffer->data.hasAccelerationIncludingGravityZ = true;
buffer->data.rotationRateAlpha = 7;
buffer->data.hasRotationRateAlpha = true;
buffer->data.rotationRateBeta = 8;
buffer->data.hasRotationRateBeta = true;
buffer->data.rotationRateGamma = 9;
buffer->data.hasRotationRateGamma = true;
buffer->data.interval = 100;
buffer->data.allAvailableSensorsAreActive = true;
buffer->seqlock.WriteEnd();
}
void UpdateOrientation(device::DeviceOrientationHardwareBuffer* buffer) {
buffer->seqlock.WriteBegin();
buffer->data.alpha = 1;
buffer->data.hasAlpha = true;
buffer->data.beta = 2;
buffer->data.hasBeta = true;
buffer->data.gamma = 3;
buffer->data.hasGamma = true;
buffer->data.allAvailableSensorsAreActive = true;
buffer->seqlock.WriteEnd();
}
void UpdateLight(device::DeviceLightHardwareBuffer* buffer, double lux) {
buffer->seqlock.WriteBegin();
buffer->data.value = lux;
buffer->seqlock.WriteEnd();
}
// The below callbacks should be run on the UI thread.
base::Closure motion_started_callback_;
base::Closure orientation_started_callback_;
base::Closure light_started_callback_;
base::Closure motion_stopped_callback_;
base::Closure orientation_stopped_callback_;
base::Closure light_stopped_callback_;
bool sensor_data_available_;
private:
DISALLOW_COPY_AND_ASSIGN(FakeDataFetcher);
};
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.
light_started_runloop_.reset(new base::RunLoop());
light_stopped_runloop_.reset(new base::RunLoop());
motion_started_runloop_.reset(new base::RunLoop());
motion_stopped_runloop_.reset(new base::RunLoop());
orientation_started_runloop_.reset(new base::RunLoop());
orientation_stopped_runloop_.reset(new base::RunLoop());
#if defined(OS_ANDROID)
// On Android, the DeviceSensorService lives on the UI thread.
SetUpFetcher();
#else
// On all other platforms, the DeviceSensorService lives on the IO thread.
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&DeviceSensorBrowserTest::SetUpOnIOThread,
base::Unretained(this)));
io_loop_finished_event_.Wait();
#endif
}
void SetUpFetcher() {
fetcher_ = new FakeDataFetcher();
fetcher_->SetLightStartedCallback(light_started_runloop_->QuitClosure());
fetcher_->SetLightStoppedCallback(light_stopped_runloop_->QuitClosure());
fetcher_->SetMotionStartedCallback(motion_started_runloop_->QuitClosure());
fetcher_->SetMotionStoppedCallback(motion_stopped_runloop_->QuitClosure());
fetcher_->SetOrientationStartedCallback(
orientation_started_runloop_->QuitClosure());
fetcher_->SetOrientationStoppedCallback(
orientation_stopped_runloop_->QuitClosure());
device::DeviceSensorService::GetInstance()->SetDataFetcherForTesting(
fetcher_);
}
void SetUpOnIOThread() {
SetUpFetcher();
io_loop_finished_event_.Signal();
}
void DelayAndQuit(base::TimeDelta delay) {
base::PlatformThread::Sleep(delay);
base::MessageLoop::current()->QuitWhenIdle();
}
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();
}
void EnableExperimentalFeatures() {
// TODO(riju): remove when the DeviceLight feature goes stable.
base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
if (!cmd_line->HasSwitch(switches::kEnableExperimentalWebPlatformFeatures))
cmd_line->AppendSwitch(switches::kEnableExperimentalWebPlatformFeatures);
}
FakeDataFetcher* fetcher_;
// 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> light_started_runloop_;
std::unique_ptr<base::RunLoop> light_stopped_runloop_;
std::unique_ptr<base::RunLoop> motion_started_runloop_;
std::unique_ptr<base::RunLoop> motion_stopped_runloop_;
std::unique_ptr<base::RunLoop> orientation_started_runloop_;
std::unique_ptr<base::RunLoop> orientation_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, LightTest) {
// The test page will register an event handler for light events,
// expects to get an event with fake values, then removes the event
// handler and navigates to #pass.
EnableExperimentalFeatures();
GURL test_url = GetTestUrl("device_sensors", "device_light_test.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 2);
EXPECT_EQ("pass", shell()->web_contents()->GetLastCommittedURL().ref());
light_started_runloop_->Run();
light_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());
motion_started_runloop_->Run();
motion_stopped_runloop_->Run();
}
IN_PROC_BROWSER_TEST_F(DeviceSensorBrowserTest, LightOneOffInfintyTest) {
// The test page registers an event handler for light events and expects
// to get an event with value equal to infinity, because no sensor data can
// be provided.
EnableExperimentalFeatures();
fetcher_->SetSensorDataAvailable(false);
GURL test_url =
GetTestUrl("device_sensors", "device_light_infinity_test.html");
NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 2);
EXPECT_EQ("pass", shell()->web_contents()->GetLastCommittedURL().ref());
light_started_runloop_->Run();
light_stopped_runloop_->Run();
}
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, 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.
fetcher_->SetSensorDataAvailable(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());
motion_started_runloop_->Run();
motion_stopped_runloop_->Run();
}
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);
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));
motion_started_runloop_->Run();
motion_stopped_runloop_->Run();
orientation_started_runloop_->Run();
orientation_stopped_runloop_->Run();
same_tab_observer.Wait();
EXPECT_EQ("pass", shell()->web_contents()->GetLastCommittedURL().ref());
}
} // namespace
} // namespace content