nfc-helpers.js (7933B)
1 'use strict'; 2 // These tests rely on the User Agent providing an implementation of 3 // platform nfc backends. 4 // 5 // In Chromium-based browsers this implementation is provided by a polyfill 6 // in order to reduce the amount of test-only code shipped to users. To enable 7 // these tests the browser must be run with these options: 8 // 9 // --enable-blink-features=MojoJS,MojoJSTest 10 11 async function loadChromiumResources() { 12 await loadScript('/resources/testdriver.js'); 13 await loadScript('/resources/testdriver-vendor.js'); 14 await import('/resources/chromium/nfc-mock.js'); 15 } 16 17 async function initialize_nfc_tests() { 18 if (typeof WebNFCTest === 'undefined') { 19 const script = document.createElement('script'); 20 script.src = '/resources/test-only-api.js'; 21 script.async = false; 22 const p = new Promise((resolve, reject) => { 23 script.onload = () => { resolve(); }; 24 script.onerror = e => { reject(e); }; 25 }) 26 document.head.appendChild(script); 27 await p; 28 29 if (isChromiumBased) { 30 await loadChromiumResources(); 31 } 32 } 33 assert_implements( WebNFCTest, 'WebNFC testing interface is unavailable.'); 34 let NFCTest = new WebNFCTest(); 35 await NFCTest.initialize(); 36 return NFCTest; 37 } 38 39 function nfc_test(func, name, properties) { 40 promise_test(async t => { 41 let NFCTest = await initialize_nfc_tests(); 42 t.add_cleanup(async () => { 43 await NFCTest.reset(); 44 }); 45 await func(t, NFCTest.getMockNFC()); 46 }, name, properties); 47 } 48 49 const test_text_data = 'Test text data.'; 50 const test_text_byte_array = new TextEncoder().encode(test_text_data); 51 const test_number_data = 42; 52 const test_json_data = {level: 1, score: 100, label: 'Game'}; 53 const test_url_data = 'https://w3c.github.io/web-nfc/'; 54 const test_message_origin = 'https://127.0.0.1:8443'; 55 const test_buffer_data = new ArrayBuffer(test_text_byte_array.length); 56 const test_buffer_view = new Uint8Array(test_buffer_data); 57 test_buffer_view.set(test_text_byte_array); 58 const fake_tag_serial_number = 'c0:45:00:02'; 59 const test_record_id = '/test_path/test_id'; 60 61 const NFCHWStatus = {}; 62 // OS-level NFC setting is ON 63 NFCHWStatus.ENABLED = 1; 64 // no NFC chip 65 NFCHWStatus.NOT_SUPPORTED = NFCHWStatus.ENABLED + 1; 66 // OS-level NFC setting OFF 67 NFCHWStatus.DISABLED = NFCHWStatus.NOT_SUPPORTED + 1; 68 69 function encodeTextToArrayBuffer(string, encoding) { 70 // Only support 'utf-8', 'utf-16', 'utf-16be', and 'utf-16le'. 71 assert_true( 72 encoding === 'utf-8' || encoding === 'utf-16' || 73 encoding === 'utf-16be' || encoding === 'utf-16le'); 74 75 if (encoding === 'utf-8') { 76 return new TextEncoder().encode(string).buffer; 77 } 78 79 if (encoding === 'utf-16') { 80 let uint16array = new Uint16Array(string.length); 81 for (let i = 0; i < string.length; i++) { 82 uint16array[i] = string.codePointAt(i); 83 } 84 return uint16array.buffer; 85 } 86 87 const littleEndian = encoding === 'utf-16le'; 88 const buffer = new ArrayBuffer(string.length * 2); 89 const view = new DataView(buffer); 90 for (let i = 0; i < string.length; i++) { 91 view.setUint16(i * 2, string.codePointAt(i), littleEndian); 92 } 93 return buffer; 94 } 95 96 function createMessage(records) { 97 if (records !== undefined) { 98 let message = {}; 99 message.records = records; 100 return message; 101 } 102 } 103 104 function createRecord(recordType, data, id, mediaType, encoding, lang) { 105 let record = {}; 106 if (recordType !== undefined) 107 record.recordType = recordType; 108 if (id !== undefined) 109 record.id = id; 110 if (mediaType !== undefined) 111 record.mediaType = mediaType; 112 if (encoding !== undefined) 113 record.encoding = encoding; 114 if (lang !== undefined) 115 record.lang = lang; 116 if (data !== undefined) 117 record.data = data; 118 return record; 119 } 120 121 function createTextRecord(data, encoding, lang) { 122 return createRecord('text', data, test_record_id, undefined, encoding, lang); 123 } 124 125 function createMimeRecordFromJson(json) { 126 return createRecord( 127 'mime', new TextEncoder().encode(JSON.stringify(json)), 128 test_record_id, 'application/json'); 129 } 130 131 function createMimeRecord(buffer) { 132 return createRecord( 133 'mime', buffer, test_record_id, 'application/octet-stream'); 134 } 135 136 function createUnknownRecord(buffer) { 137 return createRecord('unknown', buffer, test_record_id); 138 } 139 140 function createUrlRecord(url, isAbsUrl) { 141 if (isAbsUrl) { 142 return createRecord('absolute-url', url, test_record_id); 143 } 144 return createRecord('url', url, test_record_id); 145 } 146 147 // Compares NDEFMessageSource that was provided to the API 148 // (e.g. NDEFReader.write), and NDEFMessage that was received by the 149 // mock NFC service. 150 function assertNDEFMessagesEqual(providedMessage, receivedMessage) { 151 // If simple data type is passed, e.g. String or ArrayBuffer or 152 // ArrayBufferView, convert it to NDEFMessage before comparing. 153 // https://w3c.github.io/web-nfc/#dom-ndefmessagesource 154 let provided = providedMessage; 155 if (providedMessage instanceof ArrayBuffer || 156 ArrayBuffer.isView(providedMessage)) 157 provided = createMessage([createRecord( 158 'mime', providedMessage, undefined /* id */, 159 'application/octet-stream')]); 160 else if (typeof providedMessage === 'string') 161 provided = createMessage([createRecord('text', providedMessage)]); 162 163 assert_equals(provided.records.length, receivedMessage.data.length, 164 'NDEFMessages must have same number of NDEFRecords'); 165 166 // Compare contents of each individual NDEFRecord 167 for (let i = 0; i < provided.records.length; ++i) 168 compareNDEFRecords(provided.records[i], receivedMessage.data[i]); 169 } 170 171 // Used to compare two NDEFMessage, one that is received from 172 // NDEFReader.onreading() EventHandler and another that is provided to mock NFC 173 // service. 174 function assertWebNDEFMessagesEqual(message, expectedMessage) { 175 assert_equals(message.records.length, expectedMessage.records.length); 176 177 for(let i in message.records) { 178 let record = message.records[i]; 179 let expectedRecord = expectedMessage.records[i]; 180 assert_equals(record.recordType, expectedRecord.recordType); 181 assert_equals(record.mediaType, expectedRecord.mediaType); 182 assert_equals(record.id, expectedRecord.id); 183 assert_equals(record.encoding, expectedRecord.encoding); 184 assert_equals(record.lang, expectedRecord.lang); 185 // Compares record data 186 assert_array_equals(new Uint8Array(record.data), 187 new Uint8Array(expectedRecord.data)); 188 } 189 } 190 191 function testMultiScanOptions(message, scanOptions, unmatchedScanOptions, desc) { 192 nfc_test(async (t, mockNFC) => { 193 const ndef1 = new NDEFReader(); 194 const ndef2 = new NDEFReader(); 195 const controller = new AbortController(); 196 197 // Reading from unmatched ndef will not be triggered 198 ndef1.onreading = t.unreached_func("reading event should not be fired."); 199 unmatchedScanOptions.signal = controller.signal; 200 await ndef1.scan(unmatchedScanOptions); 201 202 const ndefWatcher = new EventWatcher(t, ndef2, ["reading", "readingerror"]); 203 const promise = ndefWatcher.wait_for("reading").then(event => { 204 controller.abort(); 205 assertWebNDEFMessagesEqual(event.message, new NDEFMessage(message)); 206 }); 207 scanOptions.signal = controller.signal; 208 await ndef2.scan(scanOptions); 209 210 mockNFC.setReadingMessage(message); 211 await promise; 212 }, desc); 213 } 214 215 function testMultiMessages(message, scanOptions, unmatchedMessage, desc) { 216 nfc_test(async (t, mockNFC) => { 217 const ndef = new NDEFReader(); 218 const controller = new AbortController(); 219 const ndefWatcher = new EventWatcher(t, ndef, ["reading", "readingerror"]); 220 const promise = ndefWatcher.wait_for("reading").then(event => { 221 controller.abort(); 222 assertWebNDEFMessagesEqual(event.message, new NDEFMessage(message)); 223 }); 224 scanOptions.signal = controller.signal; 225 await ndef.scan(scanOptions); 226 227 // Unmatched message will not be read 228 mockNFC.setReadingMessage(unmatchedMessage); 229 mockNFC.setReadingMessage(message); 230 await promise; 231 }, desc); 232 }