SpeechRecognition-installOnDevice.https.html (10528B)
1 <!DOCTYPE html> 2 <title>SpeechRecognition install</title> 3 <script src="/resources/testharness.js"></script> 4 <script src="/resources/testharnessreport.js"></script> 5 <script src="/resources/testdriver.js"></script> 6 <script src="/resources/testdriver-vendor.js"></script> 7 <script> 8 promise_test(async (t) => { 9 const validLang = "en-US"; 10 const validLangAlternateLocale = "en-GB"; 11 const invalidLang = "invalid language code"; 12 window.SpeechRecognition = 13 window.SpeechRecognition || window.webkitSpeechRecognition; 14 const validOptions = { langs: [validLang], processLocally: true }; 15 const validAlternateOptions = { langs: [validLangAlternateLocale], processLocally: true }; 16 const invalidOptions = { langs: [invalidLang], processLocally: true }; 17 18 // Check the availablility of the on-device language pack. 19 const initialAvailabilityPromise = 20 SpeechRecognition.available(validOptions); 21 assert_true( 22 initialAvailabilityPromise instanceof Promise, 23 "available should return a Promise." 24 ); 25 26 const initialAvailabilityResult = await initialAvailabilityPromise; 27 assert_true( 28 typeof initialAvailabilityResult === "string", 29 "The resolved value of the available promise should be a string." 30 ); 31 32 if (initialAvailabilityResult === "downloadable") { 33 // Attempt to call install directly, without a user gesture with a 34 // language that is downloadable but not installed. 35 const installWithoutUserGesturePromise = 36 SpeechRecognition.install(validOptions); 37 38 // Assert that the promise rejects with NotAllowedError. 39 await promise_rejects_dom( 40 t, 41 "NotAllowedError", 42 window.DOMException, 43 installWithoutUserGesturePromise, "SpeechRecognition.install() " + 44 "must reject with NotAllowedError if called without a user gesture for on-device." 45 ); 46 47 // Test that it returns a promise when called with a valid language. 48 const validResultPromise = test_driver.bless( 49 "Call SpeechRecognition.install with a valid language", 50 () => SpeechRecognition.install(validOptions) 51 ); 52 const validInstallPromise = test_driver.bless( 53 "Call SpeechRecognition.install with valid options for on-device", 54 () => SpeechRecognition.install(validOptions) 55 ); 56 assert_true( 57 validInstallPromise instanceof Promise, 58 "install (with gesture, for on-device) should return a Promise." 59 ); 60 61 // Verify the resolved value is a boolean. 62 const validInstallResult = await validInstallPromise; 63 assert_true( 64 typeof validInstallResult === "boolean", 65 "The resolved value of the install promise (on-device) should be a boolean." 66 ); 67 68 // Verify that the method returns true when called with a supported language. 69 assert_equals( 70 validInstallResult, 71 true, 72 "install should resolve with `true` when called with " + 73 "supported options for on-device." 74 ); 75 76 // Verify that the newly installed language pack is available. 77 const availableOnDeviceResultPromise = 78 SpeechRecognition.available(validOptions); 79 assert_true( 80 availableOnDeviceResultPromise instanceof Promise, 81 "available should return a Promise." 82 ); 83 84 const availableOnDeviceResult = await availableOnDeviceResultPromise; 85 assert_true( 86 typeof availableOnDeviceResult === "string", 87 "The resolved value of the available promise should be a string." 88 ); 89 90 assert_true( 91 availableOnDeviceResult === "available", 92 "The resolved value of the available promise (on-device) should be " + 93 "'available'." 94 ); 95 96 // Verify that the newly installed language pack is available for an alternate locale. 97 const alternateLocaleResultPromise = 98 SpeechRecognition.available(validAlternateOptions); 99 assert_true( 100 alternateLocaleResultPromise instanceof Promise, 101 "available should return a Promise." 102 ); 103 104 const alternateLocaleResult = await alternateLocaleResultPromise; 105 assert_true( 106 typeof alternateLocaleResult === "string", 107 "The resolved value of the available promise should be a string." 108 ); 109 110 assert_true( 111 alternateLocaleResult === "available", 112 "The resolved value of the available promise (on-device, alternate locale) should be " + 113 "'available'." 114 ); 115 116 // Verify that installing an already installed language resolves to true. 117 const secondInstallPromise = test_driver.bless( 118 "Call SpeechRecognition.install for an already installed on-device language", 119 () => SpeechRecognition.install(validOptions) 120 ); 121 assert_true( 122 secondInstallPromise instanceof Promise, 123 "install (with gesture, for already installed on-device language) should " + 124 "return a Promise." 125 ); 126 const secondInstallResult = await secondInstallPromise; 127 assert_true( 128 typeof secondInstallResult === "boolean", 129 "The resolved value of the second install promise (on-device) should be a " + 130 "boolean." 131 ); 132 assert_equals( 133 secondInstallResult, 134 true, 135 "install should resolve with `true` if the on-device language is already " + 136 "installed." 137 ); 138 } 139 140 // Test that it returns a promise and resolves to false for unsupported lang. 141 const invalidInstallPromise = test_driver.bless( 142 "Call SpeechRecognition.install with unsupported on-device options", 143 () => SpeechRecognition.install(invalidOptions) 144 ); 145 assert_true( 146 invalidInstallPromise instanceof Promise, 147 "install (with gesture, for unsupported on-device options) should return " + 148 "a Promise." 149 ); 150 const invalidInstallResult = await invalidInstallPromise; 151 assert_true( 152 typeof invalidInstallResult === "boolean", 153 "The resolved value of the install promise (unsupported on-device options) " + 154 "should be a boolean." 155 ); 156 assert_equals( 157 invalidInstallResult, 158 false, 159 "install should resolve with `false` when called with " + 160 "unsupported on-device options." 161 ); 162 }, "SpeechRecognition.install resolves with a boolean value for on-device " + 163 "(with user gesture)."); 164 165 promise_test(async (t) => { 166 const iframe = document.createElement("iframe"); 167 document.body.appendChild(iframe); 168 const frameWindow = iframe.contentWindow; 169 const frameDOMException = frameWindow.DOMException; 170 const frameSpeechRecognition = 171 frameWindow.SpeechRecognition || frameWindow.webkitSpeechRecognition; 172 const options = { langs: ["en-US"], processLocally: true }; 173 174 iframe.remove(); 175 await promise_rejects_dom( 176 t, 177 "InvalidStateError", 178 frameDOMException, 179 test_driver.bless( 180 "Call SpeechRecognition.install in a detached frame context for on-device", 181 () => { 182 return frameSpeechRecognition.install(options); 183 } 184 ) 185 ); 186 }, "SpeechRecognition.install rejects in a detached context for on-device."); 187 188 promise_test(async (t) => { 189 const iframe = document.createElement("iframe"); 190 iframe.setAttribute("allow", 191 "on-device-speech-recognition 'none'"); 192 document.body.appendChild(iframe); 193 t.add_cleanup(() => iframe.remove()); 194 195 await new Promise(resolve => { 196 if (iframe.contentWindow && 197 iframe.contentWindow.document.readyState === 'complete') { 198 resolve(); 199 } else { 200 iframe.onload = resolve; 201 } 202 }); 203 204 const frameWindow = iframe.contentWindow; 205 const frameSpeechRecognition = frameWindow.SpeechRecognition || 206 frameWindow.webkitSpeechRecognition; 207 const frameDOMException = frameWindow.DOMException; 208 209 assert_true(!!frameSpeechRecognition, 210 "SpeechRecognition should exist in iframe."); 211 assert_true(!!frameSpeechRecognition.install, 212 "install method should exist on SpeechRecognition in iframe."); 213 214 const options = { langs: ["en-US"], processLocally: true }; 215 await promise_rejects_dom( 216 t, 217 "NotAllowedError", 218 frameDOMException, 219 frameSpeechRecognition.install(options), 220 "install should reject with NotAllowedError if " + 221 "'install-on-device-speech-recognition' Permission Policy is " + 222 "disabled." 223 ); 224 }, "SpeechRecognition.install rejects for on-device if " + 225 "'install-on-device-speech-recognition' Permission Policy is disabled."); 226 227 promise_test(async (t) => { 228 const html = ` 229 <!DOCTYPE html> 230 <script> 231 window.addEventListener('message', async (event) => { 232 try { 233 const SpeechRecognition = window.SpeechRecognition || 234 window.webkitSpeechRecognition; 235 if (!SpeechRecognition || !SpeechRecognition.install) { 236 parent.postMessage({ 237 type: "rejection", 238 name: "NotSupportedError", 239 message: "API not available" 240 }, "*"); 241 return; 242 } 243 244 const options = { langs: ["en-US"], processLocally: true }; 245 await SpeechRecognition.install(options); 246 parent.postMessage({ type: "resolution", result: "success" }, "*"); 247 } catch (err) { 248 parent.postMessage({ 249 type: "rejection", 250 name: err.name, 251 message: err.message 252 }, "*"); 253 } 254 }); 255 <\/script> 256 `; 257 258 // Create a cross-origin Blob URL by fetching from remote origin 259 const blob = new Blob([html], { type: "text/html" }); 260 const blobUrl = URL.createObjectURL(blob); 261 262 const iframe = document.createElement("iframe"); 263 iframe.src = blobUrl; 264 iframe.setAttribute("sandbox", "allow-scripts"); 265 document.body.appendChild(iframe); 266 t.add_cleanup(() => iframe.remove()); 267 268 await new Promise(resolve => iframe.onload = resolve); 269 270 const testResult = await new Promise((resolve, reject) => { 271 const timeoutId = t.step_timeout(() => { 272 reject(new Error("Timed out waiting for message from iframe")); 273 }, 6000); 274 275 window.addEventListener("message", t.step_func((event) => { 276 if (event.source !== iframe.contentWindow) return; 277 clearTimeout(timeoutId); 278 resolve(event.data); 279 })); 280 281 iframe.contentWindow.postMessage("runTest", "*"); 282 }); 283 284 assert_equals( 285 testResult.type, 286 "rejection", 287 "Should reject due to cross-origin restriction" 288 ); 289 assert_equals( 290 testResult.name, 291 "NotAllowedError", 292 "Should reject with NotAllowedError" 293 ); 294 assert_true( 295 testResult.message.includes("cross-origin iframe") || 296 testResult.message.includes("cross-site subframe"), 297 `Error message should reference cross-origin. Got: "${testResult.message}"` 298 ); 299 }, "SpeechRecognition.install should reject for on-device in a cross-origin iframe."); 300 </script>