| OLD | NEW |
| (Empty) |
| 1 'use strict'; | |
| 2 | |
| 3 // Bluetooth UUID constants: | |
| 4 // Services: | |
| 5 var blacklist_test_service_uuid = "611c954a-263b-4f4a-aab6-01ddb953f985"; | |
| 6 // Characteristics: | |
| 7 var blacklist_exclude_reads_characteristic_uuid = | |
| 8 "bad1c9a2-9a5b-4015-8b60-1579bbbf2135"; | |
| 9 | |
| 10 // Sometimes we need to test that using either the name, alias, or UUID | |
| 11 // produces the same result. The following objects help us do that. | |
| 12 var generic_access = { | |
| 13 alias: 0x1800, | |
| 14 name: 'generic_access', | |
| 15 uuid: '00001800-0000-1000-8000-00805f9b34fb' | |
| 16 }; | |
| 17 var device_name = { | |
| 18 alias: 0x2a00, | |
| 19 name: 'gap.device_name', | |
| 20 uuid: '00002a00-0000-1000-8000-00805f9b34fb' | |
| 21 }; | |
| 22 var reconnection_address = { | |
| 23 alias: 0x2a03, | |
| 24 name: 'gap.reconnection_address', | |
| 25 uuid: '00002a03-0000-1000-8000-00805f9b34fb' | |
| 26 }; | |
| 27 var heart_rate = { | |
| 28 alias: 0x180d, | |
| 29 name: 'heart_rate', | |
| 30 uuid: '0000180d-0000-1000-8000-00805f9b34fb' | |
| 31 }; | |
| 32 var body_sensor_location = { | |
| 33 alias: 0x2a38, | |
| 34 name: 'body_sensor_location', | |
| 35 uuid: '00002a38-0000-1000-8000-00805f9b34fb' | |
| 36 }; | |
| 37 var glucose = { | |
| 38 alias: 0x1808, | |
| 39 name: 'glucose', | |
| 40 uuid: '00001808-0000-1000-8000-00805f9b34fb' | |
| 41 }; | |
| 42 var battery_service = { | |
| 43 alias: 0x180f, | |
| 44 name: 'battery_service', | |
| 45 uuid: '0000180f-0000-1000-8000-00805f9b34fb' | |
| 46 }; | |
| 47 var battery_level = { | |
| 48 alias: 0x2A19, | |
| 49 name: 'battery_level', | |
| 50 uuid: '00002a19-0000-1000-8000-00805f9b34fb' | |
| 51 }; | |
| 52 | |
| 53 // The following tests make sure the Web Bluetooth implementation | |
| 54 // responds correctly to the different types of errors the | |
| 55 // underlying platform might return for GATT operations. | |
| 56 | |
| 57 // Each browser should map these characteristics to specific code paths | |
| 58 // that result in different errors thus increasing code coverage | |
| 59 // when testing. Therefore some of these characteristics might not be useful | |
| 60 // for all browsers. | |
| 61 // | |
| 62 // TODO(ortuno): According to the testing spec errorUUID(0x101) to | |
| 63 // errorUUID(0x1ff) should be use for the uuids of the characteristics. | |
| 64 var gatt_errors_tests = [{ | |
| 65 testName: 'GATT Error: Unknown.', | |
| 66 uuid: errorUUID(0xA1), | |
| 67 error: new DOMException( | |
| 68 'GATT Error Unknown.', | |
| 69 'NotSupportedError') | |
| 70 }, { | |
| 71 testName: 'GATT Error: Failed.', | |
| 72 uuid: errorUUID(0xA2), | |
| 73 error: new DOMException( | |
| 74 'GATT operation failed for unknown reason.', | |
| 75 'NotSupportedError') | |
| 76 }, { | |
| 77 testName: 'GATT Error: In Progress.', | |
| 78 uuid: errorUUID(0xA3), | |
| 79 error: new DOMException( | |
| 80 'GATT operation already in progress.', | |
| 81 'NetworkError') | |
| 82 }, { | |
| 83 testName: 'GATT Error: Invalid Length.', | |
| 84 uuid: errorUUID(0xA4), | |
| 85 error: new DOMException( | |
| 86 'GATT Error: invalid attribute length.', | |
| 87 'InvalidModificationError') | |
| 88 }, { | |
| 89 testName: 'GATT Error: Not Permitted.', | |
| 90 uuid: errorUUID(0xA5), | |
| 91 error: new DOMException( | |
| 92 'GATT operation not permitted.', | |
| 93 'NotSupportedError') | |
| 94 }, { | |
| 95 testName: 'GATT Error: Not Authorized.', | |
| 96 uuid: errorUUID(0xA6), | |
| 97 error: new DOMException( | |
| 98 'GATT operation not authorized.', | |
| 99 'SecurityError') | |
| 100 }, { | |
| 101 testName: 'GATT Error: Not Paired.', | |
| 102 uuid: errorUUID(0xA7), | |
| 103 // TODO(ortuno): Change to InsufficientAuthenticationError or similiar | |
| 104 // once https://github.com/WebBluetoothCG/web-bluetooth/issues/137 is | |
| 105 // resolved. | |
| 106 error: new DOMException( | |
| 107 'GATT Error: Not paired.', | |
| 108 'NetworkError') | |
| 109 }, { | |
| 110 testName: 'GATT Error: Not Supported.', | |
| 111 uuid: errorUUID(0xA8), | |
| 112 error: new DOMException( | |
| 113 'GATT Error: Not supported.', | |
| 114 'NotSupportedError') | |
| 115 }]; | |
| 116 | |
| 117 // TODO(jyasskin): Upstream this to testharness.js: https://crbug.com/509058. | |
| 118 function callWithKeyDown(functionCalledOnKeyPress) { | |
| 119 return new Promise(resolve => { | |
| 120 function onKeyPress() { | |
| 121 document.removeEventListener('keypress', onKeyPress, false); | |
| 122 resolve(functionCalledOnKeyPress()); | |
| 123 } | |
| 124 document.addEventListener('keypress', onKeyPress, false); | |
| 125 | |
| 126 eventSender.keyDown(' ', []); | |
| 127 }); | |
| 128 } | |
| 129 | |
| 130 // Calls requestDevice() in a context that's 'allowed to show a popup'. | |
| 131 function requestDeviceWithKeyDown() { | |
| 132 let args = arguments; | |
| 133 return callWithKeyDown(() => navigator.bluetooth.requestDevice.apply(navigator
.bluetooth, args)); | |
| 134 } | |
| 135 | |
| 136 // Calls testRunner.getBluetoothManualChooserEvents() until it's returned | |
| 137 // |expected_count| events. Or just once if |expected_count| is undefined. | |
| 138 function getBluetoothManualChooserEvents(expected_count) { | |
| 139 return new Promise((resolve, reject) => { | |
| 140 let events = []; | |
| 141 let accumulate_events = new_events => { | |
| 142 events.push(...new_events); | |
| 143 if (events.length >= expected_count) { | |
| 144 resolve(events); | |
| 145 } else { | |
| 146 testRunner.getBluetoothManualChooserEvents(accumulate_events); | |
| 147 } | |
| 148 }; | |
| 149 testRunner.getBluetoothManualChooserEvents(accumulate_events); | |
| 150 }); | |
| 151 } | |
| 152 | |
| 153 function setBluetoothFakeAdapter(adapter_name) { | |
| 154 return new Promise(resolve => { | |
| 155 testRunner.setBluetoothFakeAdapter(adapter_name, resolve); | |
| 156 }); | |
| 157 } | |
| 158 | |
| 159 // errorUUID(alias) returns a UUID with the top 32 bits of | |
| 160 // '00000000-97e5-4cd7-b9f1-f5a427670c59' replaced with the bits of |alias|. | |
| 161 // For example, errorUUID(0xDEADBEEF) returns | |
| 162 // 'deadbeef-97e5-4cd7-b9f1-f5a427670c59'. The bottom 96 bits of error UUIDs | |
| 163 // were generated as a type 4 (random) UUID. | |
| 164 function errorUUID(uuidAlias) { | |
| 165 // Make the number positive. | |
| 166 uuidAlias >>>= 0; | |
| 167 // Append the alias as a hex number. | |
| 168 var strAlias = '0000000' + uuidAlias.toString(16); | |
| 169 // Get last 8 digits of strAlias. | |
| 170 strAlias = strAlias.substr(-8); | |
| 171 // Append Base Error UUID | |
| 172 return strAlias + '-97e5-4cd7-b9f1-f5a427670c59'; | |
| 173 } | |
| 174 | |
| 175 // Function to test that a promise rejects with the expected error type and | |
| 176 // message. | |
| 177 function assert_promise_rejects_with_message(promise, expected, description) { | |
| 178 return promise.then(() => { | |
| 179 assert_unreached('Promise should have rejected: ' + description); | |
| 180 }, error => { | |
| 181 assert_equals(error.name, expected.name, 'Unexpected Error Name:'); | |
| 182 if (expected.message) { | |
| 183 assert_equals(error.message, expected.message, 'Unexpected Error Message:'
); | |
| 184 } | |
| 185 }); | |
| 186 } | |
| 187 | |
| 188 // Parses add-device(name)=id lines in | |
| 189 // testRunner.getBluetoothManualChooserEvents() output, and exposes the name->id | |
| 190 // mapping. | |
| 191 class AddDeviceEventSet { | |
| 192 constructor() { | |
| 193 this._idsByName = new Map(); | |
| 194 this._addDeviceRegex = /^add-device\(([^)]+)\)=(.+)$/; | |
| 195 } | |
| 196 assert_add_device_event(event, description) { | |
| 197 let match = this._addDeviceRegex.exec(event); | |
| 198 assert_true(!!match, event + "isn't an add-device event: " + description); | |
| 199 this._idsByName.set(match[1], match[2]); | |
| 200 } | |
| 201 has(name) { | |
| 202 return this._idsByName.has(name); | |
| 203 } | |
| 204 get(name) { | |
| 205 return this._idsByName.get(name); | |
| 206 } | |
| 207 } | |
| 208 | |
| 209 function runGarbageCollection() | |
| 210 { | |
| 211 // Run gc() as a promise. | |
| 212 return new Promise( | |
| 213 function(resolve, reject) { | |
| 214 GCController.collect(); | |
| 215 setTimeout(resolve, 0); | |
| 216 }); | |
| 217 } | |
| 218 | |
| 219 // Creates |num_listeners| promises. Each adds an event listener | |
| 220 // to object. The promises resolve once the object fires |event| but | |
| 221 // reject if the event is fired before |object|.|func|() resolves. | |
| 222 // Returns a promise that fulfills with the result of |object|.|func()| | |
| 223 // and |event.target.value| of each of the other promises. | |
| 224 function assert_event_fires_after_promise(object, func, event, num_listeners) { | |
| 225 num_listeners = num_listeners !== undefined ? num_listeners : 1; | |
| 226 | |
| 227 if (object[func] === undefined) { | |
| 228 return Promise.reject('Function \'' + func + '\' not available in object.'); | |
| 229 } | |
| 230 let should_resolve = false; | |
| 231 let event_promises = []; | |
| 232 for (let i = 0; i < num_listeners; i++) { | |
| 233 event_promises.push(new Promise((resolve, reject) => { | |
| 234 let event_listener = (e) => { | |
| 235 object.removeEventListener(event, event_listener); | |
| 236 if (should_resolve) { | |
| 237 resolve(e.target.value); | |
| 238 } else { | |
| 239 reject(event + ' was triggered before the promise resolved.'); | |
| 240 } | |
| 241 }; | |
| 242 object.addEventListener(event, event_listener); | |
| 243 })); | |
| 244 } | |
| 245 return object[func]().then(result => { | |
| 246 should_resolve = true; | |
| 247 return Promise.all([result, ...event_promises]); | |
| 248 }); | |
| 249 } | |
| 250 | |
| 251 // Returns a promise that resolves after 100ms unless | |
| 252 // the the event is fired on the object in which case | |
| 253 // the promise rejects. | |
| 254 function assert_no_events(object, event_name) { | |
| 255 return new Promise((resolve, reject) => { | |
| 256 let event_listener = (e) => { | |
| 257 object.removeEventListener(event_name, event_listener); | |
| 258 assert_unreached('Object should not fire an event.'); | |
| 259 }; | |
| 260 object.addEventListener(event_name, event_listener); | |
| 261 // TODO(ortuno): Remove timeout. | |
| 262 // http://crbug.com/543884 | |
| 263 setTimeout(() => { | |
| 264 object.removeEventListener(event_name, event_listener); | |
| 265 resolve(); | |
| 266 }, 100); | |
| 267 }); | |
| 268 } | |
| 269 | |
| 270 class TestCharacteristicProperties { | |
| 271 constructor(properties) { | |
| 272 this.broadcast = properties.broadcast || false; | |
| 273 this.read = properties.read || false; | |
| 274 this.writeWithoutResponse = properties.writeWithoutResponse || false; | |
| 275 this.write = properties.write || false; | |
| 276 this.notify = properties.notify || false; | |
| 277 this.indicate = properties.indicate || false; | |
| 278 this.authenticatedSignedWrites = properties.authenticatedSignedWrites || fal
se; | |
| 279 this.reliableWrite = properties.reliableWrite || false; | |
| 280 this.writableAuxiliaries = properties.writableAuxiliaries || false; | |
| 281 } | |
| 282 } | |
| 283 | |
| 284 function assert_properties_equal(properties, expected_properties) { | |
| 285 for (let key in expected_properties) { | |
| 286 assert_equals(properties[key], expected_properties[key]); | |
| 287 } | |
| 288 } | |
| 289 | |
| 290 // Generates a string of size |size|. | |
| 291 function generate_string(size, char) { | |
| 292 // When passing an array of n undefined's to String the resulting string | |
| 293 // has size n - 1. | |
| 294 return char.repeat(size); | |
| 295 } | |
| 296 | |
| 297 class EventCatcher { | |
| 298 constructor(object, event) { | |
| 299 this.eventFired = false; | |
| 300 let event_listener = e => { | |
| 301 object.removeEventListener(event, event_listener); | |
| 302 this.eventFired = true; | |
| 303 } | |
| 304 object.addEventListener(event, event_listener); | |
| 305 } | |
| 306 } | |
| 307 | |
| 308 // Bluetooth tests sometimes have left-over state that could leak into the | |
| 309 // next test. add_result_callback which is exposed by testharness.js allows us | |
| 310 // to clean up this state after each test. In the future we will split tests | |
| 311 // into separate files so that we don't have to add this callback ourselves. | |
| 312 // TODO(ortuno): Split tests into separate files. | |
| 313 // https://crbug.com/554240 | |
| 314 add_result_callback(() => { | |
| 315 // At the end of each test we clean up all the leftover data in the browser, | |
| 316 // including revoking permissions. This happens before the test document is | |
| 317 // detached. Once the document is detached any device that connected tries | |
| 318 // to disconnect but by then the document no longer has permission to | |
| 319 // interact with the device. So before we clean up the browser data | |
| 320 // we change the visibility which results in all devices disconnecing. | |
| 321 // TODO(ortuno): Remove setPageVisibility hack. In the future, the browser | |
| 322 // will notify the renderer that the device disconnected so we won't need | |
| 323 // this hack. | |
| 324 // https://crbug.com/581855 | |
| 325 testRunner.setBluetoothManualChooser(false); | |
| 326 setBluetoothFakeAdapter(''); | |
| 327 }); | |
| OLD | NEW |