SpeechRecognition-availableOnDevice.https.html (6742B)
1 <!DOCTYPE html> 2 <title>SpeechRecognition available</title> 3 <script src="/resources/testharness.js"></script> 4 <script src="/resources/testharnessreport.js"></script> 5 <script> 6 promise_test(async (t) => { 7 const options = { langs: ["en-US", "fr-FR"], processLocally: true }; 8 window.SpeechRecognition = window.SpeechRecognition || 9 window.webkitSpeechRecognition; 10 11 // Test that it returns a promise. 12 const resultPromise = SpeechRecognition.available(options); 13 assert_true( 14 resultPromise instanceof Promise, 15 "available should return a Promise." 16 ); 17 18 // Verify the resolved value is a string. 19 const result = await resultPromise; 20 assert_true( 21 typeof result === "string", 22 "The resolved value of the available promise should be a string." 23 ); 24 25 assert_true( 26 result === "unavailable" || result === "downloadable" || 27 result === "downloading" || result === "available", 28 "The resolved value of the available promise should be a " + 29 "valid value." 30 ); 31 }, "SpeechRecognition.available resolves with a string value for on-device."); 32 33 promise_test(async (t) => { 34 const iframe = document.createElement("iframe"); 35 document.body.appendChild(iframe); 36 const frameWindow = iframe.contentWindow; 37 const frameDOMException = frameWindow.DOMException; 38 const frameSpeechRecognition = 39 frameWindow.SpeechRecognition || frameWindow.webkitSpeechRecognition; 40 41 const options = { langs: ["en-US"], processLocally: true }; 42 iframe.remove(); 43 await promise_rejects_dom( 44 t, 45 "InvalidStateError", 46 frameDOMException, 47 frameSpeechRecognition.available(options), 48 ); 49 }, "SpeechRecognition.available rejects in a detached context for on-device."); 50 51 promise_test(async (t) => { 52 const iframe = document.createElement("iframe"); 53 // This policy should make the on-device speech recognition 54 // feature unavailable. 55 iframe.setAttribute("allow", "on-device-speech-recognition 'none'"); 56 document.body.appendChild(iframe); 57 t.add_cleanup(() => iframe.remove()); 58 59 await new Promise(resolve => { 60 if (iframe.contentWindow && 61 iframe.contentWindow.document.readyState === 'complete') { 62 resolve(); 63 } else { 64 iframe.onload = resolve; 65 } 66 }); 67 68 const frameWindow = iframe.contentWindow; 69 const frameSpeechRecognition = frameWindow.SpeechRecognition || 70 frameWindow.webkitSpeechRecognition; 71 72 assert_true(!!frameSpeechRecognition, 73 "SpeechRecognition should exist in iframe."); 74 assert_true(!!frameSpeechRecognition.available, 75 "available method should exist on SpeechRecognition in iframe."); 76 77 const options = { langs: ["en-US"], processLocally: true }; 78 // Call available and expect it to resolve to "unavailable". 79 const availabilityStatus = 80 await frameSpeechRecognition.available(options); 81 assert_equals(availabilityStatus, "unavailable", 82 "available should resolve to 'unavailable' if " + 83 "'on-device-speech-recognition' Permission Policy is 'none'." 84 ); 85 }, "SpeechRecognition.available resolves to 'unavailable' for on-device if " + 86 "'on-device-speech-recognition' Permission Policy is 'none'."); 87 88 promise_test(async (t) => { 89 const html = ` 90 <!DOCTYPE html> 91 <script> 92 window.addEventListener('message', async (event) => { 93 // Ensure we only process the message intended to trigger the test. 94 if (event.data !== "runTestCallAvailable") return; 95 96 try { 97 const SpeechRecognition = window.SpeechRecognition || 98 window.webkitSpeechRecognition; 99 if (!SpeechRecognition || !SpeechRecognition.available) { 100 parent.postMessage({ 101 type: "error", // Use "error" for API not found or other issues. 102 name: "NotSupportedError", 103 message: "SpeechRecognition.available API not " + 104 "available in iframe" 105 }, "*"); 106 return; 107 } 108 109 const options = { langs: ["en-US"], processLocally: true }; 110 // Call available and post its resolution. 111 const availabilityStatus = 112 await SpeechRecognition.available(options); 113 parent.postMessage( 114 { type: "resolution", result: availabilityStatus }, 115 "*" 116 ); // Post the string status 117 } catch (err) { 118 // Catch any unexpected errors during the API call or message post. 119 parent.postMessage({ 120 type: "error", 121 name: err.name, 122 message: err.message 123 }, "*"); 124 } 125 }); 126 <\/script> 127 `; 128 129 const blob = new Blob([html], { type: "text/html" }); 130 const blobUrl = URL.createObjectURL(blob); 131 // Important: Revoke the blob URL after the test to free up resources. 132 t.add_cleanup(() => URL.revokeObjectURL(blobUrl)); 133 134 const iframe = document.createElement("iframe"); 135 iframe.src = blobUrl; 136 // Sandboxing with "allow-scripts" is needed for the script inside 137 // the iframe to run. 138 // The cross-origin nature is primarily due to the blob URL's origin being 139 // treated as distinct from the parent page's origin for security 140 // purposes. 141 iframe.setAttribute("sandbox", "allow-scripts"); 142 document.body.appendChild(iframe); 143 t.add_cleanup(() => iframe.remove()); 144 145 await new Promise(resolve => iframe.onload = resolve); 146 147 const testResult = await new Promise((resolve, reject) => { 148 const timeoutId = t.step_timeout(() => { 149 reject(new Error("Test timed out waiting for message from iframe. " + 150 "Ensure iframe script is correctly posting a message.")); 151 }, 6000); // 6-second timeout 152 153 window.addEventListener("message", t.step_func((event) => { 154 // Basic check to ensure the message is from our iframe. 155 if (event.source !== iframe.contentWindow) return; 156 clearTimeout(timeoutId); 157 resolve(event.data); 158 })); 159 160 // Send a distinct message to the iframe to trigger its test logic. 161 iframe.contentWindow.postMessage("runTestCallAvailable", "*"); 162 }); 163 164 // Check if the iframe's script reported an error (e.g., API not found). 165 if (testResult.type === "error") { 166 const errorMessage = 167 `Iframe reported an error: ${testResult.name} - ` + 168 testResult.message; 169 assert_unreached(errorMessage); 170 } 171 172 assert_equals( 173 testResult.type, 174 "resolution", 175 "The call from the iframe should resolve and post a 'resolution' " + 176 "message." 177 ); 178 assert_equals( 179 testResult.result, // Expecting the string "unavailable". 180 "unavailable", 181 "available should resolve to 'unavailable' for on-device in a cross-origin " + 182 "iframe." 183 ); 184 }, "SpeechRecognition.available should resolve to 'unavailable' for on-device " + 185 "in a cross-origin iframe."); 186 </script>