| <!DOCTYPE html> | |
| <title>SpeechRecognition install</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; | |
| const validOptions = { langs: [validLang], processLocally: true }; | |
| const validAlternateOptions = { langs: [validLangAlternateLocale], processLocally: true }; | |
| const invalidOptions = { langs: [invalidLang], processLocally: true }; | |
| // Check the availablility of the on-device language pack. | |
| const initialAvailabilityPromise = | |
| SpeechRecognition.available(validOptions); | |
| assert_true( | |
| initialAvailabilityPromise instanceof Promise, | |
| "available should return a Promise." | |
| ); | |
| const initialAvailabilityResult = await initialAvailabilityPromise; | |
| assert_true( | |
| typeof initialAvailabilityResult === "string", | |
| "The resolved value of the available promise should be a string." | |
| ); | |
| if (initialAvailabilityResult === "downloadable") { | |
| // Attempt to call install directly, without a user gesture with a | |
| // language that is downloadable but not installed. | |
| const installWithoutUserGesturePromise = | |
| SpeechRecognition.install(validOptions); | |
| // Assert that the promise rejects with NotAllowedError. | |
| await promise_rejects_dom( | |
| t, | |
| "NotAllowedError", | |
| window.DOMException, | |
| installWithoutUserGesturePromise, "SpeechRecognition.install() " + | |
| "must reject with NotAllowedError if called without a user gesture for on-device." | |
| ); | |
| // Test that it returns a promise when called with a valid language. | |
| const validResultPromise = test_driver.bless( | |
| "Call SpeechRecognition.install with a valid language", | |
| () => SpeechRecognition.install(validOptions) | |
| ); | |
| const validInstallPromise = test_driver.bless( | |
| "Call SpeechRecognition.install with valid options for on-device", | |
| () => SpeechRecognition.install(validOptions) | |
| ); | |
| assert_true( | |
| validInstallPromise instanceof Promise, | |
| "install (with gesture, for on-device) should return a Promise." | |
| ); | |
| // Verify the resolved value is a boolean. | |
| const validInstallResult = await validInstallPromise; | |
| assert_true( | |
| typeof validInstallResult === "boolean", | |
| "The resolved value of the install promise (on-device) should be a boolean." | |
| ); | |
| // Verify that the method returns true when called with a supported language. | |
| assert_equals( | |
| validInstallResult, | |
| true, | |
| "install should resolve with `true` when called with " + | |
| "supported options for on-device." | |
| ); | |
| // Verify that the newly installed language pack is available. | |
| const availableOnDeviceResultPromise = | |
| SpeechRecognition.available(validOptions); | |
| assert_true( | |
| availableOnDeviceResultPromise instanceof Promise, | |
| "available should return a Promise." | |
| ); | |
| const availableOnDeviceResult = await availableOnDeviceResultPromise; | |
| assert_true( | |
| typeof availableOnDeviceResult === "string", | |
| "The resolved value of the available promise should be a string." | |
| ); | |
| assert_true( | |
| availableOnDeviceResult === "available", | |
| "The resolved value of the available promise (on-device) should be " + | |
| "'available'." | |
| ); | |
| // Verify that the newly installed language pack is available for an alternate locale. | |
| const alternateLocaleResultPromise = | |
| SpeechRecognition.available(validAlternateOptions); | |
| assert_true( | |
| alternateLocaleResultPromise instanceof Promise, | |
| "available should return a Promise." | |
| ); | |
| const alternateLocaleResult = await alternateLocaleResultPromise; | |
| assert_true( | |
| typeof alternateLocaleResult === "string", | |
| "The resolved value of the available promise should be a string." | |
| ); | |
| assert_true( | |
| alternateLocaleResult === "available", | |
| "The resolved value of the available promise (on-device, alternate locale) should be " + | |
| "'available'." | |
| ); | |
| // Verify that installing an already installed language resolves to true. | |
| const secondInstallPromise = test_driver.bless( | |
| "Call SpeechRecognition.install for an already installed on-device language", | |
| () => SpeechRecognition.install(validOptions) | |
| ); | |
| assert_true( | |
| secondInstallPromise instanceof Promise, | |
| "install (with gesture, for already installed on-device language) should " + | |
| "return a Promise." | |
| ); | |
| const secondInstallResult = await secondInstallPromise; | |
| assert_true( | |
| typeof secondInstallResult === "boolean", | |
| "The resolved value of the second install promise (on-device) should be a " + | |
| "boolean." | |
| ); | |
| assert_equals( | |
| secondInstallResult, | |
| true, | |
| "install should resolve with `true` if the on-device language is already " + | |
| "installed." | |
| ); | |
| } | |
| // Test that it returns a promise and resolves to false for unsupported lang. | |
| const invalidInstallPromise = test_driver.bless( | |
| "Call SpeechRecognition.install with unsupported on-device options", | |
| () => SpeechRecognition.install(invalidOptions) | |
| ); | |
| assert_true( | |
| invalidInstallPromise instanceof Promise, | |
| "install (with gesture, for unsupported on-device options) should return " + | |
| "a Promise." | |
| ); | |
| const invalidInstallResult = await invalidInstallPromise; | |
| assert_true( | |
| typeof invalidInstallResult === "boolean", | |
| "The resolved value of the install promise (unsupported on-device options) " + | |
| "should be a boolean." | |
| ); | |
| assert_equals( | |
| invalidInstallResult, | |
| false, | |
| "install should resolve with `false` when called with " + | |
| "unsupported on-device options." | |
| ); | |
| }, "SpeechRecognition.install resolves with a boolean value for on-device " + | |
| "(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; | |
| const options = { langs: ["en-US"], processLocally: true }; | |
| iframe.remove(); | |
| await promise_rejects_dom( | |
| t, | |
| "InvalidStateError", | |
| frameDOMException, | |
| test_driver.bless( | |
| "Call SpeechRecognition.install in a detached frame context for on-device", | |
| () => { | |
| return frameSpeechRecognition.install(options); | |
| } | |
| ) | |
| ); | |
| }, "SpeechRecognition.install rejects in a detached context for on-device."); | |
| 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.install, | |
| "install method should exist on SpeechRecognition in iframe."); | |
| const options = { langs: ["en-US"], processLocally: true }; | |
| await promise_rejects_dom( | |
| t, | |
| "NotAllowedError", | |
| frameDOMException, | |
| frameSpeechRecognition.install(options), | |
| "install should reject with NotAllowedError if " + | |
| "'install-on-device-speech-recognition' Permission Policy is " + | |
| "disabled." | |
| ); | |
| }, "SpeechRecognition.install rejects for on-device 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.install) { | |
| parent.postMessage({ | |
| type: "rejection", | |
| name: "NotSupportedError", | |
| message: "API not available" | |
| }, "*"); | |
| return; | |
| } | |
| const options = { langs: ["en-US"], processLocally: true }; | |
| await SpeechRecognition.install(options); | |
| 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.install should reject for on-device in a cross-origin iframe."); | |
| </script> |