tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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>