| 'use strict'; |
| |
| // Wraps callback and calls rejectFunc if callback throws an error. |
| class CallbackWrapper { |
| constructor(callback, rejectFunc) { |
| this.wrapperFunc_ = (args) => { |
| try { |
| callback(args); |
| } catch(e) { |
| rejectFunc(e); |
| } |
| } |
| } |
| |
| get callback() { |
| return this.wrapperFunc_; |
| } |
| } |
| |
| function sensorMocks() { |
| // Helper function that returns resolved promise with result. |
| function sensorResponse(success) { |
| return Promise.resolve({success}); |
| } |
| |
| // Class that mocks Sensor interface defined in sensor.mojom |
| class MockSensor { |
| constructor(sensorRequest, handle, offset, size, reportingMode) { |
| this.client_ = null; |
| this.startShouldFail_ = false; |
| this.reportingMode_ = reportingMode; |
| this.sensorReadingTimerId_ = null; |
| this.updateReadingFunction_ = null; |
| this.suspendCalled_ = null; |
| this.resumeCalled_ = null; |
| this.addConfigurationCalled_ = null; |
| this.removeConfigurationCalled_ = null; |
| this.requestedFrequencies_ = []; |
| let rv = handle.mapBuffer(offset, size); |
| assert_equals(rv.result, Mojo.RESULT_OK, "Failed to map shared buffer"); |
| this.bufferArray_ = rv.buffer; |
| this.buffer_ = new Float64Array(this.bufferArray_); |
| this.resetBuffer(); |
| this.binding_ = new mojo.Binding(device.mojom.Sensor, this, |
| sensorRequest); |
| this.binding_.setConnectionErrorHandler(() => { |
| this.reset(); |
| }); |
| } |
| |
| // Returns default configuration. |
| getDefaultConfiguration() { |
| return Promise.resolve({frequency: 5}); |
| } |
| |
| // Adds configuration for the sensor and starts reporting fake data |
| // through updateReadingFunction_ callback. |
| addConfiguration(configuration) { |
| assert_not_equals(configuration, null, "Invalid sensor configuration."); |
| |
| this.requestedFrequencies_.push(configuration.frequency); |
| // Sort using descending order. |
| this.requestedFrequencies_.sort( |
| (first, second) => { return second - first }); |
| |
| if (!this.startShouldFail_ ) |
| this.startReading(); |
| |
| if (this.addConfigurationCalled_ != null) |
| this.addConfigurationCalled_(this); |
| |
| return sensorResponse(!this.startShouldFail_); |
| } |
| |
| // Removes sensor configuration from the list of active configurations and |
| // stops notification about sensor reading changes if |
| // requestedFrequencies_ is empty. |
| removeConfiguration(configuration) { |
| if (this.removeConfigurationCalled_ != null) { |
| this.removeConfigurationCalled_(this); |
| } |
| |
| let index = this.requestedFrequencies_.indexOf(configuration.frequency); |
| if (index == -1) |
| return; |
| |
| this.requestedFrequencies_.splice(index, 1); |
| if (this.requestedFrequencies_.length === 0) |
| this.stopReading(); |
| } |
| |
| // Suspends sensor. |
| suspend() { |
| this.stopReading(); |
| if (this.suspendCalled_ != null) { |
| this.suspendCalled_(this); |
| } |
| } |
| |
| // Resumes sensor. |
| resume() { |
| assert_equals(this.sensorReadingTimerId_, null); |
| this.startReading(); |
| if (this.resumeCalled_ != null) { |
| this.resumeCalled_(this); |
| } |
| } |
| |
| // Mock functions |
| |
| // Resets mock Sensor state. |
| reset() { |
| this.stopReading(); |
| |
| this.startShouldFail_ = false; |
| this.updateReadingFunction_ = null; |
| this.requestedFrequencies_ = []; |
| this.suspendCalled_ = null; |
| this.resumeCalled_ = null; |
| this.addConfigurationCalled_ = null; |
| this.removeConfigurationCalled_ = null; |
| this.resetBuffer(); |
| this.bufferArray_ = null; |
| this.binding_.close(); |
| } |
| |
| // Zeroes shared buffer. |
| resetBuffer() { |
| for (let i = 0; i < this.buffer_.length; ++i) { |
| this.buffer_[i] = 0; |
| } |
| } |
| |
| // Sets callback that is used to deliver sensor reading updates. |
| setUpdateSensorReadingFunction(updateReadingFunction) { |
| this.updateReadingFunction_ = updateReadingFunction; |
| return Promise.resolve(this); |
| } |
| |
| // Sets flag that forces sensor to fail when addConfiguration is invoked. |
| setStartShouldFail(shouldFail) { |
| this.startShouldFail_ = shouldFail; |
| } |
| |
| // Returns resolved promise if suspend() was called, rejected otherwise. |
| suspendCalled() { |
| return new Promise((resolve, reject) => { |
| this.suspendCalled_ = resolve; |
| }); |
| } |
| |
| // Returns resolved promise if resume() was called, rejected otherwise. |
| resumeCalled() { |
| return new Promise((resolve, reject) => { |
| this.resumeCalled_ = resolve; |
| }); |
| } |
| |
| // Resolves promise when addConfiguration() is called. |
| addConfigurationCalled() { |
| return new Promise((resolve, reject) => { |
| this.addConfigurationCalled_ = resolve; |
| }); |
| } |
| |
| // Resolves promise when removeConfiguration() is called. |
| removeConfigurationCalled() { |
| return new Promise((resolve, reject) => { |
| this.removeConfigurationCalled_ = resolve; |
| }); |
| } |
| |
| startReading() { |
| if (this.updateReadingFunction_ != null) { |
| this.stopReading(); |
| let maxFrequencyUsed = this.requestedFrequencies_[0]; |
| let timeout = (1 / maxFrequencyUsed) * 1000; |
| this.sensorReadingTimerId_ = window.setInterval(() => { |
| if (this.updateReadingFunction_) { |
| this.updateReadingFunction_(this.buffer_); |
| // For all tests sensor reading should have monotonically |
| // increasing timestamp in seconds. |
| this.buffer_[1] = window.performance.now() * 0.001; |
| } |
| if (this.reportingMode_ === device.mojom.ReportingMode.ON_CHANGE) { |
| this.client_.sensorReadingChanged(); |
| } |
| }, timeout); |
| } |
| } |
| |
| stopReading() { |
| if (this.sensorReadingTimerId_ != null) { |
| window.clearInterval(this.sensorReadingTimerId_); |
| this.sensorReadingTimerId_ = null; |
| } |
| } |
| |
| getSamplingFrequency() { |
| assert_true(this.requestedFrequencies_.length > 0); |
| return this.requestedFrequencies_[0]; |
| } |
| |
| } |
| |
| // Class that mocks SensorProvider interface defined in |
| // sensor_provider.mojom |
| class MockSensorProvider { |
| constructor() { |
| this.readingSizeInBytes_ = |
| device.mojom.SensorInitParams.kReadBufferSizeForTests; |
| this.sharedBufferSizeInBytes_ = this.readingSizeInBytes_ * |
| device.mojom.SensorType.LAST; |
| let rv = Mojo.createSharedBuffer(this.sharedBufferSizeInBytes_); |
| assert_equals(rv.result, Mojo.RESULT_OK, "Failed to create buffer"); |
| this.sharedBufferHandle_ = rv.handle; |
| this.activeSensor_ = null; |
| this.getSensorShouldFail_ = false; |
| this.permissionsDenied_ = false; |
| this.resolveFunc_ = null; |
| this.isContinuous_ = false; |
| this.maxFrequency_ = 60; |
| this.minFrequency_ = 1; |
| this.binding_ = new mojo.Binding(device.mojom.SensorProvider, this); |
| |
| this.interceptor_ = new MojoInterfaceInterceptor( |
| device.mojom.SensorProvider.name); |
| this.interceptor_.oninterfacerequest = e => { |
| this.bindToPipe(e.handle); |
| }; |
| this.interceptor_.start(); |
| } |
| |
| // Returns initialized Sensor proxy to the client. |
| async getSensor(type) { |
| if (this.getSensorShouldFail_) { |
| return {result: device.mojom.SensorCreationResult.ERROR_NOT_AVAILABLE, |
| initParams: null}; |
| } |
| if (this.permissionsDenied_) { |
| return {result: device.mojom.SensorCreationResult.ERROR_NOT_ALLOWED, |
| initParams: null}; |
| } |
| |
| let offset = (device.mojom.SensorType.LAST - type) * |
| this.readingSizeInBytes_; |
| let reportingMode = device.mojom.ReportingMode.ON_CHANGE; |
| if (this.isContinuous_) { |
| reportingMode = device.mojom.ReportingMode.CONTINUOUS; |
| } |
| |
| let sensorPtr = new device.mojom.SensorPtr(); |
| if (this.activeSensor_ == null) { |
| let mockSensor = new MockSensor( |
| mojo.makeRequest(sensorPtr), this.sharedBufferHandle_, offset, |
| this.readingSizeInBytes_, reportingMode); |
| this.activeSensor_ = mockSensor; |
| this.activeSensor_.client_ = new device.mojom.SensorClientPtr(); |
| } |
| |
| let rv = this.sharedBufferHandle_.duplicateBufferHandle(); |
| |
| assert_equals(rv.result, Mojo.RESULT_OK); |
| |
| let defaultConfig = {frequency: 5}; |
| // Consider sensor traits to meet assertions in C++ code (see |
| // services/device/public/cpp/generic_sensor/sensor_traits.h) |
| if (type == device.mojom.SensorType.AMBIENT_LIGHT || |
| type == device.mojom.SensorType.MAGNETOMETER) { |
| if (this.maxFrequency_ > 10) |
| this.maxFrequency_ = 10; |
| } |
| |
| let initParams = new device.mojom.SensorInitParams({ |
| sensor: sensorPtr, |
| clientRequest: mojo.makeRequest(this.activeSensor_.client_), |
| memory: rv.handle, |
| bufferOffset: offset, |
| mode: reportingMode, |
| defaultConfiguration: defaultConfig, |
| minimumFrequency: this.minFrequency_, |
| maximumFrequency: this.maxFrequency_ |
| }); |
| |
| if (this.resolveFunc_ !== null) { |
| this.resolveFunc_(this.activeSensor_); |
| } |
| |
| return {result: device.mojom.SensorCreationResult.SUCCESS, |
| initParams: initParams}; |
| } |
| |
| // Binds object to mojo message pipe |
| bindToPipe(pipe) { |
| this.binding_.bind(pipe); |
| this.binding_.setConnectionErrorHandler(() => { |
| this.reset(); |
| }); |
| } |
| |
| // Mock functions |
| |
| // Resets state of mock SensorProvider between test runs. |
| reset() { |
| if (this.activeSensor_ != null) { |
| this.activeSensor_.reset(); |
| this.activeSensor_ = null; |
| } |
| |
| this.getSensorShouldFail_ = false; |
| this.permissionsDenied_ = false; |
| this.resolveFunc_ = null; |
| this.maxFrequency_ = 60; |
| this.minFrequency_ = 1; |
| this.isContinuous_ = false; |
| this.binding_.close(); |
| this.interceptor_.stop(); |
| } |
| |
| // Sets flag that forces mock SensorProvider to fail when getSensor() is |
| // invoked. |
| setGetSensorShouldFail(shouldFail) { |
| this.getSensorShouldFail_ = shouldFail; |
| } |
| |
| setPermissionsDenied(permissionsDenied) { |
| this.permissionsDenied_ = permissionsDenied; |
| } |
| |
| // Returns mock sensor that was created in getSensor to the layout test. |
| getCreatedSensor() { |
| if (this.activeSensor_ != null) { |
| return Promise.resolve(this.activeSensor_); |
| } |
| |
| return new Promise((resolve, reject) => { |
| this.resolveFunc_ = resolve; |
| }); |
| } |
| |
| // Forces sensor to use |reportingMode| as an update mode. |
| setContinuousReportingMode() { |
| this.isContinuous_ = true; |
| } |
| |
| // Sets the maximum frequency for a concrete sensor. |
| setMaximumSupportedFrequency(frequency) { |
| this.maxFrequency_ = frequency; |
| } |
| |
| // Sets the minimum frequency for a concrete sensor. |
| setMinimumSupportedFrequency(frequency) { |
| this.minFrequency_ = frequency; |
| } |
| } |
| |
| let mockSensorProvider = new MockSensorProvider; |
| return {mockSensorProvider: mockSensorProvider}; |
| } |
| |
| function sensor_test(func, name, properties) { |
| promise_test(async () => { |
| let sensor = sensorMocks(); |
| |
| // Clean up and reset mock sensor stubs asynchronously, so that the blink |
| // side closes its proxies and notifies JS sensor objects before new test is |
| // started. |
| try { |
| await func(sensor); |
| } finally { |
| sensor.mockSensorProvider.reset(); |
| await new Promise(resolve => { setTimeout(resolve, 0); }); |
| }; |
| }, name, properties); |
| } |
| |
| // TODO(Mikhail): Refactor further to remove code duplication |
| // in <concrete sensor>.html files. |
| function verify_sensor_reading(pattern, values, timestamp, is_null) { |
| function round(val) { |
| return Number.parseFloat(val).toPrecision(6); |
| } |
| |
| if (is_null) { |
| return (values === null || values.every(r => r === null)) && |
| timestamp === null; |
| } |
| return values.every((r, i) => round(r) === round(pattern[i])) && |
| timestamp !== null; |
| } |
| |
| function verify_xyz_sensor_reading(pattern, {x, y, z, timestamp}, is_null) { |
| return verify_sensor_reading(pattern, [x, y, z], timestamp, is_null); |
| } |
| |
| function verify_quat_sensor_reading(pattern, {quaternion, timestamp}, is_null) { |
| return verify_sensor_reading(pattern, quaternion, timestamp, is_null); |
| } |