Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test has a WPT meta file that expects 4 subtest issues.
 - This WPT test may be referenced by the following Test IDs:
            
- /speech-api/SpeechRecognition-availableOnDevice.https.html - WPT Dashboard Interop Dashboard
 
 
<!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>