blob: c5733f9e1413dbb4a6b803c1fe1233700efd1745 [file] [log] [blame]
import '/resources/testdriver.js';
import '/resources/testdriver-vendor.js';
import '/resources/testharness.js';
import '/resources/testharnessreport.js';
import {HidConnection, HidConnectionReceiver, HidDeviceInfo, HidManagerClientRemote} from '/gen/services/device/public/mojom/hid.mojom.m.js';
import {HidService, HidServiceReceiver} from '/gen/third_party/blink/public/mojom/hid/hid.mojom.m.js';
// Compare two DataViews byte-by-byte.
export function compareDataViews(actual, expected) {
assert_true(actual instanceof DataView, 'actual is DataView');
assert_true(expected instanceof DataView, 'expected is DataView');
assert_equals(actual.byteLength, expected.byteLength, 'lengths equal');
for (let i = 0; i < expected.byteLength; ++i) {
assert_equals(actual.getUint8(i), expected.getUint8(i),
`Mismatch at byte ${i}.`);
}
}
// Returns a Promise that resolves once |device| receives an input report.
export function oninputreport(device) {
assert_true(device instanceof HIDDevice);
return new Promise(resolve => { device.oninputreport = resolve; });
}
// Fake implementation of device.mojom.HidConnection. HidConnection represents
// an open connection to a HID device and can be used to send and receive
// reports.
class FakeHidConnection {
constructor(client) {
this.client_ = client;
this.receiver_ = new HidConnectionReceiver(this);
this.expectedWrites_ = [];
this.expectedGetFeatureReports_ = [];
this.expectedSendFeatureReports_ = [];
}
bindNewPipeAndPassRemote() {
return this.receiver_.$.bindNewPipeAndPassRemote();
}
// Simulate an input report sent from the device to the host. The connection
// client's onInputReport method will be called with the provided |reportId|
// and |buffer|.
simulateInputReport(reportId, reportData) {
if (this.client_) {
this.client_.onInputReport(reportId, reportData);
}
}
// Specify the result for an expected call to write. If |success| is true the
// write will be successful, otherwise it will simulate a failure. The
// parameters of the next write call must match |reportId| and |buffer|.
queueExpectedWrite(success, reportId, reportData) {
this.expectedWrites_.push({
params: {reportId, data: reportData},
result: {success},
});
}
// Specify the result for an expected call to getFeatureReport. If |success|
// is true the operation is successful, otherwise it will simulate a failure.
// The parameter of the next getFeatureReport call must match |reportId|.
queueExpectedGetFeatureReport(success, reportId, reportData) {
this.expectedGetFeatureReports_.push({
params: {reportId},
result: {success, buffer: reportData},
});
}
// Specify the result for an expected call to sendFeatureReport. If |success|
// is true the operation is successful, otherwise it will simulate a failure.
// The parameters of the next sendFeatureReport call must match |reportId| and
// |buffer|.
queueExpectedSendFeatureReport(success, reportId, reportData) {
this.expectedSendFeatureReports_.push({
params: {reportId, data: reportData},
result: {success},
});
}
// Asserts that there are no more expected operations.
assertExpectationsMet() {
assert_equals(this.expectedWrites_.length, 0);
assert_equals(this.expectedGetFeatureReports_.length, 0);
assert_equals(this.expectedSendFeatureReports_.length, 0);
}
read() {}
// Implementation of HidConnection::Write. Causes an assertion failure if
// there are no expected write operations, or if the parameters do not match
// the expected call.
async write(reportId, buffer) {
let expectedWrite = this.expectedWrites_.shift();
assert_not_equals(expectedWrite, undefined);
assert_equals(reportId, expectedWrite.params.reportId);
let actual = new Uint8Array(buffer);
compareDataViews(
new DataView(actual.buffer, actual.byteOffset),
new DataView(expectedWrite.params.data.buffer,
expectedWrite.params.data.byteOffset));
return expectedWrite.result;
}
// Implementation of HidConnection::GetFeatureReport. Causes an assertion
// failure if there are no expected write operations, or if the parameters do
// not match the expected call.
async getFeatureReport(reportId) {
let expectedGetFeatureReport = this.expectedGetFeatureReports_.shift();
assert_not_equals(expectedGetFeatureReport, undefined);
assert_equals(reportId, expectedGetFeatureReport.params.reportId);
return expectedGetFeatureReport.result;
}
// Implementation of HidConnection::SendFeatureReport. Causes an assertion
// failure if there are no expected write operations, or if the parameters do
// not match the expected call.
async sendFeatureReport(reportId, buffer) {
let expectedSendFeatureReport = this.expectedSendFeatureReports_.shift();
assert_not_equals(expectedSendFeatureReport, undefined);
assert_equals(reportId, expectedSendFeatureReport.params.reportId);
let actual = new Uint8Array(buffer);
compareDataViews(
new DataView(actual.buffer, actual.byteOffset),
new DataView(expectedSendFeatureReport.params.data.buffer,
expectedSendFeatureReport.params.data.byteOffset));
return expectedSendFeatureReport.result;
}
}
// A fake implementation of the HidService mojo interface. HidService manages
// HID device access for clients in the render process. Typically, when a client
// requests access to a HID device a chooser dialog is shown with a list of
// available HID devices. Selecting a device from the chooser also grants
// permission for the client to access that device.
//
// The fake implementation allows tests to simulate connected devices. It also
// skips the chooser dialog and instead allows tests to specify which device
// should be selected. All devices are treated as if the user had already
// granted permission.
class FakeHidService {
constructor() {
this.interceptor_ = new MojoInterfaceInterceptor(HidService.$interfaceName);
this.interceptor_.oninterfacerequest = e => this.bind(e.handle);
this.receiver_ = new HidServiceReceiver(this);
this.nextGuidValue_ = 0;
this.reset();
}
start() {
this.interceptor_.start();
}
stop() {
this.interceptor_.stop();
}
reset() {
this.devices_ = new Map();
this.fakeConnections_ = new Map();
this.selectedDevices_ = [];
this.client_ = null;
}
// Creates and returns a HidDeviceInfo with the specified device IDs.
makeDevice(vendorId, productId) {
let guidValue = ++this.nextGuidValue_;
let info = new HidDeviceInfo();
info.guid = 'guid-' + guidValue.toString();
info.physicalDeviceId = 'physical-device-id-' + guidValue.toString();
info.vendorId = vendorId;
info.productId = productId;
info.productName = 'product name';
info.serialNumber = '0';
info.reportDescriptor = new Uint8Array();
info.collections = [];
info.deviceNode = 'device node';
return info;
}
// Simulates a connected device. Returns the key used to store the device in
// the map. The key is either the physical device ID, or the device GUID if it
// has no physical device ID.
addDevice(deviceInfo) {
let key = deviceInfo.physicalDeviceId;
if (key.length === 0)
key = deviceInfo.guid;
let devices = this.devices_.get(key);
if (devices === undefined)
devices = [];
devices.push(deviceInfo);
this.devices_.set(key, devices);
if (this.client_)
this.client_.deviceAdded(deviceInfo);
return key;
}
// Simulates disconnecting a connected device.
removeDevice(key) {
let devices = this.devices_.get(key);
this.devices_.delete(key);
if (this.client_ && devices) {
devices.forEach(deviceInfo => {
this.client_.deviceRemoved(deviceInfo);
});
}
}
// Sets the key of the device that will be returned as the selected item the
// next time requestDevice is called. The device with this key must have been
// previously added with addDevice.
setSelectedDevice(key) {
this.selectedDevices_ = this.devices_.get(key);
}
// Returns the fake HidConnection object for this device, if there is one. A
// connection is created once the device is opened.
getFakeConnection(guid) {
return this.fakeConnections_.get(guid);
}
bind(handle) {
this.receiver_.$.bindHandle(handle);
}
registerClient(client) {
this.client_ = client;
}
// Returns an array of connected devices. Normally this would only include
// devices that the client has already been granted permission to access, but
// for the fake implementation all simulated devices are returned.
async getDevices() {
let devices = [];
this.devices_.forEach((value) => {
devices = devices.concat(value);
});
return {devices};
}
// Simulates a device chooser prompt, returning |selectedDevices_| as the
// simulated selection. |filters| is ignored.
async requestDevice(filters) {
return {devices: this.selectedDevices_};
}
// Returns a fake connection to the device with the specified GUID. If
// |connectionClient| is not null, its onInputReport method will be called
// when input reports are received.
async connect(guid, connectionClient) {
const fakeConnection = new FakeHidConnection(connectionClient);
this.fakeConnections_.set(guid, fakeConnection);
return {connection: fakeConnection.bindNewPipeAndPassRemote()};
}
}
let fakeHidService = new FakeHidService();
export function hid_test(func, name, properties) {
promise_test(async (test) => {
fakeHidService.start();
try {
await func(test, fakeHidService);
} finally {
fakeHidService.stop();
fakeHidService.reset();
}
}, name, properties);
}
export function trustedClick() {
return new Promise(resolve => {
let button = document.createElement('button');
button.textContent = 'click to continue test';
button.style.display = 'block';
button.style.fontSize = '20px';
button.style.padding = '10px';
button.onclick = () => {
document.body.removeChild(button);
resolve();
};
document.body.appendChild(button);
test_driver.click(button);
});
}