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