| <!DOCTYPE html> |
| <title>SpeechRecognition installOnDevice</title> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="/resources/testdriver.js"></script> |
| <script src="/resources/testdriver-vendor.js"></script> |
| <script> |
| promise_test(async (t) => { |
| const validLang = "en-US"; |
| const validLangAlternateLocale = "en-GB"; |
| const invalidLang = "invalid language code"; |
| window.SpeechRecognition = |
| window.SpeechRecognition || window.webkitSpeechRecognition; |
| |
| // Check the availablility of the on-device language pack. |
| const initialAvailabilityPromise = |
| SpeechRecognition.availableOnDevice(validLang); |
| assert_true( |
| initialAvailabilityPromise instanceof Promise, |
| "availableOnDevice should return a Promise." |
| ); |
| |
| const initialAvailabilityResult = await initialAvailabilityPromise; |
| assert_true( |
| typeof initialAvailabilityResult === "string", |
| "The resolved value of the availableOnDevice promise should be a string." |
| ); |
| |
| if (initialAvailabilityResult === "downloadable") { |
| // Attempt to call installOnDevice directly, without a user gesture with a |
| // language that is downloadable but not installed. |
| const installWithoutUserGesturePromise = |
| SpeechRecognition.installOnDevice(validLang); |
| |
| // Assert that the promise rejects with NotAllowedError. |
| await promise_rejects_dom( |
| t, |
| "NotAllowedError", |
| window.DOMException, |
| installWithoutUserGesturePromise, "SpeechRecognition.installOnDevice() " + |
| "must reject with NotAllowedError if called without a user gesture." |
| ); |
| |
| // Test that it returns a promise when called with a valid language. |
| const validResultPromise = test_driver.bless( |
| "Call SpeechRecognition.installOnDevice with a valid language", |
| () => SpeechRecognition.installOnDevice(validLang) |
| ); |
| assert_true( |
| validResultPromise instanceof Promise, |
| "installOnDevice (with gesture) should return a Promise." |
| ); |
| |
| // Verify the resolved value is a boolean. |
| const validResult = await validResultPromise; |
| assert_true( |
| typeof validResult === "boolean", |
| "The resolved value of the installOnDevice promise should be a boolean." |
| ); |
| |
| // Verify that the method returns true when called with a supported language. |
| assert_equals( |
| validResult, |
| true, |
| "installOnDevice should resolve with `true` when called with a " + |
| "supported language code." |
| ); |
| |
| // Verify that the newly installed language pack is available. |
| const availableOnDeviceResultPromise = |
| SpeechRecognition.availableOnDevice(validLang); |
| assert_true( |
| availableOnDeviceResultPromise instanceof Promise, |
| "availableOnDevice should return a Promise." |
| ); |
| |
| const availableOnDeviceResult = await availableOnDeviceResultPromise; |
| assert_true( |
| typeof availableOnDeviceResult === "string", |
| "The resolved value of the availableOnDevice promise should be a string." |
| ); |
| |
| assert_true( |
| availableOnDeviceResult === "available", |
| "The resolved value of the availableOnDevice promise should be " + |
| "'available'." |
| ); |
| |
| // Verify that the newly installed language pack is available for an alternate locale. |
| const alternateLocaleResultPromise = |
| SpeechRecognition.availableOnDevice(validLangAlternateLocale); |
| assert_true( |
| alternateLocaleResultPromise instanceof Promise, |
| "availableOnDevice should return a Promise." |
| ); |
| |
| const alternateLocaleResult = await alternateLocaleResultPromise; |
| assert_true( |
| typeof alternateLocaleResult === "string", |
| "The resolved value of the availableOnDevice promise should be a string." |
| ); |
| |
| assert_true( |
| alternateLocaleResult === "available", |
| "The resolved value of the availableOnDevice promise should be " + |
| "'available'." |
| ); |
| |
| // Verify that installing an already installed language resolves to true. |
| const secondResultPromise = test_driver.bless( |
| "Call SpeechRecognition.installOnDevice for an already installed language", |
| () => SpeechRecognition.installOnDevice(validLang) |
| ); |
| assert_true( |
| secondResultPromise instanceof Promise, |
| "installOnDevice (with gesture, for already installed language) should " + |
| "return a Promise." |
| ); |
| const secondResult = await secondResultPromise; |
| assert_true( |
| typeof secondResult === "boolean", |
| "The resolved value of the second installOnDevice promise should be a " + |
| "boolean." |
| ); |
| assert_equals( |
| secondResult, |
| true, |
| "installOnDevice should resolve with `true` if the language is already " + |
| "installed." |
| ); |
| } |
| |
| // Test that it returns a promise and resolves to false for unsupported lang. |
| const invalidResultPromise = test_driver.bless( |
| "Call SpeechRecognition.installOnDevice with an unsupported language code", |
| () => SpeechRecognition.installOnDevice(invalidLang) |
| ); |
| assert_true( |
| invalidResultPromise instanceof Promise, |
| "installOnDevice (with gesture, for unsupported language) should return " + |
| "a Promise." |
| ); |
| const invalidResult = await invalidResultPromise; |
| assert_true( |
| typeof invalidResult === "boolean", |
| "The resolved value of the installOnDevice promise (unsupported language) " + |
| "should be a boolean." |
| ); |
| assert_equals( |
| invalidResult, |
| false, |
| "installOnDevice should resolve with `false` when called with an " + |
| "unsupported language code." |
| ); |
| }, "SpeechRecognition.installOnDevice resolves with a boolean value " + |
| "(with user gesture)."); |
| |
| promise_test(async (t) => { |
| const iframe = document.createElement("iframe"); |
| document.body.appendChild(iframe); |
| const frameWindow = iframe.contentWindow; |
| const frameDOMException = frameWindow.DOMException; |
| const frameSpeechRecognition = |
| frameWindow.SpeechRecognition || frameWindow.webkitSpeechRecognition; |
| |
| iframe.remove(); |
| await promise_rejects_dom( |
| t, |
| "InvalidStateError", |
| frameDOMException, |
| test_driver.bless( |
| "Call SpeechRecognition.installOnDevice in a detached frame context", |
| () => { |
| return frameSpeechRecognition.installOnDevice("en-US"); |
| } |
| ) |
| ); |
| }, "SpeechRecognition.installOnDevice rejects in a detached context."); |
| |
| promise_test(async (t) => { |
| const iframe = document.createElement("iframe"); |
| iframe.setAttribute("allow", |
| "on-device-speech-recognition 'none'"); |
| document.body.appendChild(iframe); |
| t.add_cleanup(() => iframe.remove()); |
| |
| await new Promise(resolve => { |
| if (iframe.contentWindow && |
| iframe.contentWindow.document.readyState === 'complete') { |
| resolve(); |
| } else { |
| iframe.onload = resolve; |
| } |
| }); |
| |
| const frameWindow = iframe.contentWindow; |
| const frameSpeechRecognition = frameWindow.SpeechRecognition || |
| frameWindow.webkitSpeechRecognition; |
| const frameDOMException = frameWindow.DOMException; |
| |
| assert_true(!!frameSpeechRecognition, |
| "SpeechRecognition should exist in iframe."); |
| assert_true(!!frameSpeechRecognition.installOnDevice, |
| "installOnDevice method should exist on SpeechRecognition in iframe."); |
| |
| await promise_rejects_dom( |
| t, |
| "NotAllowedError", |
| frameDOMException, |
| frameSpeechRecognition.installOnDevice("en-US"), |
| "installOnDevice should reject with NotAllowedError if " + |
| "'install-on-device-speech-recognition' Permission Policy is " + |
| "disabled." |
| ); |
| }, "SpeechRecognition.installOnDevice rejects if " + |
| "'install-on-device-speech-recognition' Permission Policy is disabled."); |
| |
| promise_test(async (t) => { |
| const html = ` |
| <!DOCTYPE html> |
| <script> |
| window.addEventListener('message', async (event) => { |
| try { |
| const SpeechRecognition = window.SpeechRecognition || |
| window.webkitSpeechRecognition; |
| if (!SpeechRecognition || !SpeechRecognition.installOnDevice) { |
| parent.postMessage({ |
| type: "rejection", |
| name: "NotSupportedError", |
| message: "API not available" |
| }, "*"); |
| return; |
| } |
| |
| await SpeechRecognition.installOnDevice("en-US"); |
| parent.postMessage({ type: "resolution", result: "success" }, "*"); |
| } catch (err) { |
| parent.postMessage({ |
| type: "rejection", |
| name: err.name, |
| message: err.message |
| }, "*"); |
| } |
| }); |
| <\/script> |
| `; |
| |
| // Create a cross-origin Blob URL by fetching from remote origin |
| const blob = new Blob([html], { type: "text/html" }); |
| const blobUrl = URL.createObjectURL(blob); |
| |
| const iframe = document.createElement("iframe"); |
| iframe.src = blobUrl; |
| iframe.setAttribute("sandbox", "allow-scripts"); |
| document.body.appendChild(iframe); |
| t.add_cleanup(() => iframe.remove()); |
| |
| await new Promise(resolve => iframe.onload = resolve); |
| |
| const testResult = await new Promise((resolve, reject) => { |
| const timeoutId = t.step_timeout(() => { |
| reject(new Error("Timed out waiting for message from iframe")); |
| }, 6000); |
| |
| window.addEventListener("message", t.step_func((event) => { |
| if (event.source !== iframe.contentWindow) return; |
| clearTimeout(timeoutId); |
| resolve(event.data); |
| })); |
| |
| iframe.contentWindow.postMessage("runTest", "*"); |
| }); |
| |
| assert_equals( |
| testResult.type, |
| "rejection", |
| "Should reject due to cross-origin restriction" |
| ); |
| assert_equals( |
| testResult.name, |
| "NotAllowedError", |
| "Should reject with NotAllowedError" |
| ); |
| assert_true( |
| testResult.message.includes("cross-origin iframe") || |
| testResult.message.includes("cross-site subframe"), |
| `Error message should reference cross-origin. Got: "${testResult.message}"` |
| ); |
| }, "SpeechRecognition.installOnDevice should reject in a cross-origin iframe."); |
| </script> |