| <!DOCTYPE html> |
| <title>SpeechRecognition available</title> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script> |
| promise_test(async (t) => { |
| const options = { langs: ["en-US", "fr-FR"], processLocally: true }; |
| window.SpeechRecognition = window.SpeechRecognition || |
| window.webkitSpeechRecognition; |
| |
| // Test that it returns a promise. |
| const resultPromise = SpeechRecognition.available(options); |
| assert_true( |
| resultPromise instanceof Promise, |
| "available should return a Promise." |
| ); |
| |
| // Verify the resolved value is a string. |
| const result = await resultPromise; |
| assert_true( |
| typeof result === "string", |
| "The resolved value of the available promise should be a string." |
| ); |
| |
| assert_true( |
| result === "unavailable" || result === "downloadable" || |
| result === "downloading" || result === "available", |
| "The resolved value of the available promise should be a " + |
| "valid value." |
| ); |
| }, "SpeechRecognition.available resolves with a string value for on-device."); |
| |
| 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, |
| frameSpeechRecognition.available(options), |
| ); |
| }, "SpeechRecognition.available rejects in a detached context for on-device."); |
| |
| promise_test(async (t) => { |
| const iframe = document.createElement("iframe"); |
| // This policy should make the on-device speech recognition |
| // feature unavailable. |
| 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; |
| |
| assert_true(!!frameSpeechRecognition, |
| "SpeechRecognition should exist in iframe."); |
| assert_true(!!frameSpeechRecognition.available, |
| "available method should exist on SpeechRecognition in iframe."); |
| |
| const options = { langs: ["en-US"], processLocally: true }; |
| // Call available and expect it to resolve to "unavailable". |
| const availabilityStatus = |
| await frameSpeechRecognition.available(options); |
| assert_equals(availabilityStatus, "unavailable", |
| "available should resolve to 'unavailable' if " + |
| "'on-device-speech-recognition' Permission Policy is 'none'." |
| ); |
| }, "SpeechRecognition.available resolves to 'unavailable' for on-device if " + |
| "'on-device-speech-recognition' Permission Policy is 'none'."); |
| |
| promise_test(async (t) => { |
| const html = ` |
| <!DOCTYPE html> |
| <script> |
| window.addEventListener('message', async (event) => { |
| // Ensure we only process the message intended to trigger the test. |
| if (event.data !== "runTestCallAvailable") return; |
| |
| try { |
| const SpeechRecognition = window.SpeechRecognition || |
| window.webkitSpeechRecognition; |
| if (!SpeechRecognition || !SpeechRecognition.available) { |
| parent.postMessage({ |
| type: "error", // Use "error" for API not found or other issues. |
| name: "NotSupportedError", |
| message: "SpeechRecognition.available API not " + |
| "available in iframe" |
| }, "*"); |
| return; |
| } |
| |
| const options = { langs: ["en-US"], processLocally: true }; |
| // Call available and post its resolution. |
| const availabilityStatus = |
| await SpeechRecognition.available(options); |
| parent.postMessage( |
| { type: "resolution", result: availabilityStatus }, |
| "*" |
| ); // Post the string status |
| } catch (err) { |
| // Catch any unexpected errors during the API call or message post. |
| parent.postMessage({ |
| type: "error", |
| name: err.name, |
| message: err.message |
| }, "*"); |
| } |
| }); |
| <\/script> |
| `; |
| |
| const blob = new Blob([html], { type: "text/html" }); |
| const blobUrl = URL.createObjectURL(blob); |
| // Important: Revoke the blob URL after the test to free up resources. |
| t.add_cleanup(() => URL.revokeObjectURL(blobUrl)); |
| |
| const iframe = document.createElement("iframe"); |
| iframe.src = blobUrl; |
| // Sandboxing with "allow-scripts" is needed for the script inside |
| // the iframe to run. |
| // The cross-origin nature is primarily due to the blob URL's origin being |
| // treated as distinct from the parent page's origin for security |
| // purposes. |
| 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("Test timed out waiting for message from iframe. " + |
| "Ensure iframe script is correctly posting a message.")); |
| }, 6000); // 6-second timeout |
| |
| window.addEventListener("message", t.step_func((event) => { |
| // Basic check to ensure the message is from our iframe. |
| if (event.source !== iframe.contentWindow) return; |
| clearTimeout(timeoutId); |
| resolve(event.data); |
| })); |
| |
| // Send a distinct message to the iframe to trigger its test logic. |
| iframe.contentWindow.postMessage("runTestCallAvailable", "*"); |
| }); |
| |
| // Check if the iframe's script reported an error (e.g., API not found). |
| if (testResult.type === "error") { |
| const errorMessage = |
| `Iframe reported an error: ${testResult.name} - ` + |
| testResult.message; |
| assert_unreached(errorMessage); |
| } |
| |
| assert_equals( |
| testResult.type, |
| "resolution", |
| "The call from the iframe should resolve and post a 'resolution' " + |
| "message." |
| ); |
| assert_equals( |
| testResult.result, // Expecting the string "unavailable". |
| "unavailable", |
| "available should resolve to 'unavailable' for on-device in a cross-origin " + |
| "iframe." |
| ); |
| }, "SpeechRecognition.available should resolve to 'unavailable' for on-device " + |
| "in a cross-origin iframe."); |
| </script> |